Initial commit
authorSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sat, 21 Sep 2024 02:06:46 +0000 (05:06 +0300)
committerSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sat, 21 Sep 2024 02:06:46 +0000 (05:06 +0300)
210 files changed:
.gitignore [new file with mode: 0644]
LWJake2 README.html [new file with mode: 0644]
LWJake2 README.org [new file with mode: 0644]
baseq2 [new symlink]
diagnostic.iml [new file with mode: 0644]
doc/index.html [new file with mode: 0644]
doc/index.org [new file with mode: 0644]
doc/screenshot.png [new file with mode: 0644]
pom.xml [new file with mode: 0644]
src/main/java/lwjake2/Defines.java [new file with mode: 0644]
src/main/java/lwjake2/EFX/EFXEffect.java [new file with mode: 0644]
src/main/java/lwjake2/EFX/EFXEffectEcho.java [new file with mode: 0644]
src/main/java/lwjake2/EFX/EFXEffectReverb.java [new file with mode: 0644]
src/main/java/lwjake2/EFX/EFXEffectRingModulator.java [new file with mode: 0644]
src/main/java/lwjake2/EFX/EFXFilter.java [new file with mode: 0644]
src/main/java/lwjake2/EFX/EFXFilterLowPass.java [new file with mode: 0644]
src/main/java/lwjake2/Globals.java [new file with mode: 0644]
src/main/java/lwjake2/LWJake2.java [new file with mode: 0644]
src/main/java/lwjake2/VisualizeCode.java [new file with mode: 0644]
src/main/java/lwjake2/client/CL_ents.java [new file with mode: 0644]
src/main/java/lwjake2/client/CL_fx.java [new file with mode: 0644]
src/main/java/lwjake2/client/CL_input.java [new file with mode: 0644]
src/main/java/lwjake2/client/CL_inv.java [new file with mode: 0644]
src/main/java/lwjake2/client/CL_newfx.java [new file with mode: 0644]
src/main/java/lwjake2/client/CL_parse.java [new file with mode: 0644]
src/main/java/lwjake2/client/CL_pred.java [new file with mode: 0644]
src/main/java/lwjake2/client/CL_tent.java [new file with mode: 0644]
src/main/java/lwjake2/client/CL_view.java [new file with mode: 0644]
src/main/java/lwjake2/client/Client.java [new file with mode: 0644]
src/main/java/lwjake2/client/ClientInfo.java [new file with mode: 0644]
src/main/java/lwjake2/client/ClientStateT.java [new file with mode: 0644]
src/main/java/lwjake2/client/ClientStaticT.java [new file with mode: 0644]
src/main/java/lwjake2/client/Console.java [new file with mode: 0644]
src/main/java/lwjake2/client/Key.java [new file with mode: 0644]
src/main/java/lwjake2/client/M.java [new file with mode: 0644]
src/main/java/lwjake2/client/Menu.java [new file with mode: 0644]
src/main/java/lwjake2/client/SCR.java [new file with mode: 0644]
src/main/java/lwjake2/client/V.java [new file with mode: 0644]
src/main/java/lwjake2/client/VideoDriver.java [new file with mode: 0644]
src/main/java/lwjake2/client/centity_t.java [new file with mode: 0644]
src/main/java/lwjake2/client/cl_sustain_t.java [new file with mode: 0644]
src/main/java/lwjake2/client/console_t.java [new file with mode: 0644]
src/main/java/lwjake2/client/cparticle_t.java [new file with mode: 0644]
src/main/java/lwjake2/client/dlight_t.java [new file with mode: 0644]
src/main/java/lwjake2/client/entity_t.java [new file with mode: 0644]
src/main/java/lwjake2/client/frame_t.java [new file with mode: 0644]
src/main/java/lwjake2/client/kbutton_t.java [new file with mode: 0644]
src/main/java/lwjake2/client/lightstyle_t.java [new file with mode: 0644]
src/main/java/lwjake2/client/particle_t.java [new file with mode: 0644]
src/main/java/lwjake2/client/refdef_t.java [new file with mode: 0644]
src/main/java/lwjake2/client/refexport_t.java [new file with mode: 0644]
src/main/java/lwjake2/client/viddef_t.java [new file with mode: 0644]
src/main/java/lwjake2/client/vidmode_t.java [new file with mode: 0644]
src/main/java/lwjake2/client/vrect_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/Client.java [new file with mode: 0644]
src/main/java/lwjake2/game/Cmd.java [new file with mode: 0644]
src/main/java/lwjake2/game/CvarT.java [new file with mode: 0644]
src/main/java/lwjake2/game/EDict.java [new file with mode: 0644]
src/main/java/lwjake2/game/EdictFindFilter.java [new file with mode: 0644]
src/main/java/lwjake2/game/EdictIterator.java [new file with mode: 0644]
src/main/java/lwjake2/game/EndianHandler.java [new file with mode: 0644]
src/main/java/lwjake2/game/EntBlockedAdapter.java [new file with mode: 0644]
src/main/java/lwjake2/game/EntDodgeAdapter.java [new file with mode: 0644]
src/main/java/lwjake2/game/EntInteractAdapter.java [new file with mode: 0644]
src/main/java/lwjake2/game/EntThinkAdapter.java [new file with mode: 0644]
src/main/java/lwjake2/game/EntTouchAdapter.java [new file with mode: 0644]
src/main/java/lwjake2/game/EntUseAdapter.java [new file with mode: 0644]
src/main/java/lwjake2/game/GameAI.java [new file with mode: 0644]
src/main/java/lwjake2/game/GameBase.java [new file with mode: 0644]
src/main/java/lwjake2/game/GameFunc.java [new file with mode: 0644]
src/main/java/lwjake2/game/GameMisc.java [new file with mode: 0644]
src/main/java/lwjake2/game/GameSVCmds.java [new file with mode: 0644]
src/main/java/lwjake2/game/GameSave.java [new file with mode: 0644]
src/main/java/lwjake2/game/GameSpawn.java [new file with mode: 0644]
src/main/java/lwjake2/game/GameTarget.java [new file with mode: 0644]
src/main/java/lwjake2/game/GameTrigger.java [new file with mode: 0644]
src/main/java/lwjake2/game/GameUtil.java [new file with mode: 0644]
src/main/java/lwjake2/game/Info.java [new file with mode: 0644]
src/main/java/lwjake2/game/ItemDropAdapter.java [new file with mode: 0644]
src/main/java/lwjake2/game/ItemUseAdapter.java [new file with mode: 0644]
src/main/java/lwjake2/game/M_Player.java [new file with mode: 0644]
src/main/java/lwjake2/game/Move.java [new file with mode: 0644]
src/main/java/lwjake2/game/PlayerClient.java [new file with mode: 0644]
src/main/java/lwjake2/game/PlayerHud.java [new file with mode: 0644]
src/main/java/lwjake2/game/PlayerTrail.java [new file with mode: 0644]
src/main/java/lwjake2/game/PlayerView.java [new file with mode: 0644]
src/main/java/lwjake2/game/SuperAdapter.java [new file with mode: 0644]
src/main/java/lwjake2/game/client_persistant_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/client_respawn_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/cmdalias_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/cmodel_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/cplane_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/csurface_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/entity_state_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/game_import_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/game_locals_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/gitem_armor_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/gitem_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/level_locals_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/link_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/mapsurface_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/mframe_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/mmove_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/monsterinfo_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/moveinfo_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/player_state_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/pmove_state_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/pushed_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/spawn_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/spawn_temp_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/trace_t.java [new file with mode: 0644]
src/main/java/lwjake2/game/usercmd_t.java [new file with mode: 0644]
src/main/java/lwjake2/qcommon/CM.java [new file with mode: 0644]
src/main/java/lwjake2/qcommon/CRC.java [new file with mode: 0644]
src/main/java/lwjake2/qcommon/Com.java [new file with mode: 0644]
src/main/java/lwjake2/qcommon/CommandBuffer.java [new file with mode: 0644]
src/main/java/lwjake2/qcommon/Cvar.java [new file with mode: 0644]
src/main/java/lwjake2/qcommon/FS.java [new file with mode: 0644]
src/main/java/lwjake2/qcommon/MD4.java [new file with mode: 0644]
src/main/java/lwjake2/qcommon/MSG.java [new file with mode: 0644]
src/main/java/lwjake2/qcommon/NetadrT.java [new file with mode: 0644]
src/main/java/lwjake2/qcommon/Netchan.java [new file with mode: 0644]
src/main/java/lwjake2/qcommon/PMove.java [new file with mode: 0644]
src/main/java/lwjake2/qcommon/QCommon.java [new file with mode: 0644]
src/main/java/lwjake2/qcommon/SZ.java [new file with mode: 0644]
src/main/java/lwjake2/qcommon/cmd_function_t.java [new file with mode: 0644]
src/main/java/lwjake2/qcommon/longjmpException.java [new file with mode: 0644]
src/main/java/lwjake2/qcommon/lump_t.java [new file with mode: 0644]
src/main/java/lwjake2/qcommon/netchan_t.java [new file with mode: 0644]
src/main/java/lwjake2/qcommon/qfiles.java [new file with mode: 0644]
src/main/java/lwjake2/qcommon/sizebuf_t.java [new file with mode: 0644]
src/main/java/lwjake2/qcommon/texinfo_t.java [new file with mode: 0644]
src/main/java/lwjake2/qcommon/xcommand_t.java [new file with mode: 0644]
src/main/java/lwjake2/render/DummyRenderer.java [new file with mode: 0644]
src/main/java/lwjake2/render/Image.java [new file with mode: 0644]
src/main/java/lwjake2/render/LWJGLRenderer.java [new file with mode: 0644]
src/main/java/lwjake2/render/Model.java [new file with mode: 0644]
src/main/java/lwjake2/render/Ref.java [new file with mode: 0644]
src/main/java/lwjake2/render/Renderer.java [new file with mode: 0644]
src/main/java/lwjake2/render/glconfig_t.java [new file with mode: 0644]
src/main/java/lwjake2/render/glpoly_t.java [new file with mode: 0644]
src/main/java/lwjake2/render/glstate_t.java [new file with mode: 0644]
src/main/java/lwjake2/render/lwjgl/Anorms.java [new file with mode: 0644]
src/main/java/lwjake2/render/lwjgl/Base.java [new file with mode: 0644]
src/main/java/lwjake2/render/lwjgl/Draw.java [new file with mode: 0644]
src/main/java/lwjake2/render/lwjgl/Image.java [new file with mode: 0644]
src/main/java/lwjake2/render/lwjgl/LWJGLBase.java [new file with mode: 0644]
src/main/java/lwjake2/render/lwjgl/Light.java [new file with mode: 0644]
src/main/java/lwjake2/render/lwjgl/Main.java [new file with mode: 0644]
src/main/java/lwjake2/render/lwjgl/Mesh.java [new file with mode: 0644]
src/main/java/lwjake2/render/lwjgl/Misc.java [new file with mode: 0644]
src/main/java/lwjake2/render/lwjgl/Model.java [new file with mode: 0644]
src/main/java/lwjake2/render/lwjgl/Polygon.java [new file with mode: 0644]
src/main/java/lwjake2/render/lwjgl/Surf.java [new file with mode: 0644]
src/main/java/lwjake2/render/lwjgl/Warp.java [new file with mode: 0644]
src/main/java/lwjake2/render/medge_t.java [new file with mode: 0644]
src/main/java/lwjake2/render/mleaf_t.java [new file with mode: 0644]
src/main/java/lwjake2/render/mmodel_t.java [new file with mode: 0644]
src/main/java/lwjake2/render/mnode_t.java [new file with mode: 0644]
src/main/java/lwjake2/render/msurface_t.java [new file with mode: 0644]
src/main/java/lwjake2/render/mtexinfo_t.java [new file with mode: 0644]
src/main/java/lwjake2/render/mvertex_t.java [new file with mode: 0644]
src/main/java/lwjake2/server/SV.java [new file with mode: 0644]
src/main/java/lwjake2/server/SV_CCMDS.java [new file with mode: 0644]
src/main/java/lwjake2/server/SV_ENTS.java [new file with mode: 0644]
src/main/java/lwjake2/server/SV_GAME.java [new file with mode: 0644]
src/main/java/lwjake2/server/SV_INIT.java [new file with mode: 0644]
src/main/java/lwjake2/server/SV_SEND.java [new file with mode: 0644]
src/main/java/lwjake2/server/SV_USER.java [new file with mode: 0644]
src/main/java/lwjake2/server/SV_WORLD.java [new file with mode: 0644]
src/main/java/lwjake2/server/Server.java [new file with mode: 0644]
src/main/java/lwjake2/server/areanode_t.java [new file with mode: 0644]
src/main/java/lwjake2/server/challenge_t.java [new file with mode: 0644]
src/main/java/lwjake2/server/client_frame_t.java [new file with mode: 0644]
src/main/java/lwjake2/server/client_t.java [new file with mode: 0644]
src/main/java/lwjake2/server/moveclip_t.java [new file with mode: 0644]
src/main/java/lwjake2/server/server_static_t.java [new file with mode: 0644]
src/main/java/lwjake2/server/server_t.java [new file with mode: 0644]
src/main/java/lwjake2/sound/DummyDriver.java [new file with mode: 0644]
src/main/java/lwjake2/sound/S.java [new file with mode: 0644]
src/main/java/lwjake2/sound/Sound.java [new file with mode: 0644]
src/main/java/lwjake2/sound/WaveLoader.java [new file with mode: 0644]
src/main/java/lwjake2/sound/lwjgl/Channel.java [new file with mode: 0644]
src/main/java/lwjake2/sound/lwjgl/LWJGLSoundImpl.java [new file with mode: 0644]
src/main/java/lwjake2/sound/lwjgl/PlaySound.java [new file with mode: 0644]
src/main/java/lwjake2/sound/sfx_t.java [new file with mode: 0644]
src/main/java/lwjake2/sound/sfxcache_t.java [new file with mode: 0644]
src/main/java/lwjake2/sound/wavinfo_t.java [new file with mode: 0644]
src/main/java/lwjake2/sys/InputListener.java [new file with mode: 0644]
src/main/java/lwjake2/sys/JOGLKBD.java [new file with mode: 0644]
src/main/java/lwjake2/sys/KBD.java [new file with mode: 0644]
src/main/java/lwjake2/sys/LWJGLKBD.java [new file with mode: 0644]
src/main/java/lwjake2/sys/LWJake2InputEvent.java [new file with mode: 0644]
src/main/java/lwjake2/sys/NET.java [new file with mode: 0644]
src/main/java/lwjake2/sys/NanoTimer.java [new file with mode: 0644]
src/main/java/lwjake2/sys/StandardTimer.java [new file with mode: 0644]
src/main/java/lwjake2/sys/Sys.java [new file with mode: 0644]
src/main/java/lwjake2/sys/Timer.java [new file with mode: 0644]
src/main/java/lwjake2/sys/UserInputHandler.java [new file with mode: 0644]
src/main/java/lwjake2/util/Lib.java [new file with mode: 0644]
src/main/java/lwjake2/util/Math3D.java [new file with mode: 0644]
src/main/java/lwjake2/util/PrintfFormat.java [new file with mode: 0644]
src/main/java/lwjake2/util/QuakeFile.java [new file with mode: 0644]
src/main/java/lwjake2/util/Vargs.java [new file with mode: 0644]
src/main/java/lwjake2/util/Vec3Cache.java [new file with mode: 0644]
src/main/resources/rebel.xml [new file with mode: 0644]
tools/commit and push [new file with mode: 0755]
tools/debug [new file with mode: 0755]
tools/open with IntelliJ IDEA [new file with mode: 0755]
tools/update web site [new file with mode: 0755]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..2047d25
--- /dev/null
@@ -0,0 +1,6 @@
+/.idea
+/built
+/target/
+/.classpath
+/.settings/
+/.project
\ No newline at end of file
diff --git a/LWJake2 README.html b/LWJake2 README.html
new file mode 100644 (file)
index 0000000..c4a59e8
--- /dev/null
@@ -0,0 +1,325 @@
+<!doctype html>
+<html lang="en">
+<head>
+<title>LWJake2 README</title>
+<!-- 2017-07-07 Fri 14:10 -->
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta name="generator" content="Org-mode">
+<meta name="author" content="Svjatoslav Agejenko">
+
+<link  href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
+<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/js/bootstrap.min.js"></script>
+<style type="text/css">
+/* org mode styles on top of twbs */
+
+html {
+    position: relative;
+    min-height: 100%;
+}
+
+body {
+    font-size: 18px;
+    margin-bottom: 105px;
+}
+
+footer {
+    position: absolute;
+    bottom: 0;
+    width: 100%;
+    height: 101px;
+    background-color: #f5f5f5;
+}
+
+footer > div {
+    padding: 10px;
+}
+
+footer p {
+    margin: 0 0 5px;
+    text-align: center;
+    font-size: 16px;
+}
+
+#table-of-contents {
+    margin-top: 20px;
+    margin-bottom: 20px;
+}
+
+blockquote p {
+    font-size: 18px;
+}
+
+pre {
+    font-size: 16px;
+}
+
+.footpara {
+    display: inline-block;
+}
+
+figcaption {
+  font-size: 16px;
+  color: #666;
+  font-style: italic;
+  padding-bottom: 15px;
+}
+
+/* from twbs docs */
+
+.bs-docs-sidebar.affix {
+    position: static;
+}
+@media (min-width: 768px) {
+    .bs-docs-sidebar {
+        padding-left: 20px;
+    }
+}
+
+/* All levels of nav */
+.bs-docs-sidebar .nav > li > a {
+    display: block;
+    padding: 4px 20px;
+    font-size: 14px;
+    font-weight: 500;
+    color: #999;
+}
+.bs-docs-sidebar .nav > li > a:hover,
+.bs-docs-sidebar .nav > li > a:focus {
+    padding-left: 19px;
+    color: #A1283B;
+    text-decoration: none;
+    background-color: transparent;
+    border-left: 1px solid #A1283B;
+}
+.bs-docs-sidebar .nav > .active > a,
+.bs-docs-sidebar .nav > .active:hover > a,
+.bs-docs-sidebar .nav > .active:focus > a {
+    padding-left: 18px;
+    font-weight: bold;
+    color: #A1283B;
+    background-color: transparent;
+    border-left: 2px solid #A1283B;
+}
+
+/* Nav: second level (shown on .active) */
+.bs-docs-sidebar .nav .nav {
+    display: none; /* Hide by default, but at >768px, show it */
+    padding-bottom: 10px;
+}
+.bs-docs-sidebar .nav .nav > li > a {
+    padding-top: 1px;
+    padding-bottom: 1px;
+    padding-left: 30px;
+    font-size: 12px;
+    font-weight: normal;
+}
+.bs-docs-sidebar .nav .nav > li > a:hover,
+.bs-docs-sidebar .nav .nav > li > a:focus {
+    padding-left: 29px;
+}
+.bs-docs-sidebar .nav .nav > .active > a,
+.bs-docs-sidebar .nav .nav > .active:hover > a,
+.bs-docs-sidebar .nav .nav > .active:focus > a {
+    padding-left: 28px;
+    font-weight: 500;
+}
+
+/* Nav: third level (shown on .active) */
+.bs-docs-sidebar .nav .nav .nav {
+    padding-bottom: 10px;
+}
+.bs-docs-sidebar .nav .nav .nav > li > a {
+    padding-top: 1px;
+    padding-bottom: 1px;
+    padding-left: 40px;
+    font-size: 12px;
+    font-weight: normal;
+}
+.bs-docs-sidebar .nav .nav .nav > li > a:hover,
+.bs-docs-sidebar .nav .nav .nav > li > a:focus {
+    padding-left: 39px;
+}
+.bs-docs-sidebar .nav .nav .nav > .active > a,
+.bs-docs-sidebar .nav .nav .nav > .active:hover > a,
+.bs-docs-sidebar .nav .nav .nav > .active:focus > a {
+    padding-left: 38px;
+    font-weight: 500;
+}
+
+/* Show and affix the side nav when space allows it */
+@media (min-width: 992px) {
+    .bs-docs-sidebar .nav > .active > ul {
+        display: block;
+    }
+    /* Widen the fixed sidebar */
+    .bs-docs-sidebar.affix,
+    .bs-docs-sidebar.affix-bottom {
+        width: 213px;
+    }
+    .bs-docs-sidebar.affix {
+        position: fixed; /* Undo the static from mobile first approach */
+        top: 20px;
+    }
+    .bs-docs-sidebar.affix-bottom {
+        position: absolute; /* Undo the static from mobile first approach */
+    }
+    .bs-docs-sidebar.affix .bs-docs-sidenav,.bs-docs-sidebar.affix-bottom .bs-docs-sidenav {
+        margin-top: 0;
+        margin-bottom: 0
+    }
+}
+@media (min-width: 1200px) {
+    /* Widen the fixed sidebar again */
+    .bs-docs-sidebar.affix-bottom,
+    .bs-docs-sidebar.affix {
+        width: 263px;
+    }
+}
+</style>
+<script type="text/javascript">
+$(function() {
+    'use strict';
+
+    $('.bs-docs-sidebar li').first().addClass('active');
+
+    $(document.body).scrollspy({target: '.bs-docs-sidebar'});
+
+    $('.bs-docs-sidebar').affix();
+});
+</script>
+</head>
+<body>
+<div id="content" class="container">
+<div class="row"><div class="col-md-9"><h1 class="title">LWJake2 README</h1>
+<div id="outline-container-sec-1" class="outline-2">
+<h2 id="sec-1"><span class="section-number-2">1</span> LWJake2 README</h2>
+<div class="outline-text-2" id="text-1">
+<p>
+LWJake2 Website: <a href="https://github.com/flibitijibibo/LWJake2">https://github.com/flibitijibibo/LWJake2</a>
+</p>
+
+<p>
+LWJake2 is a fork of Jake2, a port of the GPL'd Quake 2 engine from id Software
+to Java.
+</p>
+
+<p>
+LWJake2 is distributed under the terms of the GPLv2 (see LICENSE).
+</p>
+
+<p>
+The port was done completely in Java. No native libraries are used for the
+game functionality.
+</p>
+
+<p>
+This version of Jake2 uses the Lightweight Java Game Library (LWJGL) for
+OpenGL/OpenAL bindings, rather than JOGL/JOAL.
+</p>
+
+<p>
+LWJGL Website - <a href="http://www.lwjgl.org/">http://www.lwjgl.org/</a>
+</p>
+
+<p>
+LWJake2 is still under development. Send bug reports and feedback to
+flibitijibibo@flibitijibibo.com.
+</p>
+
+<p>
+Currently LWJake2 supports GNU/Linux, Windows and Mac OSX. The LWJake2 dedicated
+server runs on every Java supported platform.
+</p>
+
+<p>
+System Requirements:
+</p>
+<ul class="org-ul">
+<li>A computer from the last ~5 years.
+</li>
+<li>The latest version of Java.
+</li>
+</ul>
+</div>
+</div>
+<div id="outline-container-sec-2" class="outline-2">
+<h2 id="sec-2"><span class="section-number-2">2</span> Installation</h2>
+<div class="outline-text-2" id="text-2">
+<ul class="org-ul">
+<li>Extract the archive that this is in.
+</li>
+<li>Install Quake 2 data (see below).
+</li>
+<li>Run your respective executable (LWJake2.sh, for example)
+</li>
+<li>Play!
+</li>
+</ul>
+
+<p>
+Installation of Quake 2 data:
+By default, LWJake2 looks in its own directory for baseq2/, so by default you
+need to move your baseq2/ to that folder. However, you can change this by
+modifying the "cddir" cvar in your config.cfg. For example, if I wanted to
+host my data in ~/.quake2, I would add the following to my config.cfg:
+</p>
+
+<p>
+set cddir "/home/flibitijibibo/.quake2"
+</p>
+
+<p>
+Be sure to check for "set cddir" first! LWJake2 adds this in the default config.
+</p>
+</div>
+</div>
+<div id="outline-container-sec-3" class="outline-2">
+<h2 id="sec-3"><span class="section-number-2">3</span> Credits</h2>
+<div class="outline-text-2" id="text-3">
+<p>
+Bytonic Software - Original Jake2 authors
+</p>
+<hr >
+<p>
+Holger Zickner &lt;hoz@bytonic.de&gt;
+Carsten Weisse &lt;cwei@bytonic.de&gt;
+Rene Stoeckel  &lt;rst@bytonic.de&gt;
+</p>
+
+<p>
+Jake2 Community - Other Jake2 contributors
+</p>
+<hr >
+<p>
+David Sanders - Original LWJGL implementation
+</p>
+
+<p>
+12characters Games - LWJake2 authors
+</p>
+<hr >
+<p>
+Ethan Lee &lt;flibitijibibo@flibitijibibo.com&gt;
+</p>
+</div>
+</div>
+</div><div class="col-md-3"><nav id="table-of-contents">
+<div id="text-table-of-contents" class="bs-docs-sidebar">
+<ul class="nav">
+<li><a href="#sec-1">1. LWJake2 README</a></li>
+<li><a href="#sec-2">2. Installation</a></li>
+<li><a href="#sec-3">3. Credits</a></li>
+</ul>
+</div>
+</nav>
+</div></div></div>
+<footer id="postamble" class="">
+<div><p class="author">Author: Svjatoslav Agejenko</p>
+<p class="date">Created: 2017-07-07 Fri 14:10</p>
+<p class="creator"><a href="http://www.gnu.org/software/emacs/">Emacs</a> 25.1.1 (<a href="http://orgmode.org">Org-mode</a> 8.2.10)</p>
+</div>
+</footer>
+</body>
+</html>
diff --git a/LWJake2 README.org b/LWJake2 README.org
new file mode 100644 (file)
index 0000000..a475836
--- /dev/null
@@ -0,0 +1,55 @@
+* LWJake2 README
+LWJake2 Website: https://github.com/flibitijibibo/LWJake2
+
+LWJake2 is a fork of Jake2, a port of the GPL'd Quake 2 engine from id Software
+to Java.
+
+LWJake2 is distributed under the terms of the GPLv2 (see LICENSE).
+
+The port was done completely in Java. No native libraries are used for the
+game functionality.
+
+This version of Jake2 uses the Lightweight Java Game Library (LWJGL) for
+OpenGL/OpenAL bindings, rather than JOGL/JOAL.
+
+LWJGL Website - http://www.lwjgl.org/
+
+LWJake2 is still under development. Send bug reports and feedback to
+flibitijibibo@flibitijibibo.com.
+
+Currently LWJake2 supports GNU/Linux, Windows and Mac OSX. The LWJake2 dedicated
+server runs on every Java supported platform.
+
+System Requirements:
+- A computer from the last ~5 years.
+- The latest version of Java.
+* Installation
+
+- Extract the archive that this is in.
+- Install Quake 2 data (see below).
+- Run your respective executable (LWJake2.sh, for example)
+- Play!
+
+Installation of Quake 2 data:
+By default, LWJake2 looks in its own directory for baseq2/, so by default you
+need to move your baseq2/ to that folder. However, you can change this by
+modifying the "cddir" cvar in your config.cfg. For example, if I wanted to
+host my data in ~/.quake2, I would add the following to my config.cfg:
+
+set cddir "/home/flibitijibibo/.quake2"
+
+Be sure to check for "set cddir" first! LWJake2 adds this in the default config.
+* Credits
+Bytonic Software - Original Jake2 authors
+-----------------------------------------
+Holger Zickner <hoz@bytonic.de>
+Carsten Weisse <cwei@bytonic.de>
+Rene Stoeckel  <rst@bytonic.de>
+
+Jake2 Community - Other Jake2 contributors
+------------------------------------------
+David Sanders - Original LWJGL implementation
+
+12characters Games - LWJake2 authors
+------------------------------------
+Ethan Lee <flibitijibibo@flibitijibibo.com>
diff --git a/baseq2 b/baseq2
new file mode 120000 (symlink)
index 0000000..e3a6287
--- /dev/null
+++ b/baseq2
@@ -0,0 +1 @@
+/usr/share/games/quake2/baseq2/
\ No newline at end of file
diff --git a/diagnostic.iml b/diagnostic.iml
new file mode 100644 (file)
index 0000000..f0f224e
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8" inherit-compiler-output="false">
+    <output url="file://$MODULE_DIR$/target/classes" />
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+      <excludeFolder url="file://$MODULE_DIR$/target" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.8.1" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.commons:commons-lang3:3.0" level="project" />
+    <orderEntry type="library" name="Maven: org.lwjgl.lwjgl:lwjgl:2.9.3" level="project" />
+    <orderEntry type="library" name="Maven: org.lwjgl.lwjgl:lwjgl-platform:natives-windows:2.9.3" level="project" />
+    <orderEntry type="library" name="Maven: org.lwjgl.lwjgl:lwjgl-platform:natives-linux:2.9.3" level="project" />
+    <orderEntry type="library" name="Maven: org.lwjgl.lwjgl:lwjgl-platform:natives-osx:2.9.3" level="project" />
+    <orderEntry type="library" name="Maven: net.java.jinput:jinput:2.0.5" level="project" />
+    <orderEntry type="library" name="Maven: net.java.jutils:jutils:1.0.0" level="project" />
+    <orderEntry type="library" scope="RUNTIME" name="Maven: net.java.jinput:jinput-platform:natives-linux:2.0.5" level="project" />
+    <orderEntry type="library" scope="RUNTIME" name="Maven: net.java.jinput:jinput-platform:natives-windows:2.0.5" level="project" />
+    <orderEntry type="library" scope="RUNTIME" name="Maven: net.java.jinput:jinput-platform:natives-osx:2.0.5" level="project" />
+    <orderEntry type="library" name="Maven: org.lwjgl.lwjgl:lwjgl_util:2.9.3" level="project" />
+    <orderEntry type="library" name="Maven: com.flibit:EFX:1.0" level="project" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/doc/index.html b/doc/index.html
new file mode 100644 (file)
index 0000000..f7b9d7d
--- /dev/null
@@ -0,0 +1,341 @@
+<!doctype html>
+<html lang="en">
+<head>
+<title>LWJake2 - experiments</title>
+<!-- 2019-01-27 P 00:36 -->
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<meta name="generator" content="Org-mode">
+<meta name="author" content="Svjatoslav Agejenko">
+<link href="https://bootswatch.com/3/darkly/bootstrap.min.css" rel="stylesheet">
+<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
+<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/js/bootstrap.min.js"></script>
+<style type="text/css">
+footer {background-color: #111 !important;}
+pre {background-color: #111; color: #ccc;}
+</style>
+<style type="text/css">
+/* org mode styles on top of twbs */
+
+html {
+    position: relative;
+    min-height: 100%;
+}
+
+body {
+    font-size: 18px;
+    margin-bottom: 105px;
+}
+
+footer {
+    position: absolute;
+    bottom: 0;
+    width: 100%;
+    height: 101px;
+    background-color: #f5f5f5;
+}
+
+footer > div {
+    padding: 10px;
+}
+
+footer p {
+    margin: 0 0 5px;
+    text-align: center;
+    font-size: 16px;
+}
+
+#table-of-contents {
+    margin-top: 20px;
+    margin-bottom: 20px;
+}
+
+blockquote p {
+    font-size: 18px;
+}
+
+pre {
+    font-size: 16px;
+}
+
+.footpara {
+    display: inline-block;
+}
+
+figcaption {
+  font-size: 16px;
+  color: #666;
+  font-style: italic;
+  padding-bottom: 15px;
+}
+
+/* from twbs docs */
+
+.bs-docs-sidebar.affix {
+    position: static;
+}
+@media (min-width: 768px) {
+    .bs-docs-sidebar {
+        padding-left: 20px;
+    }
+}
+
+/* All levels of nav */
+.bs-docs-sidebar .nav > li > a {
+    display: block;
+    padding: 4px 20px;
+    font-size: 14px;
+    font-weight: 500;
+    color: #999;
+}
+.bs-docs-sidebar .nav > li > a:hover,
+.bs-docs-sidebar .nav > li > a:focus {
+    padding-left: 19px;
+    color: #A1283B;
+    text-decoration: none;
+    background-color: transparent;
+    border-left: 1px solid #A1283B;
+}
+.bs-docs-sidebar .nav > .active > a,
+.bs-docs-sidebar .nav > .active:hover > a,
+.bs-docs-sidebar .nav > .active:focus > a {
+    padding-left: 18px;
+    font-weight: bold;
+    color: #A1283B;
+    background-color: transparent;
+    border-left: 2px solid #A1283B;
+}
+
+/* Nav: second level (shown on .active) */
+.bs-docs-sidebar .nav .nav {
+    display: none; /* Hide by default, but at >768px, show it */
+    padding-bottom: 10px;
+}
+.bs-docs-sidebar .nav .nav > li > a {
+    padding-top: 1px;
+    padding-bottom: 1px;
+    padding-left: 30px;
+    font-size: 12px;
+    font-weight: normal;
+}
+.bs-docs-sidebar .nav .nav > li > a:hover,
+.bs-docs-sidebar .nav .nav > li > a:focus {
+    padding-left: 29px;
+}
+.bs-docs-sidebar .nav .nav > .active > a,
+.bs-docs-sidebar .nav .nav > .active:hover > a,
+.bs-docs-sidebar .nav .nav > .active:focus > a {
+    padding-left: 28px;
+    font-weight: 500;
+}
+
+/* Nav: third level (shown on .active) */
+.bs-docs-sidebar .nav .nav .nav {
+    padding-bottom: 10px;
+}
+.bs-docs-sidebar .nav .nav .nav > li > a {
+    padding-top: 1px;
+    padding-bottom: 1px;
+    padding-left: 40px;
+    font-size: 12px;
+    font-weight: normal;
+}
+.bs-docs-sidebar .nav .nav .nav > li > a:hover,
+.bs-docs-sidebar .nav .nav .nav > li > a:focus {
+    padding-left: 39px;
+}
+.bs-docs-sidebar .nav .nav .nav > .active > a,
+.bs-docs-sidebar .nav .nav .nav > .active:hover > a,
+.bs-docs-sidebar .nav .nav .nav > .active:focus > a {
+    padding-left: 38px;
+    font-weight: 500;
+}
+
+/* Show and affix the side nav when space allows it */
+@media (min-width: 992px) {
+    .bs-docs-sidebar .nav > .active > ul {
+        display: block;
+    }
+    /* Widen the fixed sidebar */
+    .bs-docs-sidebar.affix,
+    .bs-docs-sidebar.affix-bottom {
+        width: 213px;
+    }
+    .bs-docs-sidebar.affix {
+        position: fixed; /* Undo the static from mobile first approach */
+        top: 20px;
+    }
+    .bs-docs-sidebar.affix-bottom {
+        position: absolute; /* Undo the static from mobile first approach */
+    }
+    .bs-docs-sidebar.affix .bs-docs-sidenav,.bs-docs-sidebar.affix-bottom .bs-docs-sidenav {
+        margin-top: 0;
+        margin-bottom: 0
+    }
+}
+@media (min-width: 1200px) {
+    /* Widen the fixed sidebar again */
+    .bs-docs-sidebar.affix-bottom,
+    .bs-docs-sidebar.affix {
+        width: 263px;
+    }
+}
+</style>
+<script type="text/javascript">
+$(function() {
+    'use strict';
+
+    $('.bs-docs-sidebar li').first().addClass('active');
+
+    $(document.body).scrollspy({target: '.bs-docs-sidebar'});
+
+    $('.bs-docs-sidebar').affix();
+});
+</script>
+</head>
+<body>
+<div id="content" class="container">
+<div class="row"><div class="col-md-9"><h1 class="title">LWJake2 - experiments</h1>
+
+<div id="outline-container-sec-1" class="outline-2">
+<h2 id="sec-1"><span class="section-number-2">1</span> General</h2>
+<div class="outline-text-2" id="text-1">
+<ul class="org-ul">
+<li>This program is free software: you can redistribute it and/or modify
+it under the terms of the GPLv2 as published by the Free Software
+Foundation.
+</li>
+
+<li>Program authors:
+<ol class="org-ol">
+<li>Id Software is very kind for releasing <a href="https://github.com/id-Software/Quake-2">source code</a> for their <a href="https://en.wikipedia.org/wiki/Quake_II">Quake 2</a>
+first person shooter game.
+</li>
+
+<li>Quake 2 source code got ported to Java as <a href="https://bytonic.de/html/jake2.html">jake2</a>.
+<ul class="org-ul">
+<li>Bytonic Software - Original Jake2 authors:
+<ul class="org-ul">
+<li>Holger Zickner &lt;hoz@bytonic.de&gt;
+</li>
+<li>Carsten Weisse &lt;cwei@bytonic.de&gt;
+</li>
+<li>Rene Stoeckel  &lt;rst@bytonic.de&gt;
+</li>
+</ul>
+</li>
+</ul>
+</li>
+
+<li>jake2 got forked and ported to <a href="https://www.lwjgl.org/">LightWeight Java Gaming Library</a> as <a href="https://github.com/flibitijibibo/LWJake2">LWJake2</a>.
+<ul class="org-ul">
+<li>David Sanders
+</li>
+<li>Ethan Lee &lt;flibitijibibo@flibitijibibo.com&gt;
+</li>
+</ul>
+</li>
+
+<li>Svjatoslav Agejenko took <a href="https://github.com/flibitijibibo/LWJake2">LWJake2</a> as a base for <a href="#sec-2">exeriments</a>
+<ul class="org-ul">
+<li>Homepage: <a href="https://svjatoslav.eu">https://svjatoslav.eu</a>
+</li>
+<li>Email: <a href="mailto://svjatoslav@svjatoslav.eu">mailto://svjatoslav@svjatoslav.eu</a>
+</li>
+<li><a href="https://www.svjatoslav.eu/projects/">Other software projects hosted at svjatoslav.eu</a>
+</li>
+</ul>
+</li>
+</ol>
+</li>
+</ul>
+</div>
+<div id="outline-container-sec-1-1" class="outline-3">
+<h3 id="sec-1-1"><span class="section-number-3">1.1</span> Source code</h3>
+<div class="outline-text-3" id="text-1-1">
+<ul class="org-ul">
+<li><a href="https://www2.svjatoslav.eu/gitweb/?p=quake2.git;a=snapshot;h=HEAD;sf=tgz">Download latest snapshot in TAR GZ format</a>
+</li>
+<li><a href="https://www2.svjatoslav.eu/gitweb/?p=quake2.git;a=summary">Browse Git repository online</a>
+</li>
+<li>Clone Git repository using command:
+<pre class="example">
+git clone http://www2.svjatoslav.eu/git/quake2.git
+
+</pre>
+</li>
+</ul>
+</div>
+</div>
+</div>
+
+<div id="outline-container-sec-2" class="outline-2">
+<h2 id="sec-2"><a id="ID-a4bce93d-9a44-4197-82dc-4c9c163f80fb" name="ID-a4bce93d-9a44-4197-82dc-4c9c163f80fb"></a><span class="section-number-2">2</span> Current status and goals</h2>
+<div class="outline-text-2" id="text-2">
+<p>
+Recent screenshot:
+<img src="screenshot.png" class="img-responsive" alt="screenshot.png">
+</p>
+
+<p>
+Goals:
+</p>
+<ul class="org-ul">
+<li>Remove all violence and overall Quake game logic and get minimal,
+easy to understand and experiment with 3D engine.
+</li>
+
+<li>Remove dependency on Quake proprietary game files.
+<ul class="org-ul">
+<li>Create world programmatically using <a href="https://en.wikipedia.org/wiki/Constructive_solid_geometry">CSG</a> instead.
+</li>
+<li>Add support for other formats (Perhaps <a href="https://en.wikipedia.org/wiki/Wavefront_.obj_file">Wavefront OBJ</a>).
+</li>
+</ul>
+</li>
+
+<li>Learn in the process, how 3D engine is done.
+<ul class="org-ul">
+<li>Perhaps integrate or transfer some knowledge to <a href="https://www3.svjatoslav.eu/projects/sixth-3d/">Sixth 3D engine</a>
+</li>
+</ul>
+</li>
+</ul>
+
+
+<p>
+Done:
+</p>
+<ul class="org-ul">
+<li>Removed Microsoft Windows support.
+</li>
+<li>Converted from previously Ant to Maven project.
+</li>
+<li>Code refactoring according to Java conventions.
+</li>
+<li>Deleted lots of weapons, monsters, kill &amp; damage related code.
+</li>
+</ul>
+</div>
+</div>
+</div><div class="col-md-3"><nav id="table-of-contents">
+<div id="text-table-of-contents" class="bs-docs-sidebar">
+<ul class="nav">
+<li><a href="#sec-1">1. General</a>
+<ul class="nav">
+<li><a href="#sec-1-1">1.1. Source code</a></li>
+</ul>
+</li>
+<li><a href="#sec-2">2. Current status and goals</a></li>
+</ul>
+</div>
+</nav>
+</div></div></div>
+<footer id="postamble" class="">
+<div><p class="author">Author: Svjatoslav Agejenko</p>
+<p class="date">Created: 2019-01-27 P 00:36</p>
+<p class="creator"><a href="http://www.gnu.org/software/emacs/">Emacs</a> 26.1 (<a href="http://orgmode.org">Org-mode</a> 9.1.9)</p>
+</div>
+</footer>
+</body>
+</html>
diff --git a/doc/index.org b/doc/index.org
new file mode 100644 (file)
index 0000000..761211e
--- /dev/null
@@ -0,0 +1,67 @@
+#+TITLE: LWJake2 - experiments
+
+* (document settings) :noexport:
+** use dark style for TWBS-HTML exporter
+#+HTML_HEAD: <link href="https://bootswatch.com/3/darkly/bootstrap.min.css" rel="stylesheet">
+#+HTML_HEAD: <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
+#+HTML_HEAD: <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/js/bootstrap.min.js"></script>
+#+HTML_HEAD: <style type="text/css">
+#+HTML_HEAD:   footer {background-color: #111 !important;}
+#+HTML_HEAD:   pre {background-color: #111; color: #ccc;}
+#+HTML_HEAD: </style>
+
+* General
+
+- This program is free software: you can redistribute it and/or modify
+  it under the terms of the GPLv2 as published by the Free Software
+  Foundation.
+
+- Program authors:
+  1. Id Software is very kind for releasing [[https://github.com/id-Software/Quake-2][source code]] for their [[https://en.wikipedia.org/wiki/Quake_II][Quake 2]]
+     first person shooter game.
+
+  2. Quake 2 source code got ported to Java as [[https://bytonic.de/html/jake2.html][jake2]].
+     - Bytonic Software - Original Jake2 authors:
+       - Holger Zickner <hoz@bytonic.de>
+       - Carsten Weisse <cwei@bytonic.de>
+       - Rene Stoeckel  <rst@bytonic.de>
+
+  3. jake2 got forked and ported to [[https://www.lwjgl.org/][LightWeight Java Gaming Library]] as [[https://github.com/flibitijibibo/LWJake2][LWJake2]].
+     - David Sanders
+     - Ethan Lee <flibitijibibo@flibitijibibo.com>
+
+  4. Svjatoslav Agejenko took [[https://github.com/flibitijibibo/LWJake2][LWJake2]] as a base for [[id:a4bce93d-9a44-4197-82dc-4c9c163f80fb][exeriments]]
+     - Homepage: https://svjatoslav.eu
+     - Email: mailto://svjatoslav@svjatoslav.eu
+     - [[https://www.svjatoslav.eu/projects/][Other software projects hosted at svjatoslav.eu]]
+** Source code
+- [[https://www2.svjatoslav.eu/gitweb/?p=quake2.git;a=snapshot;h=HEAD;sf=tgz][Download latest snapshot in TAR GZ format]]
+- [[https://www2.svjatoslav.eu/gitweb/?p=quake2.git;a=summary][Browse Git repository online]]
+- Clone Git repository using command:
+  : git clone http://www2.svjatoslav.eu/git/quake2.git
+
+* Current status and goals
+  :PROPERTIES:
+  :ID:       a4bce93d-9a44-4197-82dc-4c9c163f80fb
+  :END:
+
+Recent screenshot:
+[[file:screenshot.png]]
+
+Goals:
++ Remove all violence and overall Quake game logic and get minimal,
+  easy to understand and experiment with 3D engine.
+
++ Remove dependency on Quake proprietary game files.
+  + Create world programmatically using [[https://en.wikipedia.org/wiki/Constructive_solid_geometry][CSG]] instead.
+  + Add support for other formats (Perhaps [[https://en.wikipedia.org/wiki/Wavefront_.obj_file][Wavefront OBJ]]).
+
++ Learn in the process, how 3D engine is done.
+  + Perhaps integrate or transfer some knowledge to [[https://www3.svjatoslav.eu/projects/sixth-3d/][Sixth 3D engine]]
+
+
+Done:
++ Removed Microsoft Windows support.
++ Converted from previously Ant to Maven project.
++ Code refactoring according to Java conventions.
++ Deleted lots of weapons, monsters, kill & damage related code.
diff --git a/doc/screenshot.png b/doc/screenshot.png
new file mode 100644 (file)
index 0000000..23b154d
Binary files /dev/null and b/doc/screenshot.png differ
diff --git a/pom.xml b/pom.xml
new file mode 100644 (file)
index 0000000..8558741
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,126 @@
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>lwjake2</groupId>
+    <artifactId>quake2</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <name>Quake2</name>
+    <packaging>jar</packaging>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.8.1</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.lwjgl.lwjgl</groupId>
+            <artifactId>lwjgl</artifactId>
+            <version>2.9.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.lwjgl.lwjgl</groupId>
+            <artifactId>lwjgl_util</artifactId>
+            <version>2.9.3</version>
+        </dependency>
+
+        <dependency>
+            <groupId>eu.svjatoslav</groupId>
+            <artifactId>javainspect</artifactId>
+            <version>1.6</version>
+        </dependency>
+    </dependencies>
+
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.1</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                    <optimize>true</optimize>
+                    <encoding>UTF-8</encoding>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-source-plugin</artifactId>
+                <version>2.2.1</version>
+                <executions>
+                    <execution>
+                        <id>attach-sources</id>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <version>2.9</version>
+                <executions>
+                    <execution>
+                        <id>attach-javadocs</id>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <addClasspath>true</addClasspath>
+                            <classpathPrefix>lib/</classpathPrefix>
+                            <mainClass>lwjake2.LWJake2</mainClass>
+                        </manifest>
+                    </archive>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>com.googlecode.mavennatives</groupId>
+                <artifactId>maven-nativedependencies-plugin</artifactId>
+                <version>0.0.5</version>
+                <executions>
+                    <execution>
+                        <id>unpacknatives</id>
+                        <phase>generate-resources</phase>
+                        <goals>
+                            <goal>copy</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+
+    </build>
+
+    <repositories>
+        <repository>
+            <id>svjatoslav.eu</id>
+            <name>Svjatoslav repository</name>
+            <url>http://www2.svjatoslav.eu/maven/</url>
+        </repository>
+    </repositories>
+</project>
diff --git a/src/main/java/lwjake2/Defines.java b/src/main/java/lwjake2/Defines.java
new file mode 100644 (file)
index 0000000..8412d89
--- /dev/null
@@ -0,0 +1,754 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+/**
+ * Contains the definitions for the game engine.
+ */
+
+package lwjake2;
+
+import java.nio.ByteOrder;
+
+public class Defines {
+    // -----------------
+    // client/q_shared.h
+
+    // can accelerate and turn
+    public final static int PM_NORMAL = 0;
+    public final static int PM_SPECTATOR = 1;
+    // no acceleration or turning
+    public final static int PM_DEAD = 2;
+    public final static int PM_GIB = 3; // different bounding box
+    public final static int PM_FREEZE = 4;
+
+    public final static int EV_ITEM_RESPAWN = 1;
+    public final static int EV_FOOTSTEP = 2;
+    public final static int EV_FALLSHORT = 3;
+    public final static int EV_FALL = 4;
+    public final static int EV_FALLFAR = 5;
+    public final static int EV_PLAYER_TELEPORT = 6;
+    public final static int EV_OTHER_TELEPORT = 7;
+
+    // angle indexes
+    public final static int PITCH = 0; // up / down
+    public final static int YAW = 1; // left / right
+    public final static int ROLL = 2; // fall over
+
+    public final static int MAX_STRING_CHARS = 1024; // max length of a string passed to Cmd_TokenizeString
+    public final static int MAX_STRING_TOKENS = 80; // max tokens resulting from Cmd_TokenizeString
+    public final static int MAX_TOKEN_CHARS = 1024; // max length of an individual token
+
+    public final static int MAX_QPATH = 64; // max length of a quake game pathname
+    public final static int MAX_OSPATH = 128; // max length of a filesystem pathname
+
+    // per-level limits
+    public final static int MAX_CLIENTS = 256; // absolute limit
+    public final static int MAX_EDICTS = 1024; // must change protocol to increase more
+    public final static int MAX_LIGHTSTYLES = 256;
+    public final static int MAX_MODELS = 256; // these are sent over the net as bytes
+    public final static int MAX_SOUNDS = 256; // so they cannot be blindly increased
+    public final static int MAX_IMAGES = 256;
+    public final static int MAX_ITEMS = 256;
+    public final static int MAX_GENERAL = (MAX_CLIENTS * 2); // general config strings
+
+    public final static int PRINT_MEDIUM = 1; // death messages
+    public final static int PRINT_HIGH = 2; // critical messages
+    public final static int PRINT_CHAT = 3; // chat messages
+
+    public final static int ERR_FATAL = 0; // exit the entire game with a popup window
+    public final static int ERR_DROP = 1; // print to console and disconnect from game
+    public final static int ERR_DISCONNECT = 2; // don't kill server
+
+    public final static int PRINT_ALL = 0;
+    public final static int PRINT_DEVELOPER = 1; // only print when "developer 1"
+
+    // key / value info strings
+    public final static int MAX_INFO_KEY = 64;
+    public final static int MAX_INFO_STRING = 512;
+
+    public final static int SFF_HIDDEN = 0x02;
+    public final static int SFF_SUBDIR = 0x08;
+    public final static int SFF_SYSTEM = 0x10;
+
+    public final static int CVAR_ARCHIVE = 1; // set to cause it to be saved to vars.rc
+    public final static int CVAR_USERINFO = 2; // added to userinfo when changed
+    public final static int CVAR_SERVERINFO = 4; // added to serverinfo when changed
+    public final static int CVAR_NOSET = 8; // don't allow change from console at all,
+    // but can be set from the command line
+    public final static int CVAR_LATCH = 16; // save changes until server restart
+
+    // lower bits are stronger, and will eat weaker brushes completely
+    public final static int CONTENTS_SOLID = 1; // an eye is never valid in a solid
+    public final static int CONTENTS_WINDOW = 2; // translucent, but not watery
+    public final static int CONTENTS_LAVA = 8;
+    public final static int CONTENTS_SLIME = 16;
+    public final static int CONTENTS_WATER = 32;
+
+    public final static int CONTENTS_PLAYERCLIP = 0x10000;
+    public final static int CONTENTS_MONSTERCLIP = 0x20000;
+
+    // currents can be added to any other contents, and may be mixed
+    public final static int CONTENTS_CURRENT_0 = 0x40000;
+    public final static int CONTENTS_CURRENT_90 = 0x80000;
+    public final static int CONTENTS_CURRENT_180 = 0x100000;
+    public final static int CONTENTS_CURRENT_270 = 0x200000;
+    public final static int CONTENTS_CURRENT_UP = 0x400000;
+    public final static int CONTENTS_CURRENT_DOWN = 0x800000;
+
+
+    public final static int CONTENTS_MONSTER = 0x2000000; // should never be on a brush, only in game
+    public final static int CONTENTS_DEADMONSTER = 0x4000000;
+    public final static int CONTENTS_LADDER = 0x20000000;
+
+    public final static int SURF_SLICK = 0x2; // effects game physics
+
+    public final static int SURF_SKY = 0x4; // don't draw, but add to skybox
+    public final static int SURF_WARP = 0x8; // turbulent water warp
+    public final static int SURF_TRANS33 = 0x10;
+    public final static int SURF_TRANS66 = 0x20;
+    public final static int SURF_FLOWING = 0x40; // scroll towards angle
+
+    //
+    // button bits
+    //
+    public final static int BUTTON_ATTACK = 1;
+    public final static int BUTTON_USE = 2;
+    public final static int BUTTON_ANY = 128; // any key whatsoever
+
+    public final static int MAXTOUCH = 32;
+
+    // entity_state_t->effects
+    // Effects are things handled on the client side (lights, particles, frame animations)
+    // that happen constantly on the given entity.
+    // An entity that has effects will be sent to the client
+    // even if it has a zero index model.
+    public final static int EF_ROTATE = 0x00000001; // rotate (bonus items)
+    public final static int EF_GIB = 0x00000002; // leave a trail
+    public final static int EF_BLASTER = 0x00000008; // redlight + trail
+    public final static int EF_ROCKET = 0x00000010; // redlight + trail
+    public final static int EF_GRENADE = 0x00000020;
+    public final static int EF_HYPERBLASTER = 0x00000040;
+    public final static int EF_BFG = 0x00000080;
+    public final static int EF_COLOR_SHELL = 0x00000100;
+    public final static int EF_POWERSCREEN = 0x00000200;
+    public final static int EF_ANIM01 = 0x00000400; // automatically cycle between frames 0 and 1 at 2 hz
+    public final static int EF_ANIM23 = 0x00000800; // automatically cycle between frames 2 and 3 at 2 hz
+    public final static int EF_ANIM_ALL = 0x00001000; // automatically cycle through all frames at 2hz
+    public final static int EF_ANIM_ALLFAST = 0x00002000; // automatically cycle through all frames at 10hz
+    public final static int EF_FLIES = 0x00004000;
+    public final static int EF_QUAD = 0x00008000;
+    public final static int EF_PENT = 0x00010000;
+    public final static int EF_TELEPORTER = 0x00020000; // particle fountain
+    public final static int EF_FLAG1 = 0x00040000;
+    public final static int EF_FLAG2 = 0x00080000;
+    // RAFAEL
+    public final static int EF_IONRIPPER = 0x00100000;
+    public final static int EF_GREENGIB = 0x00200000;
+    public final static int EF_BLUEHYPERBLASTER = 0x00400000;
+    public final static int EF_SPINNINGLIGHTS = 0x00800000;
+    public final static int EF_PLASMA = 0x01000000;
+    public final static int EF_TRAP = 0x02000000;
+
+    //ROGUE
+    public final static int EF_TRACKER = 0x04000000;
+    public final static int EF_DOUBLE = 0x08000000;
+    public final static int EF_SPHERETRANS = 0x10000000;
+    public final static int EF_TAGTRAIL = 0x20000000;
+    public final static int EF_HALF_DAMAGE = 0x40000000;
+    public final static int EF_TRACKERTRAIL = 0x80000000;
+    //ROGUE
+
+    // entity_state_t->renderfx flags
+    public final static int RF_MINLIGHT = 1; // allways have some light (viewmodel)
+    public final static int RF_VIEWERMODEL = 2; // don't draw through eyes, only mirrors
+    public final static int RF_FULLBRIGHT = 8; // allways draw full intensity
+    public final static int RF_TRANSLUCENT = 32;
+    public final static int RF_FRAMELERP = 64;
+    public final static int RF_SHELL_RED = 1024;
+    public final static int RF_SHELL_GREEN = 2048;
+    public final static int RF_SHELL_BLUE = 4096;
+
+    //ROGUE
+    public final static int RF_IR_VISIBLE = 0x00008000; // 32768
+    public final static int RF_SHELL_DOUBLE = 0x00010000; // 65536
+    public final static int RF_SHELL_HALF_DAM = 0x00020000;
+    public final static int RF_USE_DISGUISE = 0x00040000;
+    //ROGUE
+
+    // player_state_t->refdef flags
+    public final static int RDF_UNDERWATER = 1; // warp the screen as apropriate
+    public final static int RDF_NOWORLDMODEL = 2; // used for player configuration screen
+
+    //ROGUE
+    public final static int RDF_IRGOGGLES = 4;
+    //ROGUE
+
+    public final static int MZ_LOGIN = 9;
+    public final static int MZ_LOGOUT = 10;
+
+    public final static int CHAN_AUTO = 0;
+    public final static int CHAN_VOICE = 2;
+    public final static int CHAN_ITEM = 3;
+    public final static int CHAN_BODY = 4;
+    //    modifier flags
+    public final static int CHAN_NO_PHS_ADD = 8;
+    // send to all clients, not just ones in PHS (ATTN 0 will also do this)
+    public final static int CHAN_RELIABLE = 16; // send by reliable message, not datagram
+
+    //    sound attenuation values
+    public final static int ATTN_NONE = 0; // full volume the entire level
+    public final static int ATTN_NORM = 1;
+    public final static int ATTN_STATIC = 3; // diminish very rapidly with distance
+
+    //    player_state->stats[] indexes
+    public final static int STAT_HEALTH_ICON = 0;
+    public final static int STAT_HEALTH = 1;
+    public final static int STAT_PICKUP_ICON = 7;
+    public final static int STAT_PICKUP_STRING = 8;
+    public final static int STAT_TIMER_ICON = 9;
+    public final static int STAT_TIMER = 10;
+    public final static int STAT_HELPICON = 11;
+    public final static int STAT_SELECTED_ITEM = 12;
+    public final static int STAT_LAYOUTS = 13;
+    public final static int STAT_FRAGS = 14;
+    public final static int STAT_FLASHES = 15; // cleared each frame, 1 = health, 2 = armor
+    public final static int STAT_CHASE = 16;
+    public final static int STAT_SPECTATOR = 17;
+
+    public final static int MAX_STATS = 32;
+
+    //    dmflags->value flags
+    public final static int DF_NO_ITEMS = 0x00000002; // 2
+    public final static int DF_NO_FALLING = 0x00000008; // 8
+    public final static int DF_INSTANT_ITEMS = 0x00000010; // 16
+    public final static int DF_SAME_LEVEL = 0x00000020; // 32
+    public final static int DF_SKINTEAMS = 0x00000040; // 64
+    public final static int DF_MODELTEAMS = 0x00000080; // 128
+    public final static int DF_NO_FRIENDLY_FIRE = 0x00000100; // 256
+    public final static int DF_SPAWN_FARTHEST = 0x00000200; // 512
+    public final static int DF_FORCE_RESPAWN = 0x00000400; // 1024
+    public final static int DF_NO_ARMOR = 0x00000800; // 2048
+    public final static int DF_ALLOW_EXIT = 0x00001000; // 4096
+    public final static int DF_INFINITE_AMMO = 0x00002000; // 8192
+    public final static int DF_QUAD_DROP = 0x00004000; // 16384
+    public final static int DF_FIXED_FOV = 0x00008000; // 32768
+
+    //   ROGUE
+    public final static int DF_NO_MINES = 0x00020000;
+    public final static int DF_NO_STACK_DOUBLE = 0x00040000;
+    public final static int DF_NO_NUKES = 0x00080000;
+    public final static int DF_NO_SPHERES = 0x00100000;
+    //   ROGUE
+
+    //
+    // config strings are a general means of communication from
+    // the server to all connected clients.
+    // Each config string can be at most MAX_QPATH characters.
+    //
+    public final static int CS_NAME = 0;
+    public final static int CS_CDTRACK = 1;
+    public final static int CS_SKY = 2;
+    public final static int CS_SKYAXIS = 3; // %f %f %f format
+    public final static int CS_SKYROTATE = 4;
+    public final static int CS_STATUSBAR = 5; // display program string
+
+    public final static int CS_AIRACCEL = 29; // air acceleration control
+    public final static int CS_MAXCLIENTS = 30;
+    public final static int CS_MAPCHECKSUM = 31; // for catching cheater maps
+
+    public final static int CS_MODELS = 32;
+    public final static int CS_SOUNDS = (CS_MODELS + MAX_MODELS);
+    public final static int CS_IMAGES = (CS_SOUNDS + MAX_SOUNDS);
+    public final static int CS_LIGHTS = (CS_IMAGES + MAX_IMAGES);
+    public final static int CS_ITEMS = (CS_LIGHTS + MAX_LIGHTSTYLES);
+    public final static int CS_PLAYERSKINS = (CS_ITEMS + MAX_ITEMS);
+    public final static int CS_GENERAL = (CS_PLAYERSKINS + MAX_CLIENTS);
+    public final static int MAX_CONFIGSTRINGS = (CS_GENERAL + MAX_GENERAL);
+
+    // gi.BoxEdicts() can return a list of either solid or trigger entities
+    // FIXME: eliminate AREA_ distinction?
+    public final static int AREA_SOLID = 1;
+    public final static int AREA_TRIGGERS = 2;
+
+    public final static int TE_GUNSHOT = 0;
+    public final static int TE_BLOOD = 1;
+    public final static int TE_BLASTER = 2;
+    public final static int TE_RAILTRAIL = 3;
+    public final static int TE_SHOTGUN = 4;
+    public final static int TE_EXPLOSION1 = 5;
+    public final static int TE_EXPLOSION2 = 6;
+    public final static int TE_ROCKET_EXPLOSION = 7;
+    public final static int TE_GRENADE_EXPLOSION = 8;
+    public final static int TE_SPARKS = 9;
+    public final static int TE_SPLASH = 10;
+    public final static int TE_BUBBLETRAIL = 11;
+    public final static int TE_SCREEN_SPARKS = 12;
+    public final static int TE_SHIELD_SPARKS = 13;
+    public final static int TE_BULLET_SPARKS = 14;
+    public final static int TE_LASER_SPARKS = 15;
+    public final static int TE_PARASITE_ATTACK = 16;
+    public final static int TE_ROCKET_EXPLOSION_WATER = 17;
+    public final static int TE_GRENADE_EXPLOSION_WATER = 18;
+    public final static int TE_MEDIC_CABLE_ATTACK = 19;
+    public final static int TE_BFG_EXPLOSION = 20;
+    public final static int TE_BFG_BIGEXPLOSION = 21;
+    public final static int TE_BOSSTPORT = 22; // used as '22' in a map, so DON'T RENUMBER!!!
+    public final static int TE_BFG_LASER = 23;
+    public final static int TE_GRAPPLE_CABLE = 24;
+    public final static int TE_WELDING_SPARKS = 25;
+    public final static int TE_GREENBLOOD = 26;
+    public final static int TE_BLUEHYPERBLASTER = 27;
+    public final static int TE_PLASMA_EXPLOSION = 28;
+    public final static int TE_TUNNEL_SPARKS = 29;
+    //ROGUE
+    public final static int TE_BLASTER2 = 30;
+    public final static int TE_LIGHTNING = 33;
+    public final static int TE_DEBUGTRAIL = 34;
+    public final static int TE_PLAIN_EXPLOSION = 35;
+    public final static int TE_FLASHLIGHT = 36;
+    public final static int TE_FORCEWALL = 37;
+    public final static int TE_HEATBEAM = 38;
+    public final static int TE_MONSTER_HEATBEAM = 39;
+    public final static int TE_STEAM = 40;
+    public final static int TE_BUBBLETRAIL2 = 41;
+    public final static int TE_MOREBLOOD = 42;
+    public final static int TE_HEATBEAM_SPARKS = 43;
+    public final static int TE_HEATBEAM_STEAM = 44;
+    public final static int TE_CHAINFIST_SMOKE = 45;
+    public final static int TE_ELECTRIC_SPARKS = 46;
+    public final static int TE_TRACKER_EXPLOSION = 47;
+    public final static int TE_TELEPORT_EFFECT = 48;
+    public final static int TE_DBALL_GOAL = 49;
+    public final static int TE_WIDOWBEAMOUT = 50;
+    public final static int TE_NUKEBLAST = 51;
+    public final static int TE_WIDOWSPLASH = 52;
+    public final static int TE_EXPLOSION1_BIG = 53;
+    public final static int TE_EXPLOSION1_NP = 54;
+    public final static int TE_FLECHETTE = 55;
+
+    // content masks
+    public final static int MASK_SOLID = (CONTENTS_SOLID | CONTENTS_WINDOW);
+    public final static int MASK_PLAYERSOLID = (CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_WINDOW | CONTENTS_MONSTER);
+    public final static int MASK_DEADSOLID = (CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_WINDOW);
+    public final static int MASK_MONSTERSOLID = (CONTENTS_SOLID | CONTENTS_MONSTERCLIP | CONTENTS_WINDOW | CONTENTS_MONSTER);
+    public final static int MASK_WATER = (CONTENTS_WATER | CONTENTS_LAVA | CONTENTS_SLIME);
+    public final static int MASK_OPAQUE = (CONTENTS_SOLID | CONTENTS_SLIME | CONTENTS_LAVA);
+    public final static int MASK_SHOT = (CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_WINDOW | CONTENTS_DEADMONSTER);
+    public final static int MASK_CURRENT =
+            (CONTENTS_CURRENT_0
+                    | CONTENTS_CURRENT_90
+                    | CONTENTS_CURRENT_180
+                    | CONTENTS_CURRENT_270
+                    | CONTENTS_CURRENT_UP
+                    | CONTENTS_CURRENT_DOWN);
+
+
+    // (machen nur GL)
+    public final static int VIDREF_GL = 1;
+    public final static int VIDREF_SOFT = 2;
+
+
+    public final static int ANIM_BASIC = 0; // stand / run
+    public final static int ANIM_WAVE = 1;
+    public final static int ANIM_JUMP = 2;
+    public final static int ANIM_DEATH = 5;
+    public final static int ANIM_REVERSE = 6;
+
+    // view pitching times
+    public final static float DAMAGE_TIME = 0.5f;
+    public final static float FALL_TIME = 0.3f;
+
+    public final static int DAMAGE_NO_PROTECTION = 0x00000020;
+    // armor, shields, invulnerability, and godmode have no effect
+
+    public final static int DAMAGE_NO = 0;
+    public final static int DAMAGE_YES = 1; // will take damage if hit
+    public final static int DAMAGE_AIM = 2; // auto targeting recognizes this
+
+    public final static int MOD_BLASTER = 1;
+    public final static int MOD_SHOTGUN = 2;
+    public final static int MOD_SSHOTGUN = 3;
+    public final static int MOD_MACHINEGUN = 4;
+    public final static int MOD_CHAINGUN = 5;
+    public final static int MOD_GRENADE = 6;
+    public final static int MOD_G_SPLASH = 7;
+    public final static int MOD_ROCKET = 8;
+    public final static int MOD_R_SPLASH = 9;
+    public final static int MOD_HYPERBLASTER = 10;
+    public final static int MOD_RAILGUN = 11;
+    public final static int MOD_BFG_LASER = 12;
+    public final static int MOD_BFG_BLAST = 13;
+    public final static int MOD_BFG_EFFECT = 14;
+    public final static int MOD_HANDGRENADE = 15;
+    public final static int MOD_HG_SPLASH = 16;
+    public final static int MOD_WATER = 17;
+    public final static int MOD_SLIME = 18;
+    public final static int MOD_LAVA = 19;
+    public final static int MOD_CRUSH = 20;
+    public final static int MOD_TELEFRAG = 21;
+    public final static int MOD_FALLING = 22;
+    public final static int MOD_SUICIDE = 23;
+    public final static int MOD_HELD_GRENADE = 24;
+    public final static int MOD_EXPLOSIVE = 25;
+    public final static int MOD_BARREL = 26;
+    public final static int MOD_BOMB = 27;
+    public final static int MOD_EXIT = 28;
+    public final static int MOD_SPLASH = 29;
+    public final static int MOD_TARGET_LASER = 30;
+    public final static int MOD_TRIGGER_HURT = 31;
+    public final static int MOD_TARGET_BLASTER = 33;
+    public final static int MOD_FRIENDLY_FIRE = 0x8000000;
+
+    // edict->spawnflags
+    // these are set with checkboxes on each entity in the map editor
+    public final static int SPAWNFLAG_NOT_EASY = 0x00000100;
+    public final static int SPAWNFLAG_NOT_MEDIUM = 0x00000200;
+    public final static int SPAWNFLAG_NOT_HARD = 0x00000400;
+    public final static int SPAWNFLAG_NOT_DEATHMATCH = 0x00000800;
+    public final static int SPAWNFLAG_NOT_COOP = 0x00001000;
+
+    // edict->flags
+    public final static int FL_FLY = 0x00000001;
+    public final static int FL_SWIM = 0x00000002; // implied immunity to drowining
+    public final static int FL_INWATER = 0x00000008;
+    public final static int FL_GODMODE = 0x00000010;
+    public final static int FL_NOTARGET = 0x00000020;
+    public final static int FL_PARTIALGROUND = 0x00000100; // not all corners are valid
+    public final static int FL_TEAMSLAVE = 0x00000400; // not the first on the team
+    public final static int FL_NO_KNOCKBACK = 0x00000800;
+    public final static int FL_POWER_ARMOR = 0x00001000; // power armor (if any) is active
+
+    public final static float FRAMETIME = 0.1f;
+
+
+    public final static int BODY_QUEUE_SIZE = 8;
+
+    public final static int AI_GOOD_GUY = 0x00000100;
+    public final static int AI_NOSTEP = 0x00000400;
+    public final static int AI_COMBAT_POINT = 0x00001000;
+
+    public final static int SFL_CROSS_TRIGGER_MASK = 0x000000ff;
+    // edict->movetype values
+    public final static int MOVETYPE_NONE = 0; // never moves
+    public final static int MOVETYPE_NOCLIP = 1; // origin and angles change with no interaction
+    public final static int MOVETYPE_PUSH = 2; // no clip to world, push on box contact
+    public final static int MOVETYPE_STOP = 3; // no clip to world, stops on box contact
+
+    public final static int MOVETYPE_WALK = 4; // gravity
+    public final static int MOVETYPE_STEP = 5; // gravity, special edge handling
+    public final static int MOVETYPE_FLY = 6;
+    public final static int MOVETYPE_TOSS = 7; // gravity
+    public final static int MOVETYPE_FLYMISSILE = 8; // extra size to monsters
+    public final static int MOVETYPE_BOUNCE = 9;
+
+    public final static int MULTICAST_ALL = 0;
+    public final static int MULTICAST_PHS = 1;
+    public final static int MULTICAST_PVS = 2;
+    public final static int MULTICAST_ALL_R = 3;
+    public final static int MULTICAST_PHS_R = 4;
+    public final static int MULTICAST_PVS_R = 5;
+
+    // -------------
+    // client/game.h
+
+    public final static int SOLID_NOT = 0; // no interaction with other objects
+    public final static int SOLID_TRIGGER = 1; // only touch when inside, after moving
+    public final static int SOLID_BBOX = 2; // touch on edge
+    public final static int SOLID_BSP = 3; // bsp clip, touch on edge
+
+    //    edict->svflags
+    public final static int SVF_NOCLIENT = 0x00000001; // don't send entity to clients, even if it has effects
+    public final static int SVF_DEADMONSTER = 0x00000002; // treat as CONTENTS_DEADMONSTER for collision
+    public final static int SVF_MONSTER = 0x00000004; // treat as CONTENTS_MONSTER for collision
+
+    public final static int MAX_ENT_CLUSTERS = 16;
+
+    public final static int sv_stopspeed = 100;
+    public final static int sv_friction = 6;
+    public final static int sv_waterfriction = 1;
+
+
+    // R E N D E R E R
+    ////////////////////
+    public static final int MAX_DLIGHTS = 32;
+    public static final int MAX_ENTITIES = 128;
+    public static final int MAX_PARTICLES = 4096;
+
+    // gl_model.h
+    public static final int SURF_PLANEBACK = 2;
+    public static final int SURF_DRAWSKY = 4;
+    public static final int SURF_DRAWTURB = 0x10;
+
+    public static final float POWERSUIT_SCALE = 4.0f;
+
+    // protocol bytes that can be directly added to messages
+
+    public final static int svc_temp_entity = 3;
+    public final static int svc_layout = 4;
+    public final static int svc_inventory = 5;
+
+    // the rest are private to the client and server
+    public final static int svc_nop = 6;
+    public final static int svc_disconnect = 7;
+    public final static int svc_reconnect = 8;
+    public final static int svc_sound = 9; // <see code>
+    public final static int svc_print = 10; // [byte] id [string] null terminated string
+    public final static int svc_stufftext = 11;
+    // [string] stuffed into client's console buffer, should be \n terminated
+    public final static int svc_serverdata = 12; // [long] protocol ...
+    public final static int svc_configstring = 13; // [short] [string]
+    public final static int svc_spawnbaseline = 14;
+    public final static int svc_centerprint = 15; // [string] to put in center of the screen
+    public final static int svc_download = 16; // [short] size [size bytes]
+    public final static int svc_playerinfo = 17; // variable
+    public final static int svc_packetentities = 18; // [...]
+    public final static int svc_deltapacketentities = 19; // [...]
+    public final static int svc_frame = 20;
+
+    public static final int NUMVERTEXNORMALS = 162;
+    public static final int PROTOCOL_VERSION = 34;
+    public final static int PORT_MASTER = 27900;
+    public final static int PORT_CLIENT = 27901;
+    public final static int PORT_SERVER = 27910;
+    public final static int PORT_ANY = -1;
+
+    public final static int PS_M_TYPE = (1);
+    public final static int PS_M_ORIGIN = (1 << 1);
+    public final static int PS_M_VELOCITY = (1 << 2);
+    public final static int PS_M_TIME = (1 << 3);
+    public final static int PS_M_FLAGS = (1 << 4);
+    public final static int PS_M_GRAVITY = (1 << 5);
+    public final static int PS_M_DELTA_ANGLES = (1 << 6);
+
+    public final static int UPDATE_BACKUP = 16; // copies of entity_state_t to keep buffered
+    // must be power of two
+    public final static int UPDATE_MASK = (UPDATE_BACKUP - 1);
+
+    public final static int PS_VIEWOFFSET = (1 << 7);
+    public final static int PS_VIEWANGLES = (1 << 8);
+    public final static int PS_KICKANGLES = (1 << 9);
+    public final static int PS_BLEND = (1 << 10);
+    public final static int PS_FOV = (1 << 11);
+    public final static int PS_WEAPONINDEX = (1 << 12);
+    public final static int PS_RDFLAGS = (1 << 14);
+
+    public static final int CM_ANGLE1 = (1);
+    public static final int CM_ANGLE2 = (1 << 1);
+    public static final int CM_ANGLE3 = (1 << 2);
+    public static final int CM_FORWARD = (1 << 3);
+    public static final int CM_SIDE = (1 << 4);
+    public static final int CM_UP = (1 << 5);
+    public static final int CM_BUTTONS = (1 << 6);
+    public static final int CM_IMPULSE = (1 << 7);
+
+    // try to pack the common update flags into the first byte
+    public final static int U_ORIGIN1 = (1);
+    public final static int U_ORIGIN2 = (1 << 1);
+    public final static int U_ANGLE2 = (1 << 2);
+    public final static int U_ANGLE3 = (1 << 3);
+    public final static int U_FRAME8 = (1 << 4); // frame is a byte
+    public final static int U_EVENT = (1 << 5);
+    public final static int U_REMOVE = (1 << 6); // REMOVE this entity, don't add it
+    public final static int U_MOREBITS1 = (1 << 7); // read one additional byte
+
+    // second byte
+    public final static int U_NUMBER16 = (1 << 8); // NUMBER8 is implicit if not set
+    public final static int U_ORIGIN3 = (1 << 9);
+    public final static int U_ANGLE1 = (1 << 10);
+    public final static int U_MODEL = (1 << 11);
+    public final static int U_RENDERFX8 = (1 << 12); // fullbright, etc
+    public final static int U_EFFECTS8 = (1 << 14); // autorotate, trails, etc
+    public final static int U_MOREBITS2 = (1 << 15); // read one additional byte
+
+    // third byte
+    public final static int U_SKIN8 = (1 << 16);
+    public final static int U_FRAME16 = (1 << 17); // frame is a short
+    public final static int U_RENDERFX16 = (1 << 18); // 8 + 16 = 32
+    public final static int U_EFFECTS16 = (1 << 19); // 8 + 16 = 32
+    public final static int U_MODEL2 = (1 << 20); // weapons, flags, etc
+    public final static int U_MODEL3 = (1 << 21);
+    public final static int U_MODEL4 = (1 << 22);
+    public final static int U_MOREBITS3 = (1 << 23); // read one additional byte
+
+    // fourth byte
+    public final static int U_OLDORIGIN = (1 << 24); // FIXME: get rid of this
+    public final static int U_SKIN16 = (1 << 25);
+    public final static int U_SOUND = (1 << 26);
+    public final static int U_SOLID = (1 << 27);
+
+    public static final int MAX_MD2SKINS = 32;
+    public static final int MAX_SKINNAME = 64;
+
+    public static final int MAXLIGHTMAPS = 4;
+
+    public static final int clc_nop = 1;
+    public static final int clc_move = 2; // [[usercmd_t]
+    public static final int clc_userinfo = 3; // [[userinfo string]
+    public static final int clc_stringcmd = 4; // [string] message
+
+    public static final int NS_CLIENT = 0;
+    public static final int NS_SERVER = 1;
+
+    public static final int NA_LOOPBACK = 0;
+    public static final int NA_BROADCAST = 1;
+    public static final int NA_IP = 2;
+    public static final int NA_BROADCAST_IPX = 4;
+
+    public final static int SND_VOLUME = (1); // a byte
+    public final static int SND_ATTENUATION = (1 << 1); // a byte
+    public final static int SND_POS = (1 << 2); // three coordinates
+    public final static int SND_ENT = (1 << 3); // a short 0-2: channel, 3-12: entity
+    public final static int SND_OFFSET = (1 << 4); // a byte, msec offset from frame start
+
+    public final static float DEFAULT_SOUND_PACKET_VOLUME = 1.0f;
+    public final static float DEFAULT_SOUND_PACKET_ATTENUATION = 1.0f;
+
+    // --------
+    // client.h
+    public static final int MAX_PARSE_ENTITIES = 1024;
+    public static final int ca_uninitialized = 0;
+    public static final int ca_disconnected = 1;
+    public static final int ca_connecting = 2;
+    public static final int ca_connected = 3;
+    public static final int ca_active = 4;
+    public static final int MAX_ALIAS_NAME = 32;
+    public static final int MAX_NUM_ARGVS = 50;
+    public static final int MAX_MSGLEN = 1400;
+    // ---------
+    // console.h
+    public static final int NUM_CON_TIMES = 4;
+    public static final int CON_TEXTSIZE = 32768;
+    public final static int BSPVERSION = 38;
+    // upper design bounds
+    // leaffaces, leafbrushes, planes, and verts are still bounded by
+    // 16 bit short limits
+    public final static int MAX_MAP_MODELS = 1024;
+
+    // --------
+    // qfiles.h
+    public final static int MAX_MAP_BRUSHES = 8192;
+    public final static int MAX_MAP_ENTSTRING = 0x40000;
+    public final static int MAX_MAP_TEXINFO = 8192;
+    public final static int MAX_MAP_AREAS = 256;
+    public final static int MAX_MAP_AREAPORTALS = 1024;
+    public final static int MAX_MAP_PLANES = 65536;
+    public final static int MAX_MAP_NODES = 65536;
+    public final static int MAX_MAP_BRUSHSIDES = 65536;
+    public final static int MAX_MAP_LEAFS = 65536;
+    public final static int MAX_MAP_LEAFBRUSHES = 65536;
+    public final static int MAX_MAP_SURFEDGES = 256000;
+    public final static int MAX_MAP_VISIBILITY = 0x100000;
+    // 0-2 are axial planes
+    public final static int PLANE_X = 0;
+    public final static int PLANE_Y = 1;
+    public final static int PLANE_Z = 2;
+    public final static int PLANE_ANYZ = 5;
+    public final static int LUMP_ENTITIES = 0;
+    public final static int LUMP_PLANES = 1;
+    public final static int LUMP_VERTEXES = 2;
+    public final static int LUMP_VISIBILITY = 3;
+    public final static int LUMP_NODES = 4;
+    public final static int LUMP_TEXINFO = 5;
+    public final static int LUMP_FACES = 6;
+    public final static int LUMP_LIGHTING = 7;
+    public final static int LUMP_LEAFS = 8;
+    public final static int LUMP_LEAFFACES = 9;
+    public final static int LUMP_LEAFBRUSHES = 10;
+    public final static int LUMP_EDGES = 11;
+    public final static int LUMP_SURFEDGES = 12;
+    public final static int LUMP_MODELS = 13;
+    public final static int LUMP_BRUSHES = 14;
+    public final static int LUMP_BRUSHSIDES = 15;
+    public final static int LUMP_AREAS = 17;
+    public final static int LUMP_AREAPORTALS = 18;
+    public final static int HEADER_LUMPS = 19;
+    public final static int ALIAS_VERSION = 8;
+    public static final String GAMEVERSION = "baseq2";
+    public static final int API_VERSION = 3; // ref_library (refexport_t)
+    public final static int DVIS_PVS = 0;
+    public final static int DVIS_PHS = 1;
+    // ----------------
+    // client/keydest_t
+    public static final int key_game = 0;
+    public static final int key_console = 1;
+    public static final int key_message = 2;
+    public static final int key_menu = 3;
+    // ---------------
+    // server/server.h
+    public static final int cs_free = 0; // can be reused for a new connection
+    public static final int cs_zombie = 1; // client has been disconnected, but don't reuse
+    // connection for a couple seconds
+    public static final int cs_connected = 2; // has been assigned to a client_t, but not in game yet
+    public static final int cs_spawned = 3;
+    public static final int MAX_CHALLENGES = 1024;
+    public static final int ss_dead = 0; // no map loaded
+    public static final int ss_loading = 1; // spawning level edicts
+    public static final int ss_game = 2; // actively running
+    public static final int ss_cinematic = 3;
+    public static final int ss_demo = 4;
+    public static final int ss_pic = 5;
+    public final static int SV_OUTPUTBUF_LENGTH = (MAX_MSGLEN - 16);
+    public final static int RD_CLIENT = 1;
+    public final static int RD_PACKET = 2;
+    public final static int RATE_MESSAGES = 10;
+    public final static int LATENCY_COUNTS = 16;
+    public static final int MAXCMDLINE = 256;
+    public static final int MAX_MASTERS = 8;
+    //server/sv_world.h
+    public static final int AREA_DEPTH = 4;
+    public static final int AREA_NODES = 32;
+    public static final int EXEC_NOW = 0;
+    public static final int EXEC_INSERT = 1;
+    public static final int EXEC_APPEND = 2;
+    //client/qmenu.h
+    public final static int MAXMENUITEMS = 64;
+    public final static int MTYPE_SLIDER = 0;
+    public final static int MTYPE_LIST = 1;
+    public final static int MTYPE_ACTION = 2;
+    public final static int MTYPE_SPINCONTROL = 3;
+    public final static int MTYPE_SEPARATOR = 4;
+    public final static int MTYPE_FIELD = 5;
+    public final static int K_ENTER = 13;
+    public final static int K_ESCAPE = 27;
+
+    // normal keys should be passed as lowercased ascii
+    public final static int K_UPARROW = 128;
+    public final static int K_DOWNARROW = 129;
+    public final static int K_LEFTARROW = 130;
+    public final static int K_RIGHTARROW = 131;
+    public final static int QMF_LEFT_JUSTIFY = 0x00000001;
+    public final static int QMF_GRAYED = 0x00000002;
+    public final static int QMF_NUMBERSONLY = 0x00000004;
+    public final static int RCOLUMN_OFFSET = 16;
+    public final static int LCOLUMN_OFFSET = -16;
+    public final static int MAX_PLAYERMODELS = 1024;
+    public final static int MAX_LOCAL_SERVERS = 8;
+    public final static String NO_SERVER_STRING = "<no server>";
+    public final static int NUM_ADDRESSBOOK_ENTRIES = 9;
+    public final static int STEPSIZE = 18;
+    public static final float MOVE_STOP_EPSILON = 0.1f;
+    public final static float MIN_STEP_NORMAL = 0.7f; // can't step up onto very steep slopes
+    // datentyp konstanten
+    // groesse in bytes
+    public final static boolean LITTLE_ENDIAN = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN);
+    public final static int SIZE_OF_SHORT = 2;
+    public final static int SIZE_OF_INT = 4;
+    public final static int SIZE_OF_FLOAT = 4;
+    public static final int CMD_BACKUP = 64; // allow a lot of command backups for very fast systems
+
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/EFX/EFXEffect.java b/src/main/java/lwjake2/EFX/EFXEffect.java
new file mode 100644 (file)
index 0000000..d04ccbf
--- /dev/null
@@ -0,0 +1,80 @@
+/* 
+ * Copyright (C) 2011 Ethan "flibitijibibo" Lee
+ * 
+ * This file is part of flibitEFX.
+ * 
+ * flibitEFX is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * flibitEFX is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with flibitEFX.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package lwjake2.EFX;
+
+import org.lwjgl.openal.EFX10;
+
+/**
+ * @author Ethan "flibitijibibo" Lee
+ */
+
+public abstract class EFXEffect {
+
+    private final int slotIndex;
+    private final int effectIndex;
+
+    /**
+     * Constructor creates an EFX effect slot containing an effect.
+     *
+     * @param efxEffect The EFX10 effect type (i.e. EFX10.AL_EFFECT_REVERB)
+     */
+    public EFXEffect(int efxEffect) {
+        slotIndex = EFX10.alGenAuxiliaryEffectSlots();
+        effectIndex = EFX10.alGenEffects();
+        // TODO: Check to see if efxEffect is a valid AL_EFFECT_TYPE.
+        EFX10.alEffecti(effectIndex, EFX10.AL_EFFECT_TYPE, efxEffect);
+        EFX10.alAuxiliaryEffectSloti(slotIndex, EFX10.AL_EFFECTSLOT_EFFECT, effectIndex);
+    }
+
+    /**
+     * Unloads the EFX effect and effect slot.
+     */
+    public void killEffect() {
+        EFX10.alDeleteEffects(effectIndex);
+        EFX10.alDeleteAuxiliaryEffectSlots(slotIndex);
+    }
+
+    /**
+     * Returns the index of the EFX effect slot.
+     *
+     * @return The index of the EFX effect slot
+     */
+    public int getIndex() {
+        return slotIndex;
+    }
+
+    /**
+     * Applies an effect property to this EFX effect.
+     *
+     * @param passedProperty The EFX10 effect property
+     * @param passedValue    The EFX10 effect value
+     */
+    protected void addEffectf(int passedProperty, float passedValue) {
+        EFX10.alEffectf(effectIndex, passedProperty, passedValue);
+    }
+
+    /**
+     * Applies an effect property to this EFX effect.
+     *
+     * @param passedValue The EFX10 effect value
+     */
+    protected void addEffecti(int passedValue) {
+        EFX10.alEffecti(effectIndex, EFX10.AL_RING_MODULATOR_WAVEFORM, passedValue);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/EFX/EFXEffectEcho.java b/src/main/java/lwjake2/EFX/EFXEffectEcho.java
new file mode 100644 (file)
index 0000000..368da99
--- /dev/null
@@ -0,0 +1,105 @@
+/* 
+ * Copyright (C) 2011 Ethan "flibitijibibo" Lee
+ * 
+ * This file is part of flibitEFX.
+ * 
+ * flibitEFX is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * flibitEFX is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with flibitEFX.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package lwjake2.EFX;
+
+import org.lwjgl.openal.EFX10;
+
+/**
+ * @author Ethan "flibitijibibo" Lee
+ */
+
+public class EFXEffectEcho extends EFXEffect {
+
+    /**
+     * Constructor creates a generic echo effect.
+     */
+    public EFXEffectEcho() {
+        super(EFX10.AL_EFFECT_ECHO);
+    }
+
+    /**
+     * Sets the amount of high frequency damping to apply to the echo effect.
+     *
+     * @param passedValue The new damping value to apply to the echo effect
+     */
+    public void setDamping(float passedValue) {
+        if (passedValue < EFX10.AL_ECHO_MIN_DAMPING)
+            addEffectf(EFX10.AL_ECHO_DAMPING, EFX10.AL_ECHO_MIN_DAMPING);
+        else if (passedValue > EFX10.AL_ECHO_MAX_DAMPING)
+            addEffectf(EFX10.AL_ECHO_DAMPING, EFX10.AL_ECHO_MAX_DAMPING);
+        else
+            addEffectf(EFX10.AL_ECHO_DAMPING, passedValue);
+    }
+
+    /**
+     * Sets the echo effect's delay time.
+     *
+     * @param passedValue The echo effect's new delay time
+     */
+    public void setDelay(float passedValue) {
+        if (passedValue < EFX10.AL_ECHO_MIN_DELAY)
+            addEffectf(EFX10.AL_ECHO_DELAY, EFX10.AL_ECHO_MIN_DELAY);
+        else if (passedValue > EFX10.AL_ECHO_MAX_DELAY)
+            addEffectf(EFX10.AL_ECHO_DELAY, EFX10.AL_ECHO_MAX_DELAY);
+        else
+            addEffectf(EFX10.AL_ECHO_DELAY, passedValue);
+    }
+
+    /**
+     * Sets the amount of feedback to echo back.
+     *
+     * @param passedValue The new feedback volume of the echo effect
+     */
+    public void setFeedback(float passedValue) {
+        if (passedValue < EFX10.AL_ECHO_MIN_FEEDBACK)
+            addEffectf(EFX10.AL_ECHO_FEEDBACK, EFX10.AL_ECHO_MIN_FEEDBACK);
+        else if (passedValue > EFX10.AL_ECHO_MAX_FEEDBACK)
+            addEffectf(EFX10.AL_ECHO_FEEDBACK, EFX10.AL_ECHO_MAX_FEEDBACK);
+        else
+            addEffectf(EFX10.AL_ECHO_FEEDBACK, passedValue);
+    }
+
+    /**
+     * Sets the delay between each echo "tap".
+     *
+     * @param passedValue The new delay time between each echo "tap"
+     */
+    public void setLRDelay(float passedValue) {
+        if (passedValue < EFX10.AL_ECHO_MIN_LRDELAY)
+            addEffectf(EFX10.AL_ECHO_LRDELAY, EFX10.AL_ECHO_MIN_LRDELAY);
+        else if (passedValue > EFX10.AL_ECHO_MAX_LRDELAY)
+            addEffectf(EFX10.AL_ECHO_LRDELAY, EFX10.AL_ECHO_MAX_LRDELAY);
+        else
+            addEffectf(EFX10.AL_ECHO_LRDELAY, passedValue);
+    }
+
+    /**
+     * Sets the amount of hard panning allowed for each echo.
+     *
+     * @param passedValue The new level of flexibility for the echo effect's panning
+     */
+    public void setSpread(float passedValue) {
+        if (passedValue < EFX10.AL_ECHO_MIN_SPREAD)
+            addEffectf(EFX10.AL_ECHO_SPREAD, EFX10.AL_ECHO_MIN_SPREAD);
+        else if (passedValue > EFX10.AL_ECHO_MAX_SPREAD)
+            addEffectf(EFX10.AL_ECHO_SPREAD, EFX10.AL_ECHO_MAX_SPREAD);
+        else
+            addEffectf(EFX10.AL_ECHO_SPREAD, passedValue);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/EFX/EFXEffectReverb.java b/src/main/java/lwjake2/EFX/EFXEffectReverb.java
new file mode 100644 (file)
index 0000000..cac408c
--- /dev/null
@@ -0,0 +1,37 @@
+/* 
+ * Copyright (C) 2011 Ethan "flibitijibibo" Lee
+ * 
+ * This file is part of flibitEFX.
+ * 
+ * flibitEFX is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * flibitEFX is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with flibitEFX.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package lwjake2.EFX;
+
+import org.lwjgl.openal.EFX10;
+
+/**
+ * @author Ethan "flibitijibibo" Lee
+ */
+
+public class EFXEffectReverb extends EFXEffect {
+
+    /**
+     * Constructor creates a generic reverb effect.
+     */
+    public EFXEffectReverb() {
+        super(EFX10.AL_EFFECT_REVERB);
+    }
+
+    // TODO: Add methods for all AL_EFFECT_REVERB properties.
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/EFX/EFXEffectRingModulator.java b/src/main/java/lwjake2/EFX/EFXEffectRingModulator.java
new file mode 100644 (file)
index 0000000..d2b2802
--- /dev/null
@@ -0,0 +1,78 @@
+/* 
+ * Copyright (C) 2011 Ethan "flibitijibibo" Lee
+ * 
+ * This file is part of flibitEFX.
+ * 
+ * flibitEFX is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * flibitEFX is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with flibitEFX.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package lwjake2.EFX;
+
+import org.lwjgl.openal.EFX10;
+
+/**
+ * @author Ethan "flibitijibibo" Lee
+ */
+
+public class EFXEffectRingModulator extends EFXEffect {
+
+    public static final int SINUSOID = EFX10.AL_RING_MODULATOR_SINUSOID;
+    public static final int SAWTOOTH = EFX10.AL_RING_MODULATOR_SAWTOOTH;
+    public static final int SQUARE = EFX10.AL_RING_MODULATOR_SQUARE;
+
+    /**
+     * Constructor creates a generic ring modulator effect.
+     */
+    public EFXEffectRingModulator() {
+        super(EFX10.AL_EFFECT_RING_MODULATOR);
+    }
+
+    /**
+     * Sets the frequency of the ring modulator.
+     *
+     * @param passedValue The new frequency of the ring modulator
+     */
+    public void setFrequency(float passedValue) {
+        if (passedValue > EFX10.AL_RING_MODULATOR_MAX_FREQUENCY)
+            addEffectf(EFX10.AL_RING_MODULATOR_FREQUENCY, EFX10.AL_RING_MODULATOR_MAX_FREQUENCY);
+        else if (passedValue < EFX10.AL_RING_MODULATOR_MIN_FREQUENCY)
+            addEffectf(EFX10.AL_RING_MODULATOR_FREQUENCY, EFX10.AL_RING_MODULATOR_MIN_FREQUENCY);
+        else
+            addEffectf(EFX10.AL_RING_MODULATOR_FREQUENCY, passedValue);
+    }
+
+    /**
+     * Sets the high-pass cutoff for the ring modulator.
+     *
+     * @param passedValue The new cutoff value for the ring modulator high-pass filter
+     */
+    public void setHighPassCutoff(float passedValue) {
+        if (passedValue > EFX10.AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF)
+            addEffectf(EFX10.AL_RING_MODULATOR_HIGHPASS_CUTOFF, EFX10.AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF);
+        else if (passedValue < EFX10.AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF)
+            addEffectf(EFX10.AL_RING_MODULATOR_HIGHPASS_CUTOFF, EFX10.AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF);
+        else
+            addEffectf(EFX10.AL_RING_MODULATOR_HIGHPASS_CUTOFF, passedValue);
+    }
+
+    /**
+     * Sets the waveform of the ring modulator.
+     *
+     * @param waveform Should be EFXEffectRingModulator.SINUSOID, SAWTOOTH or SQUARE
+     */
+    public void setWaveform(int waveform) {
+        if (waveform != SINUSOID && waveform != SAWTOOTH && waveform != SQUARE)
+            return;
+        addEffecti(waveform);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/EFX/EFXFilter.java b/src/main/java/lwjake2/EFX/EFXFilter.java
new file mode 100644 (file)
index 0000000..f7da051
--- /dev/null
@@ -0,0 +1,67 @@
+/* 
+ * Copyright (C) 2011 Ethan "flibitijibibo" Lee
+ * 
+ * This file is part of flibitEFX.
+ * 
+ * flibitEFX is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * flibitEFX is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with flibitEFX.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package lwjake2.EFX;
+
+import org.lwjgl.openal.EFX10;
+
+/**
+ * @author Ethan "flibitijibibo" Lee
+ */
+
+public abstract class EFXFilter {
+
+    private final int filterIndex;
+
+    /**
+     * Constructor creates an EFX effect slot containing a filter.
+     *
+     * @param efxFilter The EFX10 filter type (i.e. EFX10.AL_FILTER_HIGHPASS)
+     */
+    public EFXFilter(int efxFilter) {
+        filterIndex = EFX10.alGenFilters();
+        // TODO: Check to see if efxFilter is a valid AL_FILTER_TYPE.
+        EFX10.alFilteri(filterIndex, EFX10.AL_FILTER_TYPE, efxFilter);
+    }
+
+    /**
+     * Unloads the EFX filter.
+     */
+    public void killFilter() {
+        EFX10.alDeleteFilters(filterIndex);
+    }
+
+    /**
+     * Returns the index of the EFX filter.
+     *
+     * @return The index of the EFX filter
+     */
+    public int getIndex() {
+        return filterIndex;
+    }
+
+    /**
+     * Applies a filter property to this EFX filter.
+     *
+     * @param passedProperty The EFX10 filter property
+     * @param passedValue    The EFX10 filter value
+     */
+    protected void addFilter(int passedProperty, float passedValue) {
+        EFX10.alFilterf(filterIndex, passedProperty, passedValue);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/EFX/EFXFilterLowPass.java b/src/main/java/lwjake2/EFX/EFXFilterLowPass.java
new file mode 100644 (file)
index 0000000..67395d4
--- /dev/null
@@ -0,0 +1,53 @@
+/* 
+ * Copyright (C) 2011 Ethan "flibitijibibo" Lee
+ * 
+ * This file is part of flibitEFX.
+ * 
+ * flibitEFX is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * flibitEFX is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with flibitEFX.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package lwjake2.EFX;
+
+import org.lwjgl.openal.EFX10;
+
+/**
+ * @author Ethan "flibitijibibo" Lee
+ */
+
+public class EFXFilterLowPass extends EFXFilter {
+
+    /**
+     * Constructor creates a generic low-pass filter.
+     */
+    public EFXFilterLowPass() {
+        super(EFX10.AL_FILTER_LOWPASS);
+    }
+
+    /**
+     * Sets the gain of the low-pass filter.
+     *
+     * @param passedValue The new value of the low-pass filter gain
+     */
+    public void setGain(float passedValue) {
+        addFilter(EFX10.AL_LOWPASS_GAIN, passedValue);
+    }
+
+    /**
+     * Sets the gain of the low-pass filter's high frequencies.
+     *
+     * @param passedValue The new value of the low-pass filter's HF gain
+     */
+    public void setGainHF(float passedValue) {
+        addFilter(EFX10.AL_LOWPASS_GAINHF, passedValue);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/Globals.java b/src/main/java/lwjake2/Globals.java
new file mode 100644 (file)
index 0000000..3ef60f5
--- /dev/null
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2;
+
+import lwjake2.client.*;
+import lwjake2.game.CvarT;
+import lwjake2.game.cmdalias_t;
+import lwjake2.game.entity_state_t;
+import lwjake2.qcommon.NetadrT;
+import lwjake2.qcommon.sizebuf_t;
+import lwjake2.render.DummyRenderer;
+
+import java.io.RandomAccessFile;
+import java.util.Random;
+
+/**
+ * Globals ist the collection of global variables and constants.
+ * It is more elegant to use these vars by inheritance to separate
+ * it with eclipse refactoring later.
+ * <p>
+ * As consequence you don't have to touch that much code this time.
+ */
+public class Globals extends Defines {
+
+    public static final String __DATE__ = "2003";
+
+    public static final float VERSION = 3.21f;
+
+    public static final String BASEDIRNAME = "baseq2";
+    // client/anorms.h
+    public static final float bytedirs[][] = { /**
+    */
+            {-0.525731f, 0.000000f, 0.850651f}, {
+            -0.442863f, 0.238856f, 0.864188f}, {
+            -0.295242f, 0.000000f, 0.955423f}, {
+            -0.309017f, 0.500000f, 0.809017f}, {
+            -0.162460f, 0.262866f, 0.951056f}, {
+            0.000000f, 0.000000f, 1.000000f}, {
+            0.000000f, 0.850651f, 0.525731f}, {
+            -0.147621f, 0.716567f, 0.681718f}, {
+            0.147621f, 0.716567f, 0.681718f}, {
+            0.000000f, 0.525731f, 0.850651f}, {
+            0.309017f, 0.500000f, 0.809017f}, {
+            0.525731f, 0.000000f, 0.850651f}, {
+            0.295242f, 0.000000f, 0.955423f}, {
+            0.442863f, 0.238856f, 0.864188f}, {
+            0.162460f, 0.262866f, 0.951056f}, {
+            -0.681718f, 0.147621f, 0.716567f}, {
+            -0.809017f, 0.309017f, 0.500000f}, {
+            -0.587785f, 0.425325f, 0.688191f}, {
+            -0.850651f, 0.525731f, 0.000000f}, {
+            -0.864188f, 0.442863f, 0.238856f}, {
+            -0.716567f, 0.681718f, 0.147621f}, {
+            -0.688191f, 0.587785f, 0.425325f}, {
+            -0.500000f, 0.809017f, 0.309017f}, {
+            -0.238856f, 0.864188f, 0.442863f}, {
+            -0.425325f, 0.688191f, 0.587785f}, {
+            -0.716567f, 0.681718f, -0.147621f}, {
+            -0.500000f, 0.809017f, -0.309017f}, {
+            -0.525731f, 0.850651f, 0.000000f}, {
+            0.000000f, 0.850651f, -0.525731f}, {
+            -0.238856f, 0.864188f, -0.442863f}, {
+            0.000000f, 0.955423f, -0.295242f}, {
+            -0.262866f, 0.951056f, -0.162460f}, {
+            0.000000f, 1.000000f, 0.000000f}, {
+            0.000000f, 0.955423f, 0.295242f}, {
+            -0.262866f, 0.951056f, 0.162460f}, {
+            0.238856f, 0.864188f, 0.442863f}, {
+            0.262866f, 0.951056f, 0.162460f}, {
+            0.500000f, 0.809017f, 0.309017f}, {
+            0.238856f, 0.864188f, -0.442863f}, {
+            0.262866f, 0.951056f, -0.162460f}, {
+            0.500000f, 0.809017f, -0.309017f}, {
+            0.850651f, 0.525731f, 0.000000f}, {
+            0.716567f, 0.681718f, 0.147621f}, {
+            0.716567f, 0.681718f, -0.147621f}, {
+            0.525731f, 0.850651f, 0.000000f}, {
+            0.425325f, 0.688191f, 0.587785f}, {
+            0.864188f, 0.442863f, 0.238856f}, {
+            0.688191f, 0.587785f, 0.425325f}, {
+            0.809017f, 0.309017f, 0.500000f}, {
+            0.681718f, 0.147621f, 0.716567f}, {
+            0.587785f, 0.425325f, 0.688191f}, {
+            0.955423f, 0.295242f, 0.000000f}, {
+            1.000000f, 0.000000f, 0.000000f}, {
+            0.951056f, 0.162460f, 0.262866f}, {
+            0.850651f, -0.525731f, 0.000000f}, {
+            0.955423f, -0.295242f, 0.000000f}, {
+            0.864188f, -0.442863f, 0.238856f}, {
+            0.951056f, -0.162460f, 0.262866f}, {
+            0.809017f, -0.309017f, 0.500000f}, {
+            0.681718f, -0.147621f, 0.716567f}, {
+            0.850651f, 0.000000f, 0.525731f}, {
+            0.864188f, 0.442863f, -0.238856f}, {
+            0.809017f, 0.309017f, -0.500000f}, {
+            0.951056f, 0.162460f, -0.262866f}, {
+            0.525731f, 0.000000f, -0.850651f}, {
+            0.681718f, 0.147621f, -0.716567f}, {
+            0.681718f, -0.147621f, -0.716567f}, {
+            0.850651f, 0.000000f, -0.525731f}, {
+            0.809017f, -0.309017f, -0.500000f}, {
+            0.864188f, -0.442863f, -0.238856f}, {
+            0.951056f, -0.162460f, -0.262866f}, {
+            0.147621f, 0.716567f, -0.681718f}, {
+            0.309017f, 0.500000f, -0.809017f}, {
+            0.425325f, 0.688191f, -0.587785f}, {
+            0.442863f, 0.238856f, -0.864188f}, {
+            0.587785f, 0.425325f, -0.688191f}, {
+            0.688191f, 0.587785f, -0.425325f}, {
+            -0.147621f, 0.716567f, -0.681718f}, {
+            -0.309017f, 0.500000f, -0.809017f}, {
+            0.000000f, 0.525731f, -0.850651f}, {
+            -0.525731f, 0.000000f, -0.850651f}, {
+            -0.442863f, 0.238856f, -0.864188f}, {
+            -0.295242f, 0.000000f, -0.955423f}, {
+            -0.162460f, 0.262866f, -0.951056f}, {
+            0.000000f, 0.000000f, -1.000000f}, {
+            0.295242f, 0.000000f, -0.955423f}, {
+            0.162460f, 0.262866f, -0.951056f}, {
+            -0.442863f, -0.238856f, -0.864188f}, {
+            -0.309017f, -0.500000f, -0.809017f}, {
+            -0.162460f, -0.262866f, -0.951056f}, {
+            0.000000f, -0.850651f, -0.525731f}, {
+            -0.147621f, -0.716567f, -0.681718f}, {
+            0.147621f, -0.716567f, -0.681718f}, {
+            0.000000f, -0.525731f, -0.850651f}, {
+            0.309017f, -0.500000f, -0.809017f}, {
+            0.442863f, -0.238856f, -0.864188f}, {
+            0.162460f, -0.262866f, -0.951056f}, {
+            0.238856f, -0.864188f, -0.442863f}, {
+            0.500000f, -0.809017f, -0.309017f}, {
+            0.425325f, -0.688191f, -0.587785f}, {
+            0.716567f, -0.681718f, -0.147621f}, {
+            0.688191f, -0.587785f, -0.425325f}, {
+            0.587785f, -0.425325f, -0.688191f}, {
+            0.000000f, -0.955423f, -0.295242f}, {
+            0.000000f, -1.000000f, 0.000000f}, {
+            0.262866f, -0.951056f, -0.162460f}, {
+            0.000000f, -0.850651f, 0.525731f}, {
+            0.000000f, -0.955423f, 0.295242f}, {
+            0.238856f, -0.864188f, 0.442863f}, {
+            0.262866f, -0.951056f, 0.162460f}, {
+            0.500000f, -0.809017f, 0.309017f}, {
+            0.716567f, -0.681718f, 0.147621f}, {
+            0.525731f, -0.850651f, 0.000000f}, {
+            -0.238856f, -0.864188f, -0.442863f}, {
+            -0.500000f, -0.809017f, -0.309017f}, {
+            -0.262866f, -0.951056f, -0.162460f}, {
+            -0.850651f, -0.525731f, 0.000000f}, {
+            -0.716567f, -0.681718f, -0.147621f}, {
+            -0.716567f, -0.681718f, 0.147621f}, {
+            -0.525731f, -0.850651f, 0.000000f}, {
+            -0.500000f, -0.809017f, 0.309017f}, {
+            -0.238856f, -0.864188f, 0.442863f}, {
+            -0.262866f, -0.951056f, 0.162460f}, {
+            -0.864188f, -0.442863f, 0.238856f}, {
+            -0.809017f, -0.309017f, 0.500000f}, {
+            -0.688191f, -0.587785f, 0.425325f}, {
+            -0.681718f, -0.147621f, 0.716567f}, {
+            -0.442863f, -0.238856f, 0.864188f}, {
+            -0.587785f, -0.425325f, 0.688191f}, {
+            -0.309017f, -0.500000f, 0.809017f}, {
+            -0.147621f, -0.716567f, 0.681718f}, {
+            -0.425325f, -0.688191f, 0.587785f}, {
+            -0.162460f, -0.262866f, 0.951056f}, {
+            0.442863f, -0.238856f, 0.864188f}, {
+            0.162460f, -0.262866f, 0.951056f}, {
+            0.309017f, -0.500000f, 0.809017f}, {
+            0.147621f, -0.716567f, 0.681718f}, {
+            0.000000f, -0.525731f, 0.850651f}, {
+            0.425325f, -0.688191f, 0.587785f}, {
+            0.587785f, -0.425325f, 0.688191f}, {
+            0.688191f, -0.587785f, 0.425325f}, {
+            -0.955423f, 0.295242f, 0.000000f}, {
+            -0.951056f, 0.162460f, 0.262866f}, {
+            -1.000000f, 0.000000f, 0.000000f}, {
+            -0.850651f, 0.000000f, 0.525731f}, {
+            -0.955423f, -0.295242f, 0.000000f}, {
+            -0.951056f, -0.162460f, 0.262866f}, {
+            -0.864188f, 0.442863f, -0.238856f}, {
+            -0.951056f, 0.162460f, -0.262866f}, {
+            -0.809017f, 0.309017f, -0.500000f}, {
+            -0.864188f, -0.442863f, -0.238856f}, {
+            -0.951056f, -0.162460f, -0.262866f}, {
+            -0.809017f, -0.309017f, -0.500000f}, {
+            -0.681718f, 0.147621f, -0.716567f}, {
+            -0.681718f, -0.147621f, -0.716567f}, {
+            -0.850651f, 0.000000f, -0.525731f}, {
+            -0.688191f, 0.587785f, -0.425325f}, {
+            -0.587785f, 0.425325f, -0.688191f}, {
+            -0.425325f, 0.688191f, -0.587785f}, {
+            -0.425325f, -0.688191f, -0.587785f}, {
+            -0.587785f, -0.425325f, -0.688191f}, {
+            -0.688191f, -0.587785f, -0.425325f}
+    };
+    public static final console_t con = new console_t();
+    public static final sizebuf_t net_message = new sizebuf_t();
+    public static final sizebuf_t cmd_text = new sizebuf_t();
+    public static final byte[] defer_text_buf = new byte[8192];
+    //=============================================================================
+    public static final byte[] cmd_text_buf = new byte[8192];
+    public static final byte[] net_message_buffer = new byte[MAX_MSGLEN];
+    public static final ClientStaticT clientStaticT = new ClientStaticT();
+    public static final centity_t[] cl_entities = new centity_t[Defines.MAX_EDICTS];
+    public static final entity_state_t[] cl_parse_entities = new entity_state_t[Defines.MAX_PARSE_ENTITIES];
+    public static final viddef_t viddef = new viddef_t();
+    public static final String[] keybindings = new String[256];
+    public static final boolean[] keydown = new boolean[256];
+    public static final byte[][] key_lines = new byte[32][];
+    public static final vrect_t scr_vrect = new vrect_t();
+    public static final int chat_bufferlen = 0;
+    public static final NetadrT net_from = new NetadrT();
+    public static final float[] vec3_origin = {0.0f, 0.0f, 0.0f};
+    public static final int vidref_val = VIDREF_GL;
+    public static final Random rnd = new Random();
+    /*
+     * global variables
+     */
+    public static int curtime = 0;
+    public static boolean cmd_wait;
+    public static int alias_count;
+    public static int c_traces;
+    public static int c_brush_traces;
+    public static int c_pointcontents;
+    public static int server_state;
+    public static CvarT cl_add_blend;
+    public static CvarT cl_add_entities;
+    public static CvarT cl_add_lights;
+    public static CvarT cl_add_particles;
+    public static CvarT cl_anglespeedkey;
+    public static CvarT cl_autoskins;
+    public static CvarT cl_footsteps;
+    public static CvarT cl_forwardspeed;
+    public static CvarT cl_gun;
+    public static CvarT cl_maxfps;
+    public static CvarT cl_noskins;
+    public static CvarT cl_pitchspeed;
+    public static CvarT cl_predict;
+    public static CvarT cl_run;
+    public static CvarT cl_sidespeed;
+    public static CvarT cl_stereo;
+    public static CvarT cl_stereo_separation;
+    public static CvarT cl_timedemo = new CvarT();
+    public static CvarT cl_timeout;
+    public static CvarT cl_upspeed;
+    public static CvarT cl_yawspeed;
+    public static CvarT dedicated;
+    public static CvarT developer;
+    public static CvarT freelook;
+    public static CvarT lookspring;
+    public static CvarT lookstrafe;
+    public static CvarT nostdout;
+    public static CvarT sensitivity;
+    public static CvarT showtrace;
+    public static CvarT in_mouse;
+    /*
+    =============================================================================
+
+                            COMMAND BUFFER
+
+    =============================================================================
+    */
+    public static CvarT in_joystick;
+    public static cmdalias_t cmd_alias;
+    public static int time_before_game;
+    public static int time_after_game;
+    public static int time_before_ref;
+    public static int time_after_ref;
+    public static CvarT m_pitch;
+    public static CvarT m_yaw;
+    public static CvarT m_forward;
+    public static CvarT m_side;
+    public static CvarT cl_lightlevel;
+    //
+    //    userinfo
+    //
+    public static CvarT info_password;
+    public static CvarT info_spectator;
+    public static CvarT name;
+    public static CvarT skin;
+    public static CvarT rate;
+    public static CvarT fov;
+    public static CvarT msg;
+    public static CvarT hand;
+    public static CvarT gender;
+    public static CvarT gender_auto;
+    public static CvarT cl_vwep;
+    public static ClientStateT clientStateT = new ClientStateT();
+    public static CvarT rcon_client_password;
+    public static CvarT rcon_address;
+    public static CvarT cl_shownet;
+    public static CvarT cl_showmiss;
+    public static CvarT cl_showclamp;
+    public static CvarT cl_paused;
+    public static boolean userinfo_modified = false;
+    public static CvarT cvar_vars;
+    public static CvarT con_notifytime;
+    // Renderer interface used by VideoDriver, SCR, ...
+    public static refexport_t re = new DummyRenderer();
+    public static boolean chat_team = false;
+    public static String chat_buffer = "";
+    public static int key_linepos;
+    public static int edit_line;
+    public static CvarT crosshair;
+    public static int sys_frame_time;
+    // logfile
+    public static RandomAccessFile logfile = null;
+    public static CvarT m_filter;
+
+    static {
+        for (int i = 0; i < cl_entities.length; i++) {
+            cl_entities[i] = new centity_t();
+        }
+    }
+
+    static {
+        for (int i = 0; i < cl_parse_entities.length; i++) {
+            cl_parse_entities[i] = new entity_state_t(null);
+        }
+    }
+
+    static {
+        for (int i = 0; i < key_lines.length; i++)
+            key_lines[i] = new byte[Defines.MAXCMDLINE];
+    }
+}
diff --git a/src/main/java/lwjake2/LWJake2.java b/src/main/java/lwjake2/LWJake2.java
new file mode 100644 (file)
index 0000000..7612fcc
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2;
+
+import lwjake2.qcommon.Com;
+import lwjake2.qcommon.Cvar;
+import lwjake2.qcommon.QCommon;
+import lwjake2.sys.Timer;
+
+/**
+ * Jake2 is the main class of Quake2 for Java.
+ */
+public final class LWJake2 {
+
+    /**
+     * main is used to start the game. Quake2 for Java supports the following
+     * command line arguments:
+     *
+     * @param args
+     */
+    public static void main(String[] args) {
+
+        boolean dedicated = isDedicated(args);
+
+        // TODO: check if dedicated is set in config file
+
+        Globals.dedicated = Cvar.get("dedicated", "0", QCommon.CVAR_NOSET);
+
+        if (dedicated)
+            Globals.dedicated.value = 1.0f;
+
+        QCommon.init(argsToCArgs(args));
+
+        Globals.nostdout = Cvar.get("nostdout", "0", 0);
+
+        mainGameLoop();
+    }
+
+    private static void mainGameLoop() {
+        int oldTimeMs = Timer.getCurrentTimeMillis();
+        int currentTimeMs;
+        int timeSinceLastFrameMs;
+
+        while (true) {
+            // find time spending rendering last frame
+            currentTimeMs = Timer.getCurrentTimeMillis();
+            timeSinceLastFrameMs = currentTimeMs - oldTimeMs;
+
+            if (timeSinceLastFrameMs > 0)
+                QCommon.doFrame(timeSinceLastFrameMs);
+
+            oldTimeMs = currentTimeMs;
+        }
+    }
+
+    /**
+     * in C the first arg is the filename
+     */
+    private static String[] argsToCArgs(String[] args) {
+        int argc = (args == null) ? 1 : args.length + 1;
+        String[] c_args = new String[argc];
+        c_args[0] = "LWJake2";
+        if (argc > 1) {
+            System.arraycopy(args, 0, c_args, 1, argc - 1);
+        }
+        return c_args;
+    }
+
+    private static boolean isDedicated(String[] args) {
+        boolean dedicated = false;
+
+        // check if we are in dedicated mode to hide the java dialog.
+        for (int n = 0; n < args.length; n++) {
+            if (args[n].equals("+set")) {
+                if (n++ >= args.length)
+                    break;
+
+                if (!args[n].equals("dedicated"))
+                    continue;
+
+                if (n++ >= args.length)
+                    break;
+
+                if (args[n].equals("1") || args[n].equals("\"1\"")) {
+                    Com.Printf("Starting in dedicated mode.\n");
+                    dedicated = true;
+                }
+            }
+        }
+        return dedicated;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/VisualizeCode.java b/src/main/java/lwjake2/VisualizeCode.java
new file mode 100644 (file)
index 0000000..921598b
--- /dev/null
@@ -0,0 +1,17 @@
+package lwjake2;
+
+import eu.svjatoslav.inspector.java.structure.ClassGraph;
+
+public class VisualizeCode {
+    public static void main(String[] args) {
+        final ClassGraph graph = new ClassGraph();
+
+        graph.addProject(".");
+
+        graph.setKeepDotFile(true);
+
+        graph.hideOrphanedClasses();
+
+        graph.generateGraph("Quake source");
+    }
+}
diff --git a/src/main/java/lwjake2/client/CL_ents.java b/src/main/java/lwjake2/client/CL_ents.java
new file mode 100644 (file)
index 0000000..97138a2
--- /dev/null
@@ -0,0 +1,1115 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.Move;
+import lwjake2.game.entity_state_t;
+import lwjake2.game.player_state_t;
+import lwjake2.qcommon.Com;
+import lwjake2.qcommon.FS;
+import lwjake2.qcommon.MSG;
+import lwjake2.util.Math3D;
+
+/**
+ * CL_ents
+ */
+//        cl_ents.c -- entity parsing and management
+/*
+ * =========================================================================
+ * 
+ * FRAME PARSING
+ * 
+ * =========================================================================
+ */
+public class CL_ents {
+
+    // stack variable
+    public static final entity_t ent = new entity_t();
+    // stack variable
+    static final int[] bitcounts = new int[32]; /// just for protocol profiling
+    static final int[] bfg_lightramp = {300, 400, 600, 300, 150, 75};
+    // call by reference
+    private static final int[] iw = {0};
+
+    /*
+     * ================= CL_ParseEntityBits
+     *
+     * Returns the entity number and the header bits =================
+     */
+    public static int ParseEntityBits(int bits[]) {
+        int b, total;
+        int i;
+        int number;
+
+        total = MSG.ReadByte(Globals.net_message);
+        if ((total & Defines.U_MOREBITS1) != 0) {
+
+            b = MSG.ReadByte(Globals.net_message);
+            total |= b << 8;
+        }
+        if ((total & Defines.U_MOREBITS2) != 0) {
+
+            b = MSG.ReadByte(Globals.net_message);
+            total |= b << 16;
+        }
+        if ((total & Defines.U_MOREBITS3) != 0) {
+
+            b = MSG.ReadByte(Globals.net_message);
+            total |= b << 24;
+        }
+
+        // count the bits for net profiling
+        for (i = 0; i < 32; i++)
+            if ((total & (1 << i)) != 0)
+                bitcounts[i]++;
+
+        if ((total & Defines.U_NUMBER16) != 0)
+            number = MSG.ReadShort(Globals.net_message);
+        else
+            number = MSG.ReadByte(Globals.net_message);
+
+        bits[0] = total;
+
+        return number;
+    }
+
+    /*
+     * ================== CL_ParseDelta
+     *
+     * Can go from either a baseline or a previous packet_entity
+     * ==================
+     */
+    public static void ParseDelta(entity_state_t from, entity_state_t to, int number, int bits) {
+        // set everything to the state we are delta'ing from
+        to.set(from);
+
+        Math3D.vectorCopy(from.origin, to.old_origin);
+        to.number = number;
+
+        if ((bits & Defines.U_MODEL) != 0)
+            to.modelindex = MSG.ReadByte(Globals.net_message);
+        if ((bits & Defines.U_MODEL2) != 0)
+            to.modelindex2 = MSG.ReadByte(Globals.net_message);
+        if ((bits & Defines.U_MODEL3) != 0)
+            to.modelindex3 = MSG.ReadByte(Globals.net_message);
+        if ((bits & Defines.U_MODEL4) != 0)
+            to.modelindex4 = MSG.ReadByte(Globals.net_message);
+
+        if ((bits & Defines.U_FRAME8) != 0)
+            to.frame = MSG.ReadByte(Globals.net_message);
+        if ((bits & Defines.U_FRAME16) != 0)
+            to.frame = MSG.ReadShort(Globals.net_message);
+
+        if ((bits & Defines.U_SKIN8) != 0 && (bits & Defines.U_SKIN16) != 0) //used
+            // for
+            // laser
+            // colors
+            to.skinnum = MSG.ReadLong(Globals.net_message);
+        else if ((bits & Defines.U_SKIN8) != 0)
+            to.skinnum = MSG.ReadByte(Globals.net_message);
+        else if ((bits & Defines.U_SKIN16) != 0)
+            to.skinnum = MSG.ReadShort(Globals.net_message);
+
+        if ((bits & (Defines.U_EFFECTS8 | Defines.U_EFFECTS16)) == (Defines.U_EFFECTS8 | Defines.U_EFFECTS16))
+            to.effects = MSG.ReadLong(Globals.net_message);
+        else if ((bits & Defines.U_EFFECTS8) != 0)
+            to.effects = MSG.ReadByte(Globals.net_message);
+        else if ((bits & Defines.U_EFFECTS16) != 0)
+            to.effects = MSG.ReadShort(Globals.net_message);
+
+        if ((bits & (Defines.U_RENDERFX8 | Defines.U_RENDERFX16)) == (Defines.U_RENDERFX8 | Defines.U_RENDERFX16))
+            to.renderfx = MSG.ReadLong(Globals.net_message);
+        else if ((bits & Defines.U_RENDERFX8) != 0)
+            to.renderfx = MSG.ReadByte(Globals.net_message);
+        else if ((bits & Defines.U_RENDERFX16) != 0)
+            to.renderfx = MSG.ReadShort(Globals.net_message);
+
+        if ((bits & Defines.U_ORIGIN1) != 0)
+            to.origin[0] = MSG.ReadCoord(Globals.net_message);
+        if ((bits & Defines.U_ORIGIN2) != 0)
+            to.origin[1] = MSG.ReadCoord(Globals.net_message);
+        if ((bits & Defines.U_ORIGIN3) != 0)
+            to.origin[2] = MSG.ReadCoord(Globals.net_message);
+
+        if ((bits & Defines.U_ANGLE1) != 0)
+            to.angles[0] = MSG.ReadAngle(Globals.net_message);
+        if ((bits & Defines.U_ANGLE2) != 0)
+            to.angles[1] = MSG.ReadAngle(Globals.net_message);
+        if ((bits & Defines.U_ANGLE3) != 0)
+            to.angles[2] = MSG.ReadAngle(Globals.net_message);
+
+        if ((bits & Defines.U_OLDORIGIN) != 0)
+            MSG.ReadPos(Globals.net_message, to.old_origin);
+
+        if ((bits & Defines.U_SOUND) != 0)
+            to.sound = MSG.ReadByte(Globals.net_message);
+
+        if ((bits & Defines.U_EVENT) != 0)
+            to.event = MSG.ReadByte(Globals.net_message);
+        else
+            to.event = 0;
+
+        if ((bits & Defines.U_SOLID) != 0)
+            to.solid = MSG.ReadShort(Globals.net_message);
+    }
+
+    /*
+     * ================== CL_DeltaEntity
+     *
+     * Parses deltas from the given base and adds the resulting entity to the
+     * current frame ==================
+     */
+    public static void DeltaEntity(frame_t frame, int newnum, entity_state_t old, int bits) {
+        centity_t ent;
+        entity_state_t state;
+
+        ent = Globals.cl_entities[newnum];
+
+        state = Globals.cl_parse_entities[Globals.clientStateT.parse_entities & (Defines.MAX_PARSE_ENTITIES - 1)];
+        Globals.clientStateT.parse_entities++;
+        frame.num_entities++;
+
+        ParseDelta(old, state, newnum, bits);
+
+        // some data changes will force no lerping
+        if (state.modelindex != ent.current.modelindex || state.modelindex2 != ent.current.modelindex2
+                || state.modelindex3 != ent.current.modelindex3 || state.modelindex4 != ent.current.modelindex4
+                || Math.abs(state.origin[0] - ent.current.origin[0]) > 512 || Math.abs(state.origin[1] - ent.current.origin[1]) > 512
+                || Math.abs(state.origin[2] - ent.current.origin[2]) > 512 || state.event == Defines.EV_PLAYER_TELEPORT
+                || state.event == Defines.EV_OTHER_TELEPORT) {
+            ent.serverframe = -99;
+        }
+
+        if (ent.serverframe != Globals.clientStateT.frame.serverframe - 1) { // wasn't in
+            // last
+            // update, so
+            // initialize
+            // some
+            // things
+            ent.trailcount = 1024; // for diminishing rocket / grenade trails
+            // duplicate the current state so lerping doesn't hurt anything
+            ent.prev.set(state);
+            if (state.event == Defines.EV_OTHER_TELEPORT) {
+                Math3D.vectorCopy(state.origin, ent.prev.origin);
+                Math3D.vectorCopy(state.origin, ent.lerp_origin);
+            } else {
+                Math3D.vectorCopy(state.old_origin, ent.prev.origin);
+                Math3D.vectorCopy(state.old_origin, ent.lerp_origin);
+            }
+        } else { // shuffle the last state to previous
+            // Copy !
+            ent.prev.set(ent.current);
+        }
+
+        ent.serverframe = Globals.clientStateT.frame.serverframe;
+        // Copy !
+        ent.current.set(state);
+    }
+
+    /*
+     * ================== CL_ParsePacketEntities
+     *
+     * An svc_packetentities has just been parsed, deal with the rest of the
+     * data stream. ==================
+     */
+    public static void ParsePacketEntities(frame_t oldframe, frame_t newframe) {
+        int newnum;
+        int bits = 0;
+
+        entity_state_t oldstate = null;
+        int oldnum;
+
+        newframe.parse_entities = Globals.clientStateT.parse_entities;
+        newframe.num_entities = 0;
+
+        // delta from the entities present in oldframe
+        int oldindex = 0;
+        if (oldframe == null)
+            oldnum = 99999;
+        else {
+            // oldindex == 0. hoz
+            //                 if (oldindex >= oldframe.num_entities)
+            //                         oldnum = 99999;
+            //                 else {
+            oldstate = Globals.cl_parse_entities[(oldframe.parse_entities + oldindex) & (Defines.MAX_PARSE_ENTITIES - 1)];
+            oldnum = oldstate.number;
+            //                 }
+        }
+
+        while (true) {
+            //int iw[] = { bits };
+            iw[0] = bits;
+            newnum = ParseEntityBits(iw);
+            bits = iw[0];
+
+            if (newnum >= Defines.MAX_EDICTS)
+                Com.Error(Defines.ERR_DROP, "CL_ParsePacketEntities: bad number:" + newnum);
+
+            if (Globals.net_message.readcount > Globals.net_message.cursize)
+                Com.Error(Defines.ERR_DROP, "CL_ParsePacketEntities: end of message");
+
+            if (0 == newnum)
+                break;
+
+            while (oldnum < newnum) { // one or more entities from the old
+                // packet are unchanged
+                if (Globals.cl_shownet.value == 3)
+                    Com.Printf("   unchanged: " + oldnum + "\n");
+                DeltaEntity(newframe, oldnum, oldstate, 0);
+
+                oldindex++;
+
+                if (oldindex >= oldframe.num_entities)
+                    oldnum = 99999;
+                else {
+                    oldstate = Globals.cl_parse_entities[(oldframe.parse_entities + oldindex) & (Defines.MAX_PARSE_ENTITIES - 1)];
+                    oldnum = oldstate.number;
+                }
+            }
+
+            if ((bits & Defines.U_REMOVE) != 0) { // the entity present in
+                // oldframe is not in the
+                // current frame
+                if (Globals.cl_shownet.value == 3)
+                    Com.Printf("   remove: " + newnum + "\n");
+                if (oldnum != newnum)
+                    Com.Printf("U_REMOVE: oldnum != newnum\n");
+
+                oldindex++;
+
+                if (oldindex >= oldframe.num_entities)
+                    oldnum = 99999;
+                else {
+                    oldstate = Globals.cl_parse_entities[(oldframe.parse_entities + oldindex) & (Defines.MAX_PARSE_ENTITIES - 1)];
+                    oldnum = oldstate.number;
+                }
+                continue;
+            }
+
+            if (oldnum == newnum) { // delta from previous state
+                if (Globals.cl_shownet.value == 3)
+                    Com.Printf("   delta: " + newnum + "\n");
+                DeltaEntity(newframe, newnum, oldstate, bits);
+
+                oldindex++;
+
+                if (oldindex >= oldframe.num_entities)
+                    oldnum = 99999;
+                else {
+                    oldstate = Globals.cl_parse_entities[(oldframe.parse_entities + oldindex) & (Defines.MAX_PARSE_ENTITIES - 1)];
+                    oldnum = oldstate.number;
+                }
+                continue;
+            }
+
+            if (oldnum > newnum) { // delta from baseline
+                if (Globals.cl_shownet.value == 3)
+                    Com.Printf("   baseline: " + newnum + "\n");
+                DeltaEntity(newframe, newnum, Globals.cl_entities[newnum].baseline, bits);
+            }
+
+        }
+
+        // any remaining entities in the old frame are copied over
+        while (oldnum != 99999) { // one or more entities from the old packet
+            // are unchanged
+            if (Globals.cl_shownet.value == 3)
+                Com.Printf("   unchanged: " + oldnum + "\n");
+            DeltaEntity(newframe, oldnum, oldstate, 0);
+
+            oldindex++;
+
+            if (oldindex >= oldframe.num_entities)
+                oldnum = 99999;
+            else {
+                oldstate = Globals.cl_parse_entities[(oldframe.parse_entities + oldindex) & (Defines.MAX_PARSE_ENTITIES - 1)];
+                oldnum = oldstate.number;
+            }
+        }
+    }
+
+    /*
+     * =================== CL_ParsePlayerstate ===================
+     */
+    public static void ParsePlayerstate(frame_t oldframe, frame_t newframe) {
+        int flags;
+        player_state_t state;
+        int i;
+        int statbits;
+
+        state = newframe.playerstate;
+
+        // clear to old value before delta parsing
+        if (oldframe != null)
+            state.set(oldframe.playerstate);
+        else
+            //memset (state, 0, sizeof(*state));
+            state.clear();
+
+        flags = MSG.ReadShort(Globals.net_message);
+
+        //
+        // parse the pmove_state_t
+        //
+        if ((flags & Defines.PS_M_TYPE) != 0)
+            state.pmove.pm_type = MSG.ReadByte(Globals.net_message);
+
+        if ((flags & Defines.PS_M_ORIGIN) != 0) {
+            state.pmove.origin[0] = MSG.ReadShort(Globals.net_message);
+            state.pmove.origin[1] = MSG.ReadShort(Globals.net_message);
+            state.pmove.origin[2] = MSG.ReadShort(Globals.net_message);
+        }
+
+        if ((flags & Defines.PS_M_VELOCITY) != 0) {
+            state.pmove.velocity[0] = MSG.ReadShort(Globals.net_message);
+            state.pmove.velocity[1] = MSG.ReadShort(Globals.net_message);
+            state.pmove.velocity[2] = MSG.ReadShort(Globals.net_message);
+        }
+
+        if ((flags & Defines.PS_M_TIME) != 0) {
+            state.pmove.pm_time = (byte) MSG.ReadByte(Globals.net_message);
+        }
+
+        if ((flags & Defines.PS_M_FLAGS) != 0)
+            state.pmove.pm_flags = (byte) MSG.ReadByte(Globals.net_message);
+
+        if ((flags & Defines.PS_M_GRAVITY) != 0)
+            state.pmove.gravity = MSG.ReadShort(Globals.net_message);
+
+        if ((flags & Defines.PS_M_DELTA_ANGLES) != 0) {
+            state.pmove.delta_angles[0] = MSG.ReadShort(Globals.net_message);
+            state.pmove.delta_angles[1] = MSG.ReadShort(Globals.net_message);
+            state.pmove.delta_angles[2] = MSG.ReadShort(Globals.net_message);
+        }
+
+        if (Globals.clientStateT.attractloop)
+            state.pmove.pm_type = Defines.PM_FREEZE; // demo playback
+
+        //
+        // parse the rest of the player_state_t
+        //
+        if ((flags & Defines.PS_VIEWOFFSET) != 0) {
+            state.viewoffset[0] = MSG.ReadChar(Globals.net_message) * 0.25f;
+            state.viewoffset[1] = MSG.ReadChar(Globals.net_message) * 0.25f;
+            state.viewoffset[2] = MSG.ReadChar(Globals.net_message) * 0.25f;
+        }
+
+        if ((flags & Defines.PS_VIEWANGLES) != 0) {
+            state.viewangles[0] = MSG.ReadAngle16(Globals.net_message);
+            state.viewangles[1] = MSG.ReadAngle16(Globals.net_message);
+            state.viewangles[2] = MSG.ReadAngle16(Globals.net_message);
+        }
+
+        if ((flags & Defines.PS_KICKANGLES) != 0) {
+
+            state.kick_angles[0] = MSG.ReadChar(Globals.net_message) * 0.25f;
+            state.kick_angles[1] = MSG.ReadChar(Globals.net_message) * 0.25f;
+            state.kick_angles[2] = MSG.ReadChar(Globals.net_message) * 0.25f;
+
+        }
+
+        if ((flags & Defines.PS_BLEND) != 0) {
+            state.blend[0] = MSG.ReadByte(Globals.net_message) / 255.0f;
+            state.blend[1] = MSG.ReadByte(Globals.net_message) / 255.0f;
+            state.blend[2] = MSG.ReadByte(Globals.net_message) / 255.0f;
+            state.blend[3] = MSG.ReadByte(Globals.net_message) / 255.0f;
+        }
+
+        if ((flags & Defines.PS_FOV) != 0)
+            state.fov = MSG.ReadByte(Globals.net_message);
+
+        if ((flags & Defines.PS_RDFLAGS) != 0)
+            state.rdflags = MSG.ReadByte(Globals.net_message);
+
+        // parse stats
+        statbits = MSG.ReadLong(Globals.net_message);
+        for (i = 0; i < Defines.MAX_STATS; i++)
+            if ((statbits & (1 << i)) != 0)
+                state.stats[i] = MSG.ReadShort(Globals.net_message);
+    }
+
+       /*
+     * ==========================================================================
+        * 
+        * INTERPOLATE BETWEEN FRAMES TO GET RENDERING PARMS
+        * 
+        * ==========================================================================
+        */
+
+    /*
+     * ================== CL_FireEntityEvents
+     *
+     * ==================
+     */
+    public static void FireEntityEvents(frame_t frame) {
+        entity_state_t s1;
+        int pnum, num;
+
+        for (pnum = 0; pnum < frame.num_entities; pnum++) {
+            num = (frame.parse_entities + pnum) & (Defines.MAX_PARSE_ENTITIES - 1);
+            s1 = Globals.cl_parse_entities[num];
+            if (s1.event != 0)
+                CL_fx.EntityEvent(s1);
+
+            // EF_TELEPORTER acts like an event, but is not cleared each frame
+            if ((s1.effects & Defines.EF_TELEPORTER) != 0)
+                CL_fx.TeleporterParticles(s1);
+        }
+    }
+
+    /*
+     * ================ CL_ParseFrame ================
+     */
+    public static void ParseFrame() {
+        int cmd;
+        int len;
+        frame_t old;
+
+        //memset( cl.frame, 0, sizeof(cl.frame));
+        Globals.clientStateT.frame.reset();
+
+        Globals.clientStateT.frame.serverframe = MSG.ReadLong(Globals.net_message);
+        Globals.clientStateT.frame.deltaframe = MSG.ReadLong(Globals.net_message);
+        Globals.clientStateT.frame.servertime = Globals.clientStateT.frame.serverframe * 100;
+
+        // BIG HACK to let old demos continue to work
+        if (Globals.clientStaticT.serverProtocol != 26)
+            Globals.clientStateT.surpressCount = MSG.ReadByte(Globals.net_message);
+
+        if (Globals.cl_shownet.value == 3)
+            Com.Printf("   frame:" + Globals.clientStateT.frame.serverframe + "  delta:" + Globals.clientStateT.frame.deltaframe + "\n");
+
+        // If the frame is delta compressed from data that we
+        // no longer have available, we must suck up the rest of
+        // the frame, but not use it, then ask for a non-compressed
+        // message
+        if (Globals.clientStateT.frame.deltaframe <= 0) {
+            Globals.clientStateT.frame.valid = true; // uncompressed frame
+            old = null;
+            Globals.clientStaticT.demowaiting = false; // we can start recording now
+        } else {
+            old = Globals.clientStateT.frames[Globals.clientStateT.frame.deltaframe & Defines.UPDATE_MASK];
+            if (!old.valid) { // should never happen
+                Com.Printf("Delta from invalid frame (not supposed to happen!).\n");
+            }
+            if (old.serverframe != Globals.clientStateT.frame.deltaframe) { // The frame
+                // that the
+                // server did
+                // the delta
+                // from
+                // is too old, so we can't reconstruct it properly.
+                Com.Printf("Delta frame too old.\n");
+            } else if (Globals.clientStateT.parse_entities - old.parse_entities > Defines.MAX_PARSE_ENTITIES - 128) {
+                Com.Printf("Delta parse_entities too old.\n");
+            } else
+                Globals.clientStateT.frame.valid = true; // valid delta parse
+        }
+
+        // clamp time
+        if (Globals.clientStateT.time > Globals.clientStateT.frame.servertime)
+            Globals.clientStateT.time = Globals.clientStateT.frame.servertime;
+        else if (Globals.clientStateT.time < Globals.clientStateT.frame.servertime - 100)
+            Globals.clientStateT.time = Globals.clientStateT.frame.servertime - 100;
+
+        // read areabits
+        len = MSG.ReadByte(Globals.net_message);
+        MSG.ReadData(Globals.net_message, Globals.clientStateT.frame.areabits, len);
+
+        // read playerinfo
+        cmd = MSG.ReadByte(Globals.net_message);
+        CL_parse.SHOWNET(CL_parse.svc_strings[cmd]);
+        if (cmd != Defines.svc_playerinfo)
+            Com.Error(Defines.ERR_DROP, "CL_ParseFrame: not playerinfo");
+        ParsePlayerstate(old, Globals.clientStateT.frame);
+
+        // read packet entities
+        cmd = MSG.ReadByte(Globals.net_message);
+        CL_parse.SHOWNET(CL_parse.svc_strings[cmd]);
+        if (cmd != Defines.svc_packetentities)
+            Com.Error(Defines.ERR_DROP, "CL_ParseFrame: not packetentities");
+
+        ParsePacketEntities(old, Globals.clientStateT.frame);
+
+        // save the frame off in the backup array for later delta comparisons
+        Globals.clientStateT.frames[Globals.clientStateT.frame.serverframe & Defines.UPDATE_MASK].set(Globals.clientStateT.frame);
+
+        if (Globals.clientStateT.frame.valid) {
+            // getting a valid frame message ends the connection process
+            if (Globals.clientStaticT.state != Defines.ca_active) {
+                Globals.clientStaticT.state = Defines.ca_active;
+                Globals.clientStateT.force_refdef = true;
+
+                Globals.clientStateT.predicted_origin[0] = Globals.clientStateT.frame.playerstate.pmove.origin[0] * 0.125f;
+                Globals.clientStateT.predicted_origin[1] = Globals.clientStateT.frame.playerstate.pmove.origin[1] * 0.125f;
+                Globals.clientStateT.predicted_origin[2] = Globals.clientStateT.frame.playerstate.pmove.origin[2] * 0.125f;
+
+                Math3D.vectorCopy(Globals.clientStateT.frame.playerstate.viewangles, Globals.clientStateT.predicted_angles);
+                if (Globals.clientStaticT.disable_servercount != Globals.clientStateT.servercount && Globals.clientStateT.refresh_prepped)
+                    SCR.EndLoadingPlaque(); // get rid of loading plaque
+            }
+            Globals.clientStateT.sound_prepped = true; // can start mixing ambient sounds
+
+            // fire entity events
+            FireEntityEvents(Globals.clientStateT.frame);
+            CL_pred.CheckPredictionError();
+        }
+    }
+
+    /*
+     * ===============
+     * CL_AddPacketEntities
+     * ===============
+     */
+    static void AddPacketEntities(frame_t frame) {
+        entity_state_t s1;
+        float autorotate;
+        int i;
+        int pnum;
+        centity_t cent;
+        int autoanim;
+        ClientInfo clientInfo;
+        int effects, renderfx;
+
+        // bonus items rotate at a fixed rate
+        autorotate = Math3D.angleMod(Globals.clientStateT.time / 10);
+
+        // brush models can auto animate their frames
+        autoanim = 2 * Globals.clientStateT.time / 1000;
+
+        //memset( ent, 0, sizeof(ent));
+        ent.clear();
+
+        for (pnum = 0; pnum < frame.num_entities; pnum++) {
+            s1 = Globals.cl_parse_entities[(frame.parse_entities + pnum) & (Defines.MAX_PARSE_ENTITIES - 1)];
+
+            cent = Globals.cl_entities[s1.number];
+
+            effects = s1.effects;
+            renderfx = s1.renderfx;
+
+            // set frame
+            if ((effects & Defines.EF_ANIM01) != 0)
+                ent.frame = autoanim & 1;
+            else if ((effects & Defines.EF_ANIM23) != 0)
+                ent.frame = 2 + (autoanim & 1);
+            else if ((effects & Defines.EF_ANIM_ALL) != 0)
+                ent.frame = autoanim;
+            else if ((effects & Defines.EF_ANIM_ALLFAST) != 0)
+                ent.frame = Globals.clientStateT.time / 100;
+            else
+                ent.frame = s1.frame;
+
+            // quad and pent can do different things on client
+            if ((effects & Defines.EF_PENT) != 0) {
+                effects &= ~Defines.EF_PENT;
+                effects |= Defines.EF_COLOR_SHELL;
+                renderfx |= Defines.RF_SHELL_RED;
+            }
+
+            if ((effects & Defines.EF_QUAD) != 0) {
+                effects &= ~Defines.EF_QUAD;
+                effects |= Defines.EF_COLOR_SHELL;
+                renderfx |= Defines.RF_SHELL_BLUE;
+            }
+            //   ======
+            //    PMM
+            if ((effects & Defines.EF_DOUBLE) != 0) {
+                effects &= ~Defines.EF_DOUBLE;
+                effects |= Defines.EF_COLOR_SHELL;
+                renderfx |= Defines.RF_SHELL_DOUBLE;
+            }
+
+            if ((effects & Defines.EF_HALF_DAMAGE) != 0) {
+                effects &= ~Defines.EF_HALF_DAMAGE;
+                effects |= Defines.EF_COLOR_SHELL;
+                renderfx |= Defines.RF_SHELL_HALF_DAM;
+            }
+            //    pmm
+            //   ======
+            ent.oldframe = cent.prev.frame;
+            ent.backlerp = 1.0f - Globals.clientStateT.lerpfrac;
+
+            // interpolate origin
+            for (i = 0; i < 3; i++) {
+                ent.origin[i] = ent.oldorigin[i] = cent.prev.origin[i] + Globals.clientStateT.lerpfrac
+                        * (cent.current.origin[i] - cent.prev.origin[i]);
+            }
+
+            // create a new entity
+
+            // tweak the color of beams
+            // set skin
+            if (s1.modelindex == 255) { // use custom player skin
+                ent.skinnum = 0;
+                clientInfo = Globals.clientStateT.clientinfo[s1.skinnum & 0xff];
+                ent.skin = clientInfo.skin;
+                ent.model = clientInfo.model;
+                if (null == ent.skin || null == ent.model) {
+                    ent.skin = Globals.clientStateT.baseclientinfo.skin;
+                    ent.model = Globals.clientStateT.baseclientinfo.model;
+                }
+
+                //       ============
+                //       PGM
+                if ((renderfx & Defines.RF_USE_DISGUISE) != 0) {
+                    if (ent.skin.name.startsWith("players/male")) {
+                        ent.skin = Globals.re.RegisterSkin("players/male/disguise.pcx");
+                        ent.model = Globals.re.RegisterModel("players/male/tris.md2");
+                    } else if (ent.skin.name.startsWith("players/female")) {
+                        ent.skin = Globals.re.RegisterSkin("players/female/disguise.pcx");
+                        ent.model = Globals.re.RegisterModel("players/female/tris.md2");
+                    } else if (ent.skin.name.startsWith("players/cyborg")) {
+                        ent.skin = Globals.re.RegisterSkin("players/cyborg/disguise.pcx");
+                        ent.model = Globals.re.RegisterModel("players/cyborg/tris.md2");
+                    }
+                }
+                //       PGM
+                //       ============
+            } else {
+                ent.skinnum = s1.skinnum;
+                ent.skin = null;
+                ent.model = Globals.clientStateT.model_draw[s1.modelindex];
+            }
+
+            // only used for black hole model right now, FIXME: do better
+            if (renderfx == Defines.RF_TRANSLUCENT)
+                ent.alpha = 0.70f;
+
+            // render effects (fullbright, translucent, etc)
+            if ((effects & Defines.EF_COLOR_SHELL) != 0)
+                ent.flags = 0; // renderfx go on color shell entity
+            else
+                ent.flags = renderfx;
+
+            // calculate angles
+            if ((effects & Defines.EF_ROTATE) != 0) { // some bonus items
+                // auto-rotate
+                ent.angles[0] = 0;
+                ent.angles[1] = autorotate;
+                ent.angles[2] = 0;
+            }
+            // RAFAEL
+            else if ((effects & Defines.EF_SPINNINGLIGHTS) != 0) {
+                ent.angles[0] = 0;
+                ent.angles[1] = Math3D.angleMod(Globals.clientStateT.time / 2) + s1.angles[1];
+                ent.angles[2] = 180;
+                {
+                    float[] forward = {0, 0, 0};
+                    float[] start = {0, 0, 0};
+
+                    Math3D.angleVectors(ent.angles, forward, null, null);
+                    Math3D.vectorMA(ent.origin, 64, forward, start);
+                    V.AddLight(start, 100, 1, 0, 0);
+                }
+            } else { // interpolate angles
+                float a1, a2;
+
+                for (i = 0; i < 3; i++) {
+                    a1 = cent.current.angles[i];
+                    a2 = cent.prev.angles[i];
+                    ent.angles[i] = Math3D.lerpAngle(a2, a1, Globals.clientStateT.lerpfrac);
+                }
+            }
+
+            if (s1.number == Globals.clientStateT.playernum + 1) {
+                ent.flags |= Defines.RF_VIEWERMODEL; // only draw from mirrors
+                // FIXME: still pass to refresh
+
+                if ((effects & Defines.EF_FLAG1) != 0)
+                    V.AddLight(ent.origin, 225, 1.0f, 0.1f, 0.1f);
+                else if ((effects & Defines.EF_FLAG2) != 0)
+                    V.AddLight(ent.origin, 225, 0.1f, 0.1f, 1.0f);
+                else if ((effects & Defines.EF_TAGTRAIL) != 0) //PGM
+                    V.AddLight(ent.origin, 225, 1.0f, 1.0f, 0.0f); //PGM
+                else if ((effects & Defines.EF_TRACKERTRAIL) != 0) //PGM
+                    V.AddLight(ent.origin, 225, -1.0f, -1.0f, -1.0f); //PGM
+
+                continue;
+            }
+
+            // if set to invisible, skip
+            if (s1.modelindex == 0)
+                continue;
+
+            if ((effects & Defines.EF_BFG) != 0) {
+                ent.flags |= Defines.RF_TRANSLUCENT;
+                ent.alpha = 0.30f;
+            }
+
+            // RAFAEL
+            if ((effects & Defines.EF_PLASMA) != 0) {
+                ent.flags |= Defines.RF_TRANSLUCENT;
+                ent.alpha = 0.6f;
+            }
+
+            if ((effects & Defines.EF_SPHERETRANS) != 0) {
+                ent.flags |= Defines.RF_TRANSLUCENT;
+                // PMM - *sigh* yet more EF overloading
+                if ((effects & Defines.EF_TRACKERTRAIL) != 0)
+                    ent.alpha = 0.6f;
+                else
+                    ent.alpha = 0.3f;
+            }
+            //   pmm
+
+            // add to refresh list
+            V.AddEntity(ent);
+
+            // color shells generate a seperate entity for the main model
+            if ((effects & Defines.EF_COLOR_SHELL) != 0) {
+                /*
+                 * PMM - at this point, all of the shells have been handled if
+                                * we're in the rogue pack, set up the custom mixing, otherwise
+                                * just keep going if(Developer_searchpath(2) == 2) { all of the
+                                * solo colors are fine. we need to catch any of the
+                                * combinations that look bad (double & half) and turn them into
+                                * the appropriate color, and make double/quad something special
+                                *
+                                */
+                if ((renderfx & Defines.RF_SHELL_HALF_DAM) != 0) {
+                    if (FS.Developer_searchpath(2) == 2) {
+                        // ditch the half damage shell if any of red, blue, or
+                        // double are on
+                        if ((renderfx & (Defines.RF_SHELL_RED | Defines.RF_SHELL_BLUE | Defines.RF_SHELL_DOUBLE)) != 0)
+                            renderfx &= ~Defines.RF_SHELL_HALF_DAM;
+                    }
+                }
+
+                if ((renderfx & Defines.RF_SHELL_DOUBLE) != 0) {
+                    if (FS.Developer_searchpath(2) == 2) {
+                        // lose the yellow shell if we have a red, blue, or
+                        // green shell
+                        if ((renderfx & (Defines.RF_SHELL_RED | Defines.RF_SHELL_BLUE | Defines.RF_SHELL_GREEN)) != 0)
+                            renderfx &= ~Defines.RF_SHELL_DOUBLE;
+                        // if we have a red shell, turn it to purple by adding
+                        // blue
+                        if ((renderfx & Defines.RF_SHELL_RED) != 0)
+                            renderfx |= Defines.RF_SHELL_BLUE;
+                            // if we have a blue shell (and not a red shell), turn
+                            // it to cyan by adding green
+                        else if ((renderfx & Defines.RF_SHELL_BLUE) != 0)
+                            // go to green if it's on already, otherwise do cyan
+                            // (flash green)
+                            if ((renderfx & Defines.RF_SHELL_GREEN) != 0)
+                                renderfx &= ~Defines.RF_SHELL_BLUE;
+                            else
+                                renderfx |= Defines.RF_SHELL_GREEN;
+                    }
+                }
+                //                             }
+                // pmm
+                ent.flags = renderfx | Defines.RF_TRANSLUCENT;
+                ent.alpha = 0.30f;
+                V.AddEntity(ent);
+            }
+
+            ent.skin = null; // never use a custom skin on others
+            ent.skinnum = 0;
+            ent.flags = 0;
+            ent.alpha = 0;
+
+            // duplicate for linked models
+            if (s1.modelindex2 != 0) {
+                ent.model = Globals.clientStateT.model_draw[s1.modelindex2];
+
+                // PMM - check for the defender sphere shell .. make it
+                // translucent
+                // replaces the previous version which used the high bit on
+                // modelindex2 to determine transparency
+                if (Globals.clientStateT.configstrings[Defines.CS_MODELS + (s1.modelindex2)].equalsIgnoreCase("models/items/shell/tris.md2")) {
+                    ent.alpha = 0.32f;
+                    ent.flags = Defines.RF_TRANSLUCENT;
+                }
+                // pmm
+
+                V.AddEntity(ent);
+
+                //PGM - make sure these get reset.
+                ent.flags = 0;
+                ent.alpha = 0;
+                //PGM
+            }
+            if (s1.modelindex3 != 0) {
+                ent.model = Globals.clientStateT.model_draw[s1.modelindex3];
+                V.AddEntity(ent);
+            }
+            if (s1.modelindex4 != 0) {
+                ent.model = Globals.clientStateT.model_draw[s1.modelindex4];
+                V.AddEntity(ent);
+            }
+
+            if ((effects & Defines.EF_POWERSCREEN) != 0) {
+                ent.model = CL_tent.cl_mod_powerscreen;
+                ent.oldframe = 0;
+                ent.frame = 0;
+                ent.flags |= (Defines.RF_TRANSLUCENT | Defines.RF_SHELL_GREEN);
+                ent.alpha = 0.30f;
+                V.AddEntity(ent);
+            }
+
+            // add automatic particle trails
+            if ((effects & ~Defines.EF_ROTATE) != 0) {
+                if ((effects & Defines.EF_ROCKET) != 0) {
+                    CL_fx.RocketTrail(cent.lerp_origin, ent.origin, cent);
+                    V.AddLight(ent.origin, 200, 1, 1, 0);
+                }
+                // PGM - Do not reorder EF_BLASTER and EF_HYPERBLASTER.
+                // EF_BLASTER | EF_TRACKER is a special case for EF_BLASTER2...
+                // Cheese!
+                else if ((effects & Defines.EF_BLASTER) != 0) {
+                    //                                 CL_BlasterTrail (cent.lerp_origin, ent.origin);
+                    //   PGM
+                    if ((effects & Defines.EF_TRACKER) != 0) // lame...
+                    // problematic?
+                    {
+                        CL_newfx.BlasterTrail2(cent.lerp_origin, ent.origin);
+                        V.AddLight(ent.origin, 200, 0, 1, 0);
+                    } else {
+                        CL_fx.BlasterTrail(cent.lerp_origin, ent.origin);
+                        V.AddLight(ent.origin, 200, 1, 1, 0);
+                    }
+                    //   PGM
+                } else if ((effects & Defines.EF_HYPERBLASTER) != 0) {
+                    if ((effects & Defines.EF_TRACKER) != 0) // PGM overloaded
+                        // for blaster2.
+                        V.AddLight(ent.origin, 200, 0, 1, 0); // PGM
+                    else
+                        // PGM
+                        V.AddLight(ent.origin, 200, 1, 1, 0);
+                } else if ((effects & Defines.EF_GIB) != 0) {
+                    CL_fx.DiminishingTrail(cent.lerp_origin, ent.origin, cent, effects);
+                } else if ((effects & Defines.EF_GRENADE) != 0) {
+                    CL_fx.DiminishingTrail(cent.lerp_origin, ent.origin, cent, effects);
+                } else if ((effects & Defines.EF_FLIES) != 0) {
+                    CL_fx.FlyEffect(cent, ent.origin);
+                } else if ((effects & Defines.EF_BFG) != 0) {
+
+                    if ((effects & Defines.EF_ANIM_ALLFAST) != 0) {
+                        CL_fx.BfgParticles();
+                        i = 200;
+                    } else {
+                        i = bfg_lightramp[s1.frame];
+                    }
+                    V.AddLight(ent.origin, i, 0, 1, 0);
+                }
+                // RAFAEL
+                else if ((effects & Defines.EF_TRAP) != 0) {
+                    ent.origin[2] += 32;
+                    CL_fx.TrapParticles();
+                    i = (Globals.rnd.nextInt(100)) + 100;
+                    V.AddLight(ent.origin, i, 1, 0.8f, 0.1f);
+                } else if ((effects & Defines.EF_FLAG1) != 0) {
+                    CL_fx.FlagTrail(cent.lerp_origin, ent.origin, 242);
+                    V.AddLight(ent.origin, 225, 1, 0.1f, 0.1f);
+                } else if ((effects & Defines.EF_FLAG2) != 0) {
+                    CL_fx.FlagTrail(cent.lerp_origin, ent.origin, 115);
+                    V.AddLight(ent.origin, 225, 0.1f, 0.1f, 1);
+                }
+                //       ======
+                //       ROGUE
+                else if ((effects & Defines.EF_TAGTRAIL) != 0) {
+                    CL_newfx.TagTrail(cent.lerp_origin, ent.origin);
+                    V.AddLight(ent.origin, 225, 1.0f, 1.0f, 0.0f);
+                } else if ((effects & Defines.EF_TRACKERTRAIL) != 0) {
+                    if ((effects & Defines.EF_TRACKER) != 0) {
+                        float intensity;
+
+                        intensity = (float) (50 + (500 * (Math.sin(Globals.clientStateT.time / 500.0) + 1.0)));
+                        // FIXME - check out this effect in rendition
+                        if (Globals.vidref_val == Defines.VIDREF_GL)
+                            V.AddLight(ent.origin, intensity, -1.0f, -1.0f, -1.0f);
+                        else
+                            V.AddLight(ent.origin, -1.0f * intensity, 1.0f, 1.0f, 1.0f);
+                    } else {
+                        CL_newfx.Tracker_Shell(cent.lerp_origin);
+                        V.AddLight(ent.origin, 155, -1.0f, -1.0f, -1.0f);
+                    }
+                } else if ((effects & Defines.EF_TRACKER) != 0) {
+                    CL_newfx.TrackerTrail(cent.lerp_origin, ent.origin);
+                    // FIXME - check out this effect in rendition
+                    if (Globals.vidref_val == Defines.VIDREF_GL)
+                        V.AddLight(ent.origin, 200, -1, -1, -1);
+                    else
+                        V.AddLight(ent.origin, -200, 1, 1, 1);
+                }
+                //       ROGUE
+                //       ======
+                // RAFAEL
+                else if ((effects & Defines.EF_GREENGIB) != 0) {
+                    CL_fx.DiminishingTrail(cent.lerp_origin, ent.origin, cent, effects);
+                }
+                // RAFAEL
+                else if ((effects & Defines.EF_IONRIPPER) != 0) {
+                    CL_fx.IonripperTrail(cent.lerp_origin, ent.origin);
+                    V.AddLight(ent.origin, 100, 1, 0.5f, 0.5f);
+                }
+                // RAFAEL
+                else if ((effects & Defines.EF_BLUEHYPERBLASTER) != 0) {
+                    V.AddLight(ent.origin, 200, 0, 0, 1);
+                }
+                // RAFAEL
+                else if ((effects & Defines.EF_PLASMA) != 0) {
+                    if ((effects & Defines.EF_ANIM_ALLFAST) != 0) {
+                        CL_fx.BlasterTrail(cent.lerp_origin, ent.origin);
+                    }
+                    V.AddLight(ent.origin, 130, 1, 0.5f, 0.5f);
+                }
+            }
+
+            Math3D.vectorCopy(ent.origin, cent.lerp_origin);
+        }
+    }
+
+    /*
+     * =============== CL_CalcViewValues
+     *
+     * Sets cl.refdef view values ===============
+     */
+    static void CalcViewValues() {
+        int i;
+        float lerp, backlerp;
+        frame_t oldframe;
+        player_state_t ps, ops;
+
+        // find the previous frame to interpolate from
+        ps = Globals.clientStateT.frame.playerstate;
+
+        i = (Globals.clientStateT.frame.serverframe - 1) & Defines.UPDATE_MASK;
+        oldframe = Globals.clientStateT.frames[i];
+
+        if (oldframe.serverframe != Globals.clientStateT.frame.serverframe - 1 || !oldframe.valid)
+            oldframe = Globals.clientStateT.frame; // previous frame was dropped or
+        // involid
+        ops = oldframe.playerstate;
+
+        // see if the player entity was teleported this frame
+        if (Math.abs(ops.pmove.origin[0] - ps.pmove.origin[0]) > 256 * 8
+                || Math.abs(ops.pmove.origin[1] - ps.pmove.origin[1]) > 256 * 8
+                || Math.abs(ops.pmove.origin[2] - ps.pmove.origin[2]) > 256 * 8)
+            ops = ps; // don't interpolate
+
+        lerp = Globals.clientStateT.lerpfrac;
+
+        // calculate the origin
+        if ((Globals.cl_predict.value != 0) && 0 == (Globals.clientStateT.frame.playerstate.pmove.pm_flags & Move.PMF_NO_PREDICTION)) { // use
+            // predicted
+            // values
+            int delta;
+
+            backlerp = 1.0f - lerp;
+            for (i = 0; i < 3; i++) {
+                Globals.clientStateT.refdef.vieworg[i] = Globals.clientStateT.predicted_origin[i] + ops.viewoffset[i] + Globals.clientStateT.lerpfrac
+                        * (ps.viewoffset[i] - ops.viewoffset[i]) - backlerp * Globals.clientStateT.prediction_error[i];
+            }
+
+            // smooth out stair climbing
+            delta = Globals.clientStaticT.realtime - Globals.clientStateT.predicted_step_time;
+            if (delta < 100)
+                Globals.clientStateT.refdef.vieworg[2] -= Globals.clientStateT.predicted_step * (100 - delta) * 0.01;
+        } else { // just use interpolated values
+            for (i = 0; i < 3; i++)
+                Globals.clientStateT.refdef.vieworg[i] = ops.pmove.origin[i] * 0.125f + ops.viewoffset[i] + lerp
+                        * (ps.pmove.origin[i] * 0.125f + ps.viewoffset[i] - (ops.pmove.origin[i] * 0.125f + ops.viewoffset[i]));
+        }
+
+        // if not running a demo or on a locked frame, add the local angle
+        // movement
+        if (Globals.clientStateT.frame.playerstate.pmove.pm_type < Defines.PM_DEAD) { // use
+            // predicted
+            // values
+            for (i = 0; i < 3; i++)
+                Globals.clientStateT.refdef.viewangles[i] = Globals.clientStateT.predicted_angles[i];
+        } else { // just use interpolated values
+            for (i = 0; i < 3; i++)
+                Globals.clientStateT.refdef.viewangles[i] = Math3D.lerpAngle(ops.viewangles[i], ps.viewangles[i], lerp);
+        }
+
+        for (i = 0; i < 3; i++)
+            Globals.clientStateT.refdef.viewangles[i] += Math3D.lerpAngle(ops.kick_angles[i], ps.kick_angles[i], lerp);
+
+        Math3D.angleVectors(Globals.clientStateT.refdef.viewangles, Globals.clientStateT.v_forward, Globals.clientStateT.v_right, Globals.clientStateT.v_up);
+
+        // interpolate field of view
+        Globals.clientStateT.refdef.fov_x = ops.fov + lerp * (ps.fov - ops.fov);
+
+        // don't interpolate blend color
+        for (i = 0; i < 4; i++)
+            Globals.clientStateT.refdef.blend[i] = ps.blend[i];
+
+    }
+
+    /*
+     * =============== CL_AddEntities
+     *
+     * Emits all entities, particles, and lights to the refresh ===============
+     */
+    static void AddEntities() {
+        if (Globals.clientStaticT.state != Defines.ca_active)
+            return;
+
+        if (Globals.clientStateT.time > Globals.clientStateT.frame.servertime) {
+            if (Globals.cl_showclamp.value != 0)
+                Com.Printf("high clamp " + (Globals.clientStateT.time - Globals.clientStateT.frame.servertime) + "\n");
+            Globals.clientStateT.time = Globals.clientStateT.frame.servertime;
+            Globals.clientStateT.lerpfrac = 1.0f;
+        } else if (Globals.clientStateT.time < Globals.clientStateT.frame.servertime - 100) {
+            if (Globals.cl_showclamp.value != 0)
+                Com.Printf("low clamp " + (Globals.clientStateT.frame.servertime - 100 - Globals.clientStateT.time) + "\n");
+            Globals.clientStateT.time = Globals.clientStateT.frame.servertime - 100;
+            Globals.clientStateT.lerpfrac = 0;
+        } else
+            Globals.clientStateT.lerpfrac = 1.0f - (Globals.clientStateT.frame.servertime - Globals.clientStateT.time) * 0.01f;
+
+        if (Globals.cl_timedemo.value != 0)
+            Globals.clientStateT.lerpfrac = 1.0f;
+
+               /*
+         * is ok.. CL_AddPacketEntities (cl.frame); CL_AddTEnts ();
+                * CL_AddParticles (); CL_AddDLights (); CL_AddLightStyles ();
+                */
+
+        CalcViewValues();
+        // PMM - moved this here so the heat beam has the right values for the
+        // vieworg, and can lock the beam to the gun
+        AddPacketEntities(Globals.clientStateT.frame);
+
+        CL_tent.AddTEnts();
+        CL_fx.AddParticles();
+        CL_fx.AddDLights();
+        CL_fx.AddLightStyles();
+    }
+
+    /*
+     * =============== CL_GetEntitySoundOrigin
+     *
+     * Called to get the sound spatialization origin ===============
+     */
+    public static void GetEntitySoundOrigin(int ent, float[] org) {
+        centity_t old;
+
+        if (ent < 0 || ent >= Defines.MAX_EDICTS)
+            Com.Error(Defines.ERR_DROP, "CL_GetEntitySoundOrigin: bad ent");
+        old = Globals.cl_entities[ent];
+        Math3D.vectorCopy(old.lerp_origin, org);
+
+        // FIXME: bmodel issues...
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/client/CL_fx.java b/src/main/java/lwjake2/client/CL_fx.java
new file mode 100644 (file)
index 0000000..3428d2e
--- /dev/null
@@ -0,0 +1,1455 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.entity_state_t;
+import lwjake2.qcommon.Com;
+import lwjake2.sound.S;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+
+/**
+ * Client Graphics Effects.
+ */
+public class CL_fx {
+
+    static final float INSTANT_PARTICLE = -10000.0f;
+    static final int PARTICLE_GRAVITY = 40;
+    static final cparticle_t[] particles = new cparticle_t[Defines.MAX_PARTICLES];
+    static final float[][] avelocities = new float[Defines.NUMVERTEXNORMALS][3];
+    static final clightstyle_t[] cl_lightstyle = new clightstyle_t[Defines.MAX_LIGHTSTYLES];
+    static final cdlight_t[] cl_dlights = new cdlight_t[Defines.MAX_DLIGHTS];
+    // stack variable
+    private static final float[] fv = {0, 0, 0};
+    private static final float[] rv = {0, 0, 0};
+    // stack variable
+    private static final float[] origin = {0, 0, 0};
+    private static final float[] forward = {0, 0, 0};
+    private static final float[] right = {0, 0, 0};
+    // stack variable
+    private static final float[] move = {0, 0, 0};
+    /*
+     * ==============================================================
+     *
+     * LIGHT STYLE MANAGEMENT
+     *
+     * ==============================================================
+     */
+    private static final float[] vec = {0, 0, 0};
+    // stack variable
+    private static final float[] v = {0, 0, 0};
+    /*
+     * ==============================================================
+     *
+     * DLIGHT MANAGEMENT
+     *
+     * ==============================================================
+     */
+    // stack variable
+    // move, vec
+    private static final float[] start = {0, 0, 0};
+    private static final float[] end = {0, 0, 0};
+    // stack variable
+    private static final float[] dir = {0, 0, 0};
+    // stack variable
+    private static final float[] org = {0, 0, 0};
+    private static final int BEAMLENGTH = 16;
+    /*
+     * =============== CL_BigTeleportParticles ===============
+     */
+    private static final int[] colortable = {2 * 8, 13 * 8, 21 * 8, 18 * 8};
+    static int cl_numparticles = Defines.MAX_PARTICLES;
+    static int lastofs;
+    static cparticle_t active_particles, free_particles;
+
+    static {
+        for (int i = 0; i < particles.length; i++)
+            particles[i] = new cparticle_t();
+    }
+
+    static {
+        for (int i = 0; i < cl_lightstyle.length; i++) {
+            cl_lightstyle[i] = new clightstyle_t();
+        }
+    }
+
+    static {
+        for (int i = 0; i < cl_dlights.length; i++)
+            cl_dlights[i] = new cdlight_t();
+    }
+
+    /*
+     * ================ CL_ClearDlights ================
+     */
+    static void ClearDlights() {
+        //             memset (cl_dlights, 0, sizeof(cl_dlights));
+        for (cdlight_t cl_dlight : cl_dlights) {
+            cl_dlight.clear();
+        }
+    }
+
+    /*
+     * ================ CL_ClearLightStyles ================
+     */
+    static void ClearLightStyles() {
+        //memset (cl_lightstyle, 0, sizeof(cl_lightstyle));
+        for (clightstyle_t aCl_lightstyle : cl_lightstyle) aCl_lightstyle.clear();
+        lastofs = -1;
+    }
+
+    /*
+     * ================ CL_RunLightStyles ================
+     */
+    static void RunLightStyles() {
+        clightstyle_t ls;
+
+        int ofs = Globals.clientStateT.time / 100;
+        if (ofs == lastofs)
+            return;
+        lastofs = ofs;
+
+        for (clightstyle_t aCl_lightstyle : cl_lightstyle) {
+            ls = aCl_lightstyle;
+            if (ls.length == 0) {
+                ls.value[0] = ls.value[1] = ls.value[2] = 1.0f;
+                continue;
+            }
+            if (ls.length == 1)
+                ls.value[0] = ls.value[1] = ls.value[2] = ls.map[0];
+            else
+                ls.value[0] = ls.value[1] = ls.value[2] = ls.map[ofs % ls.length];
+        }
+    }
+
+    static void SetLightstyle(int i) {
+        String s;
+        int j, k;
+
+        s = Globals.clientStateT.configstrings[i + Defines.CS_LIGHTS];
+
+        j = s.length();
+        if (j >= Defines.MAX_QPATH)
+            Com.Error(Defines.ERR_DROP, "svc_lightstyle length=" + j);
+
+        cl_lightstyle[i].length = j;
+
+        for (k = 0; k < j; k++)
+            cl_lightstyle[i].map[k] = (float) (s.charAt(k) - 'a') / (float) ('m' - 'a');
+    }
+
+    /*
+     * ================ CL_AddLightStyles ================
+     */
+    static void AddLightStyles() {
+        clightstyle_t ls;
+
+        for (int i = 0; i < cl_lightstyle.length; i++) {
+            ls = cl_lightstyle[i];
+            V.AddLightStyle(i, ls.value[0], ls.value[1], ls.value[2]);
+        }
+    }
+
+    /*
+     * =============== CL_AllocDlight
+     *
+     * ===============
+     */
+    static cdlight_t AllocDlight(int key) {
+        int i;
+        cdlight_t dl;
+
+        //        first look for an exact key match
+        if (key != 0) {
+            for (i = 0; i < Defines.MAX_DLIGHTS; i++) {
+                dl = cl_dlights[i];
+                if (dl.key == key) {
+                    //memset (dl, 0, sizeof(*dl));
+                    dl.clear();
+                    dl.key = key;
+                    return dl;
+                }
+            }
+        }
+
+        //        then look for anything else
+        for (i = 0; i < Defines.MAX_DLIGHTS; i++) {
+            dl = cl_dlights[i];
+            if (dl.die < Globals.clientStateT.time) {
+                //memset (dl, 0, sizeof(*dl));
+                dl.clear();
+                dl.key = key;
+                return dl;
+            }
+        }
+
+        //dl = &cl_dlights[0];
+        //memset (dl, 0, sizeof(*dl));
+        dl = cl_dlights[0];
+        dl.clear();
+        dl.key = key;
+        return dl;
+    }
+
+    /*
+     * ===============
+     * CL_RunDLights
+     * ===============
+     */
+    static void RunDLights() {
+        cdlight_t dl;
+
+        for (int i = 0; i < Defines.MAX_DLIGHTS; i++) {
+            dl = cl_dlights[i];
+            if (dl.radius == 0.0f)
+                continue;
+
+            if (dl.die < Globals.clientStateT.time) {
+                dl.radius = 0.0f;
+                return;
+            }
+        }
+    }
+
+    /*
+     * =============== CL_AddDLights
+     *
+     * ===============
+     */
+    static void AddDLights() {
+        cdlight_t dl;
+
+        //       =====
+        //       PGM
+        if (Globals.vidref_val == Defines.VIDREF_GL) {
+            for (int i = 0; i < Defines.MAX_DLIGHTS; i++) {
+                dl = cl_dlights[i];
+                if (dl.radius == 0.0f)
+                    continue;
+                V.AddLight(dl.origin, dl.radius, dl.color[0], dl.color[1], dl.color[2]);
+            }
+        } else {
+            for (int i = 0; i < Defines.MAX_DLIGHTS; i++) {
+                dl = cl_dlights[i];
+                if (dl.radius == 0.0f)
+                    continue;
+
+                // negative light in software. only black allowed
+                if ((dl.color[0] < 0) || (dl.color[1] < 0) || (dl.color[2] < 0)) {
+                    dl.radius = -(dl.radius);
+                    dl.color[0] = 1;
+                    dl.color[1] = 1;
+                    dl.color[2] = 1;
+                }
+                V.AddLight(dl.origin, dl.radius, dl.color[0], dl.color[1], dl.color[2]);
+            }
+        }
+        //       PGM
+        //       =====
+    }
+
+    /*
+     * =============== CL_ClearParticles ===============
+     */
+    static void ClearParticles() {
+        free_particles = particles[0];
+        active_particles = null;
+
+        for (int i = 0; i < particles.length - 1; i++)
+            particles[i].next = particles[i + 1];
+        particles[particles.length - 1].next = null;
+    }
+
+    /*
+     * =============== CL_ParticleEffect
+     *
+     * Wall impact puffs ===============
+     */
+    static void ParticleEffect(int color, int count) {
+        int j;
+        cparticle_t p;
+        float d;
+
+        for (int i = 0; i < count; i++) {
+            if (free_particles == null)
+                return;
+            p = free_particles;
+            free_particles = p.next;
+            p.next = active_particles;
+            active_particles = p;
+
+            p.time = Globals.clientStateT.time;
+            p.color = color + (Lib.rand() & 7);
+
+            d = Lib.rand() & 31;
+            for (j = 0; j < 3; j++) {
+                p.org[j] = CL_tent.pos[j] + ((Lib.rand() & 7) - 4) + d * CL_tent.dir[j];
+                p.vel[j] = Lib.crand() * 20;
+            }
+
+            p.accel[0] = p.accel[1] = 0;
+            p.accel[2] = -PARTICLE_GRAVITY;
+            p.alpha = 1.0f;
+
+            p.alphavel = -1.0f / (0.5f + Globals.rnd.nextFloat() * 0.3f);
+        }
+    }
+
+    /*
+     * =============== CL_ParticleEffect2 ===============
+     */
+    static void ParticleEffect2(int color, int count) {
+        int j;
+        cparticle_t p;
+        float d;
+
+        for (int i = 0; i < count; i++) {
+            if (free_particles == null)
+                return;
+            p = free_particles;
+            free_particles = p.next;
+            p.next = active_particles;
+            active_particles = p;
+
+            p.time = Globals.clientStateT.time;
+            p.color = color;
+
+            d = Lib.rand() & 7;
+            for (j = 0; j < 3; j++) {
+                p.org[j] = CL_tent.pos[j] + ((Lib.rand() & 7) - 4) + d * CL_tent.dir[j];
+                p.vel[j] = Lib.crand() * 20;
+            }
+
+            p.accel[0] = p.accel[1] = 0;
+            p.accel[2] = -PARTICLE_GRAVITY;
+            p.alpha = 1.0f;
+
+            p.alphavel = -1.0f / (0.5f + Globals.rnd.nextFloat() * 0.3f);
+        }
+    }
+
+    //    RAFAEL
+    /*
+     * =============== CL_ParticleEffect3 ===============
+        */
+    static void ParticleEffect3(int color, int count) {
+        int j;
+        cparticle_t p;
+        float d;
+
+        for (int i = 0; i < count; i++) {
+            if (free_particles == null)
+                return;
+            p = free_particles;
+            free_particles = p.next;
+            p.next = active_particles;
+            active_particles = p;
+
+            p.time = Globals.clientStateT.time;
+            p.color = color;
+
+            d = Lib.rand() & 7;
+            for (j = 0; j < 3; j++) {
+                p.org[j] = CL_tent.pos[j] + ((Lib.rand() & 7) - 4) + d * CL_tent.dir[j];
+                p.vel[j] = Lib.crand() * 20;
+            }
+
+            p.accel[0] = p.accel[1] = 0;
+            p.accel[2] = PARTICLE_GRAVITY;
+            p.alpha = 1.0f;
+
+            p.alphavel = -1.0f / (0.5f + Globals.rnd.nextFloat() * 0.3f);
+        }
+    }
+
+    /*
+     * =============== CL_TeleporterParticles ===============
+     */
+    static void TeleporterParticles(entity_state_t ent) {
+        int j;
+        cparticle_t p;
+
+        for (int i = 0; i < 8; i++) {
+            if (free_particles == null)
+                return;
+            p = free_particles;
+            free_particles = p.next;
+            p.next = active_particles;
+            active_particles = p;
+
+            p.time = Globals.clientStateT.time;
+            p.color = 0xdb;
+
+            for (j = 0; j < 2; j++) {
+                p.org[j] = ent.origin[j] - 16 + (Lib.rand() & 31);
+                p.vel[j] = Lib.crand() * 14;
+            }
+
+            p.org[2] = ent.origin[2] - 8 + (Lib.rand() & 7);
+            p.vel[2] = 80 + (Lib.rand() & 7);
+
+            p.accel[0] = p.accel[1] = 0;
+            p.accel[2] = -PARTICLE_GRAVITY;
+            p.alpha = 1.0f;
+
+            p.alphavel = -0.5f;
+        }
+    }
+
+    /*
+     * =============== CL_LogoutEffect
+     *
+     * ===============
+     */
+    static void LogoutEffect(float[] org, int type) {
+        int j;
+        cparticle_t p;
+
+        for (int i = 0; i < 500; i++) {
+            if (free_particles == null)
+                return;
+            p = free_particles;
+            free_particles = p.next;
+            p.next = active_particles;
+            active_particles = p;
+
+            p.time = Globals.clientStateT.time;
+
+            if (type == Defines.MZ_LOGIN)
+                p.color = 0xd0 + (Lib.rand() & 7); // green
+            else if (type == Defines.MZ_LOGOUT)
+                p.color = 0x40 + (Lib.rand() & 7); // red
+            else
+                p.color = 0xe0 + (Lib.rand() & 7); // yellow
+
+            p.org[0] = org[0] - 16 + Globals.rnd.nextFloat() * 32;
+            p.org[1] = org[1] - 16 + Globals.rnd.nextFloat() * 32;
+            p.org[2] = org[2] - 24 + Globals.rnd.nextFloat() * 56;
+
+            for (j = 0; j < 3; j++)
+                p.vel[j] = Lib.crand() * 20;
+
+            p.accel[0] = p.accel[1] = 0;
+            p.accel[2] = -PARTICLE_GRAVITY;
+            p.alpha = 1.0f;
+
+            p.alphavel = -1.0f / (1.0f + Globals.rnd.nextFloat() * 0.3f);
+        }
+    }
+
+    /*
+     * =============== CL_ItemRespawnParticles
+     *
+     * ===============
+     */
+    static void ItemRespawnParticles(float[] org) {
+        int j;
+        cparticle_t p;
+
+        for (int i = 0; i < 64; i++) {
+            if (free_particles == null)
+                return;
+            p = free_particles;
+            free_particles = p.next;
+            p.next = active_particles;
+            active_particles = p;
+
+            p.time = Globals.clientStateT.time;
+
+            p.color = 0xd4 + (Lib.rand() & 3); // green
+
+            p.org[0] = org[0] + Lib.crand() * 8;
+            p.org[1] = org[1] + Lib.crand() * 8;
+            p.org[2] = org[2] + Lib.crand() * 8;
+
+            for (j = 0; j < 3; j++)
+                p.vel[j] = Lib.crand() * 8;
+
+            p.accel[0] = p.accel[1] = 0;
+            p.accel[2] = -PARTICLE_GRAVITY * 0.2f;
+            p.alpha = 1.0f;
+
+            p.alphavel = -1.0f / (1.0f + Globals.rnd.nextFloat() * 0.3f);
+        }
+    }
+
+    /*
+     * =============== CL_ExplosionParticles ===============
+     */
+    static void ExplosionParticles() {
+        int j;
+        cparticle_t p;
+
+        for (int i = 0; i < 256; i++) {
+            if (free_particles == null)
+                return;
+            p = free_particles;
+            free_particles = p.next;
+            p.next = active_particles;
+            active_particles = p;
+
+            p.time = Globals.clientStateT.time;
+            p.color = 0xe0 + (Lib.rand() & 7);
+
+            for (j = 0; j < 3; j++) {
+                p.org[j] = CL_tent.pos[j] + ((Lib.rand() % 32) - 16);
+                p.vel[j] = (Lib.rand() % 384) - 192;
+            }
+
+            p.accel[0] = p.accel[1] = 0.0f;
+            p.accel[2] = -PARTICLE_GRAVITY;
+            p.alpha = 1.0f;
+
+            p.alphavel = -0.8f / (0.5f + Globals.rnd.nextFloat() * 0.3f);
+        }
+    }
+
+    static void BigTeleportParticles() {
+        cparticle_t p;
+        float angle, dist;
+
+        for (int i = 0; i < 4096; i++) {
+            if (free_particles == null)
+                return;
+            p = free_particles;
+            free_particles = p.next;
+            p.next = active_particles;
+            active_particles = p;
+
+            p.time = Globals.clientStateT.time;
+
+            p.color = colortable[Lib.rand() & 3];
+
+            angle = (float) (Math.PI * 2 * (Lib.rand() & 1023) / 1023.0);
+            dist = Lib.rand() & 31;
+            p.org[0] = (float) (CL_tent.pos[0] + Math.cos(angle) * dist);
+            p.vel[0] = (float) (Math.cos(angle) * (70 + (Lib.rand() & 63)));
+            p.accel[0] = (float) (-Math.cos(angle) * 100);
+
+            p.org[1] = (float) (CL_tent.pos[1] + Math.sin(angle) * dist);
+            p.vel[1] = (float) (Math.sin(angle) * (70 + (Lib.rand() & 63)));
+            p.accel[1] = (float) (-Math.sin(angle) * 100);
+
+            p.org[2] = CL_tent.pos[2] + 8 + (Lib.rand() % 90);
+            p.vel[2] = -100 + (Lib.rand() & 31);
+            p.accel[2] = PARTICLE_GRAVITY * 4;
+            p.alpha = 1.0f;
+
+            p.alphavel = -0.3f / (0.5f + Globals.rnd.nextFloat() * 0.3f);
+        }
+    }
+
+    /*
+     * =============== CL_BlasterParticles
+     *
+     * Wall impact puffs ===============
+     */
+    static void BlasterParticles() {
+        int j;
+        cparticle_t p;
+        float d;
+
+        int count = 40;
+        for (int i = 0; i < count; i++) {
+            if (free_particles == null)
+                return;
+            p = free_particles;
+            free_particles = p.next;
+            p.next = active_particles;
+            active_particles = p;
+
+            p.time = Globals.clientStateT.time;
+            p.color = 0xe0 + (Lib.rand() & 7);
+
+            d = Lib.rand() & 15;
+            for (j = 0; j < 3; j++) {
+                p.org[j] = CL_tent.pos[j] + ((Lib.rand() & 7) - 4) + d * CL_tent.dir[j];
+                p.vel[j] = CL_tent.dir[j] * 30 + Lib.crand() * 40;
+            }
+
+            p.accel[0] = p.accel[1] = 0;
+            p.accel[2] = -PARTICLE_GRAVITY;
+            p.alpha = 1.0f;
+
+            p.alphavel = -1.0f / (0.5f + Globals.rnd.nextFloat() * 0.3f);
+        }
+    }
+
+    /*
+     * =============== CL_BlasterTrail
+     *
+     * ===============
+     */
+    static void BlasterTrail(float[] start, float[] end) {
+        float len;
+        int j;
+        cparticle_t p;
+        int dec;
+
+        Math3D.vectorCopy(start, move);
+        Math3D.vectorSubtract(end, start, vec);
+        len = Math3D.vectorNormalize(vec);
+
+        dec = 5;
+        Math3D.vectorScale(vec, 5, vec);
+
+        // FIXME: this is a really silly way to have a loop
+        while (len > 0) {
+            len -= dec;
+
+            if (free_particles == null)
+                return;
+            p = free_particles;
+            free_particles = p.next;
+            p.next = active_particles;
+            active_particles = p;
+            Math3D.vectorClear(p.accel);
+
+            p.time = Globals.clientStateT.time;
+
+            p.alpha = 1.0f;
+            p.alphavel = -1.0f / (0.3f + Globals.rnd.nextFloat() * 0.2f);
+            p.color = 0xe0;
+            for (j = 0; j < 3; j++) {
+                p.org[j] = move[j] + Lib.crand();
+                p.vel[j] = Lib.crand() * 5;
+                p.accel[j] = 0;
+            }
+
+            Math3D.vectorAdd(move, vec, move);
+        }
+    }
+
+    // stack variable
+    // move, vec
+    /*
+     * ===============
+        *  CL_FlagTrail
+        * ===============
+        */
+    static void FlagTrail(float[] start, float[] end, float color) {
+        float len;
+        int j;
+        cparticle_t p;
+        int dec;
+
+        Math3D.vectorCopy(start, move);
+        Math3D.vectorSubtract(end, start, vec);
+        len = Math3D.vectorNormalize(vec);
+
+        dec = 5;
+        Math3D.vectorScale(vec, 5, vec);
+
+        while (len > 0) {
+            len -= dec;
+
+            if (free_particles == null)
+                return;
+            p = free_particles;
+            free_particles = p.next;
+            p.next = active_particles;
+            active_particles = p;
+            Math3D.vectorClear(p.accel);
+
+            p.time = Globals.clientStateT.time;
+
+            p.alpha = 1.0f;
+            p.alphavel = -1.0f / (0.8f + Globals.rnd.nextFloat() * 0.2f);
+            p.color = color;
+            for (j = 0; j < 3; j++) {
+                p.org[j] = move[j] + Lib.crand() * 16;
+                p.vel[j] = Lib.crand() * 5;
+                p.accel[j] = 0;
+            }
+
+            Math3D.vectorAdd(move, vec, move);
+        }
+    }
+
+    // stack variable
+    // move, vec
+    /*
+     * =============== CL_DiminishingTrail
+        *
+        * ===============
+        */
+    static void DiminishingTrail(float[] start, float[] end, centity_t old, int flags) {
+        cparticle_t p;
+        float orgscale;
+        float velscale;
+
+        Math3D.vectorCopy(start, move);
+        Math3D.vectorSubtract(end, start, vec);
+        float len = Math3D.vectorNormalize(vec);
+
+        float dec = 0.5f;
+        Math3D.vectorScale(vec, dec, vec);
+
+        if (old.trailcount > 900) {
+            orgscale = 4;
+            velscale = 15;
+        } else if (old.trailcount > 800) {
+            orgscale = 2;
+            velscale = 10;
+        } else {
+            orgscale = 1;
+            velscale = 5;
+        }
+
+        while (len > 0) {
+            len -= dec;
+
+            if (free_particles == null)
+                return;
+
+            // drop less particles as it flies
+            if ((Lib.rand() & 1023) < old.trailcount) {
+                p = free_particles;
+                free_particles = p.next;
+                p.next = active_particles;
+                active_particles = p;
+                Math3D.vectorClear(p.accel);
+
+                p.time = Globals.clientStateT.time;
+
+                if ((flags & Defines.EF_GIB) != 0) {
+                    p.alpha = 1.0f;
+                    p.alphavel = -1.0f / (1.0f + Globals.rnd.nextFloat() * 0.4f);
+                    p.color = 0xe8 + (Lib.rand() & 7);
+                    for (int j = 0; j < 3; j++) {
+                        p.org[j] = move[j] + Lib.crand() * orgscale;
+                        p.vel[j] = Lib.crand() * velscale;
+                        p.accel[j] = 0;
+                    }
+                    p.vel[2] -= PARTICLE_GRAVITY;
+                } else if ((flags & Defines.EF_GREENGIB) != 0) {
+                    p.alpha = 1.0f;
+                    p.alphavel = -1.0f / (1.0f + Globals.rnd.nextFloat() * 0.4f);
+                    p.color = 0xdb + (Lib.rand() & 7);
+                    for (int j = 0; j < 3; j++) {
+                        p.org[j] = move[j] + Lib.crand() * orgscale;
+                        p.vel[j] = Lib.crand() * velscale;
+                        p.accel[j] = 0;
+                    }
+                    p.vel[2] -= PARTICLE_GRAVITY;
+                } else {
+                    p.alpha = 1.0f;
+                    p.alphavel = -1.0f / (1.0f + Globals.rnd.nextFloat() * 0.2f);
+                    p.color = 4 + (Lib.rand() & 7);
+                    for (int j = 0; j < 3; j++) {
+                        p.org[j] = move[j] + Lib.crand() * orgscale;
+                        p.vel[j] = Lib.crand() * velscale;
+                    }
+                    p.accel[2] = 20;
+                }
+            }
+
+            old.trailcount -= 5;
+            if (old.trailcount < 100)
+                old.trailcount = 100;
+            Math3D.vectorAdd(move, vec, move);
+        }
+    }
+
+    // stack variable
+    // move, vec
+    /*
+     * =============== CL_RocketTrail
+        *
+        * ===============
+        */
+    static void RocketTrail(float[] start, float[] end, centity_t old) {
+        float len;
+        int j;
+        cparticle_t p;
+        float dec;
+
+        // smoke
+        DiminishingTrail(start, end, old, Defines.EF_ROCKET);
+
+        // fire
+        Math3D.vectorCopy(start, move);
+        Math3D.vectorSubtract(end, start, vec);
+        len = Math3D.vectorNormalize(vec);
+
+        dec = 1;
+        Math3D.vectorScale(vec, dec, vec);
+
+        while (len > 0) {
+            len -= dec;
+
+            if (free_particles == null)
+                return;
+
+            if ((Lib.rand() & 7) == 0) {
+                p = free_particles;
+                free_particles = p.next;
+                p.next = active_particles;
+                active_particles = p;
+
+                Math3D.vectorClear(p.accel);
+                p.time = Globals.clientStateT.time;
+
+                p.alpha = 1.0f;
+                p.alphavel = -1.0f / (1.0f + Globals.rnd.nextFloat() * 0.2f);
+                p.color = 0xdc + (Lib.rand() & 3);
+                for (j = 0; j < 3; j++) {
+                    p.org[j] = move[j] + Lib.crand() * 5;
+                    p.vel[j] = Lib.crand() * 20;
+                }
+                p.accel[2] = -PARTICLE_GRAVITY;
+            }
+            Math3D.vectorAdd(move, vec, move);
+        }
+    }
+
+    // stack variable
+    // move, vec
+       /*
+        * =============== CL_RailTrail
+        *
+        * ===============
+        */
+    static void RailTrail() {
+        float len;
+        int j;
+        cparticle_t p;
+        float dec;
+        float[] right = new float[3];
+        float[] up = new float[3];
+        int i;
+        float d, c, s;
+        float[] dir = new float[3];
+        byte clr = 0x74;
+
+        Math3D.vectorCopy(CL_tent.pos, move);
+        Math3D.vectorSubtract(CL_tent.pos2, CL_tent.pos, vec);
+        len = Math3D.vectorNormalize(vec);
+
+        Math3D.makeNormalVectors(vec, right, up);
+
+        for (i = 0; i < len; i++) {
+            if (free_particles == null)
+                return;
+
+            p = free_particles;
+            free_particles = p.next;
+            p.next = active_particles;
+            active_particles = p;
+
+            p.time = Globals.clientStateT.time;
+            Math3D.vectorClear(p.accel);
+
+            d = i * 0.1f;
+            c = (float) Math.cos(d);
+            s = (float) Math.sin(d);
+
+            Math3D.vectorScale(right, c, dir);
+            Math3D.vectorMA(dir, s, up, dir);
+
+            p.alpha = 1.0f;
+            p.alphavel = -1.0f / (1.0f + Globals.rnd.nextFloat() * 0.2f);
+            p.color = clr + (Lib.rand() & 7);
+            for (j = 0; j < 3; j++) {
+                p.org[j] = move[j] + dir[j] * 3;
+                p.vel[j] = dir[j] * 6;
+            }
+
+            Math3D.vectorAdd(move, vec, move);
+        }
+
+        dec = 0.75f;
+        Math3D.vectorScale(vec, dec, vec);
+        Math3D.vectorCopy(CL_tent.pos, move);
+
+        while (len > 0) {
+            len -= dec;
+
+            if (free_particles == null)
+                return;
+            p = free_particles;
+            free_particles = p.next;
+            p.next = active_particles;
+            active_particles = p;
+
+            p.time = Globals.clientStateT.time;
+            Math3D.vectorClear(p.accel);
+
+            p.alpha = 1.0f;
+            p.alphavel = -1.0f / (0.6f + Globals.rnd.nextFloat() * 0.2f);
+            p.color = Lib.rand() & 15;
+
+            for (j = 0; j < 3; j++) {
+                p.org[j] = move[j] + Lib.crand() * 3;
+                p.vel[j] = Lib.crand() * 3;
+                p.accel[j] = 0;
+            }
+
+            Math3D.vectorAdd(move, vec, move);
+        }
+    }
+
+    // stack variable
+    // move, vec
+       /*
+        * =============== CL_IonripperTrail ===============
+        */
+    static void IonripperTrail(float[] start, float[] ent) {
+        float len;
+        int j;
+        cparticle_t p;
+        int dec;
+        int left = 0;
+
+        Math3D.vectorCopy(start, move);
+        Math3D.vectorSubtract(ent, start, vec);
+        len = Math3D.vectorNormalize(vec);
+
+        dec = 5;
+        Math3D.vectorScale(vec, 5, vec);
+
+        while (len > 0) {
+            len -= dec;
+
+            if (free_particles == null)
+                return;
+            p = free_particles;
+            free_particles = p.next;
+            p.next = active_particles;
+            active_particles = p;
+            Math3D.vectorClear(p.accel);
+
+            p.time = Globals.clientStateT.time;
+            p.alpha = 0.5f;
+            p.alphavel = -1.0f / (0.3f + Globals.rnd.nextFloat() * 0.2f);
+            p.color = 0xe4 + (Lib.rand() & 3);
+
+            for (j = 0; j < 3; j++) {
+                p.org[j] = move[j];
+                p.accel[j] = 0;
+            }
+            if (left != 0) {
+                left = 0;
+                p.vel[0] = 10;
+            } else {
+                left = 1;
+                p.vel[0] = -10;
+            }
+
+            p.vel[1] = 0;
+            p.vel[2] = 0;
+
+            Math3D.vectorAdd(move, vec, move);
+        }
+    }
+
+    // stack variable
+    // move, vec
+       /*
+        * =============== CL_BubbleTrail
+        *
+        * ===============
+        */
+    static void BubbleTrail() {
+        float len;
+        int i, j;
+        cparticle_t p;
+        float dec;
+
+        Math3D.vectorCopy(CL_tent.pos, move);
+        Math3D.vectorSubtract(CL_tent.pos2, CL_tent.pos, vec);
+        len = Math3D.vectorNormalize(vec);
+
+        dec = 32;
+        Math3D.vectorScale(vec, dec, vec);
+
+        for (i = 0; i < len; i += dec) {
+            if (free_particles == null)
+                return;
+
+            p = free_particles;
+            free_particles = p.next;
+            p.next = active_particles;
+            active_particles = p;
+
+            Math3D.vectorClear(p.accel);
+            p.time = Globals.clientStateT.time;
+
+            p.alpha = 1.0f;
+            p.alphavel = -1.0f / (1.0f + Globals.rnd.nextFloat() * 0.2f);
+            p.color = 4 + (Lib.rand() & 7);
+            for (j = 0; j < 3; j++) {
+                p.org[j] = move[j] + Lib.crand() * 2;
+                p.vel[j] = Lib.crand() * 5;
+            }
+            p.vel[2] += 6;
+
+            Math3D.vectorAdd(move, vec, move);
+        }
+    }
+
+    // stack variable
+    // forward
+       /*
+        * =============== CL_FlyParticles ===============
+        */
+    static void FlyParticles(float[] origin, int count) {
+        int i;
+        cparticle_t p;
+        float angle;
+        float sp, sy, cp, cy;
+        float dist = 64;
+        float ltime;
+
+        if (count > Defines.NUMVERTEXNORMALS)
+            count = Defines.NUMVERTEXNORMALS;
+
+        if (avelocities[0][0] == 0.0f) {
+            for (i = 0; i < Defines.NUMVERTEXNORMALS; i++) {
+                avelocities[i][0] = (Lib.rand() & 255) * 0.01f;
+                avelocities[i][1] = (Lib.rand() & 255) * 0.01f;
+                avelocities[i][2] = (Lib.rand() & 255) * 0.01f;
+            }
+        }
+
+        ltime = Globals.clientStateT.time / 1000.0f;
+        for (i = 0; i < count; i += 2) {
+            angle = ltime * avelocities[i][0];
+            sy = (float) Math.sin(angle);
+            cy = (float) Math.cos(angle);
+            angle = ltime * avelocities[i][1];
+            sp = (float) Math.sin(angle);
+            cp = (float) Math.cos(angle);
+            angle = ltime * avelocities[i][2];
+
+            forward[0] = cp * cy;
+            forward[1] = cp * sy;
+            forward[2] = -sp;
+
+            if (free_particles == null)
+                return;
+            p = free_particles;
+            free_particles = p.next;
+            p.next = active_particles;
+            active_particles = p;
+
+            p.time = Globals.clientStateT.time;
+
+            dist = (float) Math.sin(ltime + i) * 64;
+            p.org[0] = origin[0] + Globals.bytedirs[i][0] * dist + forward[0] * BEAMLENGTH;
+            p.org[1] = origin[1] + Globals.bytedirs[i][1] * dist + forward[1] * BEAMLENGTH;
+            p.org[2] = origin[2] + Globals.bytedirs[i][2] * dist + forward[2] * BEAMLENGTH;
+
+            Math3D.vectorClear(p.vel);
+            Math3D.vectorClear(p.accel);
+
+            p.color = 0;
+            //p.colorvel = 0;
+
+            p.alpha = 1;
+            p.alphavel = -100;
+        }
+    }
+
+    static void FlyEffect(centity_t ent, float[] origin) {
+        int n;
+        int count;
+        int starttime;
+
+        if (ent.fly_stoptime < Globals.clientStateT.time) {
+            starttime = Globals.clientStateT.time;
+            ent.fly_stoptime = Globals.clientStateT.time + 60000;
+        } else {
+            starttime = ent.fly_stoptime - 60000;
+        }
+
+        n = Globals.clientStateT.time - starttime;
+        if (n < 20000)
+            count = (int) ((n * 162) / 20000.0);
+        else {
+            n = ent.fly_stoptime - Globals.clientStateT.time;
+            if (n < 20000)
+                count = (int) ((n * 162) / 20000.0);
+            else
+                count = 162;
+        }
+
+        FlyParticles(origin, count);
+    }
+
+    // forward
+       /*
+        * =============== CL_BfgParticles ===============
+        */
+    //#define BEAMLENGTH 16
+    static void BfgParticles() {
+        int i;
+        cparticle_t p;
+        float angle;
+        float sp, sy, cp, cy;
+        float dist = 64;
+        float ltime;
+
+        if (avelocities[0][0] == 0.0f) {
+            for (i = 0; i < Defines.NUMVERTEXNORMALS; i++) {
+                avelocities[i][0] = (Lib.rand() & 255) * 0.01f;
+                avelocities[i][1] = (Lib.rand() & 255) * 0.01f;
+                avelocities[i][2] = (Lib.rand() & 255) * 0.01f;
+            }
+        }
+
+        ltime = Globals.clientStateT.time / 1000.0f;
+        for (i = 0; i < Defines.NUMVERTEXNORMALS; i++) {
+            angle = ltime * avelocities[i][0];
+            sy = (float) Math.sin(angle);
+            cy = (float) Math.cos(angle);
+            angle = ltime * avelocities[i][1];
+            sp = (float) Math.sin(angle);
+            cp = (float) Math.cos(angle);
+            angle = ltime * avelocities[i][2];
+
+            forward[0] = cp * cy;
+            forward[1] = cp * sy;
+            forward[2] = -sp;
+
+            if (free_particles == null)
+                return;
+            p = free_particles;
+            free_particles = p.next;
+            p.next = active_particles;
+            active_particles = p;
+
+            p.time = Globals.clientStateT.time;
+
+            dist = (float) (Math.sin(ltime + i) * 64);
+            p.org[0] = CL_ents.ent.origin[0] + Globals.bytedirs[i][0] * dist + forward[0] * BEAMLENGTH;
+            p.org[1] = CL_ents.ent.origin[1] + Globals.bytedirs[i][1] * dist + forward[1] * BEAMLENGTH;
+            p.org[2] = CL_ents.ent.origin[2] + Globals.bytedirs[i][2] * dist + forward[2] * BEAMLENGTH;
+
+            Math3D.vectorClear(p.vel);
+            Math3D.vectorClear(p.accel);
+
+            Math3D.vectorSubtract(p.org, CL_ents.ent.origin, v);
+            dist = Math3D.vectorLength(v) / 90.0f;
+            p.color = (float) Math.floor(0xd0 + dist * 7);
+            //p.colorvel = 0;
+
+            p.alpha = 1.0f - dist;
+            p.alphavel = -100;
+        }
+    }
+
+    /*
+     * =============== CL_TrapParticles ===============
+     */
+    //    RAFAEL
+    static void TrapParticles() {
+        float len;
+        int j;
+        cparticle_t p;
+        int dec;
+
+        CL_ents.ent.origin[2] -= 14;
+        Math3D.vectorCopy(CL_ents.ent.origin, start);
+        Math3D.vectorCopy(CL_ents.ent.origin, end);
+        end[2] += 64;
+
+        Math3D.vectorCopy(start, move);
+        Math3D.vectorSubtract(end, start, vec);
+        len = Math3D.vectorNormalize(vec);
+
+        dec = 5;
+        Math3D.vectorScale(vec, 5, vec);
+
+        // FIXME: this is a really silly way to have a loop
+        while (len > 0) {
+            len -= dec;
+
+            if (free_particles == null)
+                return;
+            p = free_particles;
+            free_particles = p.next;
+            p.next = active_particles;
+            active_particles = p;
+            Math3D.vectorClear(p.accel);
+
+            p.time = Globals.clientStateT.time;
+
+            p.alpha = 1.0f;
+            p.alphavel = -1.0f / (0.3f + Globals.rnd.nextFloat() * 0.2f);
+            p.color = 0xe0;
+            for (j = 0; j < 3; j++) {
+                p.org[j] = move[j] + Lib.crand();
+                p.vel[j] = Lib.crand() * 15;
+                p.accel[j] = 0;
+            }
+            p.accel[2] = PARTICLE_GRAVITY;
+
+            Math3D.vectorAdd(move, vec, move);
+        }
+
+        int i, k;
+        //cparticle_t p;
+        float vel;
+        float[] dir = new float[3];
+        float[] org = new float[3];
+
+        CL_ents.ent.origin[2] += 14;
+        Math3D.vectorCopy(CL_ents.ent.origin, org);
+
+        for (i = -2; i <= 2; i += 4)
+            for (j = -2; j <= 2; j += 4)
+                for (k = -2; k <= 4; k += 4) {
+                    if (free_particles == null)
+                        return;
+                    p = free_particles;
+                    free_particles = p.next;
+                    p.next = active_particles;
+                    active_particles = p;
+
+                    p.time = Globals.clientStateT.time;
+                    p.color = 0xe0 + (Lib.rand() & 3);
+
+                    p.alpha = 1.0f;
+                    p.alphavel = -1.0f / (0.3f + (Lib.rand() & 7) * 0.02f);
+
+                    p.org[0] = org[0] + i + ((Lib.rand() & 23) * Lib.crand());
+                    p.org[1] = org[1] + j + ((Lib.rand() & 23) * Lib.crand());
+                    p.org[2] = org[2] + k + ((Lib.rand() & 23) * Lib.crand());
+
+                    dir[0] = j * 8;
+                    dir[1] = i * 8;
+                    dir[2] = k * 8;
+
+                    Math3D.vectorNormalize(dir);
+                    vel = 50 + Lib.rand() & 63;
+                    Math3D.vectorScale(dir, vel, p.vel);
+
+                    p.accel[0] = p.accel[1] = 0;
+                    p.accel[2] = -PARTICLE_GRAVITY;
+                }
+
+    }
+
+    /*
+     * =============== CL_BFGExplosionParticles ===============
+     */
+    //   FIXME combined with CL_ExplosionParticles
+    static void BFGExplosionParticles() {
+        int j;
+        cparticle_t p;
+
+        for (int i = 0; i < 256; i++) {
+            if (free_particles == null)
+                return;
+            p = free_particles;
+            free_particles = p.next;
+            p.next = active_particles;
+            active_particles = p;
+
+            p.time = Globals.clientStateT.time;
+            p.color = 0xd0 + (Lib.rand() & 7);
+
+            for (j = 0; j < 3; j++) {
+                p.org[j] = CL_tent.pos[j] + ((Lib.rand() % 32) - 16);
+                p.vel[j] = (Lib.rand() % 384) - 192;
+            }
+
+            p.accel[0] = p.accel[1] = 0;
+            p.accel[2] = -PARTICLE_GRAVITY;
+            p.alpha = 1.0f;
+
+            p.alphavel = -0.8f / (0.5f + Globals.rnd.nextFloat() * 0.3f);
+        }
+    }
+
+    /*
+     * =============== CL_TeleportParticles
+     *
+     * ===============
+     */
+    static void TeleportParticles() {
+        cparticle_t p;
+        float vel;
+
+        for (int i = -16; i <= 16; i += 4)
+            for (int j = -16; j <= 16; j += 4)
+                for (int k = -16; k <= 32; k += 4) {
+                    if (free_particles == null)
+                        return;
+                    p = free_particles;
+                    free_particles = p.next;
+                    p.next = active_particles;
+                    active_particles = p;
+
+                    p.time = Globals.clientStateT.time;
+                    p.color = 7 + (Lib.rand() & 7);
+
+                    p.alpha = 1.0f;
+                    p.alphavel = -1.0f / (0.3f + (Lib.rand() & 7) * 0.02f);
+
+                    p.org[0] = CL_tent.pos[0] + i + (Lib.rand() & 3);
+                    p.org[1] = CL_tent.pos[1] + j + (Lib.rand() & 3);
+                    p.org[2] = CL_tent.pos[2] + k + (Lib.rand() & 3);
+
+                    dir[0] = j * 8;
+                    dir[1] = i * 8;
+                    dir[2] = k * 8;
+
+                    Math3D.vectorNormalize(dir);
+                    vel = 50 + (Lib.rand() & 63);
+                    Math3D.vectorScale(dir, vel, p.vel);
+
+                    p.accel[0] = p.accel[1] = 0;
+                    p.accel[2] = -PARTICLE_GRAVITY;
+                }
+    }
+
+    /*
+     * =============== CL_AddParticles ===============
+     */
+    static void AddParticles() {
+        cparticle_t p, next;
+        float alpha;
+        float time = 0.0f;
+        float time2;
+        int color;
+        cparticle_t active, tail;
+
+        active = null;
+        tail = null;
+
+        for (p = active_particles; p != null; p = next) {
+            next = p.next;
+
+            // PMM - added INSTANT_PARTICLE handling for heat beam
+            if (p.alphavel != INSTANT_PARTICLE) {
+                time = (Globals.clientStateT.time - p.time) * 0.001f;
+                alpha = p.alpha + time * p.alphavel;
+                if (alpha <= 0) { // faded out
+                    p.next = free_particles;
+                    free_particles = p;
+                    continue;
+                }
+            } else {
+                alpha = p.alpha;
+            }
+
+            p.next = null;
+            if (tail == null)
+                active = tail = p;
+            else {
+                tail.next = p;
+                tail = p;
+            }
+
+            if (alpha > 1.0)
+                alpha = 1;
+            color = (int) p.color;
+
+            time2 = time * time;
+
+            org[0] = p.org[0] + p.vel[0] * time + p.accel[0] * time2;
+            org[1] = p.org[1] + p.vel[1] * time + p.accel[1] * time2;
+            org[2] = p.org[2] + p.vel[2] * time + p.accel[2] * time2;
+
+            V.AddParticle(org, color, alpha);
+            // PMM
+            if (p.alphavel == INSTANT_PARTICLE) {
+                p.alphavel = 0.0f;
+                p.alpha = 0.0f;
+            }
+        }
+
+        active_particles = active;
+    }
+
+       /*
+        * ==============================================================
+        * 
+        * PARTICLE MANAGEMENT
+        * 
+        * ==============================================================
+        */
+
+    /*
+     * ============== CL_EntityEvent
+     *
+     * An entity has just been parsed that has an event value
+     *
+     * the female events are there for backwards compatability ==============
+     */
+    static void EntityEvent(entity_state_t ent) {
+        switch (ent.event) {
+            case Defines.EV_FOOTSTEP:
+                if (Globals.cl_footsteps.value != 0.0f)
+                    S.StartSound(null, ent.number, Defines.CHAN_BODY, CL_tent.cl_sfx_footsteps[Lib.rand() & 3], 1, Defines.ATTN_NORM, 0);
+                break;
+            case Defines.EV_FALLSHORT:
+                S.StartSound(null, ent.number, Defines.CHAN_AUTO, S.RegisterSound("player/land1.wav"), 1, Defines.ATTN_NORM, 0);
+                break;
+            case Defines.EV_FALL:
+                S.StartSound(null, ent.number, Defines.CHAN_AUTO, S.RegisterSound("*fall2.wav"), 1, Defines.ATTN_NORM, 0);
+                break;
+            case Defines.EV_FALLFAR:
+                S.StartSound(null, ent.number, Defines.CHAN_AUTO, S.RegisterSound("*fall1.wav"), 1, Defines.ATTN_NORM, 0);
+                break;
+        }
+    }
+
+    /*
+     * ============== CL_ClearEffects
+     *
+     * ==============
+     */
+    static void ClearEffects() {
+        ClearParticles();
+        ClearDlights();
+        ClearLightStyles();
+    }
+
+    static class cdlight_t {
+        final float[] color = {0, 0, 0};
+        final float[] origin = {0, 0, 0};
+        int key; // so entities can reuse same entry
+        float radius;
+
+        float die; // stop lighting after this time
+
+        float minlight; // don't add when contributing less
+
+        void clear() {
+            radius = minlight = color[0] = color[1] = color[2] = 0;
+        }
+    }
+
+    static class clightstyle_t {
+        final float[] value = new float[3];
+        final float[] map = new float[Defines.MAX_QPATH];
+        int length;
+
+        void clear() {
+            value[0] = value[1] = value[2] = length = 0;
+            for (int i = 0; i < map.length; i++)
+                map[i] = 0.0f;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/client/CL_input.java b/src/main/java/lwjake2/client/CL_input.java
new file mode 100644 (file)
index 0000000..06b9e19
--- /dev/null
@@ -0,0 +1,727 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.Cmd;
+import lwjake2.game.CvarT;
+import lwjake2.game.usercmd_t;
+import lwjake2.qcommon.*;
+import lwjake2.sys.UserInputHandler;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+
+/**
+ * CL_input
+ */
+public class CL_input {
+
+    /*
+     * ===============================================================================
+     *
+     * KEY BUTTONS
+     *
+     * Continuous button event tracking is complicated by the fact that two
+     * different input sources (say, mouse button 1 and the control key) can
+     * both press the same button, but the button should only be released when
+     * both of the pressing key have been released.
+     *
+     * When a key event issues a button command (+forward, +attack, etc), it
+     * appends its key number as a parameter to the command so it can be matched
+     * up with the release.
+     *
+     * state bit 0 is the current state of the key state bit 1 is edge triggered
+     * on the up to down transition state bit 2 is edge triggered on the down to
+     * up transition
+     *
+     *
+     * Key_Event (int key, qboolean down, unsigned time);
+     *
+     * +mlook src time
+     *
+     * ===============================================================================
+     */
+    public static final kbutton_t in_strafe = new kbutton_t();
+    static final kbutton_t in_klook = new kbutton_t();
+    static final kbutton_t in_left = new kbutton_t();
+    static final kbutton_t in_right = new kbutton_t();
+    static final kbutton_t in_forward = new kbutton_t();
+    static final kbutton_t in_back = new kbutton_t();
+    static final kbutton_t in_lookup = new kbutton_t();
+    static final kbutton_t in_lookdown = new kbutton_t();
+    static final kbutton_t in_moveleft = new kbutton_t();
+    static final kbutton_t in_moveright = new kbutton_t();
+    static final kbutton_t in_speed = new kbutton_t();
+    static final kbutton_t in_use = new kbutton_t();
+    static final kbutton_t in_attack = new kbutton_t();
+    static final kbutton_t in_up = new kbutton_t();
+    static final kbutton_t in_down = new kbutton_t();
+    private static final sizebuf_t buf = new sizebuf_t();
+    private static final byte[] data = new byte[128];
+    private static final usercmd_t nullcmd = new usercmd_t();
+    static long frame_msec;
+    static long old_sys_frame_time;
+    static CvarT cl_nodelta;
+    static int in_impulse;
+
+    static void KeyDown(kbutton_t b) {
+        int k;
+        String c;
+
+        c = Cmd.Argv(1);
+        if (c.length() > 0)
+            k = Lib.atoi(c);
+        else
+            k = -1; // typed manually at the console for continuous down
+
+        if (k == b.down[0] || k == b.down[1])
+            return; // repeating key
+
+        if (b.down[0] == 0)
+            b.down[0] = k;
+        else if (b.down[1] == 0)
+            b.down[1] = k;
+        else {
+            Com.Printf("Three keys down for a button!\n");
+            return;
+        }
+
+        if ((b.state & 1) != 0)
+            return; // still down
+
+        // save timestamp
+        c = Cmd.Argv(2);
+        b.downtime = Lib.atoi(c);
+        if (b.downtime == 0)
+            b.downtime = Globals.sys_frame_time - 100;
+
+        b.state |= 3; // down + impulse down
+    }
+
+    static void KeyUp(kbutton_t b) {
+        int k;
+        String c;
+        int uptime;
+
+        c = Cmd.Argv(1);
+        if (c.length() > 0)
+            k = Lib.atoi(c);
+        else {
+            // typed manually at the console, assume for unsticking, so clear
+            // all
+            b.down[0] = b.down[1] = 0;
+            b.state = 4; // impulse up
+            return;
+        }
+
+        if (b.down[0] == k)
+            b.down[0] = 0;
+        else if (b.down[1] == k)
+            b.down[1] = 0;
+        else
+            return; // key up without coresponding down (menu pass through)
+        if (b.down[0] != 0 || b.down[1] != 0)
+            return; // some other key is still holding it down
+
+        if ((b.state & 1) == 0)
+            return; // still up (this should not happen)
+
+        // save timestamp
+        c = Cmd.Argv(2);
+        uptime = Lib.atoi(c);
+        if (uptime != 0)
+            b.msec += uptime - b.downtime;
+        else
+            b.msec += 10;
+
+        b.state &= ~1; // now up
+        b.state |= 4; // impulse up
+    }
+
+    static void IN_KLookDown() {
+        KeyDown(in_klook);
+    }
+
+    static void IN_KLookUp() {
+        KeyUp(in_klook);
+    }
+
+    static void IN_UpDown() {
+        KeyDown(in_up);
+    }
+
+    static void IN_UpUp() {
+        KeyUp(in_up);
+    }
+
+    static void IN_DownDown() {
+        KeyDown(in_down);
+    }
+
+    static void IN_DownUp() {
+        KeyUp(in_down);
+    }
+
+    static void IN_LeftDown() {
+        KeyDown(in_left);
+    }
+
+    static void IN_LeftUp() {
+        KeyUp(in_left);
+    }
+
+    static void IN_RightDown() {
+        KeyDown(in_right);
+    }
+
+    static void IN_RightUp() {
+        KeyUp(in_right);
+    }
+
+    static void IN_ForwardDown() {
+        KeyDown(in_forward);
+    }
+
+    static void IN_ForwardUp() {
+        KeyUp(in_forward);
+    }
+
+    static void IN_BackDown() {
+        KeyDown(in_back);
+    }
+
+    static void IN_BackUp() {
+        KeyUp(in_back);
+    }
+
+    static void IN_LookupDown() {
+        KeyDown(in_lookup);
+    }
+
+    static void IN_LookupUp() {
+        KeyUp(in_lookup);
+    }
+
+    static void IN_LookdownDown() {
+        KeyDown(in_lookdown);
+    }
+
+    static void IN_LookdownUp() {
+        KeyUp(in_lookdown);
+    }
+
+    static void IN_MoveleftDown() {
+        KeyDown(in_moveleft);
+    }
+
+    static void IN_MoveleftUp() {
+        KeyUp(in_moveleft);
+    }
+
+    static void IN_MoverightDown() {
+        KeyDown(in_moveright);
+    }
+
+    static void IN_MoverightUp() {
+        KeyUp(in_moveright);
+    }
+
+    static void IN_SpeedDown() {
+        KeyDown(in_speed);
+    }
+
+    static void IN_SpeedUp() {
+        KeyUp(in_speed);
+    }
+
+    static void IN_StrafeDown() {
+        KeyDown(in_strafe);
+    }
+
+    static void IN_StrafeUp() {
+        KeyUp(in_strafe);
+    }
+
+    static void IN_AttackDown() {
+        KeyDown(in_attack);
+    }
+
+    static void IN_AttackUp() {
+        KeyUp(in_attack);
+    }
+
+    static void IN_UseDown() {
+        KeyDown(in_use);
+    }
+
+    //   ==========================================================================
+
+    static void IN_UseUp() {
+        KeyUp(in_use);
+    }
+
+    static void IN_Impulse() {
+        in_impulse = Lib.atoi(Cmd.Argv(1));
+    }
+
+    /*
+     * =============== CL_KeyState
+     *
+     * Returns the fraction of the frame that the key was down ===============
+     */
+    static float KeyState(kbutton_t key) {
+        float val;
+        long msec;
+
+        key.state &= 1; // clear impulses
+
+        msec = key.msec;
+        key.msec = 0;
+
+        if (key.state != 0) {
+            // still down
+            msec += Globals.sys_frame_time - key.downtime;
+            key.downtime = Globals.sys_frame_time;
+        }
+
+        val = (float) msec / frame_msec;
+        if (val < 0)
+            val = 0;
+        if (val > 1)
+            val = 1;
+
+        return val;
+    }
+
+    /*
+     * ================ CL_AdjustAngles
+     *
+     * Moves the local angle positions ================
+     */
+    static void AdjustAngles() {
+        float speed;
+        float up, down;
+
+        if ((in_speed.state & 1) != 0)
+            speed = Globals.clientStaticT.frametime * Globals.cl_anglespeedkey.value;
+        else
+            speed = Globals.clientStaticT.frametime;
+
+        if ((in_strafe.state & 1) == 0) {
+            Globals.clientStateT.viewangles[Defines.YAW] -= speed * Globals.cl_yawspeed.value * KeyState(in_right);
+            Globals.clientStateT.viewangles[Defines.YAW] += speed * Globals.cl_yawspeed.value * KeyState(in_left);
+        }
+        if ((in_klook.state & 1) != 0) {
+            Globals.clientStateT.viewangles[Defines.PITCH] -= speed * Globals.cl_pitchspeed.value * KeyState(in_forward);
+            Globals.clientStateT.viewangles[Defines.PITCH] += speed * Globals.cl_pitchspeed.value * KeyState(in_back);
+        }
+
+        up = KeyState(in_lookup);
+        down = KeyState(in_lookdown);
+
+        Globals.clientStateT.viewangles[Defines.PITCH] -= speed * Globals.cl_pitchspeed.value * up;
+        Globals.clientStateT.viewangles[Defines.PITCH] += speed * Globals.cl_pitchspeed.value * down;
+    }
+
+    /*
+     * ================ CL_BaseMove
+     *
+     * Send the intended movement message to the server ================
+     */
+    static void BaseMove(usercmd_t cmd) {
+        AdjustAngles();
+
+        //memset (cmd, 0, sizeof(*cmd));
+        cmd.clear();
+
+        Math3D.vectorCopy(Globals.clientStateT.viewangles, cmd.angles);
+        if ((in_strafe.state & 1) != 0) {
+            cmd.sidemove += Globals.cl_sidespeed.value * KeyState(in_right);
+            cmd.sidemove -= Globals.cl_sidespeed.value * KeyState(in_left);
+        }
+
+        cmd.sidemove += Globals.cl_sidespeed.value * KeyState(in_moveright);
+        cmd.sidemove -= Globals.cl_sidespeed.value * KeyState(in_moveleft);
+
+        cmd.upmove += Globals.cl_upspeed.value * KeyState(in_up);
+        cmd.upmove -= Globals.cl_upspeed.value * KeyState(in_down);
+
+        if ((in_klook.state & 1) == 0) {
+            cmd.forwardmove += Globals.cl_forwardspeed.value * KeyState(in_forward);
+            cmd.forwardmove -= Globals.cl_forwardspeed.value * KeyState(in_back);
+        }
+
+        //
+        //        adjust for speed key / running
+        //
+        if (((in_speed.state & 1) ^ (int) (Globals.cl_run.value)) != 0) {
+            cmd.forwardmove *= 2;
+            cmd.sidemove *= 2;
+            cmd.upmove *= 2;
+        }
+
+    }
+
+    static void ClampPitch() {
+
+        float pitch;
+
+        pitch = Math3D.short2Angle(Globals.clientStateT.frame.playerstate.pmove.delta_angles[Defines.PITCH]);
+        if (pitch > 180)
+            pitch -= 360;
+
+        if (Globals.clientStateT.viewangles[Defines.PITCH] + pitch < -360)
+            Globals.clientStateT.viewangles[Defines.PITCH] += 360; // wrapped
+        if (Globals.clientStateT.viewangles[Defines.PITCH] + pitch > 360)
+            Globals.clientStateT.viewangles[Defines.PITCH] -= 360; // wrapped
+
+        if (Globals.clientStateT.viewangles[Defines.PITCH] + pitch > 89)
+            Globals.clientStateT.viewangles[Defines.PITCH] = 89 - pitch;
+        if (Globals.clientStateT.viewangles[Defines.PITCH] + pitch < -89)
+            Globals.clientStateT.viewangles[Defines.PITCH] = -89 - pitch;
+    }
+
+    /*
+     * ============== CL_FinishMove ==============
+     */
+    static void FinishMove(usercmd_t cmd) {
+        int ms;
+        int i;
+
+        //
+        //        figure button bits
+        //
+        if ((in_attack.state & 3) != 0)
+            cmd.buttons |= Defines.BUTTON_ATTACK;
+        in_attack.state &= ~2;
+
+        if ((in_use.state & 3) != 0)
+            cmd.buttons |= Defines.BUTTON_USE;
+        in_use.state &= ~2;
+
+        if (Key.anykeydown != 0 && Globals.clientStaticT.key_dest == Defines.key_game)
+            cmd.buttons |= Defines.BUTTON_ANY;
+
+        // send milliseconds of time to apply the move
+        ms = (int) (Globals.clientStaticT.frametime * 1000);
+        if (ms > 250)
+            ms = 100; // time was unreasonable
+        cmd.msec = (byte) ms;
+
+        ClampPitch();
+        for (i = 0; i < 3; i++)
+            cmd.angles[i] = (short) Math3D.angle2Short(Globals.clientStateT.viewangles[i]);
+
+        cmd.impulse = (byte) in_impulse;
+        in_impulse = 0;
+
+        // send the ambient light level at the player's current position
+        cmd.lightlevel = (byte) Globals.cl_lightlevel.value;
+    }
+
+    /*
+     * ================= CL_CreateCmd =================
+     */
+    static void CreateCmd(usercmd_t cmd) {
+        //usercmd_t cmd = new usercmd_t();
+
+        frame_msec = Globals.sys_frame_time - old_sys_frame_time;
+        if (frame_msec < 1)
+            frame_msec = 1;
+        if (frame_msec > 200)
+            frame_msec = 200;
+
+        // get basic movement from keyboard
+        BaseMove(cmd);
+
+        // allow mice or other external controllers to add to the move
+        UserInputHandler.Move(cmd);
+
+        FinishMove(cmd);
+
+        old_sys_frame_time = Globals.sys_frame_time;
+
+        //return cmd;
+    }
+
+    /*
+     * ============ CL_InitInput ============
+     */
+    static void InitInput() {
+        Cmd.AddCommand("centerview", new xcommand_t() {
+            public void execute() {
+                UserInputHandler.CenterView();
+            }
+        });
+
+        Cmd.AddCommand("+moveup", new xcommand_t() {
+            public void execute() {
+                IN_UpDown();
+            }
+        });
+        Cmd.AddCommand("-moveup", new xcommand_t() {
+            public void execute() {
+                IN_UpUp();
+            }
+        });
+        Cmd.AddCommand("+movedown", new xcommand_t() {
+            public void execute() {
+                IN_DownDown();
+            }
+        });
+        Cmd.AddCommand("-movedown", new xcommand_t() {
+            public void execute() {
+                IN_DownUp();
+            }
+        });
+        Cmd.AddCommand("+left", new xcommand_t() {
+            public void execute() {
+                IN_LeftDown();
+            }
+        });
+        Cmd.AddCommand("-left", new xcommand_t() {
+            public void execute() {
+                IN_LeftUp();
+            }
+        });
+        Cmd.AddCommand("+right", new xcommand_t() {
+            public void execute() {
+                IN_RightDown();
+            }
+        });
+        Cmd.AddCommand("-right", new xcommand_t() {
+            public void execute() {
+                IN_RightUp();
+            }
+        });
+        Cmd.AddCommand("+forward", new xcommand_t() {
+            public void execute() {
+                IN_ForwardDown();
+            }
+        });
+        Cmd.AddCommand("-forward", new xcommand_t() {
+            public void execute() {
+                IN_ForwardUp();
+            }
+        });
+        Cmd.AddCommand("+back", new xcommand_t() {
+            public void execute() {
+                IN_BackDown();
+            }
+        });
+        Cmd.AddCommand("-back", new xcommand_t() {
+            public void execute() {
+                IN_BackUp();
+            }
+        });
+        Cmd.AddCommand("+lookup", new xcommand_t() {
+            public void execute() {
+                IN_LookupDown();
+            }
+        });
+        Cmd.AddCommand("-lookup", new xcommand_t() {
+            public void execute() {
+                IN_LookupUp();
+            }
+        });
+        Cmd.AddCommand("+lookdown", new xcommand_t() {
+            public void execute() {
+                IN_LookdownDown();
+            }
+        });
+        Cmd.AddCommand("-lookdown", new xcommand_t() {
+            public void execute() {
+                IN_LookdownUp();
+            }
+        });
+        Cmd.AddCommand("+strafe", new xcommand_t() {
+            public void execute() {
+                IN_StrafeDown();
+            }
+        });
+        Cmd.AddCommand("-strafe", new xcommand_t() {
+            public void execute() {
+                IN_StrafeUp();
+            }
+        });
+        Cmd.AddCommand("+moveleft", new xcommand_t() {
+            public void execute() {
+                IN_MoveleftDown();
+            }
+        });
+        Cmd.AddCommand("-moveleft", new xcommand_t() {
+            public void execute() {
+                IN_MoveleftUp();
+            }
+        });
+        Cmd.AddCommand("+moveright", new xcommand_t() {
+            public void execute() {
+                IN_MoverightDown();
+            }
+        });
+        Cmd.AddCommand("-moveright", new xcommand_t() {
+            public void execute() {
+                IN_MoverightUp();
+            }
+        });
+        Cmd.AddCommand("+speed", new xcommand_t() {
+            public void execute() {
+                IN_SpeedDown();
+            }
+        });
+        Cmd.AddCommand("-speed", new xcommand_t() {
+            public void execute() {
+                IN_SpeedUp();
+            }
+        });
+        Cmd.AddCommand("+attack", new xcommand_t() {
+            public void execute() {
+                IN_AttackDown();
+            }
+        });
+        Cmd.AddCommand("-attack", new xcommand_t() {
+            public void execute() {
+                IN_AttackUp();
+            }
+        });
+        Cmd.AddCommand("+use", new xcommand_t() {
+            public void execute() {
+                IN_UseDown();
+            }
+        });
+        Cmd.AddCommand("-use", new xcommand_t() {
+            public void execute() {
+                IN_UseUp();
+            }
+        });
+        Cmd.AddCommand("impulse", new xcommand_t() {
+            public void execute() {
+                IN_Impulse();
+            }
+        });
+        Cmd.AddCommand("+klook", new xcommand_t() {
+            public void execute() {
+                IN_KLookDown();
+            }
+        });
+        Cmd.AddCommand("-klook", new xcommand_t() {
+            public void execute() {
+                IN_KLookUp();
+            }
+        });
+
+        cl_nodelta = Cvar.get("cl_nodelta", "0", 0);
+    }
+
+    /*
+     * ================= CL_SendCmd =================
+     */
+    static void SendCmd() {
+        int i;
+        usercmd_t cmd, oldcmd;
+        int checksumIndex;
+
+        // build a command even if not connected
+
+        // save this command off for prediction
+        i = Globals.clientStaticT.netchan.outgoing_sequence & (Defines.CMD_BACKUP - 1);
+        cmd = Globals.clientStateT.cmds[i];
+        Globals.clientStateT.cmd_time[i] = Globals.clientStaticT.realtime; // for netgraph
+        // ping calculation
+
+        // fill the cmd
+        CreateCmd(cmd);
+
+        Globals.clientStateT.cmd.set(cmd);
+
+        if (Globals.clientStaticT.state == Defines.ca_disconnected || Globals.clientStaticT.state == Defines.ca_connecting)
+            return;
+
+        if (Globals.clientStaticT.state == Defines.ca_connected) {
+            if (Globals.clientStaticT.netchan.message.cursize != 0 || Globals.curtime - Globals.clientStaticT.netchan.last_sent > 1000)
+                Netchan.Transmit(Globals.clientStaticT.netchan, 0, new byte[0]);
+            return;
+        }
+
+        // send a userinfo update if needed
+        if (Globals.userinfo_modified) {
+            Client.FixUpGender();
+            Globals.userinfo_modified = false;
+            MSG.WriteByte(Globals.clientStaticT.netchan.message, Defines.clc_userinfo);
+            MSG.WriteString(Globals.clientStaticT.netchan.message, Cvar.userinfo());
+        }
+
+        SZ.Init(buf, data, data.length);
+
+        if (cmd.buttons != 0 && Globals.clientStateT.cinematictime > 0 && !Globals.clientStateT.attractloop
+                && Globals.clientStaticT.realtime - Globals.clientStateT.cinematictime > 1000) { // skip
+            // the
+            // rest
+            // of
+            // the
+            // cinematic
+            SCR.FinishCinematic();
+        }
+
+        // begin a client move command
+        MSG.WriteByte(buf, Defines.clc_move);
+
+        // save the position for a checksum byte
+        checksumIndex = buf.cursize;
+        MSG.WriteByte(buf, 0);
+
+        // let the server know what the last frame we
+        // got was, so the next message can be delta compressed
+        if (cl_nodelta.value != 0.0f || !Globals.clientStateT.frame.valid || Globals.clientStaticT.demowaiting)
+            MSG.WriteLong(buf, -1); // no compression
+        else
+            MSG.WriteLong(buf, Globals.clientStateT.frame.serverframe);
+
+        // send this and the previous cmds in the message, so
+        // if the last packet was dropped, it can be recovered
+        i = (Globals.clientStaticT.netchan.outgoing_sequence - 2) & (Defines.CMD_BACKUP - 1);
+        cmd = Globals.clientStateT.cmds[i];
+        //memset (nullcmd, 0, sizeof(nullcmd));
+        nullcmd.clear();
+
+        MSG.WriteDeltaUsercmd(buf, nullcmd, cmd);
+        oldcmd = cmd;
+
+        i = (Globals.clientStaticT.netchan.outgoing_sequence - 1) & (Defines.CMD_BACKUP - 1);
+        cmd = Globals.clientStateT.cmds[i];
+
+        MSG.WriteDeltaUsercmd(buf, oldcmd, cmd);
+        oldcmd = cmd;
+
+        i = (Globals.clientStaticT.netchan.outgoing_sequence) & (Defines.CMD_BACKUP - 1);
+        cmd = Globals.clientStateT.cmds[i];
+
+        MSG.WriteDeltaUsercmd(buf, oldcmd, cmd);
+
+        // calculate a checksum over the move commands
+        buf.data[checksumIndex] = Com.BlockSequenceCRCByte(buf.data, checksumIndex + 1, buf.cursize - checksumIndex - 1,
+                Globals.clientStaticT.netchan.outgoing_sequence);
+
+        //
+        // deliver the message
+        //
+        Netchan.Transmit(Globals.clientStaticT.netchan, buf.cursize, buf.data);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/client/CL_inv.java b/src/main/java/lwjake2/client/CL_inv.java
new file mode 100644 (file)
index 0000000..acc38bb
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.qcommon.Com;
+import lwjake2.qcommon.MSG;
+import lwjake2.util.Lib;
+import lwjake2.util.Vargs;
+
+/**
+ * CL_inv
+ */
+public class CL_inv {
+
+    /*
+     * ================ CL_DrawInventory ================
+     */
+    static final int DISPLAY_ITEMS = 17;
+
+    /*
+     * ================ CL_ParseInventory ================
+     */
+    static void ParseInventory() {
+        int i;
+
+        for (i = 0; i < Defines.MAX_ITEMS; i++)
+            Globals.clientStateT.inventory[i] = MSG.ReadShort(Globals.net_message);
+    }
+
+    /*
+     * ================ Inv_DrawString ================
+     */
+    static void Inv_DrawString(int x, int y, String string) {
+        for (int i = 0; i < string.length(); i++) {
+            Globals.re.DrawChar(x, y, string.charAt(i));
+            x += 8;
+        }
+    }
+
+    static String getHighBitString(String s) {
+        byte[] b = Lib.stringToBytes(s);
+        for (int i = 0; i < b.length; i++) {
+            b[i] = (byte) (b[i] | 128);
+        }
+        return Lib.bytesToString(b);
+    }
+
+    static void DrawInventory() {
+        int i, j;
+        int num, selected_num, item;
+        int[] index = new int[Defines.MAX_ITEMS];
+        String string;
+        int x, y;
+        String binding;
+        String bind;
+        int selected;
+        int top;
+
+        selected = Globals.clientStateT.frame.playerstate.stats[Defines.STAT_SELECTED_ITEM];
+
+        num = 0;
+        selected_num = 0;
+        for (i = 0; i < Defines.MAX_ITEMS; i++) {
+            if (i == selected)
+                selected_num = num;
+            if (Globals.clientStateT.inventory[i] != 0) {
+                index[num] = i;
+                num++;
+            }
+        }
+
+        // determine scroll point
+        top = selected_num - DISPLAY_ITEMS / 2;
+        if (num - top < DISPLAY_ITEMS)
+            top = num - DISPLAY_ITEMS;
+        if (top < 0)
+            top = 0;
+
+        x = (Globals.viddef.width - 256) / 2;
+        y = (Globals.viddef.height - 240) / 2;
+
+        // repaint everything next frame
+        SCR.DirtyScreen();
+
+        Globals.re.DrawPic(x, y + 8, "inventory");
+
+        y += 24;
+        x += 24;
+        Inv_DrawString(x, y, "hotkey ### item");
+        Inv_DrawString(x, y + 8, "------ --- ----");
+        y += 16;
+        for (i = top; i < num && i < top + DISPLAY_ITEMS; i++) {
+            item = index[i];
+            // search for a binding
+            //Com_sprintf (binding, sizeof(binding), "use %s",
+            // cl.configstrings[CS_ITEMS+item]);
+            binding = "use " + Globals.clientStateT.configstrings[Defines.CS_ITEMS + item];
+            bind = "";
+            for (j = 0; j < 256; j++)
+                if (Globals.keybindings[j] != null && Globals.keybindings[j].equals(binding)) {
+                    bind = Key.KeynumToString(j);
+                    break;
+                }
+
+            string = Com.sprintf("%6s %3i %s", new Vargs(3).add(bind).add(Globals.clientStateT.inventory[item]).add(
+                    Globals.clientStateT.configstrings[Defines.CS_ITEMS + item]));
+            if (item != selected)
+                string = getHighBitString(string);
+            else // draw a blinky cursor by the selected item
+            {
+                if ((Globals.clientStaticT.realtime * 10 & 1) != 0)
+                    Globals.re.DrawChar(x - 8, y, 15);
+            }
+            Inv_DrawString(x, y, string);
+            y += 8;
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/client/CL_newfx.java b/src/main/java/lwjake2/client/CL_newfx.java
new file mode 100644 (file)
index 0000000..cc27b6a
--- /dev/null
@@ -0,0 +1,824 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+
+/**
+ * CL_newfx
+ */
+public class CL_newfx {
+
+    // stack variable
+    private static final float[] move = {0, 0, 0};
+    private static final float[] vec = {0, 0, 0};
+    private static final float[] right = {0, 0, 0};
+    private static final float[] up = {0, 0, 0};
+    // stack variable
+    // move, vec, right, up
+    private static final float[] dir = {0, 0, 0};
+    private static final float[] end = {0, 0, 0};
+    // stack variable
+    private static final float[] r = {0, 0, 0};
+    private static final float[] u = {0, 0, 0};
+    // stack variable
+    // move, vec, right, up
+    private static final float[] forward = {0, 0, 0};
+    private static final float[] angle_dir = {0, 0, 0};
+    private static final int[] wb_colortable = {2 * 8, 13 * 8, 21 * 8, 18 * 8};
+    private static final int[] nb_colortable = {110, 112, 114, 116};
+    private static final int[] ws_colortable = {2 * 8, 13 * 8, 21 * 8, 18 * 8};
+
+    static void Flashlight(int ent) {
+        CL_fx.cdlight_t dl;
+
+        dl = CL_fx.AllocDlight(ent);
+        Math3D.vectorCopy(CL_tent.pos, dl.origin);
+        dl.radius = 400;
+        dl.minlight = 250;
+        dl.die = Globals.clientStateT.time + 100;
+        dl.color[0] = 1;
+        dl.color[1] = 1;
+        dl.color[2] = 1;
+    }
+
+    /*
+     * ====== CL_ColorFlash - flash of light ======
+     */
+    static void ColorFlash(float r,
+                           float g, float b) {
+        CL_fx.cdlight_t dl;
+
+        if ((Globals.vidref_val == Defines.VIDREF_SOFT)
+                && ((r < 0) || (g < 0) || (b < 0))) {
+            r = -r;
+            g = -g;
+            b = -b;
+        }
+
+        dl = CL_fx.AllocDlight(0);
+        Math3D.vectorCopy(CL_tent.pos, dl.origin);
+        dl.radius = 150;
+        dl.minlight = 250;
+        dl.die = Globals.clientStateT.time + 100;
+        dl.color[0] = r;
+        dl.color[1] = g;
+        dl.color[2] = b;
+    }
+
+    /*
+     * ====== CL_DebugTrail ======
+     */
+    static void DebugTrail() {
+        float len;
+        //             int j;
+        cparticle_t p;
+        float dec;
+        //             int i;
+        //             float d, c, s;
+        //             float[] dir;
+
+        Math3D.vectorCopy(CL_tent.pos, move);
+        Math3D.vectorSubtract(CL_tent.pos2, CL_tent.pos, vec);
+        len = Math3D.vectorNormalize(vec);
+
+        Math3D.makeNormalVectors(vec, right, up);
+
+        //             VectorScale(vec, RT2_SKIP, vec);
+
+        //             dec = 1.0;
+        //             dec = 0.75;
+        dec = 3;
+        Math3D.vectorScale(vec, dec, vec);
+        Math3D.vectorCopy(CL_tent.pos, move);
+
+        while (len > 0) {
+            len -= dec;
+
+            if (CL_fx.free_particles == null)
+                return;
+            p = CL_fx.free_particles;
+            CL_fx.free_particles = p.next;
+            p.next = CL_fx.active_particles;
+            CL_fx.active_particles = p;
+
+            p.time = Globals.clientStateT.time;
+            Math3D.vectorClear(p.accel);
+            Math3D.vectorClear(p.vel);
+            p.alpha = 1.0f;
+            p.alphavel = -0.1f;
+            //                 p.alphavel = 0;
+            p.color = 0x74 + (Lib.rand() & 7);
+            Math3D.vectorCopy(move, p.org);
+            /*
+             * for (j=0 ; j <3 ; j++) { p.org[j] = move[j] + crand()*2; p.vel[j] =
+             * crand()*3; p.accel[j] = 0; }
+             */
+            Math3D.vectorAdd(move, vec, move);
+        }
+
+    }
+
+    // stack variable
+    // move, vec
+    static void ForceWall(int color) {
+        float len;
+        int j;
+        cparticle_t p;
+
+        Math3D.vectorCopy(CL_tent.pos, move);
+        Math3D.vectorSubtract(CL_tent.pos2, CL_tent.pos, vec);
+        len = Math3D.vectorNormalize(vec);
+
+        Math3D.vectorScale(vec, 4, vec);
+
+        // FIXME: this is a really silly way to have a loop
+        while (len > 0) {
+            len -= 4;
+
+            if (CL_fx.free_particles == null)
+                return;
+
+            if (Globals.rnd.nextFloat() > 0.3) {
+                p = CL_fx.free_particles;
+                CL_fx.free_particles = p.next;
+                p.next = CL_fx.active_particles;
+                CL_fx.active_particles = p;
+                Math3D.vectorClear(p.accel);
+
+                p.time = Globals.clientStateT.time;
+
+                p.alpha = 1.0f;
+                p.alphavel = -1.0f / (3.0f + Globals.rnd.nextFloat() * 0.5f);
+                p.color = color;
+                for (j = 0; j < 3; j++) {
+                    p.org[j] = move[j] + Lib.crand() * 3;
+                    p.accel[j] = 0;
+                }
+                p.vel[0] = 0;
+                p.vel[1] = 0;
+                p.vel[2] = -40 - (Lib.crand() * 10);
+            }
+
+            Math3D.vectorAdd(move, vec, move);
+        }
+    }
+
+    // stack variable
+    // move, vec
+    /*
+     * =============== CL_BubbleTrail2 (lets you control the # of bubbles by
+     * setting the distance between the spawns)
+     *
+     * ===============
+     */
+    static void BubbleTrail2(int dist) {
+        float len;
+        int i, j;
+        cparticle_t p;
+        float dec;
+
+        Math3D.vectorCopy(CL_tent.pos, move);
+        Math3D.vectorSubtract(CL_tent.pos2, CL_tent.pos, vec);
+        len = Math3D.vectorNormalize(vec);
+
+        dec = dist;
+        Math3D.vectorScale(vec, dec, vec);
+
+        for (i = 0; i < len; i += dec) {
+            if (CL_fx.free_particles == null)
+                return;
+
+            p = CL_fx.free_particles;
+            CL_fx.free_particles = p.next;
+            p.next = CL_fx.active_particles;
+            CL_fx.active_particles = p;
+
+            Math3D.vectorClear(p.accel);
+            p.time = Globals.clientStateT.time;
+
+            p.alpha = 1.0f;
+            p.alphavel = -1.0f / (1 + Globals.rnd.nextFloat() * 0.1f);
+            p.color = 4 + (Lib.rand() & 7);
+            for (j = 0; j < 3; j++) {
+                p.org[j] = move[j] + Lib.crand() * 2;
+                p.vel[j] = Lib.crand() * 10;
+            }
+            p.org[2] -= 4;
+            //                 p.vel[2] += 6;
+            p.vel[2] += 20;
+
+            Math3D.vectorAdd(move, vec, move);
+        }
+    }
+
+    static void Heatbeam() {
+        float len;
+        int j;
+        cparticle_t p;
+        int i;
+        float c, s;
+        float ltime;
+        float step = 32.0f, rstep;
+        float start_pt;
+        float rot;
+        float variance;
+
+        Math3D.vectorMA(CL_tent.org, 4096, CL_tent.dist, end);
+
+        Math3D.vectorCopy(CL_tent.org, move);
+        Math3D.vectorSubtract(end, CL_tent.org, vec);
+        len = Math3D.vectorNormalize(vec);
+
+        // FIXME - pmm - these might end up using old values?
+        //             MakeNormalVectors (vec, right, up);
+        Math3D.vectorCopy(Globals.clientStateT.v_right, right);
+        Math3D.vectorCopy(Globals.clientStateT.v_up, up);
+        if (Globals.vidref_val == Defines.VIDREF_GL) { // GL mode
+            Math3D.vectorMA(move, -0.5f, right, move);
+            Math3D.vectorMA(move, -0.5f, up, move);
+        }
+        // otherwise assume SOFT
+
+        ltime = (float) Globals.clientStateT.time / 1000.0f;
+        start_pt = ltime * 96.0f % step;
+        Math3D.vectorMA(move, start_pt, vec, move);
+
+        Math3D.vectorScale(vec, step, vec);
+
+        //             Com_Printf ("%f\n", ltime);
+        rstep = (float) (Math.PI / 10.0);
+        float M_PI2 = (float) (Math.PI * 2.0);
+        for (i = (int) start_pt; i < len; i += step) {
+            if (i > step * 5) // don't bother after the 5th ring
+                break;
+
+            for (rot = 0; rot < M_PI2; rot += rstep) {
+
+                if (CL_fx.free_particles == null)
+                    return;
+
+                p = CL_fx.free_particles;
+                CL_fx.free_particles = p.next;
+                p.next = CL_fx.active_particles;
+                CL_fx.active_particles = p;
+
+                p.time = Globals.clientStateT.time;
+                Math3D.vectorClear(p.accel);
+                //                             rot+= fmod(ltime, 12.0)*M_PI;
+                //                             c = cos(rot)/2.0;
+                //                             s = sin(rot)/2.0;
+                //                             variance = 0.4 + ((float)rand()/(float)RAND_MAX) *0.2;
+                variance = 0.5f;
+                c = (float) (Math.cos(rot) * variance);
+                s = (float) (Math.sin(rot) * variance);
+
+                // trim it so it looks like it's starting at the origin
+                if (i < 10) {
+                    Math3D.vectorScale(right, c * (i / 10.0f), dir);
+                    Math3D.vectorMA(dir, s * (i / 10.0f), up, dir);
+                } else {
+                    Math3D.vectorScale(right, c, dir);
+                    Math3D.vectorMA(dir, s, up, dir);
+                }
+
+                p.alpha = 0.5f;
+                //             p.alphavel = -1.0 / (1+frand()*0.2);
+                p.alphavel = -1000.0f;
+                //             p.color = 0x74 + (rand()&7);
+                p.color = 223 - (Lib.rand() & 7);
+                for (j = 0; j < 3; j++) {
+                    p.org[j] = move[j] + dir[j] * 3;
+                    //                 p.vel[j] = dir[j]*6;
+                    p.vel[j] = 0;
+                }
+            }
+            Math3D.vectorAdd(move, vec, move);
+        }
+    }
+
+    /*
+     * =============== CL_ParticleSteamEffect
+     *
+     * Puffs with velocity along direction, with some randomness thrown in
+     * ===============
+     */
+    static void ParticleSteamEffect(int color,
+                                    int count, int magnitude) {
+        int i, j;
+        cparticle_t p;
+        float d;
+
+        //             vectoangles2 (dir, angle_dir);
+        //             AngleVectors (angle_dir, f, r, u);
+
+        Math3D.makeNormalVectors(CL_tent.dir, r, u);
+
+        for (i = 0; i < count; i++) {
+            if (CL_fx.free_particles == null)
+                return;
+            p = CL_fx.free_particles;
+            CL_fx.free_particles = p.next;
+            p.next = CL_fx.active_particles;
+            CL_fx.active_particles = p;
+
+            p.time = Globals.clientStateT.time;
+            p.color = color + (Lib.rand() & 7);
+
+            for (j = 0; j < 3; j++) {
+                p.org[j] = CL_tent.pos[j] + magnitude * 0.1f * Lib.crand();
+                //                             p.vel[j] = dir[j]*magnitude;
+            }
+            Math3D.vectorScale(CL_tent.dir, magnitude, p.vel);
+            d = Lib.crand() * magnitude / 3;
+            Math3D.vectorMA(p.vel, d, r, p.vel);
+            d = Lib.crand() * magnitude / 3;
+            Math3D.vectorMA(p.vel, d, u, p.vel);
+
+            p.accel[0] = p.accel[1] = 0;
+            p.accel[2] = -CL_fx.PARTICLE_GRAVITY / 2;
+            p.alpha = 1.0f;
+
+            p.alphavel = -1.0f / (0.5f + Globals.rnd.nextFloat() * 0.3f);
+        }
+    }
+
+    // stack variable
+    // r, u, dir
+    static void ParticleSteamEffect2(cl_sustain_t self)
+    //   float[] org, float[] dir, int color, int count, int magnitude)
+    {
+        int i, j;
+        cparticle_t p;
+        float d;
+
+        //             vectoangles2 (dir, angle_dir);
+        //             AngleVectors (angle_dir, f, r, u);
+
+        Math3D.vectorCopy(self.dir, dir);
+        Math3D.makeNormalVectors(dir, r, u);
+
+        for (i = 0; i < self.count; i++) {
+            if (CL_fx.free_particles == null)
+                return;
+            p = CL_fx.free_particles;
+            CL_fx.free_particles = p.next;
+            p.next = CL_fx.active_particles;
+            CL_fx.active_particles = p;
+
+            p.time = Globals.clientStateT.time;
+            p.color = self.color + (Lib.rand() & 7);
+
+            for (j = 0; j < 3; j++) {
+                p.org[j] = self.org[j] + self.magnitude * 0.1f * Lib.crand();
+                //                             p.vel[j] = dir[j]*magnitude;
+            }
+            Math3D.vectorScale(dir, self.magnitude, p.vel);
+            d = Lib.crand() * self.magnitude / 3;
+            Math3D.vectorMA(p.vel, d, r, p.vel);
+            d = Lib.crand() * self.magnitude / 3;
+            Math3D.vectorMA(p.vel, d, u, p.vel);
+
+            p.accel[0] = p.accel[1] = 0;
+            p.accel[2] = -CL_fx.PARTICLE_GRAVITY / 2;
+            p.alpha = 1.0f;
+
+            p.alphavel = -1.0f / (0.5f + Globals.rnd.nextFloat() * 0.3f);
+        }
+        self.nextthink += self.thinkinterval;
+    }
+
+    /*
+     * =============== CL_TrackerTrail ===============
+     */
+    static void TrackerTrail(float[] start, float[] end) {
+        float len;
+        cparticle_t p;
+        int dec;
+        float dist;
+
+        Math3D.vectorCopy(start, move);
+        Math3D.vectorSubtract(end, start, vec);
+        len = Math3D.vectorNormalize(vec);
+
+        Math3D.vectorCopy(vec, forward);
+        Math3D.vecToAngles(forward, angle_dir);
+        Math3D.angleVectors(angle_dir, forward, right, up);
+
+        dec = 3;
+        Math3D.vectorScale(vec, 3, vec);
+
+        // FIXME: this is a really silly way to have a loop
+        while (len > 0) {
+            len -= dec;
+
+            if (CL_fx.free_particles == null)
+                return;
+            p = CL_fx.free_particles;
+            CL_fx.free_particles = p.next;
+            p.next = CL_fx.active_particles;
+            CL_fx.active_particles = p;
+            Math3D.vectorClear(p.accel);
+
+            p.time = Globals.clientStateT.time;
+
+            p.alpha = 1.0f;
+            p.alphavel = -2.0f;
+            p.color = 0;
+            dist = Math3D.dotProduct(move, forward);
+            Math3D.vectorMA(move, (float) (8 * Math.cos(dist)), up, p.org);
+            for (int j = 0; j < 3; j++) {
+                p.vel[j] = 0;
+                p.accel[j] = 0;
+            }
+            p.vel[2] = 5;
+
+            Math3D.vectorAdd(move, vec, move);
+        }
+    }
+
+    // stack variable
+    // dir
+    static void Tracker_Shell(float[] origin) {
+        cparticle_t p;
+
+        for (int i = 0; i < 300; i++) {
+            if (CL_fx.free_particles == null)
+                return;
+            p = CL_fx.free_particles;
+            CL_fx.free_particles = p.next;
+            p.next = CL_fx.active_particles;
+            CL_fx.active_particles = p;
+            Math3D.vectorClear(p.accel);
+
+            p.time = Globals.clientStateT.time;
+
+            p.alpha = 1.0f;
+            p.alphavel = CL_fx.INSTANT_PARTICLE;
+            p.color = 0;
+
+            dir[0] = Lib.crand();
+            dir[1] = Lib.crand();
+            dir[2] = Lib.crand();
+            Math3D.vectorNormalize(dir);
+
+            Math3D.vectorMA(origin, 40, dir, p.org);
+        }
+    }
+
+    // stack variable
+    // dir
+    static void MonsterPlasma_Shell(float[] origin) {
+        cparticle_t p;
+
+        for (int i = 0; i < 40; i++) {
+            if (CL_fx.free_particles == null)
+                return;
+            p = CL_fx.free_particles;
+            CL_fx.free_particles = p.next;
+            p.next = CL_fx.active_particles;
+            CL_fx.active_particles = p;
+            Math3D.vectorClear(p.accel);
+
+            p.time = Globals.clientStateT.time;
+
+            p.alpha = 1.0f;
+            p.alphavel = CL_fx.INSTANT_PARTICLE;
+            p.color = 0xe0;
+
+            dir[0] = Lib.crand();
+            dir[1] = Lib.crand();
+            dir[2] = Lib.crand();
+            Math3D.vectorNormalize(dir);
+
+            Math3D.vectorMA(origin, 10, dir, p.org);
+            //                 VectorMA(origin, 10*(((rand () & 0x7fff) / ((float)0x7fff))),
+            // dir, p.org);
+        }
+    }
+
+    // stack variable
+    // dir
+    static void Widowbeamout(cl_sustain_t self) {
+        int i;
+        cparticle_t p;
+
+        float ratio;
+
+        ratio = 1.0f - (((float) self.endtime - (float) Globals.clientStateT.time) / 2100.0f);
+
+        for (i = 0; i < 300; i++) {
+            if (CL_fx.free_particles == null)
+                return;
+            p = CL_fx.free_particles;
+            CL_fx.free_particles = p.next;
+            p.next = CL_fx.active_particles;
+            CL_fx.active_particles = p;
+            Math3D.vectorClear(p.accel);
+
+            p.time = Globals.clientStateT.time;
+
+            p.alpha = 1.0f;
+            p.alphavel = CL_fx.INSTANT_PARTICLE;
+            p.color = wb_colortable[Lib.rand() & 3];
+
+            dir[0] = Lib.crand();
+            dir[1] = Lib.crand();
+            dir[2] = Lib.crand();
+            Math3D.vectorNormalize(dir);
+
+            Math3D.vectorMA(self.org, (45.0f * ratio), dir, p.org);
+            //                 VectorMA(origin, 10*(((rand () & 0x7fff) / ((float)0x7fff))),
+            // dir, p.org);
+        }
+    }
+
+    // stack variable
+    // dir
+    static void Nukeblast(cl_sustain_t self) {
+        int i;
+        cparticle_t p;
+
+        float ratio;
+
+        ratio = 1.0f - (((float) self.endtime - (float) Globals.clientStateT.time) / 1000.0f);
+
+        for (i = 0; i < 700; i++) {
+            if (CL_fx.free_particles == null)
+                return;
+            p = CL_fx.free_particles;
+            CL_fx.free_particles = p.next;
+            p.next = CL_fx.active_particles;
+            CL_fx.active_particles = p;
+            Math3D.vectorClear(p.accel);
+
+            p.time = Globals.clientStateT.time;
+
+            p.alpha = 1.0f;
+            p.alphavel = CL_fx.INSTANT_PARTICLE;
+            p.color = nb_colortable[Lib.rand() & 3];
+
+            dir[0] = Lib.crand();
+            dir[1] = Lib.crand();
+            dir[2] = Lib.crand();
+            Math3D.vectorNormalize(dir);
+
+            Math3D.vectorMA(self.org, (200.0f * ratio), dir, p.org);
+            //                 VectorMA(origin, 10*(((rand () & 0x7fff) / ((float)0x7fff))),
+            // dir, p.org);
+        }
+    }
+
+    // stack variable
+    // dir
+    static void WidowSplash() {
+        int i;
+        cparticle_t p;
+
+        for (i = 0; i < 256; i++) {
+            if (CL_fx.free_particles == null)
+                return;
+            p = CL_fx.free_particles;
+            CL_fx.free_particles = p.next;
+            p.next = CL_fx.active_particles;
+            CL_fx.active_particles = p;
+
+            p.time = Globals.clientStateT.time;
+            p.color = ws_colortable[Lib.rand() & 3];
+
+            dir[0] = Lib.crand();
+            dir[1] = Lib.crand();
+            dir[2] = Lib.crand();
+            Math3D.vectorNormalize(dir);
+            Math3D.vectorMA(CL_tent.pos, 45.0f, dir, p.org);
+            Math3D.vectorMA(Globals.vec3_origin, 40.0f, dir, p.vel);
+
+            p.accel[0] = p.accel[1] = 0;
+            p.alpha = 1.0f;
+
+            p.alphavel = -0.8f / (0.5f + Globals.rnd.nextFloat() * 0.3f);
+        }
+
+    }
+
+    // stack variable
+    // move, vec
+    /*
+     * ===============
+     *  CL_TagTrail
+     * ===============
+     */
+    static void TagTrail(float[] start, float[] end) {
+        float len;
+        int j;
+        cparticle_t p;
+        int dec;
+
+        Math3D.vectorCopy(start, move);
+        Math3D.vectorSubtract(end, start, vec);
+        len = Math3D.vectorNormalize(vec);
+
+        dec = 5;
+        Math3D.vectorScale(vec, 5, vec);
+
+        while (len >= 0) {
+            len -= dec;
+
+            if (CL_fx.free_particles == null)
+                return;
+            p = CL_fx.free_particles;
+            CL_fx.free_particles = p.next;
+            p.next = CL_fx.active_particles;
+            CL_fx.active_particles = p;
+            Math3D.vectorClear(p.accel);
+
+            p.time = Globals.clientStateT.time;
+
+            p.alpha = 1.0f;
+            p.alphavel = -1.0f / (0.8f + Globals.rnd.nextFloat() * 0.2f);
+            p.color = (float) 220;
+            for (j = 0; j < 3; j++) {
+                p.org[j] = move[j] + Lib.crand() * 16;
+                p.vel[j] = Lib.crand() * 5;
+                p.accel[j] = 0;
+            }
+
+            Math3D.vectorAdd(move, vec, move);
+        }
+    }
+
+    /*
+     * =============== CL_ColorExplosionParticles ===============
+     */
+    static void ColorExplosionParticles() {
+        int i, j;
+        cparticle_t p;
+
+        for (i = 0; i < 128; i++) {
+            if (CL_fx.free_particles == null)
+                return;
+            p = CL_fx.free_particles;
+            CL_fx.free_particles = p.next;
+            p.next = CL_fx.active_particles;
+            CL_fx.active_particles = p;
+
+            p.time = Globals.clientStateT.time;
+            p.color = (0);
+
+            for (j = 0; j < 3; j++) {
+                p.org[j] = CL_tent.pos[j] + ((Lib.rand() % 32) - 16);
+                p.vel[j] = (Lib.rand() % 256) - 128;
+            }
+
+            p.accel[0] = p.accel[1] = 0;
+            p.accel[2] = -CL_fx.PARTICLE_GRAVITY;
+            p.alpha = 1.0f;
+
+            p.alphavel = -0.4f / (0.6f + Globals.rnd.nextFloat() * 0.2f);
+        }
+    }
+
+    // stack variable
+    // r, u
+    /*
+     * =============== CL_ParticleSmokeEffect - like the steam effect, but
+     * unaffected by gravity ===============
+     */
+    static void ParticleSmokeEffect() {
+        int i, j;
+        cparticle_t p;
+        float d;
+
+        Math3D.makeNormalVectors(CL_tent.dir, r, u);
+
+        for (i = 0; i < 20; i++) {
+            if (CL_fx.free_particles == null)
+                return;
+            p = CL_fx.free_particles;
+            CL_fx.free_particles = p.next;
+            p.next = CL_fx.active_particles;
+            CL_fx.active_particles = p;
+
+            p.time = Globals.clientStateT.time;
+            p.color = (Lib.rand() & 7);
+
+            for (j = 0; j < 3; j++) {
+                p.org[j] = CL_tent.pos[j] + 20 * 0.1f * Lib.crand();
+                //                             p.vel[j] = dir[j]*magnitude;
+            }
+            Math3D.vectorScale(CL_tent.dir, 20, p.vel);
+            d = Lib.crand() * 20 / 3;
+            Math3D.vectorMA(p.vel, d, r, p.vel);
+            d = Lib.crand() * 20 / 3;
+            Math3D.vectorMA(p.vel, d, u, p.vel);
+
+            p.accel[0] = p.accel[1] = p.accel[2] = 0;
+            p.alpha = 1.0f;
+
+            p.alphavel = -1.0f / (0.5f + Globals.rnd.nextFloat() * 0.3f);
+        }
+    }
+
+    /*
+     * =============== CL_BlasterParticles2
+     * 
+     * Wall impact puffs (Green) ===============
+     */
+    static void BlasterParticles2(long color) {
+        int i, j;
+        cparticle_t p;
+        float d;
+        int count;
+
+        count = 40;
+        for (i = 0; i < count; i++) {
+            if (CL_fx.free_particles == null)
+                return;
+            p = CL_fx.free_particles;
+            CL_fx.free_particles = p.next;
+            p.next = CL_fx.active_particles;
+            CL_fx.active_particles = p;
+
+            p.time = Globals.clientStateT.time;
+            p.color = color + (Lib.rand() & 7);
+
+            d = Lib.rand() & 15;
+            for (j = 0; j < 3; j++) {
+                p.org[j] = CL_tent.pos[j] + ((Lib.rand() & 7) - 4) + d * CL_tent.dir[j];
+                p.vel[j] = CL_tent.dir[j] * 30 + Lib.crand() * 40;
+            }
+
+            p.accel[0] = p.accel[1] = 0;
+            p.accel[2] = -CL_fx.PARTICLE_GRAVITY;
+            p.alpha = 1.0f;
+
+            p.alphavel = -1.0f / (0.5f + Globals.rnd.nextFloat() * 0.3f);
+        }
+    }
+
+    // stack variable
+    // move, vec
+    /*
+     * =============== CL_BlasterTrail2
+     * 
+     * Green! ===============
+     */
+    static void BlasterTrail2(float[] start, float[] end) {
+        float len;
+        int j;
+        cparticle_t p;
+        int dec;
+
+        Math3D.vectorCopy(start, move);
+        Math3D.vectorSubtract(end, start, vec);
+        len = Math3D.vectorNormalize(vec);
+
+        dec = 5;
+        Math3D.vectorScale(vec, 5, vec);
+
+        // FIXME: this is a really silly way to have a loop
+        while (len > 0) {
+            len -= dec;
+
+            if (CL_fx.free_particles == null)
+                return;
+            p = CL_fx.free_particles;
+            CL_fx.free_particles = p.next;
+            p.next = CL_fx.active_particles;
+            CL_fx.active_particles = p;
+            Math3D.vectorClear(p.accel);
+
+            p.time = Globals.clientStateT.time;
+
+            p.alpha = 1.0f;
+            p.alphavel = -1.0f / (0.3f + Globals.rnd.nextFloat() * 0.2f);
+            p.color = 0xd0;
+            for (j = 0; j < 3; j++) {
+                p.org[j] = move[j] + Lib.crand();
+                p.vel[j] = Lib.crand() * 5;
+                p.accel[j] = 0;
+            }
+
+            Math3D.vectorAdd(move, vec, move);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/client/CL_parse.java b/src/main/java/lwjake2/client/CL_parse.java
new file mode 100644 (file)
index 0000000..845c2e1
--- /dev/null
@@ -0,0 +1,749 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.Cmd;
+import lwjake2.game.entity_state_t;
+import lwjake2.qcommon.*;
+import lwjake2.sound.S;
+import lwjake2.sys.Sys;
+import lwjake2.util.Lib;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * CL_parse
+ */
+public class CL_parse {
+
+    //// cl_parse.c -- parse a message received from the server
+
+    //   =============================================================================
+    public static final String[] svc_strings = {"svc_bad", "svc_muzzleflash",
+            "svc_muzzlflash2", "svc_temp_entity", "svc_layout",
+            "svc_inventory", "svc_nop", "svc_disconnect", "svc_reconnect",
+            "svc_sound", "svc_print", "svc_stufftext", "svc_serverdata",
+            "svc_configstring", "svc_spawnbaseline", "svc_centerprint",
+            "svc_download", "svc_playerinfo", "svc_packetentities",
+            "svc_deltapacketentities", "svc_frame"};
+    /*
+     * =============== CL_Download_f
+     *
+     * Request a download from the server ===============
+     */
+    public static final xcommand_t Download_f = new xcommand_t() {
+        public void execute() {
+            String filename;
+
+            if (Cmd.Argc() != 2) {
+                Com.Printf("Usage: download <filename>\n");
+                return;
+            }
+
+            filename = Cmd.Argv(1);
+
+            if (filename.contains("..")) {
+                Com.Printf("Refusing to download a path with ..\n");
+                return;
+            }
+
+            if (FS.LoadFile(filename) != null) { // it exists, no need to
+                // download
+                Com.Printf("File already exists.\n");
+                return;
+            }
+
+            Globals.clientStaticT.downloadname = filename;
+            Com.Printf("Downloading " + Globals.clientStaticT.downloadname + "\n");
+
+            // download to a temp name, and only rename
+            // to the real name when done, so if interrupted
+            // a runt file wont be left
+            Globals.clientStaticT.downloadtempname = Com
+                    .StripExtension(Globals.clientStaticT.downloadname);
+            Globals.clientStaticT.downloadtempname += ".tmp";
+
+            MSG.WriteByte(Globals.clientStaticT.netchan.message, Defines.clc_stringcmd);
+            MSG.WriteString(Globals.clientStaticT.netchan.message, "download "
+                    + Globals.clientStaticT.downloadname);
+
+            Globals.clientStaticT.downloadnumber++;
+        }
+    };
+    private static final float[] pos_v = {0, 0, 0};
+
+    public static String DownloadFileName(String fn) {
+        return FS.Gamedir() + "/" + fn;
+    }
+
+    /*
+     * =============== CL_CheckOrDownloadFile
+     *
+     * Returns true if the file exists, otherwise it attempts to start a
+     * download from the server. ===============
+     */
+    public static boolean CheckOrDownloadFile(String filename) {
+        RandomAccessFile fp;
+        String name;
+
+        if (filename.contains("..")) {
+            Com.Printf("Refusing to download a path with ..\n");
+            return true;
+        }
+
+        if (FS.FileLength(filename) > 0) { // it exists, no need to download
+            return true;
+        }
+
+        Globals.clientStaticT.downloadname = filename;
+
+        // download to a temp name, and only rename
+        // to the real name when done, so if interrupted
+        // a runt file wont be left
+        Globals.clientStaticT.downloadtempname = Com
+                .StripExtension(Globals.clientStaticT.downloadname);
+        Globals.clientStaticT.downloadtempname += ".tmp";
+
+        //       ZOID
+        // check to see if we already have a tmp for this file, if so, try to
+        // resume
+        // open the file if not opened yet
+        name = DownloadFileName(Globals.clientStaticT.downloadtempname);
+
+        fp = Lib.fopen(name, "r+b");
+
+        if (fp != null) {
+
+            // it exists
+            long len = 0;
+
+            try {
+                len = fp.length();
+            } catch (IOException ignored) {
+            }
+
+
+            Globals.clientStaticT.download = fp;
+
+            // give the server an offset to start the download
+            Com.Printf("Resuming " + Globals.clientStaticT.downloadname + "\n");
+            MSG.WriteByte(Globals.clientStaticT.netchan.message, Defines.clc_stringcmd);
+            MSG.WriteString(Globals.clientStaticT.netchan.message, "download "
+                    + Globals.clientStaticT.downloadname + " " + len);
+        } else {
+            Com.Printf("Downloading " + Globals.clientStaticT.downloadname + "\n");
+            MSG.WriteByte(Globals.clientStaticT.netchan.message, Defines.clc_stringcmd);
+            MSG.WriteString(Globals.clientStaticT.netchan.message, "download "
+                    + Globals.clientStaticT.downloadname);
+        }
+
+        Globals.clientStaticT.downloadnumber++;
+
+        return false;
+    }
+
+    /*
+     * ====================== CL_RegisterSounds ======================
+     */
+    public static void RegisterSounds() {
+        S.BeginRegistration();
+        CL_tent.RegisterTEntSounds();
+        for (int i = 1; i < Defines.MAX_SOUNDS; i++) {
+            if (Globals.clientStateT.configstrings[Defines.CS_SOUNDS + i] == null
+                    || Globals.clientStateT.configstrings[Defines.CS_SOUNDS + i]
+                    .equals("")
+                    || Globals.clientStateT.configstrings[Defines.CS_SOUNDS + i]
+                    .equals("\0"))
+                break;
+            Globals.clientStateT.sound_precache[i] = S
+                    .RegisterSound(Globals.clientStateT.configstrings[Defines.CS_SOUNDS
+                            + i]);
+            Sys.SendKeyEvents(); // pump message loop
+        }
+        S.EndRegistration();
+    }
+
+    /*
+     * =====================================================================
+     * 
+     * SERVER CONNECTING MESSAGES
+     * 
+     * =====================================================================
+     */
+
+    /*
+     * ===================== CL_ParseDownload
+     *
+     * A download message has been received from the server
+     * =====================
+     */
+    public static void ParseDownload() {
+
+        // read the data
+        int size = MSG.ReadShort(Globals.net_message);
+        int percent = MSG.ReadByte(Globals.net_message);
+        if (size == -1) {
+            Com.Printf("Server does not have this file.\n");
+            if (Globals.clientStaticT.download != null) {
+                // if here, we tried to resume a file but the server said no
+                try {
+                    Globals.clientStaticT.download.close();
+                } catch (IOException ignored) {
+                }
+                Globals.clientStaticT.download = null;
+            }
+            Client.RequestNextDownload();
+            return;
+        }
+
+        // open the file if not opened yet
+        if (Globals.clientStaticT.download == null) {
+            String name = DownloadFileName(Globals.clientStaticT.downloadtempname).toLowerCase();
+
+            FS.CreatePath(name);
+
+            Globals.clientStaticT.download = Lib.fopen(name, "rw");
+            if (Globals.clientStaticT.download == null) {
+                Globals.net_message.readcount += size;
+                Com.Printf("Failed to open " + Globals.clientStaticT.downloadtempname
+                        + "\n");
+                Client.RequestNextDownload();
+                return;
+            }
+        }
+
+        //fwrite(net_message.data[net_message.readcount], 1, size,
+        // cls.download);
+        try {
+            Globals.clientStaticT.download.write(Globals.net_message.data,
+                    Globals.net_message.readcount, size);
+        } catch (Exception ignored) {
+        }
+        Globals.net_message.readcount += size;
+
+        if (percent != 100) {
+            // request next block
+            //    change display routines by zoid
+            Globals.clientStaticT.downloadpercent = percent;
+            MSG.WriteByte(Globals.clientStaticT.netchan.message, Defines.clc_stringcmd);
+            SZ.Print(Globals.clientStaticT.netchan.message, "nextdl");
+        } else {
+            String oldn, newn;
+            //char oldn[MAX_OSPATH];
+            //char newn[MAX_OSPATH];
+
+            //                 Com.Printf ("100%%\n");
+
+            try {
+                Globals.clientStaticT.download.close();
+            } catch (IOException ignored) {
+            }
+
+            // rename the temp file to it's final name
+            oldn = DownloadFileName(Globals.clientStaticT.downloadtempname);
+            newn = DownloadFileName(Globals.clientStaticT.downloadname);
+            int r = Lib.rename(oldn, newn);
+            if (r != 0)
+                Com.Printf("failed to rename.\n");
+
+            Globals.clientStaticT.download = null;
+            Globals.clientStaticT.downloadpercent = 0;
+
+            // get another file if needed
+
+            Client.RequestNextDownload();
+        }
+    }
+
+    /*
+     * ================== CL_ParseServerData ==================
+     */
+    //checked once, was ok.
+    public static void ParseServerData() {
+
+        String str;
+        int i;
+
+        Com.DPrintf("ParseServerData():Serverdata packet received.\n");
+        //
+        //        wipe the ClientStateT struct
+        //
+        Client.ClearState();
+        Globals.clientStaticT.state = Defines.ca_connected;
+
+        //        parse protocol version number
+        i = MSG.ReadLong(Globals.net_message);
+        Globals.clientStaticT.serverProtocol = i;
+
+        // BIG HACK to let demos from release work with the 3.0x patch!!!
+        if (Globals.server_state != 0) {
+        } else if (i != Defines.PROTOCOL_VERSION)
+            Com.Error(Defines.ERR_DROP, "Server returned version " + i
+                    + ", not " + Defines.PROTOCOL_VERSION);
+
+        Globals.clientStateT.servercount = MSG.ReadLong(Globals.net_message);
+        Globals.clientStateT.attractloop = MSG.ReadByte(Globals.net_message) != 0;
+
+        // game directory
+        str = MSG.ReadString(Globals.net_message);
+        Globals.clientStateT.gamedir = str;
+        Com.dprintln("gamedir=" + str);
+
+        // set gamedir
+        if (str.length() > 0
+                && (FS.fs_gamedirvar.string == null
+                || FS.fs_gamedirvar.string.length() == 0 || FS.fs_gamedirvar.string
+                .equals(str))
+                || (str.length() == 0 && (FS.fs_gamedirvar.string != null || FS.fs_gamedirvar.string
+                .length() == 0)))
+            Cvar.set("game", str);
+
+        // parse player entity number
+        Globals.clientStateT.playernum = MSG.ReadShort(Globals.net_message);
+        Com.dprintln("numplayers=" + Globals.clientStateT.playernum);
+        // get the full level name
+        str = MSG.ReadString(Globals.net_message);
+        Com.dprintln("levelname=" + str);
+
+        if (Globals.clientStateT.playernum == -1) { // playing a cinematic or showing a
+            // pic, not a level
+            SCR.PlayCinematic(str);
+        } else {
+            // seperate the printfs so the server message can have a color
+            //                 Com.Printf(
+            //                         "\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n");
+            //                 Com.Printf('\02' + str + "\n");
+            Com.Printf("Levelname:" + str + "\n");
+            // need to prep refresh at next oportunity
+            Globals.clientStateT.refresh_prepped = false;
+        }
+    }
+
+    /*
+     * ================== CL_ParseBaseline ==================
+     */
+    public static void ParseBaseline() {
+        entity_state_t es;
+        int newnum;
+
+        entity_state_t nullstate = new entity_state_t(null);
+        //memset(nullstate, 0, sizeof(nullstate));
+        int bits[] = {0};
+        newnum = CL_ents.ParseEntityBits(bits);
+        es = Globals.cl_entities[newnum].baseline;
+        CL_ents.ParseDelta(nullstate, es, newnum, bits[0]);
+    }
+
+    /*
+     * ================ CL_LoadClientinfo
+     *
+     * ================
+     */
+    public static void LoadClientinfo(ClientInfo ci, String s) {
+        int i;
+        int t;
+
+        String model_name, skin_name, model_filename, skin_filename, weapon_filename;
+
+        ci.cinfo = s;
+        //ci.cinfo[sizeof(ci.cinfo) - 1] = 0;
+
+        // isolate the player's name
+        ci.name = s;
+        //ci.name[sizeof(ci.name) - 1] = 0;
+
+        t = s.indexOf('\\');
+        //t = strstr(s, "\\");
+
+        if (t != -1) {
+            ci.name = s.substring(0, t);
+            s = s.substring(t + 1, s.length());
+            //s = t + 1;
+        }
+
+        if (Globals.cl_noskins.value != 0 || s.length() == 0) {
+
+            model_filename = ("players/male/tris.md2");
+            weapon_filename = ("players/male/weapon.md2");
+            skin_filename = ("players/male/grunt.pcx");
+            ci.iconname = ("/players/male/grunt_i.pcx");
+
+            ci.model = Globals.re.RegisterModel(model_filename);
+            ci.skin = Globals.re.RegisterSkin(skin_filename);
+            ci.icon = Globals.re.RegisterPic(ci.iconname);
+
+        } else {
+            // isolate the model name
+
+            int pos = s.indexOf('/');
+
+            if (pos == -1)
+                pos = s.indexOf('/');
+            if (pos == -1) {
+                pos = 0;
+                Com.Error(Defines.ERR_FATAL, "Invalid model name:" + s);
+            }
+
+            model_name = s.substring(0, pos);
+
+            // isolate the skin name
+            skin_name = s.substring(pos + 1, s.length());
+
+            // model file
+            model_filename = "players/" + model_name + "/tris.md2";
+            ci.model = Globals.re.RegisterModel(model_filename);
+
+            if (ci.model == null) {
+                model_name = "male";
+                model_filename = "players/male/tris.md2";
+                ci.model = Globals.re.RegisterModel(model_filename);
+            }
+
+            // skin file
+            skin_filename = "players/" + model_name + "/" + skin_name + ".pcx";
+            ci.skin = Globals.re.RegisterSkin(skin_filename);
+
+            // if we don't have the skin and the model wasn't male,
+            // see if the male has it (this is for CTF's skins)
+            if (ci.skin == null && !model_name.equalsIgnoreCase("male")) {
+                // change model to male
+                model_name = "male";
+                model_filename = "players/male/tris.md2";
+                ci.model = Globals.re.RegisterModel(model_filename);
+
+                // see if the skin exists for the male model
+                skin_filename = "players/" + model_name + "/" + skin_name
+                        + ".pcx";
+                ci.skin = Globals.re.RegisterSkin(skin_filename);
+            }
+
+            // if we still don't have a skin, it means that the male model
+            // didn't have
+            // it, so default to grunt
+            if (ci.skin == null) {
+                // see if the skin exists for the male model
+                skin_filename = "players/" + model_name + "/grunt.pcx";
+                ci.skin = Globals.re.RegisterSkin(skin_filename);
+            }
+
+
+            // icon file
+            ci.iconname = "/players/" + model_name + "/" + skin_name + "_i.pcx";
+            ci.icon = Globals.re.RegisterPic(ci.iconname);
+        }
+
+        // must have loaded all data types to be valud
+        if (ci.skin == null || ci.icon == null || ci.model == null) {
+            ci.skin = null;
+            ci.icon = null;
+            ci.model = null;
+        }
+    }
+
+    /*
+     * ================ CL_ParseClientinfo
+     *
+     * Load the skin, icon, and model for a client ================
+     */
+    public static void ParseClientinfo(int player) {
+        String s;
+        ClientInfo ci;
+
+        s = Globals.clientStateT.configstrings[player + Defines.CS_PLAYERSKINS];
+
+        ci = Globals.clientStateT.clientinfo[player];
+
+        LoadClientinfo(ci, s);
+    }
+
+    /*
+     * =====================================================================
+     * 
+     * ACTION MESSAGES
+     * 
+     * =====================================================================
+     */
+
+    /*
+     * ================ CL_ParseConfigString ================
+     */
+    public static void ParseConfigString() {
+        int i;
+        String s;
+        String olds;
+
+        i = MSG.ReadShort(Globals.net_message);
+
+        if (i < 0 || i >= Defines.MAX_CONFIGSTRINGS)
+            Com.Error(Defines.ERR_DROP, "configstring > MAX_CONFIGSTRINGS");
+
+        s = MSG.ReadString(Globals.net_message);
+
+        olds = Globals.clientStateT.configstrings[i];
+        Globals.clientStateT.configstrings[i] = s;
+
+        //Com.dprintln("ParseConfigString(): configstring[" + i + "]=<"+s+">");
+
+        // do something apropriate
+
+        if (i >= Defines.CS_LIGHTS
+                && i < Defines.CS_LIGHTS + Defines.MAX_LIGHTSTYLES) {
+
+            CL_fx.SetLightstyle(i - Defines.CS_LIGHTS);
+
+        } else if (i >= Defines.CS_MODELS && i < Defines.CS_MODELS + Defines.MAX_MODELS) {
+            if (Globals.clientStateT.refresh_prepped) {
+                Globals.clientStateT.model_draw[i - Defines.CS_MODELS] = Globals.re
+                        .RegisterModel(Globals.clientStateT.configstrings[i]);
+                if (Globals.clientStateT.configstrings[i].startsWith("*"))
+                    Globals.clientStateT.model_clip[i - Defines.CS_MODELS] = CM
+                            .InlineModel(Globals.clientStateT.configstrings[i]);
+                else
+                    Globals.clientStateT.model_clip[i - Defines.CS_MODELS] = null;
+            }
+        } else if (i >= Defines.CS_SOUNDS
+                && i < Defines.CS_SOUNDS + Defines.MAX_MODELS) {
+            if (Globals.clientStateT.refresh_prepped)
+                Globals.clientStateT.sound_precache[i - Defines.CS_SOUNDS] = S
+                        .RegisterSound(Globals.clientStateT.configstrings[i]);
+        } else if (i >= Defines.CS_IMAGES
+                && i < Defines.CS_IMAGES + Defines.MAX_MODELS) {
+            if (Globals.clientStateT.refresh_prepped)
+                Globals.clientStateT.image_precache[i - Defines.CS_IMAGES] = Globals.re
+                        .RegisterPic(Globals.clientStateT.configstrings[i]);
+        } else if (i >= Defines.CS_PLAYERSKINS
+                && i < Defines.CS_PLAYERSKINS + Defines.MAX_CLIENTS) {
+            if (Globals.clientStateT.refresh_prepped && !olds.equals(s))
+                ParseClientinfo(i - Defines.CS_PLAYERSKINS);
+        }
+    }
+
+    /*
+     * ================== CL_ParseStartSoundPacket ==================
+     */
+    public static void ParseStartSoundPacket() {
+        float pos[];
+        int channel, ent;
+        int sound_num;
+        float volume;
+        float attenuation;
+        int flags;
+        float ofs;
+
+        flags = MSG.ReadByte(Globals.net_message);
+        sound_num = MSG.ReadByte(Globals.net_message);
+
+        if ((flags & Defines.SND_VOLUME) != 0)
+            volume = MSG.ReadByte(Globals.net_message) / 255.0f;
+        else
+            volume = Defines.DEFAULT_SOUND_PACKET_VOLUME;
+
+        if ((flags & Defines.SND_ATTENUATION) != 0)
+            attenuation = MSG.ReadByte(Globals.net_message) / 64.0f;
+        else
+            attenuation = Defines.DEFAULT_SOUND_PACKET_ATTENUATION;
+
+        if ((flags & Defines.SND_OFFSET) != 0)
+            ofs = MSG.ReadByte(Globals.net_message) / 1000.0f;
+        else
+            ofs = 0;
+
+        if ((flags & Defines.SND_ENT) != 0) { // entity reletive
+            channel = MSG.ReadShort(Globals.net_message);
+            ent = channel >> 3;
+            if (ent > Defines.MAX_EDICTS)
+                Com.Error(Defines.ERR_DROP, "CL_ParseStartSoundPacket: ent = "
+                        + ent);
+
+            channel &= 7;
+        } else {
+            ent = 0;
+            channel = 0;
+        }
+
+        if ((flags & Defines.SND_POS) != 0) { // positioned in space
+            MSG.ReadPos(Globals.net_message, pos_v);
+            // is ok. sound driver copies
+            pos = pos_v;
+        } else
+            // use entity number
+            pos = null;
+
+        if (null == Globals.clientStateT.sound_precache[sound_num])
+            return;
+
+        S.StartSound(pos, ent, channel, Globals.clientStateT.sound_precache[sound_num],
+                volume, attenuation, ofs);
+    }
+
+    public static void SHOWNET(String s) {
+        if (Globals.cl_shownet.value >= 2)
+            Com.Printf(Globals.net_message.readcount - 1 + ":" + s + "\n");
+    }
+
+    /*
+     * ===================== CL_ParseServerMessage =====================
+     */
+    public static void ParseServerMessage() {
+        int cmd;
+        String s;
+        int i;
+
+        //
+        //        if recording demos, copy the message out
+        //
+        //if (cl_shownet.value == 1)
+        //Com.Printf(net_message.cursize + " ");
+        //else if (cl_shownet.value >= 2)
+        //Com.Printf("------------------\n");
+
+        //
+        //        parse the message
+        //
+        while (true) {
+            if (Globals.net_message.readcount > Globals.net_message.cursize) {
+                Com.Error(Defines.ERR_FATAL,
+                        "CL_ParseServerMessage: Bad server message:");
+                break;
+            }
+
+            cmd = MSG.ReadByte(Globals.net_message);
+
+            if (cmd == -1) {
+                SHOWNET("END OF MESSAGE");
+                break;
+            }
+
+            if (Globals.cl_shownet.value >= 2) {
+                if (null == svc_strings[cmd])
+                    Com.Printf(Globals.net_message.readcount - 1 + ":BAD CMD "
+                            + cmd + "\n");
+                else
+                    SHOWNET(svc_strings[cmd]);
+            }
+
+            // other commands
+            switch (cmd) {
+                default:
+                    Com.Error(Defines.ERR_DROP,
+                            "CL_ParseServerMessage: Illegible server message\n");
+                    break;
+
+                case Defines.svc_nop:
+                    //                         Com.Printf ("svc_nop\n");
+                    break;
+
+                case Defines.svc_disconnect:
+                    Com.Error(Defines.ERR_DISCONNECT, "Server disconnected\n");
+                    break;
+
+                case Defines.svc_reconnect:
+                    Com.Printf("Server disconnected, reconnecting\n");
+                    if (Globals.clientStaticT.download != null) {
+                        //ZOID, close download
+                        try {
+                            Globals.clientStaticT.download.close();
+                        } catch (IOException ignored) {
+                        }
+                        Globals.clientStaticT.download = null;
+                    }
+                    Globals.clientStaticT.state = Defines.ca_connecting;
+                    Globals.clientStaticT.connect_time = -99999; // CL_CheckForResend() will
+                    // fire immediately
+                    break;
+
+                case Defines.svc_print:
+                    i = MSG.ReadByte(Globals.net_message);
+                    if (i == Defines.PRINT_CHAT) {
+                        S.StartLocalSound("misc/talk.wav");
+                        Globals.con.ormask = 128;
+                    }
+                    Com.Printf(MSG.ReadString(Globals.net_message));
+                    Globals.con.ormask = 0;
+                    break;
+
+                case Defines.svc_centerprint:
+                    SCR.CenterPrint(MSG.ReadString(Globals.net_message));
+                    break;
+
+                case Defines.svc_stufftext:
+                    s = MSG.ReadString(Globals.net_message);
+                    Com.DPrintf("stufftext: " + s + "\n");
+                    CommandBuffer.AddText(s);
+                    break;
+
+                case Defines.svc_serverdata:
+                    CommandBuffer.execute(); // make sure any stuffed commands are done
+                    ParseServerData();
+                    break;
+
+                case Defines.svc_configstring:
+                    ParseConfigString();
+                    break;
+
+                case Defines.svc_sound:
+                    ParseStartSoundPacket();
+                    break;
+
+                case Defines.svc_spawnbaseline:
+                    ParseBaseline();
+                    break;
+
+                case Defines.svc_temp_entity:
+                    CL_tent.ParseTEnt();
+                    break;
+
+                case Defines.svc_download:
+                    ParseDownload();
+                    break;
+
+                case Defines.svc_frame:
+                    CL_ents.ParseFrame();
+                    break;
+
+                case Defines.svc_inventory:
+                    CL_inv.ParseInventory();
+                    break;
+
+                case Defines.svc_layout:
+                    s = MSG.ReadString(Globals.net_message);
+                    Globals.clientStateT.layout = s;
+                    break;
+
+                case Defines.svc_playerinfo:
+                case Defines.svc_packetentities:
+                case Defines.svc_deltapacketentities:
+                    Com.Error(Defines.ERR_DROP, "Out of place frame data");
+                    break;
+            }
+        }
+
+        CL_view.AddNetgraph();
+
+        //
+        // we don't know if it is ok to save a demo message until
+        // after we have parsed the frame
+        //
+        if (Globals.clientStaticT.demorecording && !Globals.clientStaticT.demowaiting)
+            Client.WriteDemoMessage();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/client/CL_pred.java b/src/main/java/lwjake2/client/CL_pred.java
new file mode 100644 (file)
index 0000000..cd0d7e8
--- /dev/null
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.*;
+import lwjake2.qcommon.CM;
+import lwjake2.qcommon.Com;
+import lwjake2.qcommon.PMove;
+import lwjake2.util.Math3D;
+
+/**
+ * CL_pred
+ */
+public class CL_pred {
+
+    public static final EDict DUMMY_ENT = new EDict(-1);
+
+    /*
+     * =================== CL_CheckPredictionError ===================
+     */
+    static void CheckPredictionError() {
+        int frame;
+        int[] delta = new int[3];
+        int i;
+        int len;
+
+        if (Globals.cl_predict.value == 0.0f
+                || (Globals.clientStateT.frame.playerstate.pmove.pm_flags & Move.PMF_NO_PREDICTION) != 0)
+            return;
+
+        // calculate the last usercmd_t we sent that the server has processed
+        frame = Globals.clientStaticT.netchan.incoming_acknowledged;
+        frame &= (Defines.CMD_BACKUP - 1);
+
+        // compare what the server returned with what we had predicted it to be
+        Math3D.vectorSubtract(Globals.clientStateT.frame.playerstate.pmove.origin,
+                Globals.clientStateT.predicted_origins[frame], delta);
+
+        // save the prediction error for interpolation
+        len = Math.abs(delta[0]) + Math.abs(delta[1]) + Math.abs(delta[2]);
+        if (len > 640) // 80 world units
+        { // a teleport or something
+            Math3D.vectorClear(Globals.clientStateT.prediction_error);
+        } else {
+            if (Globals.cl_showmiss.value != 0.0f
+                    && (delta[0] != 0 || delta[1] != 0 || delta[2] != 0))
+                Com.Printf("prediction miss on " + Globals.clientStateT.frame.serverframe
+                        + ": " + (delta[0] + delta[1] + delta[2]) + "\n");
+
+            Math3D.vectorCopy(Globals.clientStateT.frame.playerstate.pmove.origin,
+                    Globals.clientStateT.predicted_origins[frame]);
+
+            // save for error itnerpolation
+            for (i = 0; i < 3; i++)
+                Globals.clientStateT.prediction_error[i] = delta[i] * 0.125f;
+        }
+    }
+
+    /*
+     * ================ CL_PMTrace ================
+     */
+
+    static void ClipMoveToEntities(float[] start, float[] mins, float[] maxs,
+                                   float[] end, trace_t tr) {
+        int i, x, zd, zu;
+        trace_t trace;
+        int headnode;
+        float[] angles;
+        entity_state_t ent;
+        int num;
+        cmodel_t cmodel;
+        float[] bmins = new float[3];
+        float[] bmaxs = new float[3];
+
+        for (i = 0; i < Globals.clientStateT.frame.num_entities; i++) {
+            num = (Globals.clientStateT.frame.parse_entities + i)
+                    & (Defines.MAX_PARSE_ENTITIES - 1);
+            ent = Globals.cl_parse_entities[num];
+
+            if (ent.solid == 0)
+                continue;
+
+            if (ent.number == Globals.clientStateT.playernum + 1)
+                continue;
+
+            if (ent.solid == 31) { // special value for bmodel
+                cmodel = Globals.clientStateT.model_clip[ent.modelindex];
+                if (cmodel == null)
+                    continue;
+                headnode = cmodel.headnode;
+                angles = ent.angles;
+            } else { // encoded bbox
+                x = 8 * (ent.solid & 31);
+                zd = 8 * ((ent.solid >>> 5) & 31);
+                zu = 8 * ((ent.solid >>> 10) & 63) - 32;
+
+                bmins[0] = bmins[1] = -x;
+                bmaxs[0] = bmaxs[1] = x;
+                bmins[2] = -zd;
+                bmaxs[2] = zu;
+
+                headnode = CM.HeadnodeForBox(bmins, bmaxs);
+                angles = Globals.vec3_origin; // boxes don't rotate
+            }
+
+            if (tr.allsolid)
+                return;
+
+            trace = CM.TransformedBoxTrace(start, end, mins, maxs, headnode,
+                    Defines.MASK_PLAYERSOLID, ent.origin, angles);
+
+            if (trace.allsolid || trace.startsolid
+                    || trace.fraction < tr.fraction) {
+                trace.ent = ent.surrounding_ent;
+                if (tr.startsolid) {
+                    tr.set(trace); // rst: solved the Z U P P E L - P R O B L E
+                    // M
+                    tr.startsolid = true;
+                } else
+                    tr.set(trace); // rst: solved the Z U P P E L - P R O B L E
+                // M
+            }
+        }
+    }
+
+    static trace_t PMTrace(float[] start, float[] mins, float[] maxs,
+                           float[] end) {
+        trace_t t;
+
+        // check against world
+        t = CM.boxTrace(start, end, mins, maxs, 0, Defines.MASK_PLAYERSOLID);
+
+        if (t.fraction < 1.0f) {
+            t.ent = DUMMY_ENT;
+        }
+
+        // check all other solid models
+        ClipMoveToEntities(start, mins, maxs, end, t);
+
+        return t;
+    }
+
+    /*
+     * ================= PMpointcontents
+     * 
+     * Returns the content identificator of the point. =================
+     */
+    static int PMpointcontents(float[] point) {
+        int i;
+        entity_state_t ent;
+        int num;
+        cmodel_t cmodel;
+        int contents;
+
+        contents = CM.PointContents(point, 0);
+
+        for (i = 0; i < Globals.clientStateT.frame.num_entities; i++) {
+            num = (Globals.clientStateT.frame.parse_entities + i)
+                    & (Defines.MAX_PARSE_ENTITIES - 1);
+            ent = Globals.cl_parse_entities[num];
+
+            if (ent.solid != 31) // special value for bmodel
+                continue;
+
+            cmodel = Globals.clientStateT.model_clip[ent.modelindex];
+            if (cmodel == null)
+                continue;
+
+            contents |= CM.TransformedPointContents(point, cmodel.headnode,
+                    ent.origin, ent.angles);
+        }
+        return contents;
+    }
+
+    /*
+     * ================= CL_PredictMovement
+     * 
+     * Sets cl.predicted_origin and cl.predicted_angles =================
+     */
+    static void predictMovement() {
+
+        if (Globals.clientStaticT.state != Defines.ca_active)
+            return;
+
+        if (Globals.cl_paused.value != 0.0f)
+            return;
+
+        if (Globals.cl_predict.value == 0.0f
+                || (Globals.clientStateT.frame.playerstate.pmove.pm_flags & Move.PMF_NO_PREDICTION) != 0) {
+            // just set angles
+            for (int i = 0; i < 3; i++) {
+                Globals.clientStateT.predicted_angles[i] = Globals.clientStateT.viewangles[i]
+                        + Math3D
+                        .short2Angle(Globals.clientStateT.frame.playerstate.pmove.delta_angles[i]);
+            }
+            return;
+        }
+
+        int ack = Globals.clientStaticT.netchan.incoming_acknowledged;
+        int current = Globals.clientStaticT.netchan.outgoing_sequence;
+
+        // if we are too far out of date, just freeze
+        if (current - ack >= Defines.CMD_BACKUP) {
+            if (Globals.cl_showmiss.value != 0.0f)
+                Com.Printf("exceeded CMD_BACKUP\n");
+            return;
+        }
+
+        // copy current state to pmove
+        //memset (pm, 0, sizeof(pm));
+        Move pm = new Move();
+
+        pm.trace = new Move.TraceAdapter() {
+            public trace_t trace(float[] start, float[] mins, float[] maxs,
+                                 float[] end) {
+                return PMTrace(start, mins, maxs, end);
+            }
+        };
+        pm.pointcontents = new Move.PointContentsAdapter() {
+            public int pointcontents(float[] point) {
+                return PMpointcontents(point);
+            }
+        };
+
+        try {
+            PMove.pm_airaccelerate = Float
+                    .parseFloat(Globals.clientStateT.configstrings[Defines.CS_AIRACCEL]);
+        } catch (Exception e) {
+            PMove.pm_airaccelerate = 0;
+        }
+
+        // bugfix (rst) yeah !!!!!!!! found the solution to the B E W E G U N G
+        // S P R O B L E M.
+        pm.s.set(Globals.clientStateT.frame.playerstate.pmove);
+
+        // SCR_DebugGraph (current - ack - 1, 0);
+        int frame = 0;
+
+        // run frames
+        usercmd_t cmd;
+        while (++ack < current) {
+            frame = ack & (Defines.CMD_BACKUP - 1);
+            cmd = Globals.clientStateT.cmds[frame];
+
+            pm.cmd.set(cmd);
+
+            PMove.Pmove(pm);
+
+            // save for debug checking
+            Math3D.vectorCopy(pm.s.origin, Globals.clientStateT.predicted_origins[frame]);
+        }
+
+        int oldframe = (ack - 2) & (Defines.CMD_BACKUP - 1);
+        int oldz = Globals.clientStateT.predicted_origins[oldframe][2];
+        int step = pm.s.origin[2] - oldz;
+        if (step > 63 && step < 160
+                && (pm.s.pm_flags & Move.PMF_ON_GROUND) != 0) {
+            Globals.clientStateT.predicted_step = step * 0.125f;
+            Globals.clientStateT.predicted_step_time = (int) (Globals.clientStaticT.realtime - Globals.clientStaticT.frametime * 500);
+        }
+
+        // copy results out for rendering
+        Globals.clientStateT.predicted_origin[0] = pm.s.origin[0] * 0.125f;
+        Globals.clientStateT.predicted_origin[1] = pm.s.origin[1] * 0.125f;
+        Globals.clientStateT.predicted_origin[2] = pm.s.origin[2] * 0.125f;
+
+        Math3D.vectorCopy(pm.viewangles, Globals.clientStateT.predicted_angles);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/client/CL_tent.java b/src/main/java/lwjake2/client/CL_tent.java
new file mode 100644 (file)
index 0000000..b203be6
--- /dev/null
@@ -0,0 +1,1555 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.player_state_t;
+import lwjake2.qcommon.Com;
+import lwjake2.qcommon.MSG;
+import lwjake2.render.Model;
+import lwjake2.sound.S;
+import lwjake2.sound.sfx_t;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+
+/**
+ * CL_tent
+ */
+public class CL_tent {
+
+    // stack variable
+    public static final float[] pos = new float[3];
+    public static final float[] dir = new float[3];
+    // stack variable
+    // pos, dir
+    public static final float[] pos2 = {0, 0, 0};
+    // stack variable
+    public static final float[] dist = new float[3];
+    public static final float[] org = new float[3];
+    static final int MAX_EXPLOSIONS = 32;
+    static final int MAX_BEAMS = 32;
+    static final int MAX_LASERS = 32;
+    //   ROGUE
+    static final int MAX_SUSTAINS = 32;
+    static final int ex_free = 0;
+    static final int ex_explosion = 1;
+    static final int ex_misc = 2;
+    static final int ex_flash = 3;
+    static final int ex_mflash = 4;
+    static final int ex_poly = 5;
+    static final int ex_poly2 = 6;
+    static final explosion_t[] cl_explosions = new explosion_t[MAX_EXPLOSIONS];
+    //   ROGUE
+    static final beam_t[] cl_beams = new beam_t[MAX_BEAMS];
+    //   PMM - added this for player-linked beams. Currently only used by the
+    // plasma beam
+    static final beam_t[] cl_playerbeams = new beam_t[MAX_BEAMS];
+    static final laser_t[] cl_lasers = new laser_t[MAX_LASERS];
+    static final cl_sustain_t[] cl_sustains = new cl_sustain_t[MAX_SUSTAINS];
+    static final sfx_t[] cl_sfx_footsteps = new sfx_t[4];
+    /*
+     * ================= CL_ParseTEnt =================
+     */
+    static final int[] splash_color = {0x00, 0xe0, 0xb0, 0x50, 0xd0, 0xe0, 0xe8};
+    // stack variable
+    private static final float[] start = new float[3];
+    private static final float[] end = new float[3];
+    // stack variable
+    // dist, org
+    private static final entity_t ent = new entity_t();
+    private static final float[] f = new float[3];
+    // all are references;
+    private static final float[] u = new float[3];
+    private static final float[] r = new float[3];
+    static Model cl_mod_explode;
+    static Model cl_mod_smoke;
+    static Model cl_mod_flash;
+    static Model cl_mod_parasite_segment;
+    static Model cl_mod_grapple_cable;
+    static Model cl_mod_parasite_tip;
+    static Model cl_mod_explo4;
+    static Model cl_mod_bfg_explo;
+    static Model cl_mod_powerscreen;
+    //    RAFAEL
+    static Model cl_mod_plasmaexplo;
+    //   ROGUE
+    static sfx_t cl_sfx_lightning;
+    static sfx_t cl_sfx_disrexp;
+    static Model cl_mod_lightning;
+    static Model cl_mod_heatbeam;
+    static Model cl_mod_monster_heatbeam;
+    static Model cl_mod_explo4_big;
+
+    static {
+        for (int i = 0; i < cl_explosions.length; i++)
+            cl_explosions[i] = new explosion_t();
+    }
+
+    static {
+        for (int i = 0; i < cl_beams.length; i++)
+            cl_beams[i] = new beam_t();
+        for (int i = 0; i < cl_playerbeams.length; i++)
+            cl_playerbeams[i] = new beam_t();
+    }
+
+    static {
+        for (int i = 0; i < cl_lasers.length; i++)
+            cl_lasers[i] = new laser_t();
+    }
+
+    //   rogue
+
+    static {
+        for (int i = 0; i < cl_sustains.length; i++)
+            cl_sustains[i] = new cl_sustain_t();
+    }
+
+    //   ROGUE
+    /*
+     * ================= CL_RegisterTEntSounds =================
+     */
+    static void RegisterTEntSounds() {
+        int i;
+        String name;
+
+        // RAFAEL
+        // cl_sfx_plasexp = S.RegisterSound ("weapons/plasexpl.wav");
+        S.RegisterSound("player/land1.wav");
+
+        S.RegisterSound("player/fall2.wav");
+        S.RegisterSound("player/fall1.wav");
+
+        for (i = 0; i < 4; i++) {
+            //Com_sprintf (name, sizeof(name), "player/step%i.wav", i+1);
+            name = "player/step" + (i + 1) + ".wav";
+            cl_sfx_footsteps[i] = S.RegisterSound(name);
+        }
+
+    }
+
+    /*
+     * ================= CL_RegisterTEntModels =================
+     */
+    static void RegisterTEntModels() {
+    }
+
+    /*
+     * ================= CL_ClearTEnts =================
+     */
+    static void ClearTEnts() {
+        //             memset (cl_beams, 0, sizeof(cl_beams));
+        for (beam_t cl_beam : cl_beams) cl_beam.clear();
+        //             memset (cl_explosions, 0, sizeof(cl_explosions));
+        for (explosion_t cl_explosion : cl_explosions) cl_explosion.clear();
+        //             memset (cl_lasers, 0, sizeof(cl_lasers));
+        for (laser_t cl_laser : cl_lasers) cl_laser.clear();
+        //
+        //       ROGUE
+        //             memset (cl_playerbeams, 0, sizeof(cl_playerbeams));
+        for (beam_t cl_playerbeam : cl_playerbeams) cl_playerbeam.clear();
+        //             memset (cl_sustains, 0, sizeof(cl_sustains));
+        for (cl_sustain_t cl_sustain : cl_sustains) cl_sustain.clear();
+        //       ROGUE
+    }
+
+    /*
+     * ================= CL_AllocExplosion =================
+     */
+    static explosion_t AllocExplosion() {
+        int i;
+        int time;
+        int index;
+
+        for (i = 0; i < MAX_EXPLOSIONS; i++) {
+            if (cl_explosions[i].type == ex_free) {
+                //memset (&cl_explosions[i], 0, sizeof (cl_explosions[i]));
+                cl_explosions[i].clear();
+                return cl_explosions[i];
+            }
+        }
+        //        find the oldest explosion
+        time = Globals.clientStateT.time;
+        index = 0;
+
+        for (i = 0; i < MAX_EXPLOSIONS; i++)
+            if (cl_explosions[i].start < time) {
+                time = (int) cl_explosions[i].start;
+                index = i;
+            }
+        //memset (&cl_explosions[index], 0, sizeof (cl_explosions[index]));
+        cl_explosions[index].clear();
+        return cl_explosions[index];
+    }
+
+    /*
+     * ================= CL_SmokeAndFlash =================
+     */
+    static void SmokeAndFlash() {
+        explosion_t ex;
+
+        ex = AllocExplosion();
+        Math3D.vectorCopy(CL_tent.pos, ex.ent.origin);
+        ex.type = ex_misc;
+        ex.frames = 4;
+        ex.ent.flags = Defines.RF_TRANSLUCENT;
+        ex.start = Globals.clientStateT.frame.servertime - 100;
+        ex.ent.model = cl_mod_smoke;
+
+        ex = AllocExplosion();
+        Math3D.vectorCopy(CL_tent.pos, ex.ent.origin);
+        ex.type = ex_flash;
+        ex.ent.flags = Defines.RF_FULLBRIGHT;
+        ex.frames = 2;
+        ex.start = Globals.clientStateT.frame.servertime - 100;
+        ex.ent.model = cl_mod_flash;
+    }
+
+    /*
+     * =================
+     * CL_ParseBeam
+     * =================
+     */
+    static int ParseBeam(Model model) {
+        int ent;
+        float[] start = new float[3];
+        float[] end = new float[3];
+        beam_t[] b;
+        int i;
+
+        ent = MSG.ReadShort(Globals.net_message);
+
+        MSG.ReadPos(Globals.net_message, start);
+        MSG.ReadPos(Globals.net_message, end);
+
+        //        override any beam with the same entity
+        b = cl_beams;
+        for (i = 0; i < MAX_BEAMS; i++)
+            if (b[i].entity == ent) {
+                b[i].entity = ent;
+                b[i].model = model;
+                b[i].endtime = Globals.clientStateT.time + 200;
+                Math3D.vectorCopy(start, b[i].start);
+                Math3D.vectorCopy(end, b[i].end);
+                Math3D.vectorClear(b[i].offset);
+                return ent;
+            }
+
+        //        find a free beam
+        b = cl_beams;
+        for (i = 0; i < MAX_BEAMS; i++) {
+            if (b[i].model == null || b[i].endtime < Globals.clientStateT.time) {
+                b[i].entity = ent;
+                b[i].model = model;
+                b[i].endtime = Globals.clientStateT.time + 200;
+                Math3D.vectorCopy(start, b[i].start);
+                Math3D.vectorCopy(end, b[i].end);
+                Math3D.vectorClear(b[i].offset);
+                return ent;
+            }
+        }
+        Com.Printf("beam list overflow!\n");
+        return ent;
+    }
+
+    /*
+     * ================= CL_ParseBeam2 =================
+     */
+    static int ParseBeam2(Model model) {
+        int ent;
+        float[] start = new float[3];
+        float[] end = new float[3];
+        float[] offset = new float[3];
+        beam_t[] b;
+        int i;
+
+        ent = MSG.ReadShort(Globals.net_message);
+
+        MSG.ReadPos(Globals.net_message, start);
+        MSG.ReadPos(Globals.net_message, end);
+        MSG.ReadPos(Globals.net_message, offset);
+
+        //             Com_Printf ("end- %f %f %f\n", end[0], end[1], end[2]);
+
+        //        override any beam with the same entity
+        b = cl_beams;
+        for (i = 0; i < MAX_BEAMS; i++)
+            if (b[i].entity == ent) {
+                b[i].entity = ent;
+                b[i].model = model;
+                b[i].endtime = Globals.clientStateT.time + 200;
+                Math3D.vectorCopy(start, b[i].start);
+                Math3D.vectorCopy(end, b[i].end);
+                Math3D.vectorCopy(offset, b[i].offset);
+                return ent;
+            }
+
+        //        find a free beam
+        b = cl_beams;
+        for (i = 0; i < MAX_BEAMS; i++) {
+            if (b[i].model == null || b[i].endtime < Globals.clientStateT.time) {
+                b[i].entity = ent;
+                b[i].model = model;
+                b[i].endtime = Globals.clientStateT.time + 200;
+                Math3D.vectorCopy(start, b[i].start);
+                Math3D.vectorCopy(end, b[i].end);
+                Math3D.vectorCopy(offset, b[i].offset);
+                return ent;
+            }
+        }
+        Com.Printf("beam list overflow!\n");
+        return ent;
+    }
+
+    //    ROGUE
+    /*
+     * ================= CL_ParsePlayerBeam - adds to the cl_playerbeam array
+     * instead of the cl_beams array =================
+     */
+    static int ParsePlayerBeam(Model model) {
+        int ent;
+        float[] start = new float[3];
+        float[] end = new float[3];
+        float[] offset = new float[3];
+        beam_t[] b;
+        int i;
+
+        ent = MSG.ReadShort(Globals.net_message);
+
+        MSG.ReadPos(Globals.net_message, start);
+        MSG.ReadPos(Globals.net_message, end);
+        // PMM - network optimization
+        if (model == cl_mod_heatbeam)
+            Math3D.vectorSet(offset, 2, 7, -3);
+        else if (model == cl_mod_monster_heatbeam) {
+            model = cl_mod_heatbeam;
+            Math3D.vectorSet(offset, 0, 0, 0);
+        } else
+            MSG.ReadPos(Globals.net_message, offset);
+
+        //             Com_Printf ("end- %f %f %f\n", end[0], end[1], end[2]);
+
+        //        override any beam with the same entity
+        //        PMM - For player beams, we only want one per player (entity) so..
+        b = cl_playerbeams;
+        for (i = 0; i < MAX_BEAMS; i++) {
+            if (b[i].entity == ent) {
+                b[i].entity = ent;
+                b[i].model = model;
+                b[i].endtime = Globals.clientStateT.time + 200;
+                Math3D.vectorCopy(start, b[i].start);
+                Math3D.vectorCopy(end, b[i].end);
+                Math3D.vectorCopy(offset, b[i].offset);
+                return ent;
+            }
+        }
+
+        //        find a free beam
+        b = cl_playerbeams;
+        for (i = 0; i < MAX_BEAMS; i++) {
+            if (b[i].model == null || b[i].endtime < Globals.clientStateT.time) {
+                b[i].entity = ent;
+                b[i].model = model;
+                b[i].endtime = Globals.clientStateT.time + 100; // PMM - this needs to be
+                // 100 to prevent multiple
+                // heatbeams
+                Math3D.vectorCopy(start, b[i].start);
+                Math3D.vectorCopy(end, b[i].end);
+                Math3D.vectorCopy(offset, b[i].offset);
+                return ent;
+            }
+        }
+        Com.Printf("beam list overflow!\n");
+        return ent;
+    }
+
+    //   ROGUE
+    //   =============
+
+    /*
+     * ================= CL_ParseLightning =================
+     */
+    static int ParseLightning(Model model) {
+        int srcEnt, destEnt;
+        beam_t[] b;
+        int i;
+
+        srcEnt = MSG.ReadShort(Globals.net_message);
+        destEnt = MSG.ReadShort(Globals.net_message);
+
+        MSG.ReadPos(Globals.net_message, start);
+        MSG.ReadPos(Globals.net_message, end);
+
+        //        override any beam with the same source AND destination entities
+        b = cl_beams;
+        for (i = 0; i < MAX_BEAMS; i++)
+            if (b[i].entity == srcEnt && b[i].dest_entity == destEnt) {
+                //                             Com_Printf("%d: OVERRIDE %d . %d\n", cl.time, srcEnt,
+                // destEnt);
+                b[i].entity = srcEnt;
+                b[i].dest_entity = destEnt;
+                b[i].model = model;
+                b[i].endtime = Globals.clientStateT.time + 200;
+                Math3D.vectorCopy(start, b[i].start);
+                Math3D.vectorCopy(end, b[i].end);
+                Math3D.vectorClear(b[i].offset);
+                return srcEnt;
+            }
+
+        //        find a free beam
+        b = cl_beams;
+        for (i = 0; i < MAX_BEAMS; i++) {
+            if (b[i].model == null || b[i].endtime < Globals.clientStateT.time) {
+                //                             Com_Printf("%d: NORMAL %d . %d\n", cl.time, srcEnt, destEnt);
+                b[i].entity = srcEnt;
+                b[i].dest_entity = destEnt;
+                b[i].model = model;
+                b[i].endtime = Globals.clientStateT.time + 200;
+                Math3D.vectorCopy(start, b[i].start);
+                Math3D.vectorCopy(end, b[i].end);
+                Math3D.vectorClear(b[i].offset);
+                return srcEnt;
+            }
+        }
+        Com.Printf("beam list overflow!\n");
+        return srcEnt;
+    }
+
+    // stack variable
+    // start, end
+    /*
+     * ================= CL_ParseLaser =================
+     */
+    static void ParseLaser() {
+
+        MSG.ReadPos(Globals.net_message, start);
+        MSG.ReadPos(Globals.net_message, end);
+
+    }
+
+    //   =============
+    //   ROGUE
+    static void ParseSteam() {
+        int id, i;
+        int r;
+        int cnt;
+        int color;
+        int magnitude;
+        cl_sustain_t[] s;
+        cl_sustain_t free_sustain;
+
+        id = MSG.ReadShort(Globals.net_message); // an id of -1 is an instant
+        // effect
+        if (id != -1) // sustains
+        {
+            //                         Com_Printf ("Sustain effect id %d\n", id);
+            free_sustain = null;
+            s = cl_sustains;
+            for (i = 0; i < MAX_SUSTAINS; i++) {
+                if (s[i].id == 0) {
+                    free_sustain = s[i];
+                    break;
+                }
+            }
+            if (free_sustain != null) {
+                s[i].id = id;
+                s[i].count = MSG.ReadByte(Globals.net_message);
+                MSG.ReadPos(Globals.net_message, s[i].org);
+                MSG.ReadDir(Globals.net_message, s[i].dir);
+                r = MSG.ReadByte(Globals.net_message);
+                s[i].color = r & 0xff;
+                s[i].magnitude = MSG.ReadShort(Globals.net_message);
+                s[i].endtime = Globals.clientStateT.time
+                        + MSG.ReadLong(Globals.net_message);
+                s[i].think = new cl_sustain_t.ThinkAdapter() {
+                    void think(cl_sustain_t self) {
+                        CL_newfx.ParticleSteamEffect2(self);
+                    }
+                };
+                s[i].thinkinterval = 100;
+                s[i].nextthink = Globals.clientStateT.time;
+            } else {
+                //                                     Com_Printf ("No free sustains!\n");
+                // FIXME - read the stuff anyway
+                cnt = MSG.ReadByte(Globals.net_message);
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadDir(Globals.net_message, dir);
+                r = MSG.ReadByte(Globals.net_message);
+                magnitude = MSG.ReadShort(Globals.net_message);
+                magnitude = MSG.ReadLong(Globals.net_message); // really
+                // interval
+            }
+        } else // instant
+        {
+            cnt = MSG.ReadByte(Globals.net_message);
+            MSG.ReadPos(Globals.net_message, pos);
+            MSG.ReadDir(Globals.net_message, dir);
+            r = MSG.ReadByte(Globals.net_message);
+            magnitude = MSG.ReadShort(Globals.net_message);
+            color = r & 0xff;
+            CL_newfx.ParticleSteamEffect(color, cnt, magnitude);
+            //                 S_StartSound (pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0);
+        }
+    }
+
+    // stack variable
+    // pos
+    static void ParseWidow() {
+        int id, i;
+        cl_sustain_t[] s;
+        cl_sustain_t free_sustain;
+
+        id = MSG.ReadShort(Globals.net_message);
+
+        free_sustain = null;
+        s = cl_sustains;
+        for (i = 0; i < MAX_SUSTAINS; i++) {
+            if (s[i].id == 0) {
+                free_sustain = s[i];
+                break;
+            }
+        }
+        if (free_sustain != null) {
+            s[i].id = id;
+            MSG.ReadPos(Globals.net_message, s[i].org);
+            s[i].endtime = Globals.clientStateT.time + 2100;
+            s[i].think = new cl_sustain_t.ThinkAdapter() {
+                void think(cl_sustain_t self) {
+                    CL_newfx.Widowbeamout(self);
+                }
+            };
+            s[i].thinkinterval = 1;
+            s[i].nextthink = Globals.clientStateT.time;
+        } else // no free sustains
+        {
+            // FIXME - read the stuff anyway
+            MSG.ReadPos(Globals.net_message, pos);
+        }
+    }
+
+    // stack variable
+    // pos
+    static void ParseNuke() {
+        int i;
+        cl_sustain_t[] s;
+        cl_sustain_t free_sustain;
+
+        free_sustain = null;
+        s = cl_sustains;
+        for (i = 0; i < MAX_SUSTAINS; i++) {
+            if (s[i].id == 0) {
+                free_sustain = s[i];
+                break;
+            }
+        }
+        if (free_sustain != null) {
+            s[i].id = 21000;
+            MSG.ReadPos(Globals.net_message, s[i].org);
+            s[i].endtime = Globals.clientStateT.time + 1000;
+            s[i].think = new cl_sustain_t.ThinkAdapter() {
+                void think(cl_sustain_t self) {
+                    CL_newfx.Nukeblast(self);
+                }
+            };
+            s[i].thinkinterval = 1;
+            s[i].nextthink = Globals.clientStateT.time;
+        } else // no free sustains
+        {
+            // FIXME - read the stuff anyway
+            MSG.ReadPos(Globals.net_message, pos);
+        }
+    }
+
+    //extern CvarT *hand;
+
+    static void ParseTEnt() {
+        int type;
+        explosion_t ex;
+        int cnt;
+        int color;
+        int r;
+        int ent;
+        int magnitude;
+
+        type = MSG.ReadByte(Globals.net_message);
+
+        switch (type) {
+            case Defines.TE_BLOOD: // bullet hitting flesh
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadDir(Globals.net_message, dir);
+                CL_fx.ParticleEffect(0xe8, 60);
+                break;
+
+            case Defines.TE_GUNSHOT: // bullet hitting wall
+            case Defines.TE_SPARKS:
+            case Defines.TE_BULLET_SPARKS:
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadDir(Globals.net_message, dir);
+                if (type == Defines.TE_GUNSHOT)
+                    CL_fx.ParticleEffect(0, 40);
+                else
+                    CL_fx.ParticleEffect(0xe0, 6);
+
+                if (type != Defines.TE_SPARKS) {
+                    SmokeAndFlash();
+
+                }
+
+                break;
+
+            case Defines.TE_SCREEN_SPARKS:
+            case Defines.TE_SHIELD_SPARKS:
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadDir(Globals.net_message, dir);
+                if (type == Defines.TE_SCREEN_SPARKS)
+                    CL_fx.ParticleEffect(0xd0, 40);
+                else
+                    CL_fx.ParticleEffect(0xb0, 40);
+
+                break;
+
+            case Defines.TE_SHOTGUN: // bullet hitting wall
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadDir(Globals.net_message, dir);
+                CL_fx.ParticleEffect(0, 20);
+                SmokeAndFlash();
+                break;
+
+            case Defines.TE_SPLASH: // bullet hitting water
+                cnt = MSG.ReadByte(Globals.net_message);
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadDir(Globals.net_message, dir);
+                r = MSG.ReadByte(Globals.net_message);
+                if (r > 6)
+                    color = 0x00;
+                else
+                    color = splash_color[r];
+                CL_fx.ParticleEffect(color, cnt);
+
+                break;
+
+            case Defines.TE_LASER_SPARKS:
+                cnt = MSG.ReadByte(Globals.net_message);
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadDir(Globals.net_message, dir);
+                color = MSG.ReadByte(Globals.net_message);
+                CL_fx.ParticleEffect2(color, cnt);
+                break;
+
+            // RAFAEL
+            case Defines.TE_BLUEHYPERBLASTER:
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadPos(Globals.net_message, dir);
+                CL_fx.BlasterParticles();
+                break;
+
+            case Defines.TE_BLASTER: // blaster hitting wall
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadDir(Globals.net_message, dir);
+                CL_fx.BlasterParticles();
+
+                ex = AllocExplosion();
+                Math3D.vectorCopy(pos, ex.ent.origin);
+                ex.ent.angles[0] = (float) (Math.acos(dir[2]) / Math.PI * 180);
+                // PMM - fixed to correct for pitch of 0
+                if (dir[0] != 0.0f)
+                    ex.ent.angles[1] = (float) (Math.atan2(dir[1], dir[0])
+                            / Math.PI * 180);
+                else if (dir[1] > 0)
+                    ex.ent.angles[1] = 90;
+                else if (dir[1] < 0)
+                    ex.ent.angles[1] = 270;
+                else
+                    ex.ent.angles[1] = 0;
+
+                ex.type = ex_misc;
+                ex.ent.flags = Defines.RF_FULLBRIGHT | Defines.RF_TRANSLUCENT;
+                ex.start = Globals.clientStateT.frame.servertime - 100;
+                ex.light = 150;
+                ex.lightcolor[0] = 1;
+                ex.lightcolor[1] = 1;
+                ex.ent.model = cl_mod_explode;
+                ex.frames = 4;
+                break;
+
+            case Defines.TE_RAILTRAIL: // railgun effect
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadPos(Globals.net_message, pos2);
+                CL_fx.RailTrail();
+                break;
+
+            case Defines.TE_EXPLOSION2:
+            case Defines.TE_GRENADE_EXPLOSION:
+            case Defines.TE_GRENADE_EXPLOSION_WATER:
+                MSG.ReadPos(Globals.net_message, pos);
+
+                ex = AllocExplosion();
+                Math3D.vectorCopy(pos, ex.ent.origin);
+                ex.type = ex_poly;
+                ex.ent.flags = Defines.RF_FULLBRIGHT;
+                ex.start = Globals.clientStateT.frame.servertime - 100;
+                ex.light = 350;
+                ex.lightcolor[0] = 1.0f;
+                ex.lightcolor[1] = 0.5f;
+                ex.lightcolor[2] = 0.5f;
+                ex.ent.model = cl_mod_explo4;
+                ex.frames = 19;
+                ex.baseframe = 30;
+                ex.ent.angles[1] = Lib.rand() % 360;
+                CL_fx.ExplosionParticles();
+                break;
+
+            // RAFAEL
+            case Defines.TE_PLASMA_EXPLOSION:
+                MSG.ReadPos(Globals.net_message, pos);
+                ex = AllocExplosion();
+                Math3D.vectorCopy(pos, ex.ent.origin);
+                ex.type = ex_poly;
+                ex.ent.flags = Defines.RF_FULLBRIGHT;
+                ex.start = Globals.clientStateT.frame.servertime - 100;
+                ex.light = 350;
+                ex.lightcolor[0] = 1.0f;
+                ex.lightcolor[1] = 0.5f;
+                ex.lightcolor[2] = 0.5f;
+                ex.ent.angles[1] = Lib.rand() % 360;
+                ex.ent.model = cl_mod_explo4;
+                if (Globals.rnd.nextFloat() < 0.5)
+                    ex.baseframe = 15;
+                ex.frames = 15;
+                CL_fx.ExplosionParticles();
+                break;
+
+            case Defines.TE_EXPLOSION1:
+            case Defines.TE_EXPLOSION1_BIG: // PMM
+            case Defines.TE_ROCKET_EXPLOSION:
+            case Defines.TE_ROCKET_EXPLOSION_WATER:
+            case Defines.TE_EXPLOSION1_NP: // PMM
+                MSG.ReadPos(Globals.net_message, pos);
+
+                ex = AllocExplosion();
+                Math3D.vectorCopy(pos, ex.ent.origin);
+                ex.type = ex_poly;
+                ex.ent.flags = Defines.RF_FULLBRIGHT;
+                ex.start = Globals.clientStateT.frame.servertime - 100;
+                ex.light = 350;
+                ex.lightcolor[0] = 1.0f;
+                ex.lightcolor[1] = 0.5f;
+                ex.lightcolor[2] = 0.5f;
+                ex.ent.angles[1] = Lib.rand() % 360;
+                if (type != Defines.TE_EXPLOSION1_BIG) // PMM
+                    ex.ent.model = cl_mod_explo4; // PMM
+                else
+                    ex.ent.model = cl_mod_explo4_big;
+                if (Globals.rnd.nextFloat() < 0.5)
+                    ex.baseframe = 15;
+                ex.frames = 15;
+                if ((type != Defines.TE_EXPLOSION1_BIG)
+                        && (type != Defines.TE_EXPLOSION1_NP)) // PMM
+                    CL_fx.ExplosionParticles(); // PMM
+                break;
+
+            case Defines.TE_BFG_EXPLOSION:
+                MSG.ReadPos(Globals.net_message, pos);
+                ex = AllocExplosion();
+                Math3D.vectorCopy(pos, ex.ent.origin);
+                ex.type = ex_poly;
+                ex.ent.flags = Defines.RF_FULLBRIGHT;
+                ex.start = Globals.clientStateT.frame.servertime - 100;
+                ex.light = 350;
+                ex.lightcolor[0] = 0.0f;
+                ex.lightcolor[1] = 1.0f;
+                ex.lightcolor[2] = 0.0f;
+                ex.ent.model = cl_mod_bfg_explo;
+                ex.ent.flags |= Defines.RF_TRANSLUCENT;
+                ex.ent.alpha = 0.30f;
+                ex.frames = 4;
+                break;
+
+            case Defines.TE_BFG_BIGEXPLOSION:
+                MSG.ReadPos(Globals.net_message, pos);
+                CL_fx.BFGExplosionParticles();
+                break;
+
+            case Defines.TE_BFG_LASER:
+                ParseLaser();
+                break;
+
+            case Defines.TE_BUBBLETRAIL:
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadPos(Globals.net_message, pos2);
+                CL_fx.BubbleTrail();
+                break;
+
+            case Defines.TE_PARASITE_ATTACK:
+            case Defines.TE_MEDIC_CABLE_ATTACK:
+                ent = ParseBeam(cl_mod_parasite_segment);
+                break;
+
+            case Defines.TE_BOSSTPORT: // boss teleporting to station
+                MSG.ReadPos(Globals.net_message, pos);
+                CL_fx.BigTeleportParticles();
+                S.StartSound(pos, 0, 0, S.RegisterSound("misc/bigtele.wav"), 1,
+                        Defines.ATTN_NONE, 0);
+                break;
+
+            case Defines.TE_GRAPPLE_CABLE:
+                ent = ParseBeam2(cl_mod_grapple_cable);
+                break;
+
+            // RAFAEL
+            case Defines.TE_WELDING_SPARKS:
+                cnt = MSG.ReadByte(Globals.net_message);
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadDir(Globals.net_message, dir);
+                color = MSG.ReadByte(Globals.net_message);
+                CL_fx.ParticleEffect2(color, cnt);
+
+                ex = AllocExplosion();
+                Math3D.vectorCopy(pos, ex.ent.origin);
+                ex.type = ex_flash;
+                // note to self
+                // we need a better no draw flag
+                break;
+
+            case Defines.TE_GREENBLOOD:
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadDir(Globals.net_message, dir);
+                CL_fx.ParticleEffect2(0xdf, 30);
+                break;
+
+            // RAFAEL
+            case Defines.TE_TUNNEL_SPARKS:
+                cnt = MSG.ReadByte(Globals.net_message);
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadDir(Globals.net_message, dir);
+                color = MSG.ReadByte(Globals.net_message);
+                CL_fx.ParticleEffect3(color, cnt);
+                break;
+
+            //   =============
+            //   PGM
+            // PMM -following code integrated for flechette (different color)
+            case Defines.TE_BLASTER2: // green blaster hitting wall
+            case Defines.TE_FLECHETTE: // flechette
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadDir(Globals.net_message, dir);
+
+                // PMM
+                if (type == Defines.TE_BLASTER2)
+                    CL_newfx.BlasterParticles2(0xd0);
+                else
+                    CL_newfx.BlasterParticles2(0x6f); // 75
+
+                ex = AllocExplosion();
+                Math3D.vectorCopy(pos, ex.ent.origin);
+                ex.ent.angles[0] = (float) (Math.acos(dir[2]) / Math.PI * 180);
+                // PMM - fixed to correct for pitch of 0
+                if (dir[0] != 0.0f)
+                    ex.ent.angles[1] = (float) (Math.atan2(dir[1], dir[0])
+                            / Math.PI * 180);
+                else if (dir[1] > 0)
+                    ex.ent.angles[1] = 90;
+                else if (dir[1] < 0)
+                    ex.ent.angles[1] = 270;
+                else
+                    ex.ent.angles[1] = 0;
+
+                ex.type = ex_misc;
+                ex.ent.flags = Defines.RF_FULLBRIGHT | Defines.RF_TRANSLUCENT;
+
+                // PMM
+                if (type == Defines.TE_BLASTER2)
+                    ex.ent.skinnum = 1;
+                else
+                    // flechette
+                    ex.ent.skinnum = 2;
+
+                ex.start = Globals.clientStateT.frame.servertime - 100;
+                ex.light = 150;
+                // PMM
+                if (type == Defines.TE_BLASTER2)
+                    ex.lightcolor[1] = 1;
+                else // flechette
+                {
+                    ex.lightcolor[0] = 0.19f;
+                    ex.lightcolor[1] = 0.41f;
+                    ex.lightcolor[2] = 0.75f;
+                }
+                ex.ent.model = cl_mod_explode;
+                ex.frames = 4;
+                break;
+
+
+            case Defines.TE_DEBUGTRAIL:
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadPos(Globals.net_message, pos2);
+                CL_newfx.DebugTrail();
+                break;
+
+            case Defines.TE_PLAIN_EXPLOSION:
+                MSG.ReadPos(Globals.net_message, pos);
+
+                ex = AllocExplosion();
+                Math3D.vectorCopy(pos, ex.ent.origin);
+                ex.type = ex_poly;
+                ex.ent.flags = Defines.RF_FULLBRIGHT;
+                ex.start = Globals.clientStateT.frame.servertime - 100;
+                ex.light = 350;
+                ex.lightcolor[0] = 1.0f;
+                ex.lightcolor[1] = 0.5f;
+                ex.lightcolor[2] = 0.5f;
+                ex.ent.angles[1] = Lib.rand() % 360;
+                ex.ent.model = cl_mod_explo4;
+                if (Globals.rnd.nextFloat() < 0.5)
+                    ex.baseframe = 15;
+                ex.frames = 15;
+                break;
+
+            case Defines.TE_FLASHLIGHT:
+                MSG.ReadPos(Globals.net_message, pos);
+                ent = MSG.ReadShort(Globals.net_message);
+                CL_newfx.Flashlight(ent);
+                break;
+
+            case Defines.TE_FORCEWALL:
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadPos(Globals.net_message, pos2);
+                color = MSG.ReadByte(Globals.net_message);
+                CL_newfx.ForceWall(color);
+                break;
+
+            case Defines.TE_HEATBEAM:
+                ent = ParsePlayerBeam(cl_mod_heatbeam);
+                break;
+
+            case Defines.TE_MONSTER_HEATBEAM:
+                ent = ParsePlayerBeam(cl_mod_monster_heatbeam);
+                break;
+
+            case Defines.TE_HEATBEAM_SPARKS:
+                //                     cnt = MSG.ReadByte (net_message);
+                cnt = 50;
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadDir(Globals.net_message, dir);
+                //                     r = MSG.ReadByte (net_message);
+                //                     magnitude = MSG.ReadShort (net_message);
+                r = 8;
+                magnitude = 60;
+                color = r & 0xff;
+                CL_newfx.ParticleSteamEffect(color, cnt, magnitude);
+                break;
+
+            case Defines.TE_HEATBEAM_STEAM:
+                //                     cnt = MSG.ReadByte (net_message);
+                cnt = 20;
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadDir(Globals.net_message, dir);
+                //                     r = MSG.ReadByte (net_message);
+                //                     magnitude = MSG.ReadShort (net_message);
+                //                     color = r & 0xff;
+                color = 0xe0;
+                magnitude = 60;
+                CL_newfx.ParticleSteamEffect(color, cnt, magnitude);
+                break;
+
+            case Defines.TE_STEAM:
+                ParseSteam();
+                break;
+
+            case Defines.TE_BUBBLETRAIL2:
+                //                     cnt = MSG.ReadByte (net_message);
+                cnt = 8;
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadPos(Globals.net_message, pos2);
+                CL_newfx.BubbleTrail2(cnt);
+                break;
+
+            case Defines.TE_MOREBLOOD:
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadDir(Globals.net_message, dir);
+                CL_fx.ParticleEffect(0xe8, 250);
+                break;
+
+            case Defines.TE_CHAINFIST_SMOKE:
+                dir[0] = 0;
+                dir[1] = 0;
+                dir[2] = 1;
+                MSG.ReadPos(Globals.net_message, pos);
+                CL_newfx.ParticleSmokeEffect();
+                break;
+
+            case Defines.TE_ELECTRIC_SPARKS:
+                MSG.ReadPos(Globals.net_message, pos);
+                MSG.ReadDir(Globals.net_message, dir);
+                //                     CL_ParticleEffect (pos, dir, 109, 40);
+                CL_fx.ParticleEffect(0x75, 40);
+                break;
+
+            case Defines.TE_TRACKER_EXPLOSION:
+                MSG.ReadPos(Globals.net_message, pos);
+                CL_newfx.ColorFlash(-1, -1, -1);
+                CL_newfx.ColorExplosionParticles();
+                S.StartSound(pos, 0, 0, cl_sfx_disrexp, 1, Defines.ATTN_NORM, 0);
+                break;
+
+            case Defines.TE_TELEPORT_EFFECT:
+            case Defines.TE_DBALL_GOAL:
+                MSG.ReadPos(Globals.net_message, pos);
+                CL_fx.TeleportParticles();
+                break;
+
+            case Defines.TE_WIDOWBEAMOUT:
+                ParseWidow();
+                break;
+
+            case Defines.TE_NUKEBLAST:
+                ParseNuke();
+                break;
+
+            case Defines.TE_WIDOWSPLASH:
+                MSG.ReadPos(Globals.net_message, pos);
+                CL_newfx.WidowSplash();
+                break;
+            //   PGM
+            //   ==============
+
+            default:
+                Com.Error(Defines.ERR_DROP, "CL_ParseTEnt: bad type");
+        }
+    }
+
+    /*
+     * ================= CL_AddBeams =================
+     */
+    static void AddBeams() {
+        int i, j;
+        beam_t[] b;
+        float d;
+        float yaw, pitch;
+        float forward;
+        float len, steps;
+        float model_length;
+
+        //        update beams
+        b = cl_beams;
+        for (i = 0; i < MAX_BEAMS; i++) {
+            if (b[i].model == null || b[i].endtime < Globals.clientStateT.time)
+                continue;
+
+            // if coming from the player, update the start position
+            if (b[i].entity == Globals.clientStateT.playernum + 1) // entity 0 is the
+            // world
+            {
+                Math3D.vectorCopy(Globals.clientStateT.refdef.vieworg, b[i].start);
+                b[i].start[2] -= 22; // adjust for view height
+            }
+            Math3D.vectorAdd(b[i].start, b[i].offset, org);
+
+            // calculate pitch and yaw
+            Math3D.vectorSubtract(b[i].end, org, dist);
+
+            if (dist[1] == 0 && dist[0] == 0) {
+                yaw = 0;
+                if (dist[2] > 0)
+                    pitch = 90;
+                else
+                    pitch = 270;
+            } else {
+                // PMM - fixed to correct for pitch of 0
+                if (dist[0] != 0.0f)
+                    yaw = (float) (Math.atan2(dist[1], dist[0]) * 180 / Math.PI);
+                else if (dist[1] > 0)
+                    yaw = 90;
+                else
+                    yaw = 270;
+                if (yaw < 0)
+                    yaw += 360;
+
+                forward = (float) Math.sqrt(dist[0] * dist[0] + dist[1]
+                        * dist[1]);
+                pitch = (float) (Math.atan2(dist[2], forward) * -180.0 / Math.PI);
+                if (pitch < 0)
+                    pitch += 360.0;
+            }
+
+            // add new entities for the beams
+            d = Math3D.vectorNormalize(dist);
+
+            //memset (&ent, 0, sizeof(ent));
+            ent.clear();
+            if (b[i].model == cl_mod_lightning) {
+                model_length = 35.0f;
+                d -= 20.0; // correction so it doesn't end in middle of tesla
+            } else {
+                model_length = 30.0f;
+            }
+            steps = (float) Math.ceil(d / model_length);
+            len = (d - model_length) / (steps - 1);
+
+            // PMM - special case for lightning model .. if the real length is
+            // shorter than the model,
+            // flip it around & draw it from the end to the start. This prevents
+            // the model from going
+            // through the tesla mine (instead it goes through the target)
+            if ((b[i].model == cl_mod_lightning) && (d <= model_length)) {
+                //                             Com_Printf ("special case\n");
+                Math3D.vectorCopy(b[i].end, ent.origin);
+                // offset to push beam outside of tesla model (negative because
+                // dist is from end to start
+                // for this beam)
+                //                             for (j=0 ; j<3 ; j++)
+                //                                     ent.origin[j] -= dist[j]*10.0;
+                ent.model = b[i].model;
+                ent.flags = Defines.RF_FULLBRIGHT;
+                ent.angles[0] = pitch;
+                ent.angles[1] = yaw;
+                ent.angles[2] = Lib.rand() % 360;
+                V.AddEntity(ent);
+                return;
+            }
+            while (d > 0) {
+                Math3D.vectorCopy(org, ent.origin);
+                ent.model = b[i].model;
+                if (b[i].model == cl_mod_lightning) {
+                    ent.flags = Defines.RF_FULLBRIGHT;
+                    ent.angles[0] = -pitch;
+                    ent.angles[1] = yaw + 180.0f;
+                    ent.angles[2] = Lib.rand() % 360;
+                } else {
+                    ent.angles[0] = pitch;
+                    ent.angles[1] = yaw;
+                    ent.angles[2] = Lib.rand() % 360;
+                }
+
+                //                             Com_Printf("B: %d . %d\n", b[i].entity, b[i].dest_entity);
+                V.AddEntity(ent);
+
+                for (j = 0; j < 3; j++)
+                    org[j] += dist[j] * len;
+                d -= model_length;
+            }
+        }
+    }
+
+    /*
+     * ================= ROGUE - draw player locked beams CL_AddPlayerBeams
+     * =================
+     */
+    static void AddPlayerBeams() {
+        float d;
+        //entity_t ent = new entity_t();
+        float yaw, pitch;
+        float forward;
+        float len, steps;
+        int framenum = 0;
+        float model_length;
+
+        float hand_multiplier;
+        frame_t oldframe;
+        player_state_t ps, ops;
+
+        //       PMM
+        if (Globals.hand != null) {
+            if (Globals.hand.value == 2)
+                hand_multiplier = 0;
+            else if (Globals.hand.value == 1)
+                hand_multiplier = -1;
+            else
+                hand_multiplier = 1;
+        } else {
+            hand_multiplier = 1;
+        }
+        //       PMM
+
+        //        update beams
+        beam_t[] b = cl_playerbeams;
+        for (int i = 0; i < MAX_BEAMS; i++) {
+
+            if (b[i].model == null || b[i].endtime < Globals.clientStateT.time)
+                continue;
+
+            if (cl_mod_heatbeam != null && (b[i].model == cl_mod_heatbeam)) {
+
+                // if coming from the player, update the start position
+                if (b[i].entity == Globals.clientStateT.playernum + 1) // entity 0 is the
+                // world
+                {
+                    // set up gun position
+                    // code straight out of CL_AddViewWeapon
+                    ps = Globals.clientStateT.frame.playerstate;
+                    int j = (Globals.clientStateT.frame.serverframe - 1)
+                            & Defines.UPDATE_MASK;
+                    oldframe = Globals.clientStateT.frames[j];
+
+                    if (oldframe.serverframe != Globals.clientStateT.frame.serverframe - 1
+                            || !oldframe.valid)
+                        oldframe = Globals.clientStateT.frame; // previous frame was
+                    // dropped or involid
+
+                    ops = oldframe.playerstate;
+
+                    Math3D.vectorMA(b[i].start,
+                            (hand_multiplier * b[i].offset[0]),
+                            Globals.clientStateT.v_right, org);
+                    Math3D.vectorMA(org, b[i].offset[1], Globals.clientStateT.v_forward,
+                            org);
+                    Math3D.vectorMA(org, b[i].offset[2], Globals.clientStateT.v_up, org);
+                    if ((Globals.hand != null) && (Globals.hand.value == 2)) {
+                        Math3D.vectorMA(org, -1, Globals.clientStateT.v_up, org);
+                    }
+                    // FIXME - take these out when final
+                    Math3D.vectorCopy(Globals.clientStateT.v_right, r);
+                    Math3D.vectorCopy(Globals.clientStateT.v_forward, f);
+                    Math3D.vectorCopy(Globals.clientStateT.v_up, u);
+
+                } else
+                    Math3D.vectorCopy(b[i].start, org);
+            } else {
+                // if coming from the player, update the start position
+                if (b[i].entity == Globals.clientStateT.playernum + 1) // entity 0 is the
+                // world
+                {
+                    Math3D.vectorCopy(Globals.clientStateT.refdef.vieworg, b[i].start);
+                    b[i].start[2] -= 22; // adjust for view height
+                }
+                Math3D.vectorAdd(b[i].start, b[i].offset, org);
+            }
+
+            // calculate pitch and yaw
+            Math3D.vectorSubtract(b[i].end, org, dist);
+
+            //   PMM
+            if (cl_mod_heatbeam != null && (b[i].model == cl_mod_heatbeam)
+                    && (b[i].entity == Globals.clientStateT.playernum + 1)) {
+
+                len = Math3D.vectorLength(dist);
+                Math3D.vectorScale(f, len, dist);
+                Math3D.vectorMA(dist, (hand_multiplier * b[i].offset[0]), r,
+                        dist);
+                Math3D.vectorMA(dist, b[i].offset[1], f, dist);
+                Math3D.vectorMA(dist, b[i].offset[2], u, dist);
+                if ((Globals.hand != null) && (Globals.hand.value == 2)) {
+                    Math3D.vectorMA(org, -1, Globals.clientStateT.v_up, org);
+                }
+            }
+            //   PMM
+
+            if (dist[1] == 0 && dist[0] == 0) {
+                yaw = 0;
+                if (dist[2] > 0)
+                    pitch = 90;
+                else
+                    pitch = 270;
+            } else {
+                // PMM - fixed to correct for pitch of 0
+                if (dist[0] != 0.0f)
+                    yaw = (float) (Math.atan2(dist[1], dist[0]) * 180 / Math.PI);
+                else if (dist[1] > 0)
+                    yaw = 90;
+                else
+                    yaw = 270;
+                if (yaw < 0)
+                    yaw += 360;
+
+                forward = (float) Math.sqrt(dist[0] * dist[0] + dist[1]
+                        * dist[1]);
+                pitch = (float) (Math.atan2(dist[2], forward) * -180.0 / Math.PI);
+                if (pitch < 0)
+                    pitch += 360.0;
+            }
+
+            if (cl_mod_heatbeam != null && (b[i].model == cl_mod_heatbeam)) {
+                if (b[i].entity != Globals.clientStateT.playernum + 1) {
+                    framenum = 2;
+                    //                                 Com_Printf ("Third person\n");
+                    ent.angles[0] = -pitch;
+                    ent.angles[1] = yaw + 180.0f;
+                    ent.angles[2] = 0;
+                    //                                 Com_Printf ("%f %f - %f %f %f\n", -pitch, yaw+180.0,
+                    // b[i].offset[0], b[i].offset[1], b[i].offset[2]);
+                    Math3D.angleVectors(ent.angles, f, r, u);
+
+                    // if it's a non-origin offset, it's a player, so use the
+                    // hardcoded player offset
+                    if (!Math3D.vectorEquals(b[i].offset, Globals.vec3_origin)) {
+                        Math3D.vectorMA(org, -(b[i].offset[0]) + 1, r, org);
+                        Math3D.vectorMA(org, -(b[i].offset[1]), f, org);
+                        Math3D.vectorMA(org, -(b[i].offset[2]) - 10, u, org);
+                    } else {
+                        // if it's a monster, do the particle effect
+                        CL_newfx.MonsterPlasma_Shell(b[i].start);
+                    }
+                } else {
+                    framenum = 1;
+                }
+            }
+
+            // if it's the heatbeam, draw the particle effect
+            if ((cl_mod_heatbeam != null && (b[i].model == cl_mod_heatbeam) && (b[i].entity == Globals.clientStateT.playernum + 1))) {
+                CL_newfx.Heatbeam();
+            }
+
+            // add new entities for the beams
+            d = Math3D.vectorNormalize(dist);
+
+            //memset (&ent, 0, sizeof(ent));
+            ent.clear();
+
+            if (b[i].model == cl_mod_heatbeam) {
+                model_length = 32.0f;
+            } else if (b[i].model == cl_mod_lightning) {
+                model_length = 35.0f;
+                d -= 20.0; // correction so it doesn't end in middle of tesla
+            } else {
+                model_length = 30.0f;
+            }
+            steps = (float) Math.ceil(d / model_length);
+            len = (d - model_length) / (steps - 1);
+
+            // PMM - special case for lightning model .. if the real length is
+            // shorter than the model,
+            // flip it around & draw it from the end to the start. This prevents
+            // the model from going
+            // through the tesla mine (instead it goes through the target)
+            if ((b[i].model == cl_mod_lightning) && (d <= model_length)) {
+                //                             Com_Printf ("special case\n");
+                Math3D.vectorCopy(b[i].end, ent.origin);
+                // offset to push beam outside of tesla model (negative because
+                // dist is from end to start
+                // for this beam)
+                //                             for (j=0 ; j<3 ; j++)
+                //                                     ent.origin[j] -= dist[j]*10.0;
+                ent.model = b[i].model;
+                ent.flags = Defines.RF_FULLBRIGHT;
+                ent.angles[0] = pitch;
+                ent.angles[1] = yaw;
+                ent.angles[2] = Lib.rand() % 360;
+                V.AddEntity(ent);
+                return;
+            }
+            while (d > 0) {
+                Math3D.vectorCopy(org, ent.origin);
+                ent.model = b[i].model;
+                if (cl_mod_heatbeam != null && (b[i].model == cl_mod_heatbeam)) {
+                    //                                 ent.flags = RF_FULLBRIGHT|RF_TRANSLUCENT;
+                    //                                 ent.alpha = 0.3;
+                    ent.flags = Defines.RF_FULLBRIGHT;
+                    ent.angles[0] = -pitch;
+                    ent.angles[1] = yaw + 180.0f;
+                    ent.angles[2] = (Globals.clientStateT.time) % 360;
+                    //                                 ent.angles[2] = rand()%360;
+                    ent.frame = framenum;
+                } else if (b[i].model == cl_mod_lightning) {
+                    ent.flags = Defines.RF_FULLBRIGHT;
+                    ent.angles[0] = -pitch;
+                    ent.angles[1] = yaw + 180.0f;
+                    ent.angles[2] = Lib.rand() % 360;
+                } else {
+                    ent.angles[0] = pitch;
+                    ent.angles[1] = yaw;
+                    ent.angles[2] = Lib.rand() % 360;
+                }
+
+                //                             Com_Printf("B: %d . %d\n", b[i].entity, b[i].dest_entity);
+                V.AddEntity(ent);
+
+                for (int j = 0; j < 3; j++)
+                    org[j] += dist[j] * len;
+                d -= model_length;
+            }
+        }
+    }
+
+    /*
+     * ================= CL_AddExplosions =================
+     */
+    static void AddExplosions() {
+        entity_t ent;
+        int i;
+        explosion_t[] ex;
+        float frac;
+        int f;
+
+        //memset (&ent, 0, sizeof(ent)); Pointer!
+        ent = null;
+        ex = cl_explosions;
+        for (i = 0; i < MAX_EXPLOSIONS; i++) {
+            if (ex[i].type == ex_free)
+                continue;
+            frac = (Globals.clientStateT.time - ex[i].start) / 100.0f;
+            f = (int) Math.floor(frac);
+
+            ent = ex[i].ent;
+
+            switch (ex[i].type) {
+                case ex_mflash:
+                    if (f >= ex[i].frames - 1)
+                        ex[i].type = ex_free;
+                    break;
+                case ex_misc:
+                    if (f >= ex[i].frames - 1) {
+                        ex[i].type = ex_free;
+                        break;
+                    }
+                    ent.alpha = 1.0f - frac / (ex[i].frames - 1);
+                    break;
+                case ex_flash:
+                    if (f >= 1) {
+                        ex[i].type = ex_free;
+                        break;
+                    }
+                    ent.alpha = 1.0f;
+                    break;
+                case ex_poly:
+                    if (f >= ex[i].frames - 1) {
+                        ex[i].type = ex_free;
+                        break;
+                    }
+
+                    ent.alpha = (16.0f - (float) f) / 16.0f;
+
+                    if (f < 10) {
+                        ent.skinnum = (f >> 1);
+                        if (ent.skinnum < 0)
+                            ent.skinnum = 0;
+                    } else {
+                        ent.flags |= Defines.RF_TRANSLUCENT;
+                        if (f < 13)
+                            ent.skinnum = 5;
+                        else
+                            ent.skinnum = 6;
+                    }
+                    break;
+                case ex_poly2:
+                    if (f >= ex[i].frames - 1) {
+                        ex[i].type = ex_free;
+                        break;
+                    }
+
+                    ent.alpha = (5.0f - (float) f) / 5.0f;
+                    ent.skinnum = 0;
+                    ent.flags |= Defines.RF_TRANSLUCENT;
+                    break;
+            }
+
+            if (ex[i].type == ex_free)
+                continue;
+            if (ex[i].light != 0.0f) {
+                V.AddLight(ent.origin, ex[i].light * ent.alpha,
+                        ex[i].lightcolor[0], ex[i].lightcolor[1],
+                        ex[i].lightcolor[2]);
+            }
+
+            Math3D.vectorCopy(ent.origin, ent.oldorigin);
+
+            if (f < 0)
+                f = 0;
+            ent.frame = ex[i].baseframe + f + 1;
+            ent.oldframe = ex[i].baseframe + f;
+            ent.backlerp = 1.0f - Globals.clientStateT.lerpfrac;
+
+            V.AddEntity(ent);
+        }
+    }
+
+    /*
+     * ================= CL_AddLasers =================
+     */
+    static void AddLasers() {
+        laser_t[] l;
+        int i;
+
+        l = cl_lasers;
+        for (i = 0; i < MAX_LASERS; i++) {
+            if (l[i].endtime >= Globals.clientStateT.time)
+                V.AddEntity(l[i].ent);
+        }
+    }
+
+    /* PMM - CL_Sustains */
+    static void ProcessSustain() {
+        cl_sustain_t[] s;
+        int i;
+
+        s = cl_sustains;
+        for (i = 0; i < MAX_SUSTAINS; i++) {
+            if (s[i].id != 0)
+                if ((s[i].endtime >= Globals.clientStateT.time)
+                        && (Globals.clientStateT.time >= s[i].nextthink)) {
+                    s[i].think.think(s[i]);
+                } else if (s[i].endtime < Globals.clientStateT.time)
+                    s[i].id = 0;
+        }
+    }
+
+    /*
+     * ================= CL_AddTEnts =================
+     */
+    static void AddTEnts() {
+        AddBeams();
+        // PMM - draw plasma beams
+        AddPlayerBeams();
+        AddExplosions();
+        AddLasers();
+        // PMM - set up sustain
+        ProcessSustain();
+    }
+
+    static class explosion_t {
+        final entity_t ent = new entity_t();
+        final float[] lightcolor = new float[3];
+        int type;
+        int frames;
+        float light;
+        float start;
+
+        int baseframe;
+
+        void clear() {
+            lightcolor[0] = lightcolor[1] = lightcolor[2] = light = start = type = frames = baseframe = 0;
+            ent.clear();
+        }
+    }
+
+    static class beam_t {
+        final float[] offset = new float[3];
+        final float[] start = new float[3];
+        final float[] end = new float[3];
+        int entity;
+        int dest_entity;
+        Model model;
+        int endtime;
+
+        void clear() {
+            offset[0] = offset[1] = offset[2] = start[0] = start[1] = start[2] = end[0] = end[1] = end[2] = entity = dest_entity = endtime = 0;
+            model = null;
+        }
+    }
+
+    static class laser_t {
+        final entity_t ent = new entity_t();
+
+        int endtime;
+
+        void clear() {
+            endtime = 0;
+            ent.clear();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/client/CL_view.java b/src/main/java/lwjake2/client/CL_view.java
new file mode 100644 (file)
index 0000000..75213d9
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.qcommon.CM;
+import lwjake2.qcommon.Com;
+import lwjake2.sys.Sys;
+
+import java.util.StringTokenizer;
+
+public class CL_view {
+
+
+    /*
+     * =================
+     * 
+     * CL_PrepRefresh
+     * 
+     * Call before entering a new level, or after changing dlls
+     * =================
+     */
+    static void PrepRefresh() {
+        String mapname;
+        int i;
+        String name;
+        float rotate;
+        float[] axis = new float[3];
+
+        if ((i = Globals.clientStateT.configstrings[Defines.CS_MODELS + 1].length()) == 0)
+            return; // no map loaded
+
+        SCR.AddDirtyPoint(0, 0);
+        SCR.AddDirtyPoint(Globals.viddef.width - 1, Globals.viddef.height - 1);
+
+        // let the render dll load the map
+        mapname = Globals.clientStateT.configstrings[Defines.CS_MODELS + 1].substring(5,
+                i - 4); // skip "maps/"
+        // cut off ".bsp"
+
+        // register models, pics, and skins
+        Com.Printf("Map: " + mapname + "\r");
+        SCR.UpdateScreen();
+        Globals.re.BeginRegistration(mapname);
+        Com.Printf("                                     \r");
+
+        // precache status bar pics
+        Com.Printf("pics\r");
+        SCR.UpdateScreen();
+        SCR.TouchPics();
+        Com.Printf("                                     \r");
+
+        CL_tent.RegisterTEntModels();
+
+        for (i = 1; i < Defines.MAX_MODELS
+                && Globals.clientStateT.configstrings[Defines.CS_MODELS + i].length() != 0; i++) {
+            name = Globals.clientStateT.configstrings[Defines.CS_MODELS + i];
+            if (name.length() > 37)
+                name = name.substring(0, 36);
+
+            if (name.charAt(0) != '*')
+                Com.Printf(name + "\r");
+
+            SCR.UpdateScreen();
+            Sys.SendKeyEvents(); // pump message loop
+            Globals.clientStateT.model_draw[i] = Globals.re
+                    .RegisterModel(Globals.clientStateT.configstrings[Defines.CS_MODELS
+                            + i]);
+            if (name.charAt(0) == '*')
+                Globals.clientStateT.model_clip[i] = CM
+                        .InlineModel(Globals.clientStateT.configstrings[Defines.CS_MODELS
+                                + i]);
+            else
+                Globals.clientStateT.model_clip[i] = null;
+            if (name.charAt(0) != '*')
+                Com.Printf("                                     \r");
+        }
+
+        Com.Printf("images\r");
+        SCR.UpdateScreen();
+        for (i = 1; i < Defines.MAX_IMAGES
+                && Globals.clientStateT.configstrings[Defines.CS_IMAGES + i].length() > 0; i++) {
+            Globals.clientStateT.image_precache[i] = Globals.re
+                    .RegisterPic(Globals.clientStateT.configstrings[Defines.CS_IMAGES + i]);
+            Sys.SendKeyEvents(); // pump message loop
+        }
+
+        Com.Printf("                                     \r");
+        for (i = 0; i < Defines.MAX_CLIENTS; i++) {
+            if (Globals.clientStateT.configstrings[Defines.CS_PLAYERSKINS + i].length() == 0)
+                continue;
+            Com.Printf("client " + i + '\r');
+            SCR.UpdateScreen();
+            Sys.SendKeyEvents(); // pump message loop
+            CL_parse.ParseClientinfo(i);
+            Com.Printf("                                     \r");
+        }
+
+        CL_parse.LoadClientinfo(Globals.clientStateT.baseclientinfo,
+                "unnamed\\male/grunt");
+
+        // set sky textures and speed
+        Com.Printf("sky\r");
+        SCR.UpdateScreen();
+        rotate = Float
+                .parseFloat(Globals.clientStateT.configstrings[Defines.CS_SKYROTATE]);
+        StringTokenizer st = new StringTokenizer(
+                Globals.clientStateT.configstrings[Defines.CS_SKYAXIS]);
+        axis[0] = Float.parseFloat(st.nextToken());
+        axis[1] = Float.parseFloat(st.nextToken());
+        axis[2] = Float.parseFloat(st.nextToken());
+        Globals.re.SetSky(Globals.clientStateT.configstrings[Defines.CS_SKY], rotate,
+                axis);
+        Com.Printf("                                     \r");
+
+        // the renderer can now free unneeded stuff
+        Globals.re.EndRegistration();
+
+        // clear any lines of console text
+        Console.ClearNotify();
+
+        SCR.UpdateScreen();
+        Globals.clientStateT.refresh_prepped = true;
+        Globals.clientStateT.force_refdef = true; // make sure we have a valid refdef
+    }
+
+    public static void AddNetgraph() {
+        int i;
+        int in;
+        int ping;
+
+        // if using the debuggraph for something else, don't
+        // add the net lines
+        if (SCR.scr_debuggraph.value == 0.0f || SCR.scr_timegraph.value == 0.0f)
+            return;
+
+        for (i = 0; i < Globals.clientStaticT.netchan.dropped; i++)
+            SCR.DebugGraph(30, 0x40);
+
+        for (i = 0; i < Globals.clientStateT.surpressCount; i++)
+            SCR.DebugGraph(30, 0xdf);
+
+        // see what the latency was on this packet
+        in = Globals.clientStaticT.netchan.incoming_acknowledged
+                & (Defines.CMD_BACKUP - 1);
+        ping = Globals.clientStaticT.realtime - Globals.clientStateT.cmd_time[in];
+        ping /= 30;
+        if (ping > 30)
+            ping = 30;
+        SCR.DebugGraph(ping, 0xd0);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/client/Client.java b/src/main/java/lwjake2/client/Client.java
new file mode 100644 (file)
index 0000000..3cd4a26
--- /dev/null
@@ -0,0 +1,1492 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.*;
+import lwjake2.qcommon.*;
+import lwjake2.server.Server;
+import lwjake2.sound.S;
+import lwjake2.sys.NET;
+import lwjake2.sys.Sys;
+import lwjake2.sys.Timer;
+import lwjake2.sys.UserInputHandler;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+import lwjake2.util.Vargs;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/**
+ * CL
+ */
+public final class Client {
+
+    public static final int PLAYER_MULT = 5;
+    //    ENV_CNT is map load, ENV_CNT+1 is first env map
+    public static final int ENV_CNT = (Defines.CS_PLAYERSKINS + Defines.MAX_CLIENTS
+            * Client.PLAYER_MULT);
+    public static final int TEXTURE_CNT = (ENV_CNT + 13);
+    public static final String[][] cheatvarsinfo = {{"timescale", "1"},
+            {"timedemo", "0"}, {"r_drawworld", "1"},
+            {"cl_testlights", "0"}, {"r_fullbright", "0"},
+            {"r_drawflat", "0"}, {"paused", "0"}, {"fixedtime", "0"},
+            {"sw_draworder", "0"}, {"gl_lightmap", "0"},
+            {"gl_saturatelighting", "0"}, {null, null}};
+    public static final cheatvar_t[] cheatvars;
+    /**
+     * Stop_f
+     * <p>
+     * Stop recording a demo.
+     */
+    static final xcommand_t Stop_f = new xcommand_t() {
+        public void execute() {
+            try {
+
+                int len;
+
+                if (!Globals.clientStaticT.demorecording) {
+                    Com.Printf("Not recording a demo.\n");
+                    return;
+                }
+
+                //        finish up
+                len = -1;
+                Globals.clientStaticT.demofile.writeInt(EndianHandler.swapInt(len));
+                Globals.clientStaticT.demofile.close();
+                Globals.clientStaticT.demofile = null;
+                Globals.clientStaticT.demorecording = false;
+                Com.Printf("Stopped demo.\n");
+            } catch (IOException ignored) {
+            }
+        }
+    };
+    static final entity_state_t nullstate = new entity_state_t(null);
+    /**
+     * Record_f
+     * <p>
+     * record &lt;demoname&gt;
+     * Begins recording a demo from the current position.
+     */
+    static final xcommand_t Record_f = new xcommand_t() {
+        public void execute() {
+            try {
+                String name;
+                byte buf_data[] = new byte[Defines.MAX_MSGLEN];
+                sizebuf_t buf = new sizebuf_t();
+                int i;
+                entity_state_t ent;
+
+                if (Cmd.Argc() != 2) {
+                    Com.Printf("record <demoname>\n");
+                    return;
+                }
+
+                if (Globals.clientStaticT.demorecording) {
+                    Com.Printf("Already recording.\n");
+                    return;
+                }
+
+                if (Globals.clientStaticT.state != Defines.ca_active) {
+                    Com.Printf("You must be in a level to record.\n");
+                    return;
+                }
+
+                //
+                // open the demo file
+                //
+                name = FS.Gamedir() + "/demos/" + Cmd.Argv(1) + ".dm2";
+
+                Com.Printf("recording to " + name + ".\n");
+                FS.CreatePath(name);
+                Globals.clientStaticT.demofile = new RandomAccessFile(name, "rw");
+                if (Globals.clientStaticT.demofile == null) {
+                    Com.Printf("ERROR: couldn't open.\n");
+                    return;
+                }
+                Globals.clientStaticT.demorecording = true;
+
+                // don't start saving messages until a non-delta compressed
+                // message is received
+                Globals.clientStaticT.demowaiting = true;
+
+                //
+                // write out messages to hold the startup information
+                //
+                SZ.Init(buf, buf_data, Defines.MAX_MSGLEN);
+
+                // send the serverdata
+                MSG.WriteByte(buf, Defines.svc_serverdata);
+                MSG.WriteInt(buf, Defines.PROTOCOL_VERSION);
+                MSG.WriteInt(buf, 0x10000 + Globals.clientStateT.servercount);
+                MSG.WriteByte(buf, 1); // demos are always attract loops
+                MSG.WriteString(buf, Globals.clientStateT.gamedir);
+                MSG.WriteShort(buf, Globals.clientStateT.playernum);
+
+                MSG.WriteString(buf, Globals.clientStateT.configstrings[Defines.CS_NAME]);
+
+                // configstrings
+                for (i = 0; i < Defines.MAX_CONFIGSTRINGS; i++) {
+                    if (Globals.clientStateT.configstrings[i].length() > 0) {
+                        if (buf.cursize + Globals.clientStateT.configstrings[i].length()
+                                + 32 > buf.maxsize) {
+                            // write it out
+                            Globals.clientStaticT.demofile.writeInt(EndianHandler.swapInt(buf.cursize));
+                            Globals.clientStaticT.demofile
+                                    .write(buf.data, 0, buf.cursize);
+                            buf.cursize = 0;
+                        }
+
+                        MSG.WriteByte(buf, Defines.svc_configstring);
+                        MSG.WriteShort(buf, i);
+                        MSG.WriteString(buf, Globals.clientStateT.configstrings[i]);
+                    }
+
+                }
+
+                // baselines
+                nullstate.clear();
+                for (i = 0; i < Defines.MAX_EDICTS; i++) {
+                    ent = Globals.cl_entities[i].baseline;
+                    if (ent.modelindex == 0)
+                        continue;
+
+                    if (buf.cursize + 64 > buf.maxsize) { // write it out
+                        Globals.clientStaticT.demofile.writeInt(EndianHandler.swapInt(buf.cursize));
+                        Globals.clientStaticT.demofile.write(buf.data, 0, buf.cursize);
+                        buf.cursize = 0;
+                    }
+
+                    MSG.WriteByte(buf, Defines.svc_spawnbaseline);
+                    MSG.WriteDeltaEntity(nullstate,
+                            Globals.cl_entities[i].baseline, buf, true, true);
+                }
+
+                MSG.WriteByte(buf, Defines.svc_stufftext);
+                MSG.WriteString(buf, "precache\n");
+
+                // write it to the demo file
+                Globals.clientStaticT.demofile.writeInt(EndianHandler.swapInt(buf.cursize));
+                Globals.clientStaticT.demofile.write(buf.data, 0, buf.cursize);
+                // the rest of the demo file will be individual frames
+
+            } catch (IOException ignored) {
+            }
+        }
+    };
+    /**
+     * ForwardToServer_f
+     */
+    static final xcommand_t ForwardToServer_f = new xcommand_t() {
+        public void execute() {
+            if (Globals.clientStaticT.state != Defines.ca_connected
+                    && Globals.clientStaticT.state != Defines.ca_active) {
+                Com.Printf("Can't \"" + Cmd.Argv(0) + "\", not connected\n");
+                return;
+            }
+
+            // don't forward the first argument
+            if (Cmd.Argc() > 1) {
+                MSG.WriteByte(Globals.clientStaticT.netchan.message,
+                        Defines.clc_stringcmd);
+                SZ.Print(Globals.clientStaticT.netchan.message, Cmd.Args());
+            }
+        }
+    };
+    /**
+     * Pause_f
+     */
+    static final xcommand_t Pause_f = new xcommand_t() {
+        public void execute() {
+            // never pause in multiplayer
+
+            if (Cvar.variableValue("maxclients") > 1
+                    || Globals.server_state == 0) {
+                Cvar.setValue("paused", 0);
+                return;
+            }
+
+            Cvar.setValue("paused", Globals.cl_paused.value);
+        }
+    };
+    /**
+     * Connect_f
+     */
+    static final xcommand_t Connect_f = new xcommand_t() {
+        public void execute() {
+            String server;
+
+            if (Cmd.Argc() != 2) {
+                Com.Printf("usage: connect <server>\n");
+                return;
+            }
+
+            if (Globals.server_state != 0) {
+                // if running a local server, kill it and reissue
+                Server.SV_Shutdown("Server quit\n", false);
+            } else {
+                Disconnect();
+            }
+
+            server = Cmd.Argv(1);
+
+            NET.Config(true); // allow remote
+
+            Disconnect();
+
+            Globals.clientStaticT.state = Defines.ca_connecting;
+            //strncpy (cls.servername, server, sizeof(cls.servername)-1);
+            Globals.clientStaticT.servername = server;
+            Globals.clientStaticT.connect_time = -99999;
+            // CL_CheckForResend() will fire immediately
+        }
+    };
+    /**
+     * Rcon_f
+     * <p>
+     * Send the rest of the command line over as an unconnected command.
+     */
+    static final xcommand_t Rcon_f = new xcommand_t() {
+        public void execute() {
+
+            if (Globals.rcon_client_password.string.length() == 0) {
+                Com.Printf("You must set 'rcon_password' before\nissuing an rcon command.\n");
+                return;
+            }
+
+            StringBuilder message = new StringBuilder(1024);
+
+            // connection less packet
+            message.append('\u00ff');
+            message.append('\u00ff');
+            message.append('\u00ff');
+            message.append('\u00ff');
+
+            // allow remote
+            NET.Config(true);
+
+            message.append("rcon ");
+            message.append(Globals.rcon_client_password.string);
+            message.append(" ");
+
+            for (int i = 1; i < Cmd.Argc(); i++) {
+                message.append(Cmd.Argv(i));
+                message.append(" ");
+            }
+
+            NetadrT to = new NetadrT();
+
+            if (Globals.clientStaticT.state >= Defines.ca_connected)
+                to = Globals.clientStaticT.netchan.remote_address;
+            else {
+                if (Globals.rcon_address.string.length() == 0) {
+                    Com.Printf("You must either be connected,\nor set the 'rcon_address' cvar\nto issue rcon commands\n");
+                    return;
+                }
+                NET.StringToAdr(Globals.rcon_address.string, to);
+                if (to.port == 0) to.port = Defines.PORT_SERVER;
+            }
+            message.append('\0');
+            String b = message.toString();
+            NET.SendPacket(Defines.NS_CLIENT, b.length(), Lib.stringToBytes(b), to);
+        }
+    };
+    /**
+     * Changing_f
+     * <p>
+     * Just sent as a hint to the client that they should drop to full console.
+     */
+    static final xcommand_t Changing_f = new xcommand_t() {
+        public void execute() {
+            //ZOID
+            //if we are downloading, we don't change!
+            // This so we don't suddenly stop downloading a map
+
+            if (Globals.clientStaticT.download != null)
+                return;
+
+            SCR.BeginLoadingPlaque();
+            Globals.clientStaticT.state = Defines.ca_connected; // not active anymore, but
+            // not disconnected
+            Com.Printf("\nChanging map...\n");
+        }
+    };
+    /**
+     * Reconnect_f
+     * <p>
+     * The server is changing levels.
+     */
+    static final xcommand_t Reconnect_f = new xcommand_t() {
+        public void execute() {
+            //ZOID
+            //if we are downloading, we don't change! This so we don't suddenly
+            // stop downloading a map
+            if (Globals.clientStaticT.download != null)
+                return;
+
+            S.StopAllSounds();
+            if (Globals.clientStaticT.state == Defines.ca_connected) {
+                Com.Printf("reconnecting...\n");
+                Globals.clientStaticT.state = Defines.ca_connected;
+                MSG.WriteChar(Globals.clientStaticT.netchan.message,
+                        Defines.clc_stringcmd);
+                MSG.WriteString(Globals.clientStaticT.netchan.message, "new");
+                return;
+            }
+
+            if (Globals.clientStaticT.servername != null) {
+                if (Globals.clientStaticT.state >= Defines.ca_connected) {
+                    Disconnect();
+                    Globals.clientStaticT.connect_time = Globals.clientStaticT.realtime - 1500;
+                } else
+                    Globals.clientStaticT.connect_time = -99999; // fire immediately
+
+                Globals.clientStaticT.state = Defines.ca_connecting;
+                Com.Printf("reconnecting...\n");
+            }
+        }
+    };
+    /**
+     * PingServers_f
+     */
+    static final xcommand_t PingServers_f = new xcommand_t() {
+        public void execute() {
+            int i;
+            NetadrT adr = new NetadrT();
+            //char name[32];
+            String name;
+            String adrstring;
+            CvarT noudp;
+            CvarT noipx;
+
+            NET.Config(true); // allow remote
+
+            // send a broadcast packet
+            Com.Printf("pinging broadcast...\n");
+
+            noudp = Cvar.get("noudp", "0", Defines.CVAR_NOSET);
+            if (noudp.value == 0.0f) {
+                adr.type = Defines.NA_BROADCAST;
+                adr.port = Defines.PORT_SERVER;
+                //adr.port = BigShort(PORT_SERVER);
+                Netchan.OutOfBandPrint(Defines.NS_CLIENT, adr, "info "
+                        + Defines.PROTOCOL_VERSION);
+            }
+
+            // we use no IPX
+            noipx = Cvar.get("noipx", "1", Defines.CVAR_NOSET);
+            if (noipx.value == 0.0f) {
+                adr.type = Defines.NA_BROADCAST_IPX;
+                //adr.port = BigShort(PORT_SERVER);
+                adr.port = Defines.PORT_SERVER;
+                Netchan.OutOfBandPrint(Defines.NS_CLIENT, adr, "info "
+                        + Defines.PROTOCOL_VERSION);
+            }
+
+            // send a packet to each address book entry
+            for (i = 0; i < 16; i++) {
+                //Com_sprintf (name, sizeof(name), "adr%i", i);
+                name = "adr" + i;
+                adrstring = Cvar.variableString(name);
+                if (adrstring == null || adrstring.length() == 0)
+                    continue;
+
+                Com.Printf("pinging " + adrstring + "...\n");
+                if (!NET.StringToAdr(adrstring, adr)) {
+                    Com.Printf("Bad address: " + adrstring + "\n");
+                    continue;
+                }
+                if (adr.port == 0)
+                    //adr.port = BigShort(PORT_SERVER);
+                    adr.port = Defines.PORT_SERVER;
+                Netchan.OutOfBandPrint(Defines.NS_CLIENT, adr, "info "
+                        + Defines.PROTOCOL_VERSION);
+            }
+        }
+    };
+    /**
+     * Skins_f
+     * <p>
+     * Load or download any custom player skins and models.
+     */
+    static final xcommand_t Skins_f = new xcommand_t() {
+        public void execute() {
+            int i;
+
+            for (i = 0; i < Defines.MAX_CLIENTS; i++) {
+                if (Globals.clientStateT.configstrings[Defines.CS_PLAYERSKINS + i] == null)
+                    continue;
+                Com.Printf("client " + i + ": "
+                        + Globals.clientStateT.configstrings[Defines.CS_PLAYERSKINS + i]
+                        + "\n");
+                SCR.UpdateScreen();
+                Sys.SendKeyEvents(); // pump message loop
+                CL_parse.ParseClientinfo(i);
+            }
+        }
+    };
+    /**
+     * Userinfo_f
+     */
+    static final xcommand_t Userinfo_f = new xcommand_t() {
+        public void execute() {
+            Com.Printf("User info settings:\n");
+            Info.Print(Cvar.userinfo());
+        }
+    };
+    /**
+     * Snd_Restart_f
+     * <p>
+     * Restart the sound subsystem so it can pick up new parameters and flush
+     * all sounds.
+     */
+    static final xcommand_t Snd_Restart_f = new xcommand_t() {
+        public void execute() {
+            S.Shutdown();
+            S.Init();
+            CL_parse.RegisterSounds();
+        }
+    };
+    static final String[] env_suf = {"rt", "bk", "lf", "ft", "up", "dn"};
+    static int precache_check; // for autodownload of precache items
+    static int precache_spawncount;
+    static int precache_tex;
+    static int precache_model_skin;
+    static byte precache_model[]; // used for skin checking in alias models
+    /**
+     * The server will send this command right before allowing the client into
+     * the server.
+     */
+    static final xcommand_t Precache_f = new xcommand_t() {
+        public void execute() {
+            // Yet another hack to let old demos work the old precache sequence.
+            if (Cmd.Argc() < 2) {
+
+                int iw[] = {0}; // for detecting cheater maps
+
+                CM.CM_LoadMap(Globals.clientStateT.configstrings[Defines.CS_MODELS + 1],
+                        true, iw);
+
+                CL_parse.RegisterSounds();
+                CL_view.PrepRefresh();
+                return;
+            }
+
+            Client.precache_check = Defines.CS_MODELS;
+            Client.precache_spawncount = Lib.atoi(Cmd.Argv(1));
+            Client.precache_model = null;
+            Client.precache_model_skin = 0;
+
+            RequestNextDownload();
+        }
+    };
+    static int numcheatvars;
+    /**
+     * Shutdown
+     * <p>
+     * FIXME: this is a callback from Sys_Quit and Com_Error. It would be better
+     * to run quit through here before the final handoff to the sys code.
+     */
+    static boolean isdown = false;
+    /**
+     * Quit_f
+     */
+    static final xcommand_t Quit_f = new xcommand_t() {
+        public void execute() {
+            Disconnect();
+            Com.Quit();
+        }
+    };
+    static final xcommand_t Disconnect_f = new xcommand_t() {
+        public void execute() {
+            Com.Error(Defines.ERR_DROP, "Disconnected from server");
+        }
+    };
+    private static int extratime;
+
+    static {
+        cheatvars = new cheatvar_t[cheatvarsinfo.length];
+        for (int n = 0; n < cheatvarsinfo.length; n++) {
+            cheatvars[n] = new cheatvar_t();
+            cheatvars[n].name = cheatvarsinfo[n][0];
+            cheatvars[n].value = cheatvarsinfo[n][1];
+        }
+    }
+
+    //   ============================================================================
+
+    /**
+     * WriteDemoMessage
+     * <p>
+     * Dumps the current net message, prefixed by the length
+     */
+    static void WriteDemoMessage() {
+        int swlen;
+
+        // the first eight bytes are just packet sequencing stuff
+        swlen = Globals.net_message.cursize - 8;
+
+        try {
+            Globals.clientStaticT.demofile.writeInt(EndianHandler.swapInt(swlen));
+            Globals.clientStaticT.demofile.write(Globals.net_message.data, 8, swlen);
+        } catch (IOException ignored) {
+        }
+
+    }
+
+    /**
+     * SendConnectPacket
+     * <p>
+     * We have gotten a challenge from the server, so try and connect.
+     */
+    static void SendConnectPacket() {
+        NetadrT adr = new NetadrT();
+        int port;
+
+        if (!NET.StringToAdr(Globals.clientStaticT.servername, adr)) {
+            Com.Printf("Bad server address\n");
+            Globals.clientStaticT.connect_time = 0;
+            return;
+        }
+        if (adr.port == 0)
+            adr.port = Defines.PORT_SERVER;
+        //                     adr.port = BigShort(PORT_SERVER);
+
+        port = (int) Cvar.variableValue("qport");
+        Globals.userinfo_modified = false;
+
+        Netchan.OutOfBandPrint(Defines.NS_CLIENT, adr, "connect "
+                + Defines.PROTOCOL_VERSION + " " + port + " "
+                + Globals.clientStaticT.challenge + " \"" + Cvar.userinfo() + "\"\n");
+    }
+
+    /**
+     * CheckForResend
+     * <p>
+     * Resend a connect message if the last one has timed out.
+     */
+    static void CheckForResend() {
+        NetadrT adr = new NetadrT();
+
+        // if the local server is running and we aren't
+        // then connect
+        if (Globals.clientStaticT.state == Defines.ca_disconnected
+                && Globals.server_state != 0) {
+            Globals.clientStaticT.state = Defines.ca_connecting;
+            Globals.clientStaticT.servername = "localhost";
+            // we don't need a challenge on the localhost
+            SendConnectPacket();
+            return;
+        }
+
+        // resend if we haven't gotten a reply yet
+        if (Globals.clientStaticT.state != Defines.ca_connecting)
+            return;
+
+        if (Globals.clientStaticT.realtime - Globals.clientStaticT.connect_time < 3000)
+            return;
+
+        if (!NET.StringToAdr(Globals.clientStaticT.servername, adr)) {
+            Com.Printf("Bad server address\n");
+            Globals.clientStaticT.state = Defines.ca_disconnected;
+            return;
+        }
+        if (adr.port == 0)
+            adr.port = Defines.PORT_SERVER;
+
+        // for retransmit requests
+        Globals.clientStaticT.connect_time = Globals.clientStaticT.realtime;
+
+        Com.Printf("Connecting to " + Globals.clientStaticT.servername + "...\n");
+
+        Netchan.OutOfBandPrint(Defines.NS_CLIENT, adr, "getchallenge\n");
+    }
+
+    /**
+     * ClearState
+     */
+    static void ClearState() {
+        S.StopAllSounds();
+        CL_fx.ClearEffects();
+        CL_tent.ClearTEnts();
+
+        // wipe the entire cl structure
+
+        Globals.clientStateT = new ClientStateT();
+        for (int i = 0; i < Globals.cl_entities.length; i++) {
+            Globals.cl_entities[i] = new centity_t();
+        }
+
+        SZ.Clear(Globals.clientStaticT.netchan.message);
+    }
+
+    /**
+     * Disconnect
+     * <p>
+     * Goes from a connected state to full screen console state Sends a
+     * disconnect message to the server This is also called on Com_Error, so it
+     * shouldn't cause any errors.
+     */
+    static void Disconnect() {
+
+        String fin;
+
+        if (Globals.clientStaticT.state == Defines.ca_disconnected)
+            return;
+
+        if (Globals.cl_timedemo != null && Globals.cl_timedemo.value != 0.0f) {
+            int time;
+
+            time = Timer.getCurrentTimeMillis() - Globals.clientStateT.timedemo_start;
+            if (time > 0)
+                Com.Printf("%i frames, %3.1f seconds: %3.1f fps\n",
+                        new Vargs(3).add(Globals.clientStateT.timedemo_frames).add(
+                                time / 1000.0).add(
+                                Globals.clientStateT.timedemo_frames * 1000.0 / time));
+        }
+
+        Math3D.vectorClear(Globals.clientStateT.refdef.blend);
+
+        Globals.re.CinematicSetPalette(null);
+
+        Menu.ForceMenuOff();
+
+        Globals.clientStaticT.connect_time = 0;
+
+        SCR.StopCinematic();
+
+        if (Globals.clientStaticT.demorecording)
+            Stop_f.execute();
+
+        // send a disconnect message to the server
+        fin = (char) Defines.clc_stringcmd + "disconnect";
+        Netchan.Transmit(Globals.clientStaticT.netchan, fin.length(), Lib.stringToBytes(fin));
+        Netchan.Transmit(Globals.clientStaticT.netchan, fin.length(), Lib.stringToBytes(fin));
+        Netchan.Transmit(Globals.clientStaticT.netchan, fin.length(), Lib.stringToBytes(fin));
+
+        ClearState();
+
+        // stop download
+        if (Globals.clientStaticT.download != null) {
+            Lib.fclose(Globals.clientStaticT.download);
+            Globals.clientStaticT.download = null;
+        }
+
+        Globals.clientStaticT.state = Defines.ca_disconnected;
+    }
+
+    /**
+     * ParseStatusMessage
+     * <p>
+     * Handle a reply from a ping.
+     */
+    static void ParseStatusMessage() {
+        String s;
+
+        s = MSG.ReadString(Globals.net_message);
+
+        Com.Printf(s + "\n");
+        Menu.AddToServerList(s);
+    }
+
+    /**
+     * ConnectionlessPacket
+     * <p>
+     * Responses to broadcasts, etc
+     */
+    static void ConnectionlessPacket() {
+        String s;
+        String c;
+
+        MSG.BeginReading(Globals.net_message);
+        MSG.ReadLong(Globals.net_message); // skip the -1
+
+        s = MSG.ReadStringLine(Globals.net_message);
+
+        Cmd.TokenizeString(s.toCharArray(), false);
+
+        c = Cmd.Argv(0);
+
+        Com.Println(Globals.net_from.toString() + ": " + c);
+
+        // server connection
+        if (c.equals("client_connect")) {
+            if (Globals.clientStaticT.state == Defines.ca_connected) {
+                Com.Printf("Dup connect received.  Ignored.\n");
+                return;
+            }
+            Netchan.Setup(Defines.NS_CLIENT, Globals.clientStaticT.netchan,
+                    Globals.net_from, Globals.clientStaticT.quakePort);
+            MSG.WriteChar(Globals.clientStaticT.netchan.message, Defines.clc_stringcmd);
+            MSG.WriteString(Globals.clientStaticT.netchan.message, "new");
+            Globals.clientStaticT.state = Defines.ca_connected;
+            return;
+        }
+
+        // server responding to a status broadcast
+        if (c.equals("info")) {
+            ParseStatusMessage();
+            return;
+        }
+
+        // remote command from gui front end
+        if (c.equals("cmd")) {
+            if (!NET.IsLocalAddress(Globals.net_from)) {
+                Com.Printf("command packet from remote host.  Ignored.\n");
+                return;
+            }
+            s = MSG.ReadString(Globals.net_message);
+            CommandBuffer.AddText(s);
+            CommandBuffer.AddText("\n");
+            return;
+        }
+        // print command from somewhere
+        if (c.equals("print")) {
+            s = MSG.ReadString(Globals.net_message);
+            if (s.length() > 0)
+                Com.Printf(s);
+            return;
+        }
+
+        // ping from somewhere
+        if (c.equals("ping")) {
+            Netchan.OutOfBandPrint(Defines.NS_CLIENT, Globals.net_from, "ack");
+            return;
+        }
+
+        // challenge from the server we are connecting to
+        if (c.equals("challenge")) {
+            Globals.clientStaticT.challenge = Lib.atoi(Cmd.Argv(1));
+            SendConnectPacket();
+            return;
+        }
+
+        // echo request from server
+        if (c.equals("echo")) {
+            Netchan.OutOfBandPrint(Defines.NS_CLIENT, Globals.net_from, Cmd
+                    .Argv(1));
+            return;
+        }
+
+        Com.Printf("Unknown command.\n");
+    }
+
+    /**
+     * readPackets
+     */
+    static void readPackets() {
+        while (NET.GetPacket(Defines.NS_CLIENT, Globals.net_from,
+                Globals.net_message)) {
+
+            //
+            // remote command packet
+            //
+            if (Globals.net_message.data[0] == -1
+                    && Globals.net_message.data[1] == -1
+                    && Globals.net_message.data[2] == -1
+                    && Globals.net_message.data[3] == -1) {
+                //                     if (*(int *)net_message.data == -1)
+                ConnectionlessPacket();
+                continue;
+            }
+
+            if (Globals.clientStaticT.state == Defines.ca_disconnected
+                    || Globals.clientStaticT.state == Defines.ca_connecting)
+                continue; // dump it if not connected
+
+            if (Globals.net_message.cursize < 8) {
+                Com.Printf(NET.AdrToString(Globals.net_from)
+                        + ": Runt packet\n");
+                continue;
+            }
+
+            //
+            // packet from server
+            //
+            if (!NET.CompareAdr(Globals.net_from,
+                    Globals.clientStaticT.netchan.remote_address)) {
+                Com.DPrintf(NET.AdrToString(Globals.net_from)
+                        + ":sequenced packet without connection\n");
+                continue;
+            }
+            if (!Netchan.Process(Globals.clientStaticT.netchan, Globals.net_message))
+                continue; // wasn't accepted for some reason
+            CL_parse.ParseServerMessage();
+        }
+
+        //
+        // check timeout
+        //
+        if (Globals.clientStaticT.state >= Defines.ca_connected
+                && Globals.clientStaticT.realtime - Globals.clientStaticT.netchan.last_received > Globals.cl_timeout.value * 1000) {
+            if (++Globals.clientStateT.timeoutcount > 5) // timeoutcount saves debugger
+            {
+                Com.Printf("\nServer connection timed out.\n");
+                Disconnect();
+            }
+        } else
+            Globals.clientStateT.timeoutcount = 0;
+    }
+
+    /**
+     * FixUpGender_f
+     */
+    static void FixUpGender() {
+
+        String sk;
+
+        if (Globals.gender_auto.value != 0.0f) {
+
+            if (Globals.gender.modified) {
+                // was set directly, don't override the user
+                Globals.gender.modified = false;
+                return;
+            }
+
+            sk = Globals.skin.string;
+            if (sk.startsWith("male") || sk.startsWith("cyborg"))
+                Cvar.set("gender", "male");
+            else if (sk.startsWith("female") || sk.startsWith("crackhor"))
+                Cvar.set("gender", "female");
+            else
+                Cvar.set("gender", "none");
+            Globals.gender.modified = false;
+        }
+    }
+
+    //   =============================================================================
+
+    public static void RequestNextDownload() {
+        int map_checksum = 0; // for detecting cheater maps
+        //char fn[MAX_OSPATH];
+        String fn;
+
+        qfiles.dmdl_t pheader;
+
+        if (Globals.clientStaticT.state != Defines.ca_connected)
+            return;
+
+        if (Server.allow_download.value == 0 && Client.precache_check < ENV_CNT)
+            Client.precache_check = ENV_CNT;
+
+        //       ZOID
+        if (Client.precache_check == Defines.CS_MODELS) { // confirm map
+            Client.precache_check = Defines.CS_MODELS + 2; // 0 isn't used
+            if (Server.allow_download_maps.value != 0)
+                if (!CL_parse
+                        .CheckOrDownloadFile(Globals.clientStateT.configstrings[Defines.CS_MODELS + 1]))
+                    return; // started a download
+        }
+        if (Client.precache_check >= Defines.CS_MODELS
+                && Client.precache_check < Defines.CS_MODELS + Defines.MAX_MODELS) {
+            if (Server.allow_download_models.value != 0) {
+                while (Client.precache_check < Defines.CS_MODELS
+                        + Defines.MAX_MODELS
+                        && Globals.clientStateT.configstrings[Client.precache_check].length() > 0) {
+                    if (Globals.clientStateT.configstrings[Client.precache_check].charAt(0) == '*'
+                            || Globals.clientStateT.configstrings[Client.precache_check]
+                            .charAt(0) == '#') {
+                        Client.precache_check++;
+                        continue;
+                    }
+                    if (Client.precache_model_skin == 0) {
+                        if (!CL_parse
+                                .CheckOrDownloadFile(Globals.clientStateT.configstrings[Client.precache_check])) {
+                            Client.precache_model_skin = 1;
+                            return; // started a download
+                        }
+                        Client.precache_model_skin = 1;
+                    }
+
+                    // checking for skins in the model
+                    if (Client.precache_model == null) {
+
+                        Client.precache_model = FS
+                                .LoadFile(Globals.clientStateT.configstrings[Client.precache_check]);
+                        if (Client.precache_model == null) {
+                            Client.precache_model_skin = 0;
+                            Client.precache_check++;
+                            continue; // couldn't load it
+                        }
+                        ByteBuffer bb = ByteBuffer.wrap(Client.precache_model);
+                        bb.order(ByteOrder.LITTLE_ENDIAN);
+
+                        int header = bb.getInt();
+
+                        if (header != qfiles.IDALIASHEADER) {
+                            // not an alias model
+                            FS.FreeFile();
+                            Client.precache_model = null;
+                            Client.precache_model_skin = 0;
+                            Client.precache_check++;
+                            continue;
+                        }
+                        pheader = new qfiles.dmdl_t(ByteBuffer.wrap(
+                                Client.precache_model).order(
+                                ByteOrder.LITTLE_ENDIAN));
+                        if (pheader.version != Defines.ALIAS_VERSION) {
+                            Client.precache_check++;
+                            Client.precache_model_skin = 0;
+                            continue; // couldn't load it
+                        }
+                    }
+
+                    pheader = new qfiles.dmdl_t(ByteBuffer.wrap(
+                            Client.precache_model).order(ByteOrder.LITTLE_ENDIAN));
+
+                    int num_skins = pheader.num_skins;
+
+                    while (Client.precache_model_skin - 1 < num_skins) {
+                        //Com.Printf("critical code section because of endian
+                        // mess!\n");
+
+                        String name = Lib.CtoJava(Client.precache_model,
+                                pheader.ofs_skins
+                                        + (Client.precache_model_skin - 1)
+                                        * Defines.MAX_SKINNAME,
+                                Defines.MAX_SKINNAME * num_skins);
+
+                        if (!CL_parse.CheckOrDownloadFile(name)) {
+                            Client.precache_model_skin++;
+                            return; // started a download
+                        }
+                        Client.precache_model_skin++;
+                    }
+                    if (Client.precache_model != null) {
+                        FS.FreeFile();
+                        Client.precache_model = null;
+                    }
+                    Client.precache_model_skin = 0;
+                    Client.precache_check++;
+                }
+            }
+            Client.precache_check = Defines.CS_SOUNDS;
+        }
+        if (Client.precache_check >= Defines.CS_SOUNDS
+                && Client.precache_check < Defines.CS_SOUNDS + Defines.MAX_SOUNDS) {
+            if (Server.allow_download_sounds.value != 0) {
+                if (Client.precache_check == Defines.CS_SOUNDS)
+                    Client.precache_check++; // zero is blank
+                while (Client.precache_check < Defines.CS_SOUNDS
+                        + Defines.MAX_SOUNDS
+                        && Globals.clientStateT.configstrings[Client.precache_check].length() > 0) {
+                    if (Globals.clientStateT.configstrings[Client.precache_check].charAt(0) == '*') {
+                        Client.precache_check++;
+                        continue;
+                    }
+                    fn = "sound/"
+                            + Globals.clientStateT.configstrings[Client.precache_check++];
+                    if (!CL_parse.CheckOrDownloadFile(fn))
+                        return; // started a download
+                }
+            }
+            Client.precache_check = Defines.CS_IMAGES;
+        }
+        if (Client.precache_check >= Defines.CS_IMAGES
+                && Client.precache_check < Defines.CS_IMAGES + Defines.MAX_IMAGES) {
+            if (Client.precache_check == Defines.CS_IMAGES)
+                Client.precache_check++; // zero is blank
+
+            while (Client.precache_check < Defines.CS_IMAGES + Defines.MAX_IMAGES
+                    && Globals.clientStateT.configstrings[Client.precache_check].length() > 0) {
+                fn = "pics/" + Globals.clientStateT.configstrings[Client.precache_check++]
+                        + ".pcx";
+                if (!CL_parse.CheckOrDownloadFile(fn))
+                    return; // started a download
+            }
+            Client.precache_check = Defines.CS_PLAYERSKINS;
+        }
+        // skins are special, since a player has three things to download:
+        // model, weapon model and skin
+        // so precache_check is now *3
+        if (Client.precache_check >= Defines.CS_PLAYERSKINS
+                && Client.precache_check < Defines.CS_PLAYERSKINS
+                + Defines.MAX_CLIENTS * Client.PLAYER_MULT) {
+            // precache phase completed
+            Client.precache_check = ENV_CNT;
+        }
+
+        if (Client.precache_check == ENV_CNT) {
+            Client.precache_check = ENV_CNT + 1;
+
+            int iw[] = {map_checksum};
+
+            CM.CM_LoadMap(Globals.clientStateT.configstrings[Defines.CS_MODELS + 1],
+                    true, iw);
+            map_checksum = iw[0];
+
+            if ((map_checksum ^ Lib
+                    .atoi(Globals.clientStateT.configstrings[Defines.CS_MAPCHECKSUM])) != 0) {
+                Com
+                        .Error(
+                                Defines.ERR_DROP,
+                                "Local map version differs from server: "
+                                        + map_checksum
+                                        + " != '"
+                                        + Globals.clientStateT.configstrings[Defines.CS_MAPCHECKSUM]
+                                        + "'\n");
+                return;
+            }
+        }
+
+        if (Client.precache_check > ENV_CNT && Client.precache_check < TEXTURE_CNT) {
+            if (Server.allow_download.value != 0
+                    && Server.allow_download_maps.value != 0) {
+                while (Client.precache_check < TEXTURE_CNT) {
+                    int n = Client.precache_check++ - ENV_CNT - 1;
+
+                    if ((n & 1) != 0)
+                        fn = "env/" + Globals.clientStateT.configstrings[Defines.CS_SKY]
+                                + env_suf[n / 2] + ".pcx";
+                    else
+                        fn = "env/" + Globals.clientStateT.configstrings[Defines.CS_SKY]
+                                + env_suf[n / 2] + ".tga";
+                    if (!CL_parse.CheckOrDownloadFile(fn))
+                        return; // started a download
+                }
+            }
+            Client.precache_check = TEXTURE_CNT;
+        }
+
+        if (Client.precache_check == TEXTURE_CNT) {
+            Client.precache_check = TEXTURE_CNT + 1;
+            Client.precache_tex = 0;
+        }
+
+        // confirm existance of textures, download any that don't exist
+        if (Client.precache_check == TEXTURE_CNT + 1) {
+            // from qcommon/cmodel.c
+            // extern int numtexinfo;
+            // extern mapsurface_t map_surfaces[];
+
+            if (Server.allow_download.value != 0
+                    && Server.allow_download_maps.value != 0) {
+                while (Client.precache_tex < CM.numtexinfo) {
+                    //char fn[MAX_OSPATH];
+
+                    fn = "textures/" + CM.map_surfaces[Client.precache_tex++].rname
+                            + ".wal";
+                    if (!CL_parse.CheckOrDownloadFile(fn))
+                        return; // started a download
+                }
+            }
+            Client.precache_check = TEXTURE_CNT + 999;
+        }
+
+        //       ZOID
+        CL_parse.RegisterSounds();
+        CL_view.PrepRefresh();
+
+        MSG.WriteByte(Globals.clientStaticT.netchan.message, Defines.clc_stringcmd);
+        MSG.WriteString(Globals.clientStaticT.netchan.message, "begin "
+                + Client.precache_spawncount + "\n");
+    }
+
+    /**
+     * InitLocal
+     */
+    public static void InitLocal() {
+        Globals.clientStaticT.state = Defines.ca_disconnected;
+        Globals.clientStaticT.realtime = Timer.getCurrentTimeMillis();
+
+        CL_input.InitInput();
+
+        Cvar.get("adr0", "", Defines.CVAR_ARCHIVE);
+        Cvar.get("adr1", "", Defines.CVAR_ARCHIVE);
+        Cvar.get("adr2", "", Defines.CVAR_ARCHIVE);
+        Cvar.get("adr3", "", Defines.CVAR_ARCHIVE);
+        Cvar.get("adr4", "", Defines.CVAR_ARCHIVE);
+        Cvar.get("adr5", "", Defines.CVAR_ARCHIVE);
+        Cvar.get("adr6", "", Defines.CVAR_ARCHIVE);
+        Cvar.get("adr7", "", Defines.CVAR_ARCHIVE);
+        Cvar.get("adr8", "", Defines.CVAR_ARCHIVE);
+
+        //
+        // register our variables
+        //
+        Globals.cl_stereo_separation = Cvar.get("cl_stereo_separation", "0.4",
+                Defines.CVAR_ARCHIVE);
+        Globals.cl_stereo = Cvar.get("cl_stereo", "0", 0);
+
+        Globals.cl_add_blend = Cvar.get("cl_blend", "1", 0);
+        Globals.cl_add_lights = Cvar.get("cl_lights", "1", 0);
+        Globals.cl_add_particles = Cvar.get("cl_particles", "1", 0);
+        Globals.cl_add_entities = Cvar.get("cl_entities", "1", 0);
+        Globals.cl_gun = Cvar.get("cl_gun", "1", 0);
+        Globals.cl_footsteps = Cvar.get("cl_footsteps", "1", 0);
+        Globals.cl_noskins = Cvar.get("cl_noskins", "0", 0);
+        Globals.cl_autoskins = Cvar.get("cl_autoskins", "0", 0);
+        Globals.cl_predict = Cvar.get("cl_predict", "1", 0);
+
+        Globals.cl_maxfps = Cvar.get("cl_maxfps", "90", 0);
+
+        Globals.cl_upspeed = Cvar.get("cl_upspeed", "200", 0);
+        Globals.cl_forwardspeed = Cvar.get("cl_forwardspeed", "200", 0);
+        Globals.cl_sidespeed = Cvar.get("cl_sidespeed", "200", 0);
+        Globals.cl_yawspeed = Cvar.get("cl_yawspeed", "140", 0);
+        Globals.cl_pitchspeed = Cvar.get("cl_pitchspeed", "150", 0);
+        Globals.cl_anglespeedkey = Cvar.get("cl_anglespeedkey", "1.5", 0);
+
+        Globals.cl_run = Cvar.get("cl_run", "0", Defines.CVAR_ARCHIVE);
+        Globals.lookspring = Cvar.get("lookspring", "0", Defines.CVAR_ARCHIVE);
+        Globals.lookstrafe = Cvar.get("lookstrafe", "0", Defines.CVAR_ARCHIVE);
+        Globals.sensitivity = Cvar
+                .get("sensitivity", "3", Defines.CVAR_ARCHIVE);
+
+        Globals.m_pitch = Cvar.get("m_pitch", "0.022", Defines.CVAR_ARCHIVE);
+        Globals.m_yaw = Cvar.get("m_yaw", "0.022", 0);
+        Globals.m_forward = Cvar.get("m_forward", "1", 0);
+        Globals.m_side = Cvar.get("m_side", "1", 0);
+
+        Globals.cl_shownet = Cvar.get("cl_shownet", "0", 0);
+        Globals.cl_showmiss = Cvar.get("cl_showmiss", "0", 0);
+        Globals.cl_showclamp = Cvar.get("showclamp", "0", 0);
+        Globals.cl_timeout = Cvar.get("cl_timeout", "120", 0);
+        Globals.cl_paused = Cvar.get("paused", "0", 0);
+        Globals.cl_timedemo = Cvar.get("timedemo", "0", 0);
+
+        Globals.rcon_client_password = Cvar.get("rcon_password", "", 0);
+        Globals.rcon_address = Cvar.get("rcon_address", "", 0);
+
+        Globals.cl_lightlevel = Cvar.get("r_lightlevel", "0", 0);
+
+        //
+        // userinfo
+        //
+        Globals.info_password = Cvar.get("password", "", Defines.CVAR_USERINFO);
+        Globals.info_spectator = Cvar.get("spectator", "0",
+                Defines.CVAR_USERINFO);
+        Globals.name = Cvar.get("name", "unnamed", Defines.CVAR_USERINFO
+                | Defines.CVAR_ARCHIVE);
+        Globals.skin = Cvar.get("skin", "male/grunt", Defines.CVAR_USERINFO
+                | Defines.CVAR_ARCHIVE);
+        Globals.rate = Cvar.get("rate", "25000", Defines.CVAR_USERINFO
+                | Defines.CVAR_ARCHIVE); // FIXME
+        Globals.msg = Cvar.get("msg", "1", Defines.CVAR_USERINFO
+                | Defines.CVAR_ARCHIVE);
+        Globals.hand = Cvar.get("hand", "0", Defines.CVAR_USERINFO
+                | Defines.CVAR_ARCHIVE);
+        Globals.fov = Cvar.get("fov", "90", Defines.CVAR_USERINFO
+                | Defines.CVAR_ARCHIVE);
+        Globals.gender = Cvar.get("gender", "male", Defines.CVAR_USERINFO
+                | Defines.CVAR_ARCHIVE);
+        Globals.gender_auto = Cvar
+                .get("gender_auto", "1", Defines.CVAR_ARCHIVE);
+        Globals.gender.modified = false; // clear this so we know when user sets
+        // it manually
+
+        Globals.cl_vwep = Cvar.get("cl_vwep", "1", Defines.CVAR_ARCHIVE);
+
+        //
+        // register our commands
+        //
+        Cmd.AddCommand("cmd", ForwardToServer_f);
+        Cmd.AddCommand("pause", Pause_f);
+        Cmd.AddCommand("pingservers", PingServers_f);
+        Cmd.AddCommand("skins", Skins_f);
+
+        Cmd.AddCommand("userinfo", Userinfo_f);
+        Cmd.AddCommand("snd_restart", Snd_Restart_f);
+
+        Cmd.AddCommand("changing", Changing_f);
+        Cmd.AddCommand("disconnect", Disconnect_f);
+        Cmd.AddCommand("record", Record_f);
+        Cmd.AddCommand("stop", Stop_f);
+
+        Cmd.AddCommand("quit", Quit_f);
+
+        Cmd.AddCommand("connect", Connect_f);
+        Cmd.AddCommand("reconnect", Reconnect_f);
+
+        Cmd.AddCommand("rcon", Rcon_f);
+
+        Cmd.AddCommand("precache", Precache_f);
+
+        Cmd.AddCommand("download", CL_parse.Download_f);
+
+        //
+        // forward to server commands
+        //
+        // the only thing this does is allow command completion
+        // to work -- all unknown commands are automatically
+        // forwarded to the server
+        Cmd.AddCommand("wave", null);
+        Cmd.AddCommand("inven", null);
+        Cmd.AddCommand("kill", null);
+        Cmd.AddCommand("use", null);
+        Cmd.AddCommand("drop", null);
+        Cmd.AddCommand("say", null);
+        Cmd.AddCommand("say_team", null);
+        Cmd.AddCommand("info", null);
+        Cmd.AddCommand("prog", null);
+        Cmd.AddCommand("give", null);
+        Cmd.AddCommand("god", null);
+        Cmd.AddCommand("notarget", null);
+        Cmd.AddCommand("noclip", null);
+        Cmd.AddCommand("invuse", null);
+        Cmd.AddCommand("invprev", null);
+        Cmd.AddCommand("invnext", null);
+        Cmd.AddCommand("invdrop", null);
+        Cmd.AddCommand("weapnext", null);
+        Cmd.AddCommand("weapprev", null);
+
+    }
+
+    /**
+     * WriteConfiguration
+     * <p>
+     * Writes key bindings and archived cvars to config.cfg.
+     */
+    public static void WriteConfiguration() {
+        RandomAccessFile f;
+        String path;
+
+//        if (Globals.cls.state == Defines.ca_uninitialized)
+//            return;
+
+        path = FS.Gamedir() + "/config.cfg";
+        f = Lib.fopen(path, "rw");
+        if (f == null) {
+            Com.Printf("Couldn't write config.cfg.\n");
+            return;
+        }
+        try {
+            f.seek(0);
+            f.setLength(0);
+        } catch (IOException ignored) {
+        }
+        try {
+            f.writeBytes("// generated by quake, do not modify\n");
+        } catch (IOException ignored) {
+        }
+
+        Key.WriteBindings(f);
+        Lib.fclose(f);
+        Cvar.writeVariables(path);
+    }
+
+    /**
+     * FixCvarCheats
+     */
+    public static void FixCvarCheats() {
+        int i;
+        Client.cheatvar_t var;
+
+        if ("1".equals(Globals.clientStateT.configstrings[Defines.CS_MAXCLIENTS])
+                || 0 == Globals.clientStateT.configstrings[Defines.CS_MAXCLIENTS]
+                .length())
+            return; // single player can cheat
+
+        // find all the cvars if we haven't done it yet
+        if (0 == Client.numcheatvars) {
+            while (Client.cheatvars[Client.numcheatvars].name != null) {
+                Client.cheatvars[Client.numcheatvars].var = Cvar.get(
+                        Client.cheatvars[Client.numcheatvars].name,
+                        Client.cheatvars[Client.numcheatvars].value, 0);
+                Client.numcheatvars++;
+            }
+        }
+
+        // make sure they are all set to the proper values
+        for (i = 0; i < Client.numcheatvars; i++) {
+            var = Client.cheatvars[i];
+            if (!var.var.string.equals(var.value)) {
+                Cvar.set(var.name, var.value);
+            }
+        }
+    }
+
+    /**
+     * sendCommand
+     */
+    public static void sendCommand() {
+        // get new key events
+        Sys.SendKeyEvents();
+
+        // allow mice or other external controllers to add commands
+        UserInputHandler.Commands();
+
+        // process console commands
+        CommandBuffer.execute();
+
+        // fix any cheating cvars
+        FixCvarCheats();
+
+        // send intentions now
+        CL_input.SendCmd();
+
+        // resend a connection request if necessary
+        CheckForResend();
+    }
+
+    //   =============================================================
+
+    public static void doFrame(int msec) {
+
+        if (Globals.dedicated.value != 0)
+            return;
+
+        extratime += msec;
+
+        if (Globals.cl_timedemo.value == 0.0f) {
+            if (Globals.clientStaticT.state == Defines.ca_connected && extratime < 100) {
+                return; // don't flood packets out while connecting
+            }
+            if (extratime < 1000 / Globals.cl_maxfps.value) {
+                return; // framerate is too high
+            }
+        }
+
+        // let the mouse activate or deactivate
+        UserInputHandler.doFrame();
+
+        // decide the simulation time
+        Globals.clientStaticT.frametime = extratime / 1000.0f;
+        Globals.clientStateT.time += extratime;
+        Globals.clientStaticT.realtime = Globals.curtime;
+
+        extratime = 0;
+
+        if (Globals.clientStaticT.frametime > (1.0f / 5))
+            Globals.clientStaticT.frametime = (1.0f / 5);
+
+        // if in the debugger last frame, don't timeout
+        if (msec > 5000)
+            Globals.clientStaticT.netchan.last_received = Timer.getCurrentTimeMillis();
+
+        // fetch results from server
+        readPackets();
+
+        // send a new command message to the server
+        sendCommand();
+
+        // predict all unacknowledged movements
+        CL_pred.predictMovement();
+
+        // allow rendering DLL change
+        VideoDriver.CheckChanges();
+        if (!Globals.clientStateT.refresh_prepped
+                && Globals.clientStaticT.state == Defines.ca_active) {
+            CL_view.PrepRefresh();
+            // force GC after level loading
+            // but not on playing a cinematic
+            if (Globals.clientStateT.cinematictime == 0) System.gc();
+        }
+
+        SCR.UpdateScreen();
+
+        // update audio
+        S.Update(Globals.clientStateT.refdef.vieworg, Globals.clientStateT.v_forward,
+                Globals.clientStateT.v_right, Globals.clientStateT.v_up);
+
+        // advance local effects for next frame
+        CL_fx.RunDLights();
+        CL_fx.RunLightStyles();
+        SCR.RunCinematic();
+        SCR.RunConsole();
+
+        Globals.clientStaticT.framecount++;
+        if (Globals.clientStaticT.state != Defines.ca_active
+                || Globals.clientStaticT.key_dest != Defines.key_game) {
+            try {
+                Thread.sleep(20);
+            } catch (InterruptedException ignored) {
+            }
+        }
+    }
+
+    // private static int lasttimecalled;
+
+    /**
+     * Shutdown
+     */
+    public static void Shutdown() {
+
+        if (isdown) {
+            System.out.print("recursive shutdown\n");
+            return;
+        }
+        isdown = true;
+
+        WriteConfiguration();
+
+        S.Shutdown();
+        UserInputHandler.Shutdown();
+        VideoDriver.Shutdown();
+    }
+
+    /**
+     * Initialize client subsystem.
+     */
+    public static void Init() {
+        if (Globals.dedicated.value != 0.0f)
+            return; // nothing running on the client
+
+        // all archived variables will now be loaded
+
+        Console.Init(); //ok
+
+        S.Init(); //empty
+        VideoDriver.Init();
+
+        V.Init();
+
+        Globals.net_message.data = Globals.net_message_buffer;
+        Globals.net_message.maxsize = Globals.net_message_buffer.length;
+
+        Menu.Init();
+
+        SCR.Init();
+        //Globals.cls.disable_screen = 1.0f; // don't draw yet
+
+        InitLocal();
+        UserInputHandler.Init();
+
+        FS.ExecAutoexec();
+        CommandBuffer.execute();
+    }
+
+    /**
+     * Called after an ERR_DROP was thrown.
+     */
+    public static void Drop() {
+        if (Globals.clientStaticT.state == Defines.ca_uninitialized)
+            return;
+        if (Globals.clientStaticT.state == Defines.ca_disconnected)
+            return;
+
+        Disconnect();
+
+        // drop loading plaque unless this is the initial game start
+        if (Globals.clientStaticT.disable_servercount != -1)
+            SCR.EndLoadingPlaque(); // get rid of loading plaque
+    }
+
+    public static class cheatvar_t {
+        String name;
+
+        String value;
+
+        CvarT var;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/client/ClientInfo.java b/src/main/java/lwjake2/client/ClientInfo.java
new file mode 100644 (file)
index 0000000..cbe3afa
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.render.Image;
+import lwjake2.render.Model;
+
+public class ClientInfo {
+    String name = "";
+    String cinfo = "";
+    Image skin;    // ptr
+    Image icon;    // ptr
+    String iconname = "";
+    Model model;    // ptr
+
+//     public void reset()
+//     {
+//             set(new clientinfo_t());
+//     }
+
+    public void set(ClientInfo from) {
+        name = from.name;
+        cinfo = from.cinfo;
+        skin = from.skin;
+        icon = from.icon;
+        iconname = from.iconname;
+        model = from.model;
+    }
+}
diff --git a/src/main/java/lwjake2/client/ClientStateT.java b/src/main/java/lwjake2/client/ClientStateT.java
new file mode 100644 (file)
index 0000000..c9b5163
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.Defines;
+import lwjake2.game.cmodel_t;
+import lwjake2.game.usercmd_t;
+import lwjake2.render.Image;
+import lwjake2.render.Model;
+import lwjake2.sound.sfx_t;
+
+import java.nio.ByteBuffer;
+
+public class ClientStateT {
+
+    public final frame_t frame = new frame_t(); // received from server
+    // the client maintains its own idea of view angles, which are
+    // sent to the server each frame.  It is cleared to 0 upon entering each level.
+    // the server sends a delta each frame which is added to the locally
+    // tracked view angles to account for standing on rotating objects,
+    // and teleport direction changes
+    public final float[] viewangles = {0, 0, 0};
+    public final String[] configstrings = new String[Defines.MAX_CONFIGSTRINGS];
+    public final sfx_t[] sound_precache = new sfx_t[Defines.MAX_SOUNDS];
+    final usercmd_t cmd = new usercmd_t();
+    final usercmd_t[] cmds = new usercmd_t[Defines.CMD_BACKUP]; // each mesage will send several old cmds
+    final int[] cmd_time = new int[Defines.CMD_BACKUP]; // time sent, for calculating pings
+    final short[][] predicted_origins = new short[Defines.CMD_BACKUP][3]; // for debug comparing against server
+    final float[] predicted_origin = {0, 0, 0}; // generated by CL_PredictMovement
+    final float[] predicted_angles = {0, 0, 0};
+    final float[] prediction_error = {0, 0, 0};
+    final frame_t[] frames = new frame_t[Defines.UPDATE_BACKUP];
+    final refdef_t refdef = new refdef_t();
+    final float[] v_forward = {0, 0, 0};
+    //
+    // transient data from server
+    //
+    final float[] v_right = {0, 0, 0};
+    final float[] v_up = {0, 0, 0}; // set when refdef.angles is set
+    final int[] inventory = new int[Defines.MAX_ITEMS];
+    final byte[] cinematicpalette = new byte[768];
+    //
+    // locally derived information from server state
+    //
+    final Model[] model_draw = new Model[Defines.MAX_MODELS];
+    final cmodel_t[] model_clip = new cmodel_t[Defines.MAX_MODELS];
+    final Image[] image_precache = new Image[Defines.MAX_IMAGES];
+    final ClientInfo[] clientinfo = new ClientInfo[Defines.MAX_CLIENTS];
+    final ClientInfo baseclientinfo = new ClientInfo();
+    public boolean refresh_prepped; // false if on new level or new ref dll
+    public boolean sound_prepped; // ambient sounds can start
+    public int time; // this is the time value that the client
+    public int playernum;
+    //
+    //    the ClientStateT structure is wiped completely at every
+    //    server map change
+    //
+    int timeoutcount;
+    int timedemo_frames;
+    int timedemo_start;
+    boolean force_refdef; // vid has changed, so we can't use a paused refdef
+    int parse_entities; // index (not anded off) into cl_parse_entities[]
+    float predicted_step; // for stair up smoothing
+    int predicted_step_time;
+    int surpressCount; // number of messages rate supressed
+    // is rendering at.  always <= cls.realtime
+    float lerpfrac; // between oldframe and frame
+    String layout = ""; // general 2D overlay
+    //
+    // non-gameserver infornamtion
+    // FIXME: move this cinematic stuff into the cin_t structure
+    ByteBuffer cinematic_file;
+    int cinematictime; // cls.realtime for first cinematic frame
+    int cinematicframe;
+    boolean cinematicpalette_active;
+    //
+    // server state information
+    //
+    boolean attractloop; // running the attract loop, any key will menu
+    int servercount; // server identification for prespawns
+    String gamedir = "";
+
+    public ClientStateT() {
+        for (int n = 0; n < Defines.CMD_BACKUP; n++)
+            cmds[n] = new usercmd_t();
+        for (int i = 0; i < frames.length; i++) {
+            frames[i] = new frame_t();
+        }
+
+        for (int n = 0; n < Defines.MAX_CONFIGSTRINGS; n++)
+            configstrings[n] = "";
+
+        for (int n = 0; n < Defines.MAX_CLIENTS; n++)
+            clientinfo[n] = new ClientInfo();
+    }
+
+}
diff --git a/src/main/java/lwjake2/client/ClientStaticT.java b/src/main/java/lwjake2/client/ClientStaticT.java
new file mode 100644 (file)
index 0000000..0187e13
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.qcommon.netchan_t;
+
+import java.io.RandomAccessFile;
+
+public class ClientStaticT {
+
+    // to work around address translating routers
+    public final netchan_t netchan = new netchan_t();
+    // was enum connstate_t
+    public int state;
+    // was enum keydest_t
+    public int key_dest;
+    public int framecount;
+    public int realtime; // always increasing, no clamping, etc
+    public float frametime; // seconds since last frame
+    //    screen rendering information
+    public float disable_screen; // showing loading plaque between levels
+    // > cls.disable_servercount, clear disable_screen
+    // or changing rendering dlls
+    // if time gets > 30 seconds ahead, break it
+    public int disable_servercount; // when we receive a frame and cl.servercount
+    //    connection information
+    public String servername = ""; // name of server from original connect
+    public float connect_time; // for connection retransmits
+    public int serverProtocol; // in case we are doing some kind of version hack
+    public int challenge; // from the server to use for connecting
+    public RandomAccessFile download; // file transfer from server
+    public String downloadtempname = "";
+    public String downloadname = "";
+    public int downloadnumber;
+    // was enum dltype_t
+    public int downloadtype;
+    public int downloadpercent;
+    //    demo recording info must be here, so it isn't cleared on level change
+    public boolean demorecording;
+    public boolean demowaiting; // don't record until a non-delta message is received
+    public RandomAccessFile demofile;
+    int quakePort; // a 16 bit value that allows quake servers
+}
diff --git a/src/main/java/lwjake2/client/Console.java b/src/main/java/lwjake2/client/Console.java
new file mode 100644 (file)
index 0000000..12fc6fe
--- /dev/null
@@ -0,0 +1,592 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.Cmd;
+import lwjake2.qcommon.*;
+import lwjake2.util.Lib;
+import lwjake2.util.Vargs;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.Arrays;
+
+/**
+ * Console
+ */
+public final class Console extends Globals {
+
+    public static final xcommand_t ToggleConsole_f = new xcommand_t() {
+        public void execute() {
+            SCR.EndLoadingPlaque(); // get rid of loading plaque
+
+            if (Globals.clientStateT.attractloop) {
+                CommandBuffer.AddText("killserver\n");
+                return;
+            }
+
+            if (Globals.clientStaticT.state == Defines.ca_disconnected) {
+                // start the demo loop again
+                CommandBuffer.AddText("d1\n");
+                return;
+            }
+
+            Key.ClearTyping();
+            Console.ClearNotify();
+
+            if (Globals.clientStaticT.key_dest == Defines.key_console) {
+                Menu.ForceMenuOff();
+                Cvar.set("paused", "0");
+            } else {
+                Menu.ForceMenuOff();
+                Globals.clientStaticT.key_dest = Defines.key_console;
+
+                if (Cvar.variableValue("maxclients") == 1
+                        && Globals.server_state != 0)
+                    Cvar.set("paused", "1");
+            }
+        }
+    };
+
+    public static final xcommand_t Clear_f = new xcommand_t() {
+        public void execute() {
+            Arrays.fill(Globals.con.text, (byte) ' ');
+        }
+    };
+    /*
+     * ================ Con_ToggleChat_f ================
+     */
+    static final xcommand_t ToggleChat_f = new xcommand_t() {
+        public void execute() {
+            Key.ClearTyping();
+
+            if (clientStaticT.key_dest == key_console) {
+                if (clientStaticT.state == ca_active) {
+                    Menu.ForceMenuOff();
+                    clientStaticT.key_dest = key_game;
+                }
+            } else
+                clientStaticT.key_dest = key_console;
+
+            ClearNotify();
+        }
+    };
+    /*
+     * ================ Con_MessageMode_f ================
+     */
+    static final xcommand_t MessageMode_f = new xcommand_t() {
+        public void execute() {
+            chat_team = false;
+            clientStaticT.key_dest = key_message;
+        }
+    };
+    /*
+     * ================ Con_MessageMode2_f ================
+     */
+    static final xcommand_t MessageMode2_f = new xcommand_t() {
+        public void execute() {
+            chat_team = true;
+            clientStaticT.key_dest = key_message;
+        }
+    };
+    /*
+     * ================ Con_Print
+     *
+     * Handles cursor positioning, line wrapping, etc All console printing must
+     * go through this in order to be logged to disk If no console is visible,
+     * the text will appear at the top of the game window ================
+     */
+    private static int cr;
+    public static final xcommand_t Dump_f = new xcommand_t() {
+        public void execute() {
+
+            int l, x;
+            int line;
+            RandomAccessFile f;
+            byte[] buffer = new byte[1024];
+            String name;
+
+            if (Cmd.Argc() != 2) {
+                Com.Printf("usage: condump <filename>\n");
+                return;
+            }
+
+            //Com_sprintf (name, sizeof(name), "%s/%s.txt", FS_Gamedir(),
+            // Cmd_Argv(1));
+            name = FS.Gamedir() + "/" + Cmd.Argv(1) + ".txt";
+
+            Com.Printf("Dumped console text to " + name + ".\n");
+            FS.CreatePath(name);
+            f = Lib.fopen(name, "rw");
+            if (f == null) {
+                Com.Printf("ERROR: couldn't open.\n");
+                return;
+            }
+
+            // skip empty lines
+            for (l = con.current - con.totallines + 1; l <= con.current; l++) {
+                line = (l % con.totallines) * con.linewidth;
+                for (x = 0; x < con.linewidth; x++)
+                    if (con.text[line + x] != ' ')
+                        break;
+                if (x != con.linewidth)
+                    break;
+            }
+
+            // write the remaining lines
+            buffer[con.linewidth] = 0;
+            for (; l <= con.current; l++) {
+                line = (l % con.totallines) * con.linewidth;
+                //strncpy (buffer, line, con.linewidth);
+                System.arraycopy(con.text, line, buffer, 0, con.linewidth);
+                for (x = con.linewidth - 1; x >= 0; x--) {
+                    if (buffer[x] == ' ')
+                        buffer[x] = 0;
+                    else
+                        break;
+                }
+                for (x = 0; buffer[x] != 0; x++)
+                    buffer[x] &= 0x7f;
+
+                buffer[x] = '\n';
+                // fprintf (f, "%s\n", buffer);
+                try {
+                    f.write(buffer, 0, x + 1);
+                } catch (IOException ignored) {
+                }
+            }
+
+            Lib.fclose(f);
+
+        }
+    };
+
+    /**
+     *
+     */
+    public static void Init() {
+        Globals.con.linewidth = -1;
+
+        CheckResize();
+
+        Com.Printf("Console initialized.\n");
+
+        //
+        // register our commands
+        //
+        Globals.con_notifytime = Cvar.get("con_notifytime", "3", 0);
+
+        Cmd.AddCommand("toggleconsole", ToggleConsole_f);
+        Cmd.AddCommand("togglechat", ToggleChat_f);
+        Cmd.AddCommand("messagemode", MessageMode_f);
+        Cmd.AddCommand("messagemode2", MessageMode2_f);
+        Cmd.AddCommand("clear", Clear_f);
+        Cmd.AddCommand("condump", Dump_f);
+        Globals.con.initialized = true;
+    }
+
+    /**
+     * If the line width has changed, reformat the buffer.
+     */
+    public static void CheckResize() {
+
+        int width = (Globals.viddef.width >> 3) - 2;
+        if (width > Defines.MAXCMDLINE) width = Defines.MAXCMDLINE;
+
+        if (width == Globals.con.linewidth)
+            return;
+
+        if (width < 1) { // video hasn't been initialized yet
+            width = 38;
+            Globals.con.linewidth = width;
+            Globals.con.totallines = Defines.CON_TEXTSIZE
+                    / Globals.con.linewidth;
+            Arrays.fill(Globals.con.text, (byte) ' ');
+        } else {
+            int oldwidth = Globals.con.linewidth;
+            Globals.con.linewidth = width;
+            int oldtotallines = Globals.con.totallines;
+            Globals.con.totallines = Defines.CON_TEXTSIZE
+                    / Globals.con.linewidth;
+            int numlines = oldtotallines;
+
+            if (Globals.con.totallines < numlines)
+                numlines = Globals.con.totallines;
+
+            int numchars = oldwidth;
+
+            if (Globals.con.linewidth < numchars)
+                numchars = Globals.con.linewidth;
+
+            byte[] tbuf = new byte[Defines.CON_TEXTSIZE];
+            System
+                    .arraycopy(Globals.con.text, 0, tbuf, 0,
+                            Defines.CON_TEXTSIZE);
+            Arrays.fill(Globals.con.text, (byte) ' ');
+
+            for (int i = 0; i < numlines; i++) {
+                System.arraycopy(tbuf, ((Globals.con.current
+                        - i + oldtotallines) % oldtotallines) * oldwidth, Globals.con.text, (Globals.con.totallines - 1 - i) * Globals.con.linewidth, numchars);
+            }
+
+            Console.ClearNotify();
+        }
+
+        Globals.con.current = Globals.con.totallines - 1;
+        Globals.con.display = Globals.con.current;
+    }
+
+    public static void ClearNotify() {
+        int i;
+        for (i = 0; i < Defines.NUM_CON_TIMES; i++)
+            Globals.con.times[i] = 0;
+    }
+
+    static void DrawString(int x, int y, String s) {
+        for (int i = 0; i < s.length(); i++) {
+            Globals.re.DrawChar(x, y, s.charAt(i));
+            x += 8;
+        }
+    }
+
+    static void DrawAltString(int x, int y, String s) {
+        for (int i = 0; i < s.length(); i++) {
+            Globals.re.DrawChar(x, y, s.charAt(i) ^ 0x80);
+            x += 8;
+        }
+    }
+
+    /*
+     * =============== Con_Linefeed ===============
+     */
+    static void Linefeed() {
+        Globals.con.x = 0;
+        if (Globals.con.display == Globals.con.current)
+            Globals.con.display++;
+        Globals.con.current++;
+        int i = (Globals.con.current % Globals.con.totallines)
+                * Globals.con.linewidth;
+        int e = i + Globals.con.linewidth;
+        while (i++ < e)
+            Globals.con.text[i] = ' ';
+    }
+
+    public static void Print(String txt) {
+        int y;
+        int c, l;
+        int mask;
+        int txtpos = 0;
+
+        if (!con.initialized)
+            return;
+
+        if (txt.charAt(0) == 1 || txt.charAt(0) == 2) {
+            mask = 128; // go to colored text
+            txtpos++;
+        } else
+            mask = 0;
+
+        while (txtpos < txt.length()) {
+            c = txt.charAt(txtpos);
+            // count word length
+            for (l = 0; l < con.linewidth && l < (txt.length() - txtpos); l++)
+                if (txt.charAt(l + txtpos) <= ' ')
+                    break;
+
+            // word wrap
+            if (l != con.linewidth && (con.x + l > con.linewidth))
+                con.x = 0;
+
+            txtpos++;
+
+            if (cr != 0) {
+                con.current--;
+                cr = 0;
+            }
+
+            if (con.x == 0) {
+                Console.Linefeed();
+                // mark time for transparent overlay
+                if (con.current >= 0)
+                    con.times[con.current % NUM_CON_TIMES] = clientStaticT.realtime;
+            }
+
+            switch (c) {
+                case '\n':
+                    con.x = 0;
+                    break;
+
+                case '\r':
+                    con.x = 0;
+                    cr = 1;
+                    break;
+
+                default: // display character and advance
+                    y = con.current % con.totallines;
+                    con.text[y * con.linewidth + con.x] = (byte) (c | mask | con.ormask);
+                    con.x++;
+                    if (con.x >= con.linewidth)
+                        con.x = 0;
+                    break;
+            }
+        }
+    }
+
+    /*
+     * ============== Con_CenteredPrint ==============
+     */
+    static void CenteredPrint(String text) {
+        int l = text.length();
+        l = (con.linewidth - l) / 2;
+        if (l < 0)
+            l = 0;
+
+        StringBuilder sb = new StringBuilder(1024);
+        for (int i = 0; i < l; i++)
+            sb.append(' ');
+        sb.append(text);
+        sb.append('\n');
+
+        sb.setLength(1024);
+
+        Console.Print(sb.toString());
+    }
+
+    /*
+     * ==============================================================================
+     * 
+     * DRAWING
+     * 
+     * ==============================================================================
+     */
+
+    /*
+     * ================ Con_DrawInput
+     * 
+     * The input line scrolls horizontally if typing goes beyond the right edge
+     * ================
+     */
+    static void DrawInput() {
+        int i;
+        byte[] text;
+
+        if (clientStaticT.key_dest == key_menu)
+            return;
+        if (clientStaticT.key_dest != key_console && clientStaticT.state == ca_active)
+            return; // don't draw anything (always draw if not active)
+
+        text = key_lines[edit_line];
+
+        // add the cursor frame
+        text[key_linepos] = (byte) (10 + (clientStaticT.realtime >> 8 & 1));
+
+        // fill out remainder with spaces
+        for (i = key_linepos + 1; i < con.linewidth; i++)
+            text[i] = ' ';
+
+        // prestep if horizontally scrolling
+        //if (key_linepos >= con.linewidth)
+        //    start += 1 + key_linepos - con.linewidth;
+
+        // draw it
+        //             y = con.vislines-16;
+
+        for (i = 0; i < con.linewidth; i++)
+            re.DrawChar((i + 1) << 3, con.vislines - 22, text[i]);
+
+        // remove cursor
+        key_lines[edit_line][key_linepos] = 0;
+    }
+
+    /*
+     * ================ Con_DrawNotify
+     * 
+     * Draws the last few lines of output transparently over the game top
+     * ================
+     */
+    static void DrawNotify() {
+        int x, v;
+        int text;
+        int i;
+        int time;
+        String s;
+        int skip;
+
+        v = 0;
+        for (i = con.current - NUM_CON_TIMES + 1; i <= con.current; i++) {
+            if (i < 0)
+                continue;
+
+            time = (int) con.times[i % NUM_CON_TIMES];
+            if (time == 0)
+                continue;
+
+            time = clientStaticT.realtime - time;
+            if (time > con_notifytime.value * 1000)
+                continue;
+
+            text = (i % con.totallines) * con.linewidth;
+
+            for (x = 0; x < con.linewidth; x++)
+                re.DrawChar((x + 1) << 3, v, con.text[text + x]);
+
+            v += 8;
+        }
+
+        if (clientStaticT.key_dest == key_message) {
+            if (chat_team) {
+                DrawString(8, v, "say_team:");
+                skip = 11;
+            } else {
+                DrawString(8, v, "say:");
+                skip = 5;
+            }
+
+            s = chat_buffer;
+            if (chat_bufferlen > (viddef.width >> 3) - (skip + 1))
+                s = s.substring(chat_bufferlen
+                        - ((viddef.width >> 3) - (skip + 1)));
+
+            for (x = 0; x < s.length(); x++) {
+                re.DrawChar((x + skip) << 3, v, s.charAt(x));
+            }
+            re.DrawChar((x + skip) << 3, v,
+                    10 + ((clientStaticT.realtime >> 8) & 1));
+            v += 8;
+        }
+
+        if (v != 0) {
+            SCR.AddDirtyPoint(0, 0);
+            SCR.AddDirtyPoint(viddef.width - 1, v);
+        }
+    }
+
+    /*
+     * ================ Con_DrawConsole
+     * 
+     * Draws the console with the solid background ================
+     */
+    static void DrawConsole(float frac) {
+        int i, j, x, y, n;
+        int rows;
+        int text;
+        int row;
+        int lines;
+        String version;
+
+        lines = (int) (viddef.height * frac);
+        if (lines <= 0)
+            return;
+
+        if (lines > viddef.height)
+            lines = viddef.height;
+
+        // draw the background
+        re.DrawStretchPic(0, -viddef.height + lines, viddef.width,
+                viddef.height, "conback");
+        SCR.AddDirtyPoint(0, 0);
+        SCR.AddDirtyPoint(viddef.width - 1, lines - 1);
+
+        version = Com.sprintf("v%4.2f", new Vargs(1).add(VERSION));
+        for (x = 0; x < 5; x++)
+            re.DrawChar(viddef.width - 44 + x * 8, lines - 12, 128 + version
+                    .charAt(x));
+
+        // draw the text
+        con.vislines = lines;
+
+        rows = (lines - 22) >> 3; // rows of text to draw
+
+        y = lines - 30;
+
+        // draw from the bottom up
+        if (con.display != con.current) {
+            // draw arrows to show the buffer is backscrolled
+            for (x = 0; x < con.linewidth; x += 4)
+                re.DrawChar((x + 1) << 3, y, '^');
+
+            y -= 8;
+            rows--;
+        }
+
+        row = con.display;
+        for (i = 0; i < rows; i++, y -= 8, row--) {
+            if (row < 0)
+                break;
+            if (con.current - row >= con.totallines)
+                break; // past scrollback wrap point
+
+            int first = (row % con.totallines) * con.linewidth;
+
+            for (x = 0; x < con.linewidth; x++)
+                re.DrawChar((x + 1) << 3, y, con.text[x + first]);
+        }
+
+        //ZOID
+        // draw the download bar
+        // figure out width
+        if (clientStaticT.download != null) {
+            if ((text = clientStaticT.downloadname.lastIndexOf('/')) != 0)
+                text++;
+            else
+                text = 0;
+
+            x = con.linewidth - ((con.linewidth * 7) / 40);
+            y = x - (clientStaticT.downloadname.length() - text) - 8;
+            i = con.linewidth / 3;
+            StringBuilder dlbar = new StringBuilder(512);
+            if (clientStaticT.downloadname.length() - text > i) {
+                y = x - i - 11;
+                int end = text + i - 1;
+                dlbar.append(clientStaticT.downloadname.substring(text, end));
+                dlbar.append("...");
+            } else {
+                dlbar.append(clientStaticT.downloadname.substring(text));
+            }
+            dlbar.append(": ");
+            dlbar.append((char) 0x80);
+
+            // where's the dot go?
+            if (clientStaticT.downloadpercent == 0)
+                n = 0;
+            else
+                n = y * clientStaticT.downloadpercent / 100;
+
+            for (j = 0; j < y; j++) {
+                if (j == n)
+                    dlbar.append((char) 0x83);
+                else
+                    dlbar.append((char) 0x81);
+            }
+            dlbar.append((char) 0x82);
+            dlbar.append((clientStaticT.downloadpercent < 10) ? " 0" : " ");
+            dlbar.append(clientStaticT.downloadpercent).append('%');
+            // draw it
+            y = con.vislines - 12;
+            for (i = 0; i < dlbar.length(); i++)
+                re.DrawChar((i + 1) << 3, y, dlbar.charAt(i));
+        }
+        //ZOID
+
+        // draw the input prompt, user text, and cursor if desired
+        DrawInput();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/client/Key.java b/src/main/java/lwjake2/client/Key.java
new file mode 100644 (file)
index 0000000..9420746
--- /dev/null
@@ -0,0 +1,815 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.Cmd;
+import lwjake2.qcommon.Com;
+import lwjake2.qcommon.CommandBuffer;
+import lwjake2.qcommon.Cvar;
+import lwjake2.qcommon.xcommand_t;
+import lwjake2.util.Lib;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.Vector;
+
+/**
+ * Key
+ */
+public class Key extends Globals {
+    //
+    // these are the key numbers that should be passed to Key_Event
+    //
+    public static final int K_TAB = 9;
+    public static final int K_ENTER = 13;
+    public static final int K_ESCAPE = 27;
+    public static final int K_SPACE = 32;
+
+    // normal keys should be passed as lowercased ascii
+
+    public static final int K_BACKSPACE = 127;
+    public static final int K_UPARROW = 128;
+    public static final int K_DOWNARROW = 129;
+    public static final int K_LEFTARROW = 130;
+    public static final int K_RIGHTARROW = 131;
+
+    public static final int K_ALT = 132;
+    public static final int K_CTRL = 133;
+    public static final int K_SHIFT = 134;
+    public static final int K_F1 = 135;
+    public static final int K_F2 = 136;
+    public static final int K_F3 = 137;
+    public static final int K_F4 = 138;
+    public static final int K_F5 = 139;
+    public static final int K_F6 = 140;
+    public static final int K_F7 = 141;
+    public static final int K_F8 = 142;
+    public static final int K_F9 = 143;
+    public static final int K_F10 = 144;
+    public static final int K_F11 = 145;
+    public static final int K_F12 = 146;
+    public static final int K_INS = 147;
+    public static final int K_DEL = 148;
+    public static final int K_PGDN = 149;
+    public static final int K_PGUP = 150;
+    public static final int K_HOME = 151;
+    public static final int K_END = 152;
+
+    public static final int K_KP_HOME = 160;
+    public static final int K_KP_UPARROW = 161;
+    public static final int K_KP_PGUP = 162;
+    public static final int K_KP_LEFTARROW = 163;
+    public static final int K_KP_5 = 164;
+    public static final int K_KP_RIGHTARROW = 165;
+    public static final int K_KP_END = 166;
+    public static final int K_KP_DOWNARROW = 167;
+    public static final int K_KP_PGDN = 168;
+    public static final int K_KP_ENTER = 169;
+    public static final int K_KP_INS = 170;
+    public static final int K_KP_DEL = 171;
+    public static final int K_KP_SLASH = 172;
+    public static final int K_KP_MINUS = 173;
+    public static final int K_KP_PLUS = 174;
+
+    public static final int K_PAUSE = 255;
+
+    //
+    // mouse buttons generate virtual keys
+    //
+    public static final int K_MOUSE1 = 200;
+    public static final int K_MOUSE2 = 201;
+    public static final int K_MOUSE3 = 202;
+
+    //
+    // joystick buttons
+    //
+    public static final int K_JOY1 = 203;
+    public static final int K_JOY2 = 204;
+    public static final int K_JOY3 = 205;
+    public static final int K_JOY4 = 206;
+
+    public static final int K_MWHEELDOWN = 239;
+    public static final int K_MWHEELUP = 240;
+    static final int[] key_repeats = new int[256];
+    //static int[] keyshift = new int[256];
+    static final boolean[] menubound = new boolean[256];
+    static final boolean[] consolekeys = new boolean[256];
+    static final String[] keynames = new String[256];
+    public static final xcommand_t Bind_f = new xcommand_t() {
+        public void execute() {
+            Key_Bind_f();
+        }
+    };
+    static final xcommand_t Unbind_f = new xcommand_t() {
+        public void execute() {
+            Key_Unbind_f();
+        }
+    };
+    static final xcommand_t Unbindall_f = new xcommand_t() {
+        public void execute() {
+            Key_Unbindall_f();
+        }
+    };
+    static final xcommand_t Bindlist_f = new xcommand_t() {
+        public void execute() {
+            Key_Bindlist_f();
+        }
+    };
+    static int anykeydown = 0;
+    static int key_waiting;
+    static int history_line = 0;
+    static boolean shift_down = false;
+
+    static {
+        keynames[K_TAB] = "TAB";
+        keynames[K_ENTER] = "ENTER";
+        keynames[K_ESCAPE] = "ESCAPE";
+        keynames[K_SPACE] = "SPACE";
+        keynames[K_BACKSPACE] = "BACKSPACE";
+        keynames[K_UPARROW] = "UPARROW";
+        keynames[K_DOWNARROW] = "DOWNARROW";
+        keynames[K_LEFTARROW] = "LEFTARROW";
+        keynames[K_RIGHTARROW] = "RIGHTARROW";
+        keynames[K_ALT] = "ALT";
+        keynames[K_CTRL] = "CTRL";
+        keynames[K_SHIFT] = "SHIFT";
+
+        keynames[K_F1] = "F1";
+        keynames[K_F2] = "F2";
+        keynames[K_F3] = "F3";
+        keynames[K_F4] = "F4";
+        keynames[K_F5] = "F5";
+        keynames[K_F6] = "F6";
+        keynames[K_F7] = "F7";
+        keynames[K_F8] = "F8";
+        keynames[K_F9] = "F9";
+        keynames[K_F10] = "F10";
+        keynames[K_F11] = "F11";
+        keynames[K_F12] = "F12";
+
+        keynames[K_INS] = "INS";
+        keynames[K_DEL] = "DEL";
+        keynames[K_PGDN] = "PGDN";
+        keynames[K_PGUP] = "PGUP";
+        keynames[K_HOME] = "HOME";
+        keynames[K_END] = "END";
+
+        keynames[K_MOUSE1] = "MOUSE1";
+        keynames[K_MOUSE2] = "MOUSE2";
+        keynames[K_MOUSE3] = "MOUSE3";
+
+        //     00092         {"JOY1", K_JOY1},
+        //     00093         {"JOY2", K_JOY2},
+        //     00094         {"JOY3", K_JOY3},
+        //     00095         {"JOY4", K_JOY4},
+
+        keynames[K_KP_HOME] = "KP_HOME";
+        keynames[K_KP_UPARROW] = "KP_UPARROW";
+        keynames[K_KP_PGUP] = "KP_PGUP";
+        keynames[K_KP_LEFTARROW] = "KP_LEFTARROW";
+        keynames[K_KP_5] = "KP_5";
+        keynames[K_KP_RIGHTARROW] = "KP_RIGHTARROW";
+        keynames[K_KP_END] = "KP_END";
+        keynames[K_KP_DOWNARROW] = "KP_DOWNARROW";
+        keynames[K_KP_PGDN] = "KP_PGDN";
+        keynames[K_KP_ENTER] = "KP_ENTER";
+        keynames[K_KP_INS] = "KP_INS";
+        keynames[K_KP_DEL] = "KP_DEL";
+        keynames[K_KP_SLASH] = "KP_SLASH";
+
+        keynames[K_KP_PLUS] = "KP_PLUS";
+        keynames[K_KP_MINUS] = "KP_MINUS";
+
+        keynames[K_MWHEELUP] = "MWHEELUP";
+        keynames[K_MWHEELDOWN] = "MWHEELDOWN";
+
+        keynames[K_PAUSE] = "PAUSE";
+        keynames[';'] = "SEMICOLON"; // because a raw semicolon seperates commands
+
+        keynames[0] = "NULL";
+    }
+
+    /**
+     *
+     */
+    public static void Init() {
+        for (int i = 0; i < 32; i++) {
+            Globals.key_lines[i][0] = ']';
+            Globals.key_lines[i][1] = 0;
+        }
+        Globals.key_linepos = 1;
+
+        //
+        // init ascii characters in console mode
+        //
+        for (int i = 32; i < 128; i++)
+            consolekeys[i] = true;
+        consolekeys[K_ENTER] = true;
+        consolekeys[K_KP_ENTER] = true;
+        consolekeys[K_TAB] = true;
+        consolekeys[K_LEFTARROW] = true;
+        consolekeys[K_KP_LEFTARROW] = true;
+        consolekeys[K_RIGHTARROW] = true;
+        consolekeys[K_KP_RIGHTARROW] = true;
+        consolekeys[K_UPARROW] = true;
+        consolekeys[K_KP_UPARROW] = true;
+        consolekeys[K_DOWNARROW] = true;
+        consolekeys[K_KP_DOWNARROW] = true;
+        consolekeys[K_BACKSPACE] = true;
+        consolekeys[K_HOME] = true;
+        consolekeys[K_KP_HOME] = true;
+        consolekeys[K_END] = true;
+        consolekeys[K_KP_END] = true;
+        consolekeys[K_PGUP] = true;
+        consolekeys[K_KP_PGUP] = true;
+        consolekeys[K_PGDN] = true;
+        consolekeys[K_KP_PGDN] = true;
+        consolekeys[K_SHIFT] = true;
+        consolekeys[K_INS] = true;
+        consolekeys[K_KP_INS] = true;
+        consolekeys[K_KP_DEL] = true;
+        consolekeys[K_KP_SLASH] = true;
+        consolekeys[K_KP_PLUS] = true;
+        consolekeys[K_KP_MINUS] = true;
+        consolekeys[K_KP_5] = true;
+
+        consolekeys['`'] = false;
+        consolekeys['~'] = false;
+
+//             for (int i = 0; i < 256; i++)
+//                     keyshift[i] = i;
+//             for (int i = 'a'; i <= 'z'; i++)
+//                     keyshift[i] = i - 'a' + 'A';
+//             keyshift['1'] = '!';
+//             keyshift['2'] = '@';
+//             keyshift['3'] = '#';
+//             keyshift['4'] = '$';
+//             keyshift['5'] = '%';
+//             keyshift['6'] = '^';
+//             keyshift['7'] = '&';
+//             keyshift['8'] = '*';
+//             keyshift['9'] = '(';
+//             keyshift['0'] = ')';
+//             keyshift['-'] = '_';
+//             keyshift['='] = '+';
+//             keyshift[','] = '<';
+//             keyshift['.'] = '>';
+//             keyshift['/'] = '?';
+//             keyshift[';'] = ':';
+//             keyshift['\''] = '"';
+//             keyshift['['] = '{';
+//             keyshift[']'] = '}';
+//             keyshift['`'] = '~';
+//             keyshift['\\'] = '|';
+
+        menubound[K_ESCAPE] = true;
+        for (int i = 0; i < 12; i++)
+            menubound[K_F1 + i] = true;
+
+        //
+        // register our functions
+        //
+        Cmd.AddCommand("bind", Key.Bind_f);
+        Cmd.AddCommand("unbind", Key.Unbind_f);
+        Cmd.AddCommand("unbindall", Key.Unbindall_f);
+        Cmd.AddCommand("bindlist", Key.Bindlist_f);
+    }
+
+    public static void ClearTyping() {
+        Globals.key_lines[Globals.edit_line][1] = 0; // clear any typing
+        Globals.key_linepos = 1;
+    }
+
+    /**
+     * Called by the system between frames for both key up and key down events.
+     */
+    public static void Event(int key, boolean down, int time) {
+        String kb;
+        String cmd;
+
+        // hack for modal presses
+        if (key_waiting == -1) {
+            if (down)
+                key_waiting = key;
+            return;
+        }
+
+        // update auto-repeat status
+        if (down) {
+            key_repeats[key]++;
+            if (key_repeats[key] > 1
+                    && Globals.clientStaticT.key_dest == Defines.key_game
+                    && !(Globals.clientStaticT.state == Defines.ca_disconnected))
+                return; // ignore most autorepeats
+
+            if (key >= 200 && Globals.keybindings[key] == null)
+                Com.Printf(Key.KeynumToString(key) + " is unbound, hit F4 to set.\n");
+        } else {
+            key_repeats[key] = 0;
+        }
+
+        if (key == K_SHIFT)
+            shift_down = down;
+
+        // console key is hardcoded, so the user can never unbind it
+        if (key == '`' || key == '~') {
+            if (!down)
+                return;
+
+            Console.ToggleConsole_f.execute();
+            return;
+        }
+
+        // any key during the attract mode will bring up the menu
+        if (Globals.clientStateT.attractloop && Globals.clientStaticT.key_dest != Defines.key_menu && !(key >= K_F1 && key <= K_F12))
+            key = K_ESCAPE;
+
+        // menu key is hardcoded, so the user can never unbind it
+        if (key == K_ESCAPE) {
+            if (!down)
+                return;
+
+            if (Globals.clientStateT.frame.playerstate.stats[Defines.STAT_LAYOUTS] != 0 && Globals.clientStaticT.key_dest == Defines.key_game) {
+                // put away help computer / inventory
+                CommandBuffer.AddText("cmd putaway\n");
+                return;
+            }
+            switch (Globals.clientStaticT.key_dest) {
+                case Defines.key_message:
+                    Key.Message(key);
+                    break;
+                case Defines.key_menu:
+                    Menu.Keydown(key);
+                    break;
+                case Defines.key_game:
+                case Defines.key_console:
+                    Menu.Menu_Main_f();
+                    break;
+                default:
+                    Com.Error(Defines.ERR_FATAL, "Bad cls.key_dest");
+            }
+            return;
+        }
+
+        // track if any key is down for BUTTON_ANY
+        Globals.keydown[key] = down;
+        if (down) {
+            if (key_repeats[key] == 1)
+                Key.anykeydown++;
+        } else {
+            Key.anykeydown--;
+            if (Key.anykeydown < 0)
+                Key.anykeydown = 0;
+        }
+
+        //
+        // key up events only generate commands if the game key binding is
+        // a button command (leading + sign).  These will occur even in console mode,
+        // to keep the character from continuing an action started before a console
+        // switch.  Button commands include the kenum as a parameter, so multiple
+        // downs can be matched with ups
+        //
+        if (!down) {
+            kb = Globals.keybindings[key];
+            if (kb != null && kb.length() > 0 && kb.charAt(0) == '+') {
+                cmd = "-" + kb.substring(1) + " " + key + " " + time + "\n";
+                CommandBuffer.AddText(cmd);
+            }
+//                     if (keyshift[key] != key) {
+//                             kb = Globals.keybindings[keyshift[key]];
+//                             if (kb != null && kb.length()>0 && kb.charAt(0) == '+') {
+//                                     cmd = "-" + kb.substring(1) + " " + key + " " + time + "\n";
+//                                     CommandBuffer.AddText(cmd);
+//                             }
+//                     }
+            return;
+        }
+
+        //
+        // if not a consolekey, send to the interpreter no matter what mode is
+        //
+        if ((Globals.clientStaticT.key_dest == Defines.key_menu && menubound[key])
+                || (Globals.clientStaticT.key_dest == Defines.key_console && !consolekeys[key])
+                || (Globals.clientStaticT.key_dest == Defines.key_game && (Globals.clientStaticT.state == Defines.ca_active || !consolekeys[key]))) {
+            kb = Globals.keybindings[key];
+            if (kb != null) {
+                if (kb.length() > 0 && kb.charAt(0) == '+') {
+                    // button commands add keynum and time as a parm
+                    cmd = kb + " " + key + " " + time + "\n";
+                    CommandBuffer.AddText(cmd);
+                } else {
+                    CommandBuffer.AddText(kb + "\n");
+                }
+            }
+            return;
+        }
+
+        if (!down)
+            return; // other systems only care about key down events
+
+//             if (shift_down)
+//                     key = keyshift[key];
+
+        switch (Globals.clientStaticT.key_dest) {
+            case Defines.key_message:
+                Key.Message(key);
+                break;
+            case Defines.key_menu:
+                Menu.Keydown(key);
+                break;
+
+            case Defines.key_game:
+            case Defines.key_console:
+                Key.Console(key);
+                break;
+            default:
+                Com.Error(Defines.ERR_FATAL, "Bad cls.key_dest");
+        }
+    }
+
+    /**
+     * Returns a string (either a single ascii char, or a K_* name) for the
+     * given keynum.
+     */
+    public static String KeynumToString(int keynum) {
+        if (keynum < 0 || keynum > 255)
+            return "<KEY NOT FOUND>";
+        if (keynum > 32 && keynum < 127)
+            return Character.toString((char) keynum);
+
+        if (keynames[keynum] != null)
+            return keynames[keynum];
+
+        return "<UNKNOWN KEYNUM>";
+    }
+
+    /**
+     * Returns a key number to be used to index keybindings[] by looking at
+     * the given string. Single ascii characters return themselves, while
+     * the K_* names are matched up.
+     */
+    static int StringToKeynum(String str) {
+
+        if (str == null)
+            return -1;
+
+        if (str.length() == 1)
+            return str.charAt(0);
+
+        for (int i = 0; i < keynames.length; i++) {
+            if (str.equalsIgnoreCase(keynames[i]))
+                return i;
+        }
+
+        return -1;
+    }
+
+    public static void Message(int key) {
+
+        if (key == K_ENTER || key == K_KP_ENTER) {
+            if (Globals.chat_team)
+                CommandBuffer.AddText("say_team \"");
+            else
+                CommandBuffer.AddText("say \"");
+
+            CommandBuffer.AddText(Globals.chat_buffer);
+            CommandBuffer.AddText("\"\n");
+
+            Globals.clientStaticT.key_dest = Defines.key_game;
+            Globals.chat_buffer = "";
+            return;
+        }
+
+        if (key == K_ESCAPE) {
+            Globals.clientStaticT.key_dest = Defines.key_game;
+            Globals.chat_buffer = "";
+            return;
+        }
+
+        if (key < 32 || key > 127)
+            return; // non printable
+
+        if (key == K_BACKSPACE) {
+            if (Globals.chat_buffer.length() > 2) {
+                Globals.chat_buffer = Globals.chat_buffer.substring(0, Globals.chat_buffer.length() - 2);
+            } else
+                Globals.chat_buffer = "";
+            return;
+        }
+
+        if (Globals.chat_buffer.length() > Defines.MAXCMDLINE)
+            return; // all full
+
+        Globals.chat_buffer += (char) key;
+    }
+
+    /**
+     * Interactive line editing and console scrollback.
+     */
+    public static void Console(int key) {
+
+        switch (key) {
+            case K_KP_SLASH:
+                key = '/';
+                break;
+            case K_KP_MINUS:
+                key = '-';
+                break;
+            case K_KP_PLUS:
+                key = '+';
+                break;
+            case K_KP_HOME:
+                key = '7';
+                break;
+            case K_KP_UPARROW:
+                key = '8';
+                break;
+            case K_KP_PGUP:
+                key = '9';
+                break;
+            case K_KP_LEFTARROW:
+                key = '4';
+                break;
+            case K_KP_5:
+                key = '5';
+                break;
+            case K_KP_RIGHTARROW:
+                key = '6';
+                break;
+            case K_KP_END:
+                key = '1';
+                break;
+            case K_KP_DOWNARROW:
+                key = '2';
+                break;
+            case K_KP_PGDN:
+                key = '3';
+                break;
+            case K_KP_INS:
+                key = '0';
+                break;
+            case K_KP_DEL:
+                key = '.';
+                break;
+        }
+
+        if (key == 'l') {
+            if (Globals.keydown[K_CTRL]) {
+                CommandBuffer.AddText("clear\n");
+                return;
+            }
+        }
+
+        if (key == K_ENTER || key == K_KP_ENTER) {
+            // backslash text are commands, else chat
+            if (Globals.key_lines[Globals.edit_line][1] == '\\' || Globals.key_lines[Globals.edit_line][1] == '/')
+                CommandBuffer.AddText(
+                        new String(Globals.key_lines[Globals.edit_line], 2, Lib.strlen(Globals.key_lines[Globals.edit_line]) - 2));
+            else
+                CommandBuffer.AddText(
+                        new String(Globals.key_lines[Globals.edit_line], 1, Lib.strlen(Globals.key_lines[Globals.edit_line]) - 1));
+
+
+            CommandBuffer.AddText("\n");
+
+            Com.Printf(new String(Globals.key_lines[Globals.edit_line], 0, Lib.strlen(Globals.key_lines[Globals.edit_line])) + "\n");
+            Globals.edit_line = (Globals.edit_line + 1) & 31;
+            history_line = Globals.edit_line;
+
+            Globals.key_lines[Globals.edit_line][0] = ']';
+            Globals.key_linepos = 1;
+            if (Globals.clientStaticT.state == Defines.ca_disconnected)
+                SCR.UpdateScreen(); // force an update, because the command may take some time
+            return;
+        }
+
+        if (key == K_TAB) {
+            // command completion
+            CompleteCommand();
+            return;
+        }
+
+        if ((key == K_BACKSPACE) || (key == K_LEFTARROW) || (key == K_KP_LEFTARROW) || ((key == 'h') && (Globals.keydown[K_CTRL]))) {
+            if (Globals.key_linepos > 1)
+                Globals.key_linepos--;
+            return;
+        }
+
+        if ((key == K_UPARROW) || (key == K_KP_UPARROW) || ((key == 'p') && Globals.keydown[K_CTRL])) {
+            do {
+                history_line = (history_line - 1) & 31;
+            }
+            while (history_line != Globals.edit_line && Globals.key_lines[history_line][1] == 0);
+            if (history_line == Globals.edit_line)
+                history_line = (Globals.edit_line + 1) & 31;
+            //Lib.strcpy(Globals.key_lines[Globals.edit_line], Globals.key_lines[history_line]);
+            System.arraycopy(Globals.key_lines[history_line], 0, Globals.key_lines[Globals.edit_line], 0, Globals.key_lines[Globals.edit_line].length);
+            Globals.key_linepos = Lib.strlen(Globals.key_lines[Globals.edit_line]);
+            return;
+        }
+
+        if ((key == K_DOWNARROW) || (key == K_KP_DOWNARROW) || ((key == 'n') && Globals.keydown[K_CTRL])) {
+            if (history_line == Globals.edit_line)
+                return;
+            do {
+                history_line = (history_line + 1) & 31;
+            }
+            while (history_line != Globals.edit_line && Globals.key_lines[history_line][1] == 0);
+            if (history_line == Globals.edit_line) {
+                Globals.key_lines[Globals.edit_line][0] = ']';
+                Globals.key_linepos = 1;
+            } else {
+                //Lib.strcpy(Globals.key_lines[Globals.edit_line], Globals.key_lines[history_line]);
+                System.arraycopy(Globals.key_lines[history_line], 0, Globals.key_lines[Globals.edit_line], 0, Globals.key_lines[Globals.edit_line].length);
+                Globals.key_linepos = Lib.strlen(Globals.key_lines[Globals.edit_line]);
+            }
+            return;
+        }
+
+        if (key == K_PGUP || key == K_KP_PGUP) {
+            Globals.con.display -= 2;
+            return;
+        }
+
+        if (key == K_PGDN || key == K_KP_PGDN) {
+            Globals.con.display += 2;
+            if (Globals.con.display > Globals.con.current)
+                Globals.con.display = Globals.con.current;
+            return;
+        }
+
+        if (key == K_HOME || key == K_KP_HOME) {
+            Globals.con.display = Globals.con.current - Globals.con.totallines + 10;
+            return;
+        }
+
+        if (key == K_END || key == K_KP_END) {
+            Globals.con.display = Globals.con.current;
+            return;
+        }
+
+        if (key < 32 || key > 127)
+            return; // non printable
+
+        if (Globals.key_linepos < Defines.MAXCMDLINE - 1) {
+            Globals.key_lines[Globals.edit_line][Globals.key_linepos] = (byte) key;
+            Globals.key_linepos++;
+            Globals.key_lines[Globals.edit_line][Globals.key_linepos] = 0;
+        }
+
+    }
+
+    private static void printCompletions(String type, Vector<String> compl) {
+        Com.Printf(type);
+        for (String aCompl : compl) {
+            Com.Printf(aCompl + " ");
+        }
+        Com.Printf("\n");
+    }
+
+    static void CompleteCommand() {
+
+        int start = 1;
+        if (key_lines[edit_line][start] == '\\' || key_lines[edit_line][start] == '/')
+            start++;
+
+        int end = start;
+        while (key_lines[edit_line][end] != 0) end++;
+
+        String s = new String(key_lines[edit_line], start, end - start);
+
+        Vector<String> cmds = Cmd.CompleteCommand(s);
+        Vector<String> vars = Cvar.completeVariable(s);
+
+        int c = cmds.size();
+        int v = vars.size();
+
+        if ((c + v) > 1) {
+            if (c > 0) printCompletions("\nCommands:\n", cmds);
+            if (v > 0) printCompletions("\nVariables:\n", vars);
+            return;
+        } else if (c == 1) {
+            s = cmds.get(0);
+        } else if (v == 1) {
+            s = vars.get(0);
+        } else return;
+
+        key_lines[edit_line][1] = '/';
+        byte[] bytes = Lib.stringToBytes(s);
+        System.arraycopy(bytes, 0, key_lines[edit_line], 2, bytes.length);
+        key_linepos = bytes.length + 2;
+        key_lines[edit_line][key_linepos++] = ' ';
+        key_lines[edit_line][key_linepos] = 0;
+
+    }
+
+    static void Key_Bind_f() {
+        int c = Cmd.Argc();
+
+        if (c < 2) {
+            Com.Printf("bind <key> [command] : attach a command to a key\n");
+            return;
+        }
+        int b = StringToKeynum(Cmd.Argv(1));
+        if (b == -1) {
+            Com.Printf("\"" + Cmd.Argv(1) + "\" isn't a valid key\n");
+            return;
+        }
+
+        if (c == 2) {
+            if (Globals.keybindings[b] != null)
+                Com.Printf("\"" + Cmd.Argv(1) + "\" = \"" + Globals.keybindings[b] + "\"\n");
+            else
+                Com.Printf("\"" + Cmd.Argv(1) + "\" is not bound\n");
+            return;
+        }
+
+        // copy the rest of the command line
+        StringBuilder cmd = new StringBuilder(); // start out with a null string
+        for (int i = 2; i < c; i++) {
+            cmd.append(Cmd.Argv(i));
+            if (i != (c - 1))
+                cmd.append(" ");
+        }
+
+        SetBinding(b, cmd.toString());
+    }
+
+    static void SetBinding(int keynum, String binding) {
+        if (keynum == -1)
+            return;
+
+        // free old bindings
+        Globals.keybindings[keynum] = null;
+
+        Globals.keybindings[keynum] = binding;
+    }
+
+    static void Key_Unbind_f() {
+
+        if (Cmd.Argc() != 2) {
+            Com.Printf("unbind <key> : remove commands from a key\n");
+            return;
+        }
+
+        int b = Key.StringToKeynum(Cmd.Argv(1));
+        if (b == -1) {
+            Com.Printf("\"" + Cmd.Argv(1) + "\" isn't a valid key\n");
+            return;
+        }
+
+        Key.SetBinding(b, null);
+    }
+
+    static void Key_Unbindall_f() {
+        for (int i = 0; i < 256; i++)
+            Key.SetBinding(i, null);
+    }
+
+    static void Key_Bindlist_f() {
+        for (int i = 0; i < 256; i++)
+            if (Globals.keybindings[i] != null && Globals.keybindings[i].length() != 0)
+                Com.Printf(Key.KeynumToString(i) + " \"" + Globals.keybindings[i] + "\"\n");
+    }
+
+    static void ClearStates() {
+        int i;
+
+        Key.anykeydown = 0;
+
+        for (i = 0; i < 256; i++) {
+            if (keydown[i] || key_repeats[i] != 0)
+                Event(i, false, 0);
+            keydown[i] = false;
+            key_repeats[i] = 0;
+        }
+    }
+
+    public static void WriteBindings(RandomAccessFile f) {
+        for (int i = 0; i < 256; i++)
+            if (keybindings[i] != null && keybindings[i].length() > 0)
+                try {
+                    f.writeBytes("bind " + KeynumToString(i) + " \"" + keybindings[i] + "\"\n");
+                } catch (IOException ignored) {
+                }
+    }
+
+}
diff --git a/src/main/java/lwjake2/client/M.java b/src/main/java/lwjake2/client/M.java
new file mode 100644 (file)
index 0000000..a3a99fd
--- /dev/null
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.EDict;
+import lwjake2.game.EntThinkAdapter;
+import lwjake2.game.GameBase;
+import lwjake2.game.trace_t;
+import lwjake2.util.Math3D;
+
+/**
+ * M
+ */
+public final class M {
+
+    /**
+     * Stops the Flies.
+     */
+    public static final EntThinkAdapter M_FliesOff = new EntThinkAdapter() {
+        public String getID() {
+            return "m_fliesoff";
+        }
+
+        public void think(EDict self) {
+            self.s.effects &= ~Defines.EF_FLIES;
+            self.s.sound = 0;
+        }
+    };
+    /**
+     * Starts the Flies as setting the animation flag in the entity.
+     */
+    public static final EntThinkAdapter M_FliesOn = new EntThinkAdapter() {
+        public String getID() {
+            return "m_flies_on";
+        }
+
+        public void think(EDict self) {
+            if (self.waterlevel != 0)
+                return;
+
+            self.s.effects |= Defines.EF_FLIES;
+            self.s.sound = GameBase.gi.soundindex("infantry/inflies1.wav");
+            self.think = M_FliesOff;
+            self.nextthink = GameBase.level.time + 60;
+        }
+    };
+    public static EntThinkAdapter M_droptofloor = new EntThinkAdapter() {
+        public String getID() {
+            return "m_drop_to_floor";
+        }
+
+        public void think(EDict ent) {
+            float[] end = {0, 0, 0};
+            trace_t trace;
+
+            ent.s.origin[2] += 1;
+            Math3D.vectorCopy(ent.s.origin, end);
+            end[2] -= 256;
+
+            trace = GameBase.gi.trace(ent.s.origin, ent.mins, ent.maxs, end,
+                    ent, Defines.MASK_MONSTERSOLID);
+
+            if (trace.fraction == 1 || trace.allsolid)
+                return;
+
+            Math3D.vectorCopy(trace.endpos, ent.s.origin);
+
+            GameBase.gi.linkentity(ent);
+            M.M_CheckGround(ent);
+            M_CatagorizePosition(ent);
+        }
+    };
+    /**
+     * Adds some flies after a random time
+     */
+    public static EntThinkAdapter M_FlyCheck = new EntThinkAdapter() {
+        public String getID() {
+            return "m_fly_check";
+        }
+
+        public void think(EDict self) {
+
+            if (self.waterlevel != 0)
+                return;
+
+            if (Globals.rnd.nextFloat() > 0.5)
+                return;
+
+            self.think = M_FliesOn;
+            self.nextthink = GameBase.level.time + 5 + 10
+                    * Globals.rnd.nextFloat();
+        }
+    };
+
+    public static void M_CheckGround(EDict ent) {
+        float[] point = {0, 0, 0};
+        trace_t trace;
+
+        if ((ent.flags & (Defines.FL_SWIM | Defines.FL_FLY)) != 0)
+            return;
+
+        if (ent.velocity[2] > 100) {
+            ent.groundentity = null;
+            return;
+        }
+
+        // if the hull point one-quarter unit down is solid the entity is on
+        // ground
+        point[0] = ent.s.origin[0];
+        point[1] = ent.s.origin[1];
+        point[2] = ent.s.origin[2] - 0.25f;
+
+        trace = GameBase.gi.trace(ent.s.origin, ent.mins, ent.maxs, point, ent,
+                Defines.MASK_MONSTERSOLID);
+
+        // check steepness
+        if (trace.plane.normal[2] < 0.7 && !trace.startsolid) {
+            ent.groundentity = null;
+            return;
+        }
+
+        // ent.groundentity = trace.ent;
+        // ent.groundentity_linkcount = trace.ent.linkcount;
+        // if (!trace.startsolid && !trace.allsolid)
+        //   VectorCopy (trace.endpos, ent.s.origin);
+        if (!trace.startsolid && !trace.allsolid) {
+            Math3D.vectorCopy(trace.endpos, ent.s.origin);
+            ent.groundentity = trace.ent;
+            ent.groundentity_linkcount = trace.ent.linkcount;
+            ent.velocity[2] = 0;
+        }
+    }
+
+    /**
+     * Returns false if any part of the bottom of the entity is off an edge that
+     * is not a staircase.
+     */
+
+    public static boolean M_CheckBottom(EDict ent) {
+        float[] mins = {0, 0, 0};
+        float[] maxs = {0, 0, 0};
+        float[] start = {0, 0, 0};
+        float[] stop = {0, 0, 0};
+
+        trace_t trace;
+        int x, y;
+        float mid, bottom;
+
+        Math3D.vectorAdd(ent.s.origin, ent.mins, mins);
+        Math3D.vectorAdd(ent.s.origin, ent.maxs, maxs);
+
+        //        if all of the points under the corners are solid world, don't bother
+        //        with the tougher checks
+        //        the corners must be within 16 of the midpoint
+        start[2] = mins[2] - 1;
+        for (x = 0; x <= 1; x++)
+            for (y = 0; y <= 1; y++) {
+                start[0] = x != 0 ? maxs[0] : mins[0];
+                start[1] = y != 0 ? maxs[1] : mins[1];
+                if (GameBase.gi.pointcontents.pointcontents(start) != Defines.CONTENTS_SOLID) {
+                    GameBase.c_no++;
+                    //
+                    //    check it for real...
+                    //
+                    start[2] = mins[2];
+
+                    //    the midpoint must be within 16 of the bottom
+                    start[0] = stop[0] = (mins[0] + maxs[0]) * 0.5f;
+                    start[1] = stop[1] = (mins[1] + maxs[1]) * 0.5f;
+                    stop[2] = start[2] - 2 * GameBase.STEPSIZE;
+                    trace = GameBase.gi.trace(start, Globals.vec3_origin,
+                            Globals.vec3_origin, stop, ent,
+                            Defines.MASK_MONSTERSOLID);
+
+                    if (trace.fraction == 1.0)
+                        return false;
+                    mid = bottom = trace.endpos[2];
+
+                    //    the corners must be within 16 of the midpoint
+                    for (x = 0; x <= 1; x++)
+                        for (y = 0; y <= 1; y++) {
+                            start[0] = stop[0] = x != 0 ? maxs[0] : mins[0];
+                            start[1] = stop[1] = y != 0 ? maxs[1] : mins[1];
+
+                            trace = GameBase.gi.trace(start,
+                                    Globals.vec3_origin, Globals.vec3_origin,
+                                    stop, ent, Defines.MASK_MONSTERSOLID);
+
+                            if (trace.fraction != 1.0
+                                    && trace.endpos[2] > bottom)
+                                bottom = trace.endpos[2];
+                            if (trace.fraction == 1.0
+                                    || mid - trace.endpos[2] > GameBase.STEPSIZE)
+                                return false;
+                        }
+
+                    GameBase.c_yes++;
+                    return true;
+                }
+            }
+
+        GameBase.c_yes++;
+        return true; // we got out easy
+    }
+
+    /**
+     * M_ChangeYaw.
+     */
+    public static void M_ChangeYaw(EDict ent) {
+        float ideal;
+        float current;
+        float move;
+        float speed;
+
+        current = Math3D.angleMod(ent.s.angles[Defines.YAW]);
+        ideal = ent.ideal_yaw;
+
+        if (current == ideal)
+            return;
+
+        move = ideal - current;
+        speed = ent.yaw_speed;
+        if (ideal > current) {
+            if (move >= 180)
+                move = move - 360;
+        } else {
+            if (move <= -180)
+                move = move + 360;
+        }
+        if (move > 0) {
+            if (move > speed)
+                move = speed;
+        } else {
+            if (move < -speed)
+                move = -speed;
+        }
+
+        ent.s.angles[Defines.YAW] = Math3D.angleMod(current + move);
+    }
+
+
+    public static void M_CatagorizePosition(EDict ent) {
+        float[] point = {0, 0, 0};
+        int cont;
+
+        //
+        //     get waterlevel
+        //
+        point[0] = ent.s.origin[0];
+        point[1] = ent.s.origin[1];
+        point[2] = ent.s.origin[2] + ent.mins[2] + 1;
+        cont = GameBase.gi.pointcontents.pointcontents(point);
+
+        if (0 == (cont & Defines.MASK_WATER)) {
+            ent.waterlevel = 0;
+            ent.watertype = 0;
+            return;
+        }
+
+        ent.watertype = cont;
+        ent.waterlevel = 1;
+        point[2] += 26;
+        cont = GameBase.gi.pointcontents.pointcontents(point);
+        if (0 == (cont & Defines.MASK_WATER))
+            return;
+
+        ent.waterlevel = 2;
+        point[2] += 22;
+        cont = GameBase.gi.pointcontents.pointcontents(point);
+        if (0 != (cont & Defines.MASK_WATER))
+            ent.waterlevel = 3;
+    }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/client/Menu.java b/src/main/java/lwjake2/client/Menu.java
new file mode 100644 (file)
index 0000000..c3f3eb8
--- /dev/null
@@ -0,0 +1,4606 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.Globals;
+import lwjake2.game.Cmd;
+import lwjake2.game.CvarT;
+import lwjake2.qcommon.*;
+import lwjake2.sound.S;
+import lwjake2.sys.NET;
+import lwjake2.sys.Sys;
+import lwjake2.sys.Timer;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+import lwjake2.util.QuakeFile;
+
+import java.awt.*;
+import java.io.RandomAccessFile;
+import java.util.Arrays;
+
+/**
+ * Menu
+ */
+
+abstract class keyfunc_t {
+    abstract String execute(int key);
+}
+
+public final class Menu extends Key {
+
+    public final static int MAX_MENU_DEPTH = 8;
+    public final static int MAX_SAVEGAMES = 15;
+    public static final int SLIDER_RANGE = 10;
+    public static final menulayer_t[] m_layers = new menulayer_t[MAX_MENU_DEPTH];
+    static final int NUM_CURSOR_FRAMES = 15;
+    static final String menu_in_sound = "misc/menu1.wav";
+    static final String menu_move_sound = "misc/menu2.wav";
+    // won't disrupt the sound
+    static final String menu_out_sound = "misc/menu3.wav";
+    /*
+     * =======================================================================
+     *
+     * MAIN MENU
+     *
+     * =======================================================================
+     */
+    static final int MAIN_ITEMS = 5;
+    /*
+     * =======================================================================
+     *
+     * MULTIPLAYER MENU
+     *
+     * =======================================================================
+     */
+    static final menuframework_s s_multiplayer_menu = new menuframework_s();
+    static final menuaction_s s_join_network_server_action = new menuaction_s();
+    static final menuaction_s s_start_network_server_action = new menuaction_s();
+    static final menuaction_s s_player_setup_action = new menuaction_s();
+    /*
+     * =======================================================================
+     *
+     * KEYS MENU
+     *
+     * =======================================================================
+     */
+    static final String[][] bindnames = {{"+attack", "attack"},
+            {"weapnext", "next weapon"}, {"+forward", "walk forward"},
+            {"+back", "backpedal"}, {"+left", "turn left"},
+            {"+right", "turn right"}, {"+speed", "run"},
+            {"+moveleft", "step left"}, {"+moveright", "step right"},
+            {"+strafe", "sidestep"}, {"+lookup", "look up"},
+            {"+lookdown", "look down"}, {"centerview", "center view"},
+            {"+mlook", "mouse look"}, {"+klook", "keyboard look"},
+            {"+moveup", "up / jump"}, {"+movedown", "down / crouch"}, {
+
+            "inven", "inventory"}, {"invuse", "use item"},
+            {"invdrop", "drop item"}, {"invprev", "prev item"},
+            {"invnext", "next item"}, {
+
+            "cmd help", "help computer"}, {null, null}};
+    static final menuframework_s s_keys_menu = new menuframework_s();
+    static final menuaction_s s_keys_walk_forward_action = new menuaction_s();
+    static final menuaction_s s_keys_backpedal_action = new menuaction_s();
+    static final menuaction_s s_keys_turn_left_action = new menuaction_s();
+    static final menuaction_s s_keys_turn_right_action = new menuaction_s();
+    static final menuaction_s s_keys_run_action = new menuaction_s();
+    static final menuaction_s s_keys_step_left_action = new menuaction_s();
+    static final menuaction_s s_keys_step_right_action = new menuaction_s();
+    static final menuaction_s s_keys_sidestep_action = new menuaction_s();
+    static final menuaction_s s_keys_look_up_action = new menuaction_s();
+    static final menuaction_s s_keys_look_down_action = new menuaction_s();
+    static final menuaction_s s_keys_center_view_action = new menuaction_s();
+    static final menuaction_s s_keys_mouse_look_action = new menuaction_s();
+    static final menuaction_s s_keys_keyboard_look_action = new menuaction_s();
+    static final menuaction_s s_keys_move_up_action = new menuaction_s();
+    static final menuaction_s s_keys_move_down_action = new menuaction_s();
+    static final menuaction_s s_keys_inventory_action = new menuaction_s();
+    static final menuaction_s s_keys_inv_use_action = new menuaction_s();
+    static final menuaction_s s_keys_inv_drop_action = new menuaction_s();
+    static final menuaction_s s_keys_inv_prev_action = new menuaction_s();
+    static final menuaction_s s_keys_inv_next_action = new menuaction_s();
+    static final menuaction_s s_keys_help_computer_action = new menuaction_s();
+    static final menuframework_s s_options_menu = new menuframework_s();
+    static final menuaction_s s_options_defaults_action = new menuaction_s();
+    static final menuaction_s s_options_customize_options_action = new menuaction_s();
+    static final menuslider_s s_options_sensitivity_slider = new menuslider_s();
+    static final menulist_s s_options_freelook_box = new menulist_s();
+    static final menulist_s s_options_noalttab_box = new menulist_s();
+    static final menulist_s s_options_alwaysrun_box = new menulist_s();
+    static final menulist_s s_options_invertmouse_box = new menulist_s();
+    static final menulist_s s_options_lookspring_box = new menulist_s();
+    static final menulist_s s_options_lookstrafe_box = new menulist_s();
+    static final menulist_s s_options_crosshair_box = new menulist_s();
+    static final menuslider_s s_options_sfxvolume_slider = new menuslider_s();
+    static final menulist_s s_options_joystick_box = new menulist_s();
+    static final menulist_s s_options_cdvolume_box = new menulist_s();
+    static final menulist_s s_options_quality_list = new menulist_s();
+    //static menulist_s s_options_compatibility_list = new menulist_s();
+    static final menuaction_s s_options_console_action = new menuaction_s();
+    static final String[] cd_music_items = {"disabled", "enabled"};
+    static final String[] yesno_names = {"no", "yes"};
+    static final String[] crosshair_names = {"none", "cross", "dot", "angle"};
+    static final String[] creditsIndex = new String[256];
+    static final String[] idcredits = {"+QUAKE II BY ID SOFTWARE", "",
+            "+PROGRAMMING", "John Carmack", "John Cash", "Brian Hook", "",
+            "+JAVA PORT BY BYTONIC", "Carsten Weisse", "Holger Zickner", "Rene Stoeckel", "", "+ART",
+            "Adrian Carmack", "Kevin Cloud", "Paul Steed", "", "+LEVEL DESIGN",
+            "Tim Willits", "American McGee", "Christian Antkow",
+            "Paul Jaquays", "Brandon James", "", "+BIZ", "Todd Hollenshead",
+            "Barrett (Bear) Alexander", "Donna Jackson", "", "",
+            "+SPECIAL THANKS", "Ben Donges for beta testing", "", "", "", "",
+            "", "", "+ADDITIONAL SUPPORT", "", "+LINUX PORT AND CTF",
+            "Dave \"Zoid\" Kirsch", "", "+CINEMATIC SEQUENCES",
+            "Ending Cinematic by Blur Studio - ", "Venice, CA", "",
+            "Environment models for Introduction",
+            "Cinematic by Karl Dolgener", "",
+            "Assistance with environment design", "by Cliff Iwai", "",
+            "+SOUND EFFECTS AND MUSIC",
+            "Sound Design by Soundelux Media Labs.",
+            "Music Composed and Produced by",
+            "Soundelux Media Labs.  Special thanks",
+            "to Bill Brown, Tom Ozanich, Brian",
+            "Celano, Jeff Eisner, and The Soundelux", "Players.", "",
+            "\"Level Music\" by Sonic Mayhem", "www.sonicmayhem.com", "",
+            "\"Quake II Theme Song\"", "(C) 1997 Rob Zombie. All Rights",
+            "Reserved.", "", "Track 10 (\"Climb\") by Jer Sypult", "",
+            "Voice of computers by", "Carly Staehlin-Taylor", "",
+            "+THANKS TO ACTIVISION", "+UserInputHandler PARTICULAR:", "", "John Tam",
+            "Steve Rosenthal", "Marty Stratton", "Henk Hartong", "",
+            "Quake II(tm) (C)1997 Id Software, Inc.",
+            "All Rights Reserved.  Distributed by",
+            "Activision, Inc. under license.",
+            "Quake II(tm), the Id Software name,",
+            "the \"Q II\"(tm) logo and id(tm)",
+            "logo are trademarks of Id Software,",
+            "Inc. Activision(R) is a registered",
+            "trademark of Activision, Inc. All",
+            "other trademarks and trade names are",
+            "properties of their respective owners.", null};
+    static final String[] xatcredits = {"+QUAKE II MISSION PACK: THE RECKONING",
+            "+BY", "+XATRIX ENTERTAINMENT, INC.", "", "+DESIGN AND DIRECTION",
+            "Drew Markham", "", "+PRODUCED BY", "Greg Goodrich", "",
+            "+PROGRAMMING", "Rafael Paiz", "",
+            "+LEVEL DESIGN / ADDITIONAL GAME DESIGN", "Alex Mayberry", "",
+            "+LEVEL DESIGN", "Mal Blackwell", "Dan Koppel", "",
+            "+ART DIRECTION", "Michael \"Maxx\" Kaufman", "",
+            "+COMPUTER GRAPHICS SUPERVISOR AND",
+            "+CHARACTER ANIMATION DIRECTION", "Barry Dempsey", "",
+            "+SENIOR ANIMATOR AND MODELER", "Jason Hoover", "",
+            "+CHARACTER ANIMATION AND", "+MOTION CAPTURE SPECIALIST",
+            "Amit Doron", "", "+ART", "Claire Praderie-Markham",
+            "Viktor Antonov", "Corky Lehmkuhl", "", "+INTRODUCTION ANIMATION",
+            "Dominique Drozdz", "", "+ADDITIONAL LEVEL DESIGN", "Aaron Barber",
+            "Rhett Baldwin", "", "+3D CHARACTER ANIMATION TOOLS",
+            "Gerry Tyra, SA Technology", "",
+            "+ADDITIONAL EDITOR TOOL PROGRAMMING", "Robert Duffy", "",
+            "+ADDITIONAL PROGRAMMING", "Ryan Feltrin", "",
+            "+PRODUCTION COORDINATOR", "Victoria Sylvester", "",
+            "+SOUND DESIGN", "Gary Bradfield", "", "+MUSIC BY", "Sonic Mayhem",
+            "", "", "", "+SPECIAL THANKS", "+TO",
+            "+OUR FRIENDS AT ID SOFTWARE", "", "John Carmack", "John Cash",
+            "Brian Hook", "Adrian Carmack", "Kevin Cloud", "Paul Steed",
+            "Tim Willits", "Christian Antkow", "Paul Jaquays", "Brandon James",
+            "Todd Hollenshead", "Barrett (Bear) Alexander",
+            "Dave \"Zoid\" Kirsch", "Donna Jackson", "", "", "",
+            "+THANKS TO ACTIVISION", "+UserInputHandler PARTICULAR:", "", "Marty Stratton",
+            "Henk \"The Original Ripper\" Hartong", "Kevin Kraff",
+            "Jamey Gottlieb", "Chris Hepburn", "", "+AND THE GAME TESTERS", "",
+            "Tim Vanlaw", "Doug Jacobs", "Steven Rosenthal", "David Baker",
+            "Chris Campbell", "Aaron Casillas", "Steve Elwell",
+            "Derek Johnstone", "Igor Krinitskiy", "Samantha Lee",
+            "Michael Spann", "Chris Toft", "Juan Valdes", "",
+            "+THANKS TO INTERGRAPH COMPUTER SYTEMS", "+UserInputHandler PARTICULAR:", "",
+            "Michael T. Nicolaou", "", "",
+            "Quake II Mission Pack: The Reckoning",
+            "(tm) (C)1998 Id Software, Inc. All",
+            "Rights Reserved. Developed by Xatrix",
+            "Entertainment, Inc. for Id Software,",
+            "Inc. Distributed by Activision Inc.",
+            "under license. Quake(R) is a",
+            "registered trademark of Id Software,",
+            "Inc. Quake II Mission Pack: The",
+            "Reckoning(tm), Quake II(tm), the Id",
+            "Software name, the \"Q II\"(tm) logo",
+            "and id(tm) logo are trademarks of Id",
+            "Software, Inc. Activision(R) is a",
+            "registered trademark of Activision,",
+            "Inc. Xatrix(R) is a registered",
+            "trademark of Xatrix Entertainment,",
+            "Inc. All other trademarks and trade",
+            "names are properties of their", "respective owners.", null};
+    static final String[] roguecredits = {"+QUAKE II MISSION PACK 2: GROUND ZERO",
+            "+BY", "+ROGUE ENTERTAINMENT, INC.", "", "+PRODUCED BY",
+            "Jim Molinets", "", "+PROGRAMMING", "Peter Mack",
+            "Patrick Magruder", "", "+LEVEL DESIGN", "Jim Molinets",
+            "Cameron Lamprecht", "Berenger Fish", "Robert Selitto",
+            "Steve Tietze", "Steve Thoms", "", "+ART DIRECTION",
+            "Rich Fleider", "", "+ART", "Rich Fleider", "Steve Maines",
+            "Won Choi", "", "+ANIMATION SEQUENCES", "Creat Studios",
+            "Steve Maines", "", "+ADDITIONAL LEVEL DESIGN", "Rich Fleider",
+            "Steve Maines", "Peter Mack", "", "+SOUND", "James Grunke", "",
+            "+GROUND ZERO THEME", "+AND", "+MUSIC BY", "Sonic Mayhem", "",
+            "+VWEP MODELS", "Brent \"Hentai\" Dill", "", "", "",
+            "+SPECIAL THANKS", "+TO", "+OUR FRIENDS AT ID SOFTWARE", "",
+            "John Carmack", "John Cash", "Brian Hook", "Adrian Carmack",
+            "Kevin Cloud", "Paul Steed", "Tim Willits", "Christian Antkow",
+            "Paul Jaquays", "Brandon James", "Todd Hollenshead",
+            "Barrett (Bear) Alexander", "Katherine Anna Kang", "Donna Jackson",
+            "Dave \"Zoid\" Kirsch", "", "", "", "+THANKS TO ACTIVISION",
+            "+UserInputHandler PARTICULAR:", "", "Marty Stratton", "Henk Hartong",
+            "Mitch Lasky", "Steve Rosenthal", "Steve Elwell", "",
+            "+AND THE GAME TESTERS", "", "The Ranger Clan",
+            "Dave \"Zoid\" Kirsch", "Nihilistic Software", "Robert Duffy", "",
+            "And Countless Others", "", "", "",
+            "Quake II Mission Pack 2: Ground Zero",
+            "(tm) (C)1998 Id Software, Inc. All",
+            "Rights Reserved. Developed by Rogue",
+            "Entertainment, Inc. for Id Software,",
+            "Inc. Distributed by Activision Inc.",
+            "under license. Quake(R) is a",
+            "registered trademark of Id Software,",
+            "Inc. Quake II Mission Pack 2: Ground",
+            "Zero(tm), Quake II(tm), the Id",
+            "Software name, the \"Q II\"(tm) logo",
+            "and id(tm) logo are trademarks of Id",
+            "Software, Inc. Activision(R) is a",
+            "registered trademark of Activision,",
+            "Inc. Rogue(R) is a registered",
+            "trademark of Rogue Entertainment,",
+            "Inc. All other trademarks and trade",
+            "names are properties of their", "respective owners.", null};
+    static final menuframework_s s_game_menu = new menuframework_s();
+    static final menuaction_s s_easy_game_action = new menuaction_s();
+    static final menuaction_s s_medium_game_action = new menuaction_s();
+    static final menuaction_s s_hard_game_action = new menuaction_s();
+    static final menuaction_s s_load_game_action = new menuaction_s();
+    static final menuaction_s s_save_game_action = new menuaction_s();
+    static final menuaction_s s_credits_action = new menuaction_s();
+    static final menuseparator_s s_blankline = new menuseparator_s();
+    static final menuframework_s s_savegame_menu = new menuframework_s();
+    static final menuframework_s s_loadgame_menu = new menuframework_s();
+    static final menuaction_s[] s_loadgame_actions = new menuaction_s[MAX_SAVEGAMES];
+    //String m_savestrings[] = new String [MAX_SAVEGAMES][32];
+    static final String[] m_savestrings = new String[MAX_SAVEGAMES];
+    static final boolean[] m_savevalid = new boolean[MAX_SAVEGAMES];
+    /*
+     * =============================================================================
+     *
+     * SAVEGAME MENU
+     *
+     * =============================================================================
+     */
+    //static menuframework_s s_savegame_menu;
+    static final menuaction_s[] s_savegame_actions = new menuaction_s[MAX_SAVEGAMES];
+    static final menuframework_s s_joinserver_menu = new menuframework_s();
+    static final menuseparator_s s_joinserver_server_title = new menuseparator_s();
+    static final menuaction_s s_joinserver_search_action = new menuaction_s();
+    static final menuaction_s s_joinserver_address_book_action = new menuaction_s();
+    static final NetadrT[] local_server_netadr = new NetadrT[MAX_LOCAL_SERVERS];
+    static final String[] local_server_names = new String[MAX_LOCAL_SERVERS]; //[80];
+    static final menuaction_s[] s_joinserver_server_actions = new menuaction_s[MAX_LOCAL_SERVERS];
+    /*
+     * =============================================================================
+     *
+     * START SERVER MENU
+     *
+     * =============================================================================
+     */
+    static final menuframework_s s_startserver_menu = new menuframework_s();
+    static final menuaction_s s_startserver_start_action = new menuaction_s();
+    static final menuaction_s s_startserver_dmoptions_action = new menuaction_s();
+    static final menufield_s s_timelimit_field = new menufield_s();
+    static final menufield_s s_fraglimit_field = new menufield_s();
+    static final menufield_s s_maxclients_field = new menufield_s();
+    static final menufield_s s_hostname_field = new menufield_s();
+    static final menulist_s s_startmap_list = new menulist_s();
+    static final menulist_s s_rules_box = new menulist_s();
+    static final String[] dm_coop_names = {"deathmatch", "cooperative"};
+    static final String[] dm_coop_names_rogue = {"deathmatch", "cooperative", "tag"};
+    static final xcommand_t startServer_MenuDraw = new xcommand_t() {
+        public void execute() {
+            StartServer_MenuDraw();
+        }
+    };
+    static final menuframework_s s_dmoptions_menu = new menuframework_s();
+    static final menulist_s s_friendlyfire_box = new menulist_s();
+    static final menulist_s s_falls_box = new menulist_s();
+    /*
+     * =======================================================================
+     *
+     * VIDEO MENU
+     *
+     * =======================================================================
+     */
+    static final menulist_s s_instant_powerups_box = new menulist_s();
+    static final menulist_s s_powerups_box = new menulist_s();
+    static final menulist_s s_spawn_farthest_box = new menulist_s();
+    static final menulist_s s_teamplay_box = new menulist_s();
+    static final menulist_s s_samelevel_box = new menulist_s();
+    static final menulist_s s_force_respawn_box = new menulist_s();
+    static final menulist_s s_armor_box = new menulist_s();
+    static final menulist_s s_allow_exit_box = new menulist_s();
+    static final menulist_s s_infinite_ammo_box = new menulist_s();
+    static final menulist_s s_fixed_fov_box = new menulist_s();
+    static final menulist_s s_quad_drop_box = new menulist_s();
+    //   ROGUE
+    static final menulist_s s_no_mines_box = new menulist_s();
+    /*
+     * =============================================================================
+     *
+     * GAME MENU
+     *
+     * =============================================================================
+     */
+    static final menulist_s s_no_nukes_box = new menulist_s();
+    static final menulist_s s_stack_double_box = new menulist_s();
+    static final menulist_s s_no_spheres_box = new menulist_s();
+    //static String yes_no_names[] = { "no", "yes", 0 };
+    static final String[] teamplay_names = {"disabled", "by skin", "by model"};
+    /*
+     * =============================================================================
+     *
+     * DOWNLOADOPTIONS BOOK MENU
+     *
+     * =============================================================================
+     */
+    static final menuframework_s s_downloadoptions_menu = new menuframework_s();
+    static final menuseparator_s s_download_title = new menuseparator_s();
+    static final menulist_s s_allow_download_box = new menulist_s();
+    static final menulist_s s_allow_download_maps_box = new menulist_s();
+    static final menulist_s s_allow_download_models_box = new menulist_s();
+    static final menulist_s s_allow_download_players_box = new menulist_s();
+    static final menulist_s s_allow_download_sounds_box = new menulist_s();
+    static final String[] yes_no_names = {"no", "yes"};
+    static final menuframework_s s_addressbook_menu = new menuframework_s();
+    static final menufield_s[] s_addressbook_fields = new menufield_s[NUM_ADDRESSBOOK_ENTRIES];
+    /*
+     * =============================================================================
+     *
+     * PLAYER CONFIG MENU
+     *
+     * =============================================================================
+     */
+    static final menuframework_s s_player_config_menu = new menuframework_s();
+    static final menufield_s s_player_name_field = new menufield_s();
+    static final menulist_s s_player_model_box = new menulist_s();
+    /*
+     * =============================================================================
+     *
+     * LOADGAME MENU
+     *
+     * =============================================================================
+     */
+    static final menulist_s s_player_skin_box = new menulist_s();
+    static final menulist_s s_player_handedness_box = new menulist_s();
+    static final menulist_s s_player_rate_box = new menulist_s();
+    static final menuseparator_s s_player_skin_title = new menuseparator_s();
+    static final menuseparator_s s_player_model_title = new menuseparator_s();
+    static final menuseparator_s s_player_hand_title = new menuseparator_s();
+    static final menuseparator_s s_player_rate_title = new menuseparator_s();
+    static final menuaction_s s_player_download_action = new menuaction_s();
+    static final playermodelinfo_s[] s_pmi = new playermodelinfo_s[MAX_PLAYERMODELS];
+    static final int[] rate_tbl = {2500, 3200, 5000, 10000, 25000, 0};
+    static final String[] rate_names = {"28.8 Modem", "33.6 Modem", "Single ISDN",
+            "Dual ISDN/Cable", "T1/LAN", "User defined"};
+    static final String[] handedness = {"right", "left", "center"};
+    //   =============================================================================
+    /* Support Routines */
+    private static final entity_t entity = new entity_t();
+    public static int m_menudepth;
+    static int m_main_cursor;
+    static boolean m_entersound; // play after drawing a frame, so caching
+    static xcommand_t m_drawfunc;
+    static keyfunc_t m_keyfunc;
+    static final xcommand_t Menu_Multiplayer = new xcommand_t() {
+        public void execute() {
+            Menu_Multiplayer_f();
+        }
+    };
+    static final xcommand_t Menu_Video = new xcommand_t() {
+        public void execute() {
+            Menu_Video_f();
+        }
+    };
+    static final xcommand_t Menu_LoadGame = new xcommand_t() {
+        public void execute() {
+            Menu_LoadGame_f();
+        }
+    };
+    static final xcommand_t Menu_SaveGame = new xcommand_t() {
+        public void execute() {
+            Menu_SaveGame_f();
+        }
+    };
+    static final xcommand_t Menu_DownloadOptions = new xcommand_t() {
+        public void execute() {
+            Menu_DownloadOptions_f();
+        }
+    };
+    static final xcommand_t Menu_AddressBook = new xcommand_t() {
+        public void execute() {
+            Menu_AddressBook_f();
+        }
+    };
+    static final xcommand_t Menu_Quit = new xcommand_t() {
+        public void execute() {
+            Menu_Quit_f();
+        }
+    };
+    /*
+     * ============= DrawCursor
+     *
+     * Draws an animating cursor with the point at x,y. The pic will extend to
+     * the left of x, and both above and below y. =============
+     */
+    static boolean cached;
+    static xcommand_t Main_Draw = new xcommand_t() {
+        public void execute() {
+            Main_Draw();
+        }
+    };
+    static boolean bind_grab;
+    static final xcommand_t Menu_Keys = new xcommand_t() {
+        public void execute() {
+            Menu_Keys_f();
+        }
+    };
+    /*
+     * =======================================================================
+     *
+     * CONTROLS MENU
+     *
+     * =======================================================================
+     */
+    static CvarT win_noalttab;
+    static String compatibility_items[] = {"max compatibility",
+            "max performance"};
+    static String[] s_labels;
+    static String[] s_drivers;
+    static final xcommand_t Menu_Options = new xcommand_t() {
+        public void execute() {
+            Menu_Options_f();
+        }
+    };
+    /*
+     * =============================================================================
+     *
+     * END GAME MENU
+     *
+     * =============================================================================
+     */
+    static int credits_start_time;
+    static String creditsBuffer;
+    static String credits[] = idcredits;
+    static final xcommand_t Menu_Credits = new xcommand_t() {
+        public void execute() {
+            Menu_Credits_f();
+        }
+    };
+    static int m_game_cursor;
+    static final xcommand_t Menu_Main = new xcommand_t() {
+        public void execute() {
+            Menu_Main_f();
+        }
+    };
+    static final xcommand_t Menu_Game = new xcommand_t() {
+        public void execute() {
+            Menu_Game_f();
+        }
+    };
+    static keyfunc_t Main_Key = new keyfunc_t() {
+        public String execute(int key) {
+            return Main_Key(key);
+        }
+    };
+    static String difficulty_names[] = {"easy", "medium",
+            "fuckin shitty hard"};
+    static int m_num_servers;
+    static final xcommand_t Menu_JoinServer = new xcommand_t() {
+        public void execute() {
+            Menu_JoinServer_f();
+        }
+    };
+    static String mapnames[];
+    static int nummaps;
+    static final keyfunc_t startServer_MenuKey = new keyfunc_t() {
+        public String execute(int key) {
+            return StartServer_MenuKey(key);
+        }
+    };
+    static final xcommand_t Menu_StartServer = new xcommand_t() {
+        public void execute() {
+            Menu_StartServer_f();
+        }
+    };
+    /*
+     * =============================================================================
+     *
+     * DMOPTIONS BOOK MENU
+     *
+     * =============================================================================
+     */
+    static String dmoptions_statusbar; //[128];
+    static final xcommand_t Menu_DMOptions = new xcommand_t() {
+        public void execute() {
+            Menu_DMOptions_f();
+        }
+    };
+    static keyfunc_t AddressBook_MenuKey = new keyfunc_t() {
+        public String execute(int key) {
+            return AddressBook_MenuKey_f(key);
+        }
+    };
+    static xcommand_t AddressBook_MenuDraw = new xcommand_t() {
+        public void execute() {
+            AddressBook_MenuDraw_f();
+        }
+    };
+    static String s_pmnames[] = new String[MAX_PLAYERMODELS];
+    static int s_numplayermodels;
+    static int yaw;
+    static final xcommand_t Menu_PlayerConfig = new xcommand_t() {
+        public void execute() {
+            Menu_PlayerConfig_f();
+        }
+    };
+
+    static {
+        for (int n = 0; n < MAX_SAVEGAMES; n++)
+            s_loadgame_actions[n] = new menuaction_s();
+    }
+
+    static {
+        for (int n = 0; n < MAX_SAVEGAMES; n++)
+            m_savestrings[n] = "";
+    }
+
+    static {
+        for (int n = 0; n < MAX_SAVEGAMES; n++)
+            s_savegame_actions[n] = new menuaction_s();
+
+    }
+
+    //    user readable information
+    //    network address
+    static {
+        for (int n = 0; n < MAX_LOCAL_SERVERS; n++) {
+            local_server_netadr[n] = new NetadrT();
+            local_server_names[n] = "";
+            s_joinserver_server_actions[n] = new menuaction_s();
+            s_joinserver_server_actions[n].n = n;
+        }
+    }
+
+    static {
+        for (int n = 0; n < NUM_ADDRESSBOOK_ENTRIES; n++)
+            s_addressbook_fields[n] = new menufield_s();
+    }
+
+    int keys_cursor;
+
+    /*
+     * =============================================================================
+     * 
+     * JOIN SERVER MENU
+     * 
+     * =============================================================================
+     */
+
+    static void Banner(String name) {
+        Dimension dim = new Dimension();
+        Globals.re.DrawGetPicSize(dim, name);
+
+        Globals.re.DrawPic(viddef.width / 2 - dim.width / 2,
+                viddef.height / 2 - 110, name);
+    }
+
+    static void PushMenu(xcommand_t draw, keyfunc_t key) { //, String(*key)
+        // (int k) ) {
+        int i;
+
+        if (Cvar.variableValue("maxclients") == 1 && Globals.server_state != 0)
+            Cvar.set("paused", "1");
+
+        // if this menu is already present, drop back to that level
+        // to avoid stacking menus by hotkeys
+        for (i = 0; i < m_menudepth; i++)
+            if (m_layers[i].draw == draw && m_layers[i].key == key) {
+                m_menudepth = i;
+            }
+
+        if (i == m_menudepth) {
+            if (m_menudepth >= MAX_MENU_DEPTH)
+                Com.Error(ERR_FATAL, "PushMenu: MAX_MENU_DEPTH");
+
+            m_layers[m_menudepth].draw = draw;//m_drawfunc;
+            m_layers[m_menudepth].key = key;//m_keyfunc;
+        }
+        m_menudepth++;
+        m_drawfunc = draw;
+        m_keyfunc = key;
+
+        m_entersound = true;
+
+        clientStaticT.key_dest = key_menu;
+    }
+
+    static void ForceMenuOff() {
+        m_drawfunc = null;
+        m_keyfunc = null;
+        clientStaticT.key_dest = key_game;
+        m_menudepth = 0;
+        Key.ClearStates();
+        Cvar.set("paused", "0");
+    }
+
+    static void PopMenu() {
+        S.StartLocalSound(menu_out_sound);
+        m_menudepth--;
+        if (m_menudepth < 0)
+            Com.Error(ERR_FATAL, "PopMenu: depth < 1");
+
+        if (0 < m_menudepth) {
+            m_drawfunc = m_layers[m_menudepth - 1].draw;
+            m_keyfunc = m_layers[m_menudepth - 1].key;
+        }
+
+        if (0 == m_menudepth)
+            ForceMenuOff();
+
+
+    }
+
+    static String Default_MenuKey(menuframework_s m, int key) {
+        String sound = null;
+        menucommon_s item;
+
+        if (m != null) {
+            if ((item = Menu_ItemAtCursor(m)) != null) {
+                if (item.type == MTYPE_FIELD) {
+                    if (Field_Key((menufield_s) item, key))
+                        return null;
+                }
+            }
+        }
+
+        switch (key) {
+            case K_ESCAPE:
+                PopMenu();
+                return menu_out_sound;
+            case K_KP_UPARROW:
+            case K_UPARROW:
+                if (m != null) {
+                    m.cursor--;
+                    Menu_AdjustCursor(m, -1);
+                    sound = menu_move_sound;
+                }
+                break;
+            case K_TAB:
+                if (m != null) {
+                    m.cursor++;
+                    Menu_AdjustCursor(m, 1);
+                    sound = menu_move_sound;
+                }
+                break;
+            case K_KP_DOWNARROW:
+            case K_DOWNARROW:
+                if (m != null) {
+                    m.cursor++;
+                    Menu_AdjustCursor(m, 1);
+                    sound = menu_move_sound;
+                }
+                break;
+            case K_KP_LEFTARROW:
+            case K_LEFTARROW:
+                if (m != null) {
+                    Menu_SlideItem(m, -1);
+                    sound = menu_move_sound;
+                }
+                break;
+            case K_KP_RIGHTARROW:
+            case K_RIGHTARROW:
+                if (m != null) {
+                    Menu_SlideItem(m, 1);
+                    sound = menu_move_sound;
+                }
+                break;
+
+            case K_MOUSE1:
+            case K_MOUSE2:
+            case K_MOUSE3:
+            case K_JOY1:
+            case K_JOY2:
+            case K_JOY3:
+            case K_JOY4:
+        /*
+         * case K_AUX1 : case K_AUX2 : case K_AUX3 : case K_AUX4 : case K_AUX5 :
+         * case K_AUX6 : case K_AUX7 : case K_AUX8 : case K_AUX9 : case K_AUX10 :
+         * case K_AUX11 : case K_AUX12 : case K_AUX13 : case K_AUX14 : case
+         * K_AUX15 : case K_AUX16 : case K_AUX17 : case K_AUX18 : case K_AUX19 :
+         * case K_AUX20 : case K_AUX21 : case K_AUX22 : case K_AUX23 : case
+         * K_AUX24 : case K_AUX25 : case K_AUX26 : case K_AUX27 : case K_AUX28 :
+         * case K_AUX29 : case K_AUX30 : case K_AUX31 : case K_AUX32 :
+         */
+            case K_KP_ENTER:
+            case K_ENTER:
+                if (m != null)
+                    Menu_SelectItem(m);
+                sound = menu_move_sound;
+                break;
+        }
+
+        return sound;
+    }
+
+    /*
+     * ================ DrawCharacter
+     *
+     * Draws one solid graphics character cx and cy are in 320*240 coordinates,
+     * and will be centered on higher res screens. ================
+     */
+    public static void DrawCharacter(int cx, int cy, int num) {
+        re.DrawChar(cx + ((viddef.width - 320) >> 1), cy
+                + ((viddef.height - 240) >> 1), num);
+    }
+
+    public static void Print(int cx, int cy, String str) {
+        //while (*str)
+        for (int n = 0; n < str.length(); n++) {
+            DrawCharacter(cx, cy, str.charAt(n) + 128);
+            //str++;
+            cx += 8;
+        }
+    }
+
+    public static void PrintWhite(int cx, int cy, String str) {
+        for (int n = 0; n < str.length(); n++) {
+            DrawCharacter(cx, cy, str.charAt(n));
+            //str++;
+            cx += 8;
+        }
+    }
+
+    public static void DrawPic(int x, int y, String pic) {
+        re.DrawPic(x + ((viddef.width - 320) >> 1), y
+                + ((viddef.height - 240) >> 1), pic);
+    }
+
+    static void DrawCursor(int x, int y, int f) {
+        //char cursorname[80];
+        String cursorname;
+
+        assert (f >= 0) : "negative time and cursor bug";
+
+        f = Math.abs(f);
+
+        if (!cached) {
+            int i;
+
+            for (i = 0; i < NUM_CURSOR_FRAMES; i++) {
+                cursorname = "m_cursor" + i;
+
+                re.RegisterPic(cursorname);
+            }
+            cached = true;
+        }
+
+        cursorname = "m_cursor" + f;
+        re.DrawPic(x, y, cursorname);
+    }
+
+    public static void DrawTextBox(int x, int y, int width, int lines) {
+        int cx, cy;
+        int n;
+
+        // draw left side
+        cx = x;
+        cy = y;
+        DrawCharacter(cx, cy, 1);
+
+        for (n = 0; n < lines; n++) {
+            cy += 8;
+            DrawCharacter(cx, cy, 4);
+        }
+        DrawCharacter(cx, cy + 8, 7);
+
+        // draw middle
+        cx += 8;
+        while (width > 0) {
+            cy = y;
+            DrawCharacter(cx, cy, 2);
+
+            for (n = 0; n < lines; n++) {
+                cy += 8;
+                DrawCharacter(cx, cy, 5);
+            }
+            DrawCharacter(cx, cy + 8, 8);
+
+            width -= 1;
+            cx += 8;
+        }
+
+        // draw right side
+        cy = y;
+        DrawCharacter(cx, cy, 3);
+        for (n = 0; n < lines; n++) {
+            cy += 8;
+            DrawCharacter(cx, cy, 6);
+
+        }
+        DrawCharacter(cx, cy + 8, 9);
+
+    }
+
+    static void Main_Draw() {
+        int i;
+        int w, h;
+        int ystart;
+        int xoffset;
+        int widest = -1;
+        String litname;
+        String[] names = {"m_main_game", "m_main_multiplayer",
+                "m_main_options", "m_main_video", "m_main_quit"};
+        Dimension dim = new Dimension();
+
+        for (i = 0; i < names.length; i++) {
+            Globals.re.DrawGetPicSize(dim, names[i]);
+            w = dim.width;
+            h = dim.height;
+
+            if (w > widest)
+                widest = w;
+        }
+
+        ystart = (Globals.viddef.height / 2 - 110);
+        xoffset = (Globals.viddef.width - widest + 70) / 2;
+
+        for (i = 0; i < names.length; i++) {
+            if (i != m_main_cursor)
+                Globals.re.DrawPic(xoffset, ystart + i * 40 + 13, names[i]);
+        }
+
+        //strcat(litname, "_sel");
+        litname = names[m_main_cursor] + "_sel";
+        Globals.re.DrawPic(xoffset, ystart + m_main_cursor * 40 + 13, litname);
+
+        DrawCursor(xoffset - 25, ystart + m_main_cursor * 40 + 11,
+                (Globals.clientStaticT.realtime / 100) % NUM_CURSOR_FRAMES);
+
+        Globals.re.DrawGetPicSize(dim, "m_main_plaque");
+        w = dim.width;
+        h = dim.height;
+        Globals.re.DrawPic(xoffset - 30 - w, ystart, "m_main_plaque");
+
+        Globals.re.DrawPic(xoffset - 30 - w, ystart + h + 5, "m_main_logo");
+    }
+
+    static String Main_Key(int key) {
+        String sound = menu_move_sound;
+
+        switch (key) {
+            case Key.K_ESCAPE:
+                PopMenu();
+                break;
+
+            case Key.K_KP_DOWNARROW:
+            case Key.K_DOWNARROW:
+                if (++m_main_cursor >= MAIN_ITEMS)
+                    m_main_cursor = 0;
+                return sound;
+
+            case Key.K_KP_UPARROW:
+            case Key.K_UPARROW:
+                if (--m_main_cursor < 0)
+                    m_main_cursor = MAIN_ITEMS - 1;
+                return sound;
+
+            case Key.K_KP_ENTER:
+            case Key.K_ENTER:
+                m_entersound = true;
+
+                switch (m_main_cursor) {
+                    case 0:
+                        Menu_Game_f();
+                        break;
+
+                    case 1:
+                        Menu_Multiplayer_f();
+                        break;
+
+                    case 2:
+                        Menu_Options_f();
+                        break;
+
+                    case 3:
+                        Menu_Video_f();
+                        break;
+
+                    case 4:
+                        Menu_Quit_f();
+                        break;
+                }
+        }
+
+        return null;
+    }
+
+    static void Menu_Main_f() {
+        PushMenu(new xcommand_t() {
+            public void execute() {
+                Main_Draw();
+            }
+        }, new keyfunc_t() {
+            public String execute(int key) {
+                return Main_Key(key);
+            }
+        });
+    }
+
+    static void Multiplayer_MenuDraw() {
+        Banner("m_banner_multiplayer");
+
+        Menu_AdjustCursor(s_multiplayer_menu, 1);
+        Menu_Draw(s_multiplayer_menu);
+    }
+
+    static void PlayerSetupFunc(Object unused) {
+        Menu_PlayerConfig_f();
+    }
+
+    static void JoinNetworkServerFunc(Object unused) {
+        Menu_JoinServer_f();
+    }
+
+    static void StartNetworkServerFunc(Object unused) {
+        Menu_StartServer_f();
+    }
+
+    static void Multiplayer_MenuInit() {
+        s_multiplayer_menu.x = (int) (viddef.width * 0.50f - 64);
+        s_multiplayer_menu.nitems = 0;
+
+        s_join_network_server_action.type = MTYPE_ACTION;
+        s_join_network_server_action.flags = QMF_LEFT_JUSTIFY;
+        s_join_network_server_action.x = 0;
+        s_join_network_server_action.y = 0;
+        s_join_network_server_action.name = " join network server";
+        s_join_network_server_action.callback = new mcallback() {
+            public void execute(Object o) {
+                JoinNetworkServerFunc(o);
+            }
+
+        };
+
+        s_start_network_server_action.type = MTYPE_ACTION;
+        s_start_network_server_action.flags = QMF_LEFT_JUSTIFY;
+        s_start_network_server_action.x = 0;
+        s_start_network_server_action.y = 10;
+        s_start_network_server_action.name = " start network server";
+        s_start_network_server_action.callback = new mcallback() {
+            public void execute(Object o) {
+                StartNetworkServerFunc(o);
+            }
+        };
+
+        s_player_setup_action.type = MTYPE_ACTION;
+        s_player_setup_action.flags = QMF_LEFT_JUSTIFY;
+        s_player_setup_action.x = 0;
+        s_player_setup_action.y = 20;
+        s_player_setup_action.name = " player setup";
+        s_player_setup_action.callback = new mcallback() {
+            public void execute(Object o) {
+                PlayerSetupFunc(o);
+            }
+        };
+
+        Menu_AddItem(s_multiplayer_menu, s_join_network_server_action);
+        Menu_AddItem(s_multiplayer_menu, s_start_network_server_action);
+        Menu_AddItem(s_multiplayer_menu, s_player_setup_action);
+
+        Menu_SetStatusBar(s_multiplayer_menu, null);
+
+        Menu_Center(s_multiplayer_menu);
+    }
+
+    static String Multiplayer_MenuKey(int key) {
+        return Default_MenuKey(s_multiplayer_menu, key);
+    }
+
+    static void Menu_Multiplayer_f() {
+        Multiplayer_MenuInit();
+        PushMenu(new xcommand_t() {
+            public void execute() {
+                Multiplayer_MenuDraw();
+            }
+        }, new keyfunc_t() {
+            public String execute(int key) {
+                return Multiplayer_MenuKey(key);
+            }
+        });
+    }
+
+    static void UnbindCommand(String command) {
+        int j;
+        String b;
+
+        for (j = 0; j < 256; j++) {
+            b = keybindings[j];
+            if (b == null)
+                continue;
+            if (b.equals(command))
+                Key.SetBinding(j, "");
+        }
+    }
+
+    static void FindKeysForCommand(String command, int twokeys[]) {
+        int count;
+        int j;
+        String b;
+
+        twokeys[0] = twokeys[1] = -1;
+        count = 0;
+
+        for (j = 0; j < 256; j++) {
+            b = keybindings[j];
+            if (b == null)
+                continue;
+
+            if (b.equals(command)) {
+                twokeys[count] = j;
+                count++;
+                if (count == 2)
+                    break;
+            }
+        }
+    }
+
+    static void KeyCursorDrawFunc(menuframework_s menu) {
+        if (bind_grab)
+            re.DrawChar(menu.x, menu.y + menu.cursor * 9, '=');
+        else
+            re.DrawChar(menu.x, menu.y + menu.cursor * 9, 12 + (Timer
+                    .getCurrentTimeMillis() / 250 & 1));
+    }
+
+    static void DrawKeyBindingFunc(Object self) {
+        int keys[] = {0, 0};
+        menuaction_s a = (menuaction_s) self;
+
+        FindKeysForCommand(bindnames[a.localdata[0]][0], keys);
+
+        if (keys[0] == -1) {
+            Menu_DrawString(a.x + a.parent.x + 16, a.y + a.parent.y, "???");
+        } else {
+            int x;
+            String name;
+
+            name = Key.KeynumToString(keys[0]);
+
+            Menu_DrawString(a.x + a.parent.x + 16, a.y + a.parent.y, name);
+
+            x = name.length() * 8;
+
+            if (keys[1] != -1) {
+                Menu_DrawString(a.x + a.parent.x + 24 + x, a.y + a.parent.y,
+                        "or");
+                Menu_DrawString(a.x + a.parent.x + 48 + x, a.y + a.parent.y,
+                        Key.KeynumToString(keys[1]));
+            }
+        }
+    }
+
+    static void KeyBindingFunc(Object self) {
+        menuaction_s a = (menuaction_s) self;
+        int keys[] = {0, 0};
+
+        FindKeysForCommand(bindnames[a.localdata[0]][0], keys);
+
+        if (keys[1] != -1)
+            UnbindCommand(bindnames[a.localdata[0]][0]);
+
+        bind_grab = true;
+
+        Menu_SetStatusBar(s_keys_menu, "press a key or button for this action");
+    }
+
+    static void Keys_MenuInit() {
+        int y = 0;
+        int i = 0;
+
+        s_keys_menu.x = (int) (viddef.width * 0.50);
+        s_keys_menu.nitems = 0;
+        s_keys_menu.cursordraw = new mcallback() {
+            public void execute(Object o) {
+                KeyCursorDrawFunc((menuframework_s) o);
+            }
+        };
+
+        s_keys_walk_forward_action.type = MTYPE_ACTION;
+        s_keys_walk_forward_action.flags = QMF_GRAYED;
+        s_keys_walk_forward_action.x = 0;
+        s_keys_walk_forward_action.y = y += 9;
+        s_keys_walk_forward_action.ownerdraw = new mcallback() {
+            public void execute(Object o) {
+                DrawKeyBindingFunc(o);
+            }
+        };
+        s_keys_walk_forward_action.localdata[0] = ++i;
+        s_keys_walk_forward_action.name = bindnames[s_keys_walk_forward_action.localdata[0]][1];
+
+        s_keys_backpedal_action.type = MTYPE_ACTION;
+        s_keys_backpedal_action.flags = QMF_GRAYED;
+        s_keys_backpedal_action.x = 0;
+        s_keys_backpedal_action.y = y += 9;
+        s_keys_backpedal_action.ownerdraw = new mcallback() {
+            public void execute(Object o) {
+                DrawKeyBindingFunc(o);
+            }
+        };
+        s_keys_backpedal_action.localdata[0] = ++i;
+        s_keys_backpedal_action.name = bindnames[s_keys_backpedal_action.localdata[0]][1];
+
+        s_keys_turn_left_action.type = MTYPE_ACTION;
+        s_keys_turn_left_action.flags = QMF_GRAYED;
+        s_keys_turn_left_action.x = 0;
+        s_keys_turn_left_action.y = y += 9;
+        s_keys_turn_left_action.ownerdraw = new mcallback() {
+            public void execute(Object o) {
+                DrawKeyBindingFunc(o);
+            }
+        };
+        s_keys_turn_left_action.localdata[0] = ++i;
+        s_keys_turn_left_action.name = bindnames[s_keys_turn_left_action.localdata[0]][1];
+
+        s_keys_turn_right_action.type = MTYPE_ACTION;
+        s_keys_turn_right_action.flags = QMF_GRAYED;
+        s_keys_turn_right_action.x = 0;
+        s_keys_turn_right_action.y = y += 9;
+        s_keys_turn_right_action.ownerdraw = new mcallback() {
+            public void execute(Object o) {
+                DrawKeyBindingFunc(o);
+            }
+        };
+        s_keys_turn_right_action.localdata[0] = ++i;
+        s_keys_turn_right_action.name = bindnames[s_keys_turn_right_action.localdata[0]][1];
+
+        s_keys_run_action.type = MTYPE_ACTION;
+        s_keys_run_action.flags = QMF_GRAYED;
+        s_keys_run_action.x = 0;
+        s_keys_run_action.y = y += 9;
+        s_keys_run_action.ownerdraw = new mcallback() {
+            public void execute(Object o) {
+                DrawKeyBindingFunc(o);
+            }
+        };
+        s_keys_run_action.localdata[0] = ++i;
+        s_keys_run_action.name = bindnames[s_keys_run_action.localdata[0]][1];
+
+        s_keys_step_left_action.type = MTYPE_ACTION;
+        s_keys_step_left_action.flags = QMF_GRAYED;
+        s_keys_step_left_action.x = 0;
+        s_keys_step_left_action.y = y += 9;
+        s_keys_step_left_action.ownerdraw = new mcallback() {
+            public void execute(Object o) {
+                DrawKeyBindingFunc(o);
+            }
+        };
+        s_keys_step_left_action.localdata[0] = ++i;
+        s_keys_step_left_action.name = bindnames[s_keys_step_left_action.localdata[0]][1];
+
+        s_keys_step_right_action.type = MTYPE_ACTION;
+        s_keys_step_right_action.flags = QMF_GRAYED;
+        s_keys_step_right_action.x = 0;
+        s_keys_step_right_action.y = y += 9;
+        s_keys_step_right_action.ownerdraw = new mcallback() {
+            public void execute(Object o) {
+                DrawKeyBindingFunc(o);
+            }
+        };
+
+        s_keys_step_right_action.localdata[0] = ++i;
+        s_keys_step_right_action.name = bindnames[s_keys_step_right_action.localdata[0]][1];
+
+        s_keys_sidestep_action.type = MTYPE_ACTION;
+        s_keys_sidestep_action.flags = QMF_GRAYED;
+        s_keys_sidestep_action.x = 0;
+        s_keys_sidestep_action.y = y += 9;
+        s_keys_sidestep_action.ownerdraw = new mcallback() {
+            public void execute(Object o) {
+                DrawKeyBindingFunc(o);
+            }
+        };
+
+        s_keys_sidestep_action.localdata[0] = ++i;
+        s_keys_sidestep_action.name = bindnames[s_keys_sidestep_action.localdata[0]][1];
+
+        s_keys_look_up_action.type = MTYPE_ACTION;
+        s_keys_look_up_action.flags = QMF_GRAYED;
+        s_keys_look_up_action.x = 0;
+        s_keys_look_up_action.y = y += 9;
+        s_keys_look_up_action.ownerdraw = new mcallback() {
+            public void execute(Object o) {
+                DrawKeyBindingFunc(o);
+            }
+        };
+
+        s_keys_look_up_action.localdata[0] = ++i;
+        s_keys_look_up_action.name = bindnames[s_keys_look_up_action.localdata[0]][1];
+
+        s_keys_look_down_action.type = MTYPE_ACTION;
+        s_keys_look_down_action.flags = QMF_GRAYED;
+        s_keys_look_down_action.x = 0;
+        s_keys_look_down_action.y = y += 9;
+        s_keys_look_down_action.ownerdraw = new mcallback() {
+            public void execute(Object o) {
+                DrawKeyBindingFunc(o);
+            }
+        };
+
+        s_keys_look_down_action.localdata[0] = ++i;
+        s_keys_look_down_action.name = bindnames[s_keys_look_down_action.localdata[0]][1];
+
+        s_keys_center_view_action.type = MTYPE_ACTION;
+        s_keys_center_view_action.flags = QMF_GRAYED;
+        s_keys_center_view_action.x = 0;
+        s_keys_center_view_action.y = y += 9;
+        s_keys_center_view_action.ownerdraw = new mcallback() {
+            public void execute(Object o) {
+                DrawKeyBindingFunc(o);
+            }
+        };
+
+        s_keys_center_view_action.localdata[0] = ++i;
+        s_keys_center_view_action.name = bindnames[s_keys_center_view_action.localdata[0]][1];
+
+        s_keys_mouse_look_action.type = MTYPE_ACTION;
+        s_keys_mouse_look_action.flags = QMF_GRAYED;
+        s_keys_mouse_look_action.x = 0;
+        s_keys_mouse_look_action.y = y += 9;
+        s_keys_mouse_look_action.ownerdraw = new mcallback() {
+            public void execute(Object o) {
+                DrawKeyBindingFunc(o);
+            }
+        };
+
+        s_keys_mouse_look_action.localdata[0] = ++i;
+        s_keys_mouse_look_action.name = bindnames[s_keys_mouse_look_action.localdata[0]][1];
+
+        s_keys_keyboard_look_action.type = MTYPE_ACTION;
+        s_keys_keyboard_look_action.flags = QMF_GRAYED;
+        s_keys_keyboard_look_action.x = 0;
+        s_keys_keyboard_look_action.y = y += 9;
+        s_keys_keyboard_look_action.ownerdraw = new mcallback() {
+            public void execute(Object o) {
+                DrawKeyBindingFunc(o);
+            }
+        };
+
+        s_keys_keyboard_look_action.localdata[0] = ++i;
+        s_keys_keyboard_look_action.name = bindnames[s_keys_keyboard_look_action.localdata[0]][1];
+
+        s_keys_move_up_action.type = MTYPE_ACTION;
+        s_keys_move_up_action.flags = QMF_GRAYED;
+        s_keys_move_up_action.x = 0;
+        s_keys_move_up_action.y = y += 9;
+        s_keys_move_up_action.ownerdraw = new mcallback() {
+            public void execute(Object o) {
+                DrawKeyBindingFunc(o);
+            }
+        };
+
+        s_keys_move_up_action.localdata[0] = ++i;
+        s_keys_move_up_action.name = bindnames[s_keys_move_up_action.localdata[0]][1];
+
+        s_keys_move_down_action.type = MTYPE_ACTION;
+        s_keys_move_down_action.flags = QMF_GRAYED;
+        s_keys_move_down_action.x = 0;
+        s_keys_move_down_action.y = y += 9;
+        s_keys_move_down_action.ownerdraw = new mcallback() {
+            public void execute(Object o) {
+                DrawKeyBindingFunc(o);
+            }
+        };
+
+        s_keys_move_down_action.localdata[0] = ++i;
+        s_keys_move_down_action.name = bindnames[s_keys_move_down_action.localdata[0]][1];
+
+        s_keys_inventory_action.type = MTYPE_ACTION;
+        s_keys_inventory_action.flags = QMF_GRAYED;
+        s_keys_inventory_action.x = 0;
+        s_keys_inventory_action.y = y += 9;
+        s_keys_inventory_action.ownerdraw = new mcallback() {
+            public void execute(Object o) {
+                DrawKeyBindingFunc(o);
+            }
+        };
+
+        s_keys_inventory_action.localdata[0] = ++i;
+        s_keys_inventory_action.name = bindnames[s_keys_inventory_action.localdata[0]][1];
+
+        s_keys_inv_use_action.type = MTYPE_ACTION;
+        s_keys_inv_use_action.flags = QMF_GRAYED;
+        s_keys_inv_use_action.x = 0;
+        s_keys_inv_use_action.y = y += 9;
+        s_keys_inv_use_action.ownerdraw = new mcallback() {
+            public void execute(Object o) {
+                DrawKeyBindingFunc(o);
+            }
+        };
+
+        s_keys_inv_use_action.localdata[0] = ++i;
+        s_keys_inv_use_action.name = bindnames[s_keys_inv_use_action.localdata[0]][1];
+
+        s_keys_inv_drop_action.type = MTYPE_ACTION;
+        s_keys_inv_drop_action.flags = QMF_GRAYED;
+        s_keys_inv_drop_action.x = 0;
+        s_keys_inv_drop_action.y = y += 9;
+        s_keys_inv_drop_action.ownerdraw = new mcallback() {
+            public void execute(Object o) {
+                DrawKeyBindingFunc(o);
+            }
+        };
+
+        s_keys_inv_drop_action.localdata[0] = ++i;
+        s_keys_inv_drop_action.name = bindnames[s_keys_inv_drop_action.localdata[0]][1];
+
+        s_keys_inv_prev_action.type = MTYPE_ACTION;
+        s_keys_inv_prev_action.flags = QMF_GRAYED;
+        s_keys_inv_prev_action.x = 0;
+        s_keys_inv_prev_action.y = y += 9;
+        s_keys_inv_prev_action.ownerdraw = new mcallback() {
+            public void execute(Object o) {
+                DrawKeyBindingFunc(o);
+            }
+        };
+
+        s_keys_inv_prev_action.localdata[0] = ++i;
+        s_keys_inv_prev_action.name = bindnames[s_keys_inv_prev_action.localdata[0]][1];
+
+        s_keys_inv_next_action.type = MTYPE_ACTION;
+        s_keys_inv_next_action.flags = QMF_GRAYED;
+        s_keys_inv_next_action.x = 0;
+        s_keys_inv_next_action.y = y += 9;
+        s_keys_inv_next_action.ownerdraw = new mcallback() {
+            public void execute(Object o) {
+                DrawKeyBindingFunc(o);
+            }
+        };
+
+        s_keys_inv_next_action.localdata[0] = ++i;
+        s_keys_inv_next_action.name = bindnames[s_keys_inv_next_action.localdata[0]][1];
+
+        s_keys_help_computer_action.type = MTYPE_ACTION;
+        s_keys_help_computer_action.flags = QMF_GRAYED;
+        s_keys_help_computer_action.x = 0;
+        s_keys_help_computer_action.y = y += 9;
+        s_keys_help_computer_action.ownerdraw = new mcallback() {
+            public void execute(Object o) {
+                DrawKeyBindingFunc(o);
+            }
+        };
+
+        s_keys_help_computer_action.localdata[0] = ++i;
+        s_keys_help_computer_action.name = bindnames[s_keys_help_computer_action.localdata[0]][1];
+
+        Menu_AddItem(s_keys_menu, s_keys_walk_forward_action);
+        Menu_AddItem(s_keys_menu, s_keys_backpedal_action);
+        Menu_AddItem(s_keys_menu, s_keys_turn_left_action);
+        Menu_AddItem(s_keys_menu, s_keys_turn_right_action);
+        Menu_AddItem(s_keys_menu, s_keys_run_action);
+        Menu_AddItem(s_keys_menu, s_keys_step_left_action);
+        Menu_AddItem(s_keys_menu, s_keys_step_right_action);
+        Menu_AddItem(s_keys_menu, s_keys_sidestep_action);
+        Menu_AddItem(s_keys_menu, s_keys_look_up_action);
+        Menu_AddItem(s_keys_menu, s_keys_look_down_action);
+        Menu_AddItem(s_keys_menu, s_keys_center_view_action);
+        Menu_AddItem(s_keys_menu, s_keys_mouse_look_action);
+        Menu_AddItem(s_keys_menu, s_keys_keyboard_look_action);
+        Menu_AddItem(s_keys_menu, s_keys_move_up_action);
+        Menu_AddItem(s_keys_menu, s_keys_move_down_action);
+
+        Menu_AddItem(s_keys_menu, s_keys_inventory_action);
+        Menu_AddItem(s_keys_menu, s_keys_inv_use_action);
+        Menu_AddItem(s_keys_menu, s_keys_inv_drop_action);
+        Menu_AddItem(s_keys_menu, s_keys_inv_prev_action);
+        Menu_AddItem(s_keys_menu, s_keys_inv_next_action);
+
+        Menu_AddItem(s_keys_menu, s_keys_help_computer_action);
+
+        Menu_SetStatusBar(s_keys_menu, "enter to change, backspace to clear");
+        Menu_Center(s_keys_menu);
+    }
+
+    static void Keys_MenuDraw_f() {
+        Menu_AdjustCursor(s_keys_menu, 1);
+        Menu_Draw(s_keys_menu);
+    }
+
+    static String Keys_MenuKey_f(int key) {
+        menuaction_s item = (menuaction_s) Menu_ItemAtCursor(s_keys_menu);
+
+        if (bind_grab) {
+            if (key != K_ESCAPE && key != '`') {
+                //char cmd[1024];
+                String cmd;
+
+                //Com_sprintf(cmd, sizeof(cmd), "bind \"%s\" \"%s\"\n",
+                // Key_KeynumToString(key), bindnames[item.localdata[0]][0]);
+                cmd = "bind \"" + Key.KeynumToString(key) + "\" \""
+                        + bindnames[item.localdata[0]][0] + "\"";
+                CommandBuffer.InsertText(cmd);
+            }
+
+            Menu_SetStatusBar(s_keys_menu,
+                    "enter to change, backspace to clear");
+            bind_grab = false;
+            return menu_out_sound;
+        }
+
+        switch (key) {
+            case K_KP_ENTER:
+            case K_ENTER:
+                KeyBindingFunc(item);
+                return menu_in_sound;
+            case K_BACKSPACE: // delete bindings
+            case K_DEL: // delete bindings
+            case K_KP_DEL:
+                UnbindCommand(bindnames[item.localdata[0]][0]);
+                return menu_out_sound;
+            default:
+                return Default_MenuKey(s_keys_menu, key);
+        }
+    }
+
+    static void Menu_Keys_f() {
+        Keys_MenuInit();
+        PushMenu(new xcommand_t() {
+            public void execute() {
+                Keys_MenuDraw_f();
+            }
+        }, new keyfunc_t() {
+            public String execute(int key) {
+                return Keys_MenuKey_f(key);
+            }
+        });
+    }
+
+    static void CrosshairFunc(Object unused) {
+        Cvar.setValue("crosshair", s_options_crosshair_box.curvalue);
+    }
+
+    static void JoystickFunc(Object unused) {
+        Cvar.setValue("in_joystick", s_options_joystick_box.curvalue);
+    }
+
+    static void CustomizeControlsFunc(Object unused) {
+        Menu_Keys_f();
+    }
+
+    static void AlwaysRunFunc(Object unused) {
+        Cvar.setValue("cl_run", s_options_alwaysrun_box.curvalue);
+    }
+
+    static void FreeLookFunc(Object unused) {
+        Cvar.setValue("freelook", s_options_freelook_box.curvalue);
+    }
+
+    static void MouseSpeedFunc(Object unused) {
+        Cvar.setValue("sensitivity",
+                s_options_sensitivity_slider.curvalue / 2.0F);
+    }
+
+    static void NoAltTabFunc(Object unused) {
+        Cvar.setValue("win_noalttab", s_options_noalttab_box.curvalue);
+    }
+
+    static float ClampCvar(float max, float value) {
+        if (value < (float) 0)
+            return (float) 0;
+        if (value > max)
+            return max;
+        return value;
+    }
+
+    static void ControlsSetMenuItemValues() {
+        s_options_sfxvolume_slider.curvalue = Cvar.variableValue("s_volume") * 10;
+        s_options_cdvolume_box.curvalue = 1 - ((int) Cvar
+                .variableValue("cd_nocd"));
+        //s_options_quality_list.curvalue = 1 - ((int)
+        // Cvar.variableValue("s_loadas8bit"));
+        String s = Cvar.variableString("s_impl");
+        for (int i = 0; i < s_drivers.length; i++) {
+            if (s.equals(s_drivers[i])) {
+                s_options_quality_list.curvalue = i;
+            }
+        }
+
+        s_options_sensitivity_slider.curvalue = (sensitivity.value) * 2;
+
+        Cvar.setValue("cl_run", ClampCvar(1, cl_run.value));
+        s_options_alwaysrun_box.curvalue = (int) cl_run.value;
+
+        s_options_invertmouse_box.curvalue = m_pitch.value < 0 ? 1 : 0;
+
+        Cvar.setValue("lookspring", ClampCvar(1, lookspring.value));
+        s_options_lookspring_box.curvalue = (int) lookspring.value;
+
+        Cvar.setValue("lookstrafe", ClampCvar(1, lookstrafe.value));
+        s_options_lookstrafe_box.curvalue = (int) lookstrafe.value;
+
+        Cvar.setValue("freelook", ClampCvar(1, freelook.value));
+        s_options_freelook_box.curvalue = (int) freelook.value;
+
+        Cvar.setValue("crosshair", ClampCvar(3, Globals.crosshair.value));
+        s_options_crosshair_box.curvalue = (int) Globals.crosshair.value;
+
+        Cvar.setValue("in_joystick", ClampCvar(1, in_joystick.value));
+        s_options_joystick_box.curvalue = (int) in_joystick.value;
+
+        s_options_noalttab_box.curvalue = (int) win_noalttab.value;
+    }
+
+    static void ControlsResetDefaultsFunc(Object unused) {
+        CommandBuffer.AddText("exec default.cfg\n");
+        CommandBuffer.execute();
+
+        ControlsSetMenuItemValues();
+    }
+
+    static void InvertMouseFunc(Object unused) {
+        Cvar.setValue("m_pitch", -m_pitch.value);
+    }
+
+    static void LookspringFunc(Object unused) {
+        Cvar.setValue("lookspring", 1 - lookspring.value);
+    }
+
+    static void LookstrafeFunc(Object unused) {
+        Cvar.setValue("lookstrafe", 1 - lookstrafe.value);
+    }
+
+    static void UpdateVolumeFunc(Object unused) {
+        Cvar.setValue("s_volume", s_options_sfxvolume_slider.curvalue / 10);
+    }
+
+    static void UpdateCDVolumeFunc(Object unused) {
+        Cvar.setValue("cd_nocd", 1 - s_options_cdvolume_box.curvalue);
+    }
+
+    static void ConsoleFunc(Object unused) {
+        /*
+         * * the proper way to do this is probably to have ToggleConsole_f
+         * accept a parameter
+         */
+
+        if (clientStateT.attractloop) {
+            CommandBuffer.AddText("killserver\n");
+            return;
+        }
+
+        Key.ClearTyping();
+        Console.ClearNotify();
+
+        ForceMenuOff();
+        clientStaticT.key_dest = key_console;
+    }
+
+    static void UpdateSoundQualityFunc(Object unused) {
+        boolean driverNotChanged = false;
+        String current = s_drivers[s_options_quality_list.curvalue];
+        driverNotChanged = S.getDriverName().equals(current);
+//        if (s_options_quality_list.curvalue != 0) {
+//            //                       Cvar.setValue("s_khz", 22);
+//            //                       Cvar.setValue("s_loadas8bit", 0);
+//            driverNotChanged = S.getDriverName().equals("dummy");
+//            Cvar.Set("s_impl", "dummy");
+//        } else {
+//            //                       Cvar.setValue("s_khz", 11);
+//            //                       Cvar.setValue("s_loadas8bit", 1);
+//            driverNotChanged = S.getDriverName().equals("joal");
+//            Cvar.Set("s_impl", "joal");
+//        }
+
+        //Cvar.setValue("s_primary", s_options_compatibility_list.curvalue);
+
+        if (driverNotChanged) {
+            re.EndFrame();
+        } else {
+            Cvar.set("s_impl", current);
+
+            DrawTextBox(8, 120 - 48, 36, 3);
+            Print(16 + 16, 120 - 48 + 8, "Restarting the sound system. This");
+            Print(16 + 16, 120 - 48 + 16, "could take up to a minute, so");
+            Print(16 + 16, 120 - 48 + 24, "please be patient.");
+
+            // the text box won't show up unless we do a buffer swap
+            re.EndFrame();
+
+            Client.Snd_Restart_f.execute();
+        }
+    }
+
+    static void Options_MenuInit() {
+
+        s_drivers = S.getDriverNames();
+        s_labels = new String[s_drivers.length];
+        for (int i = 0; i < s_drivers.length; i++) {
+            if ("dummy".equals(s_drivers[i])) {
+                s_labels[i] = "off";
+            } else {
+                s_labels[i] = s_drivers[i];
+            }
+        }
+
+        win_noalttab = Cvar.get("win_noalttab", "0", CVAR_ARCHIVE);
+
+        /*
+         * * configure controls menu and menu items
+         */
+        s_options_menu.x = viddef.width / 2;
+        s_options_menu.y = viddef.height / 2 - 58;
+        s_options_menu.nitems = 0;
+
+        s_options_sfxvolume_slider.type = MTYPE_SLIDER;
+        s_options_sfxvolume_slider.x = 0;
+        s_options_sfxvolume_slider.y = 0;
+        s_options_sfxvolume_slider.name = "effects volume";
+        s_options_sfxvolume_slider.callback = new mcallback() {
+            public void execute(Object o) {
+                UpdateVolumeFunc(o);
+            }
+        };
+        s_options_sfxvolume_slider.minvalue = 0;
+        s_options_sfxvolume_slider.maxvalue = 10;
+        s_options_sfxvolume_slider.curvalue = Cvar.variableValue("s_volume") * 10;
+
+        s_options_cdvolume_box.type = MTYPE_SPINCONTROL;
+        s_options_cdvolume_box.x = 0;
+        s_options_cdvolume_box.y = 10;
+        s_options_cdvolume_box.name = "CD music";
+        s_options_cdvolume_box.callback = new mcallback() {
+            public void execute(Object o) {
+                UpdateCDVolumeFunc(o);
+            }
+        };
+        s_options_cdvolume_box.itemnames = cd_music_items;
+        s_options_cdvolume_box.curvalue = 1 - (int) Cvar
+                .variableValue("cd_nocd");
+
+        s_options_quality_list.type = MTYPE_SPINCONTROL;
+        s_options_quality_list.x = 0;
+        s_options_quality_list.y = 20;
+        s_options_quality_list.name = "sound";
+        s_options_quality_list.callback = new mcallback() {
+            public void execute(Object o) {
+                UpdateSoundQualityFunc(o);
+            }
+        };
+        s_options_quality_list.itemnames = s_labels;
+
+        s_options_sensitivity_slider.type = MTYPE_SLIDER;
+        s_options_sensitivity_slider.x = 0;
+        s_options_sensitivity_slider.y = 50;
+        s_options_sensitivity_slider.name = "mouse speed";
+        s_options_sensitivity_slider.callback = new mcallback() {
+            public void execute(Object o) {
+                MouseSpeedFunc(o);
+            }
+        };
+        s_options_sensitivity_slider.minvalue = 2;
+        s_options_sensitivity_slider.maxvalue = 22;
+
+        s_options_alwaysrun_box.type = MTYPE_SPINCONTROL;
+        s_options_alwaysrun_box.x = 0;
+        s_options_alwaysrun_box.y = 60;
+        s_options_alwaysrun_box.name = "always run";
+        s_options_alwaysrun_box.callback = new mcallback() {
+            public void execute(Object o) {
+                AlwaysRunFunc(o);
+            }
+        };
+        s_options_alwaysrun_box.itemnames = yesno_names;
+
+        s_options_invertmouse_box.type = MTYPE_SPINCONTROL;
+        s_options_invertmouse_box.x = 0;
+        s_options_invertmouse_box.y = 70;
+        s_options_invertmouse_box.name = "invert mouse";
+        s_options_invertmouse_box.callback = new mcallback() {
+            public void execute(Object o) {
+                InvertMouseFunc(o);
+            }
+        };
+        s_options_invertmouse_box.itemnames = yesno_names;
+
+        s_options_lookspring_box.type = MTYPE_SPINCONTROL;
+        s_options_lookspring_box.x = 0;
+        s_options_lookspring_box.y = 80;
+        s_options_lookspring_box.name = "lookspring";
+        s_options_lookspring_box.callback = new mcallback() {
+            public void execute(Object o) {
+                LookspringFunc(o);
+            }
+        };
+        s_options_lookspring_box.itemnames = yesno_names;
+
+        s_options_lookstrafe_box.type = MTYPE_SPINCONTROL;
+        s_options_lookstrafe_box.x = 0;
+        s_options_lookstrafe_box.y = 90;
+        s_options_lookstrafe_box.name = "lookstrafe";
+        s_options_lookstrafe_box.callback = new mcallback() {
+            public void execute(Object o) {
+                LookstrafeFunc(o);
+            }
+        };
+        s_options_lookstrafe_box.itemnames = yesno_names;
+
+        s_options_freelook_box.type = MTYPE_SPINCONTROL;
+        s_options_freelook_box.x = 0;
+        s_options_freelook_box.y = 100;
+        s_options_freelook_box.name = "free look";
+        s_options_freelook_box.callback = new mcallback() {
+            public void execute(Object o) {
+                FreeLookFunc(o);
+            }
+        };
+        s_options_freelook_box.itemnames = yesno_names;
+
+        s_options_crosshair_box.type = MTYPE_SPINCONTROL;
+        s_options_crosshair_box.x = 0;
+        s_options_crosshair_box.y = 110;
+        s_options_crosshair_box.name = "crosshair";
+        s_options_crosshair_box.callback = new mcallback() {
+            public void execute(Object o) {
+                CrosshairFunc(o);
+            }
+        };
+        s_options_crosshair_box.itemnames = crosshair_names;
+        /*
+         * s_options_noalttab_box.type = MTYPE_SPINCONTROL;
+         * s_options_noalttab_box.x = 0; s_options_noalttab_box.y = 110;
+         * s_options_noalttab_box.name = "disable alt-tab";
+         * s_options_noalttab_box.callback = NoAltTabFunc;
+         * s_options_noalttab_box.itemnames = yesno_names;
+         */
+        s_options_joystick_box.type = MTYPE_SPINCONTROL;
+        s_options_joystick_box.x = 0;
+        s_options_joystick_box.y = 120;
+        s_options_joystick_box.name = "use joystick";
+        s_options_joystick_box.callback = new mcallback() {
+            public void execute(Object o) {
+                JoystickFunc(o);
+            }
+        };
+        s_options_joystick_box.itemnames = yesno_names;
+
+        s_options_customize_options_action.type = MTYPE_ACTION;
+        s_options_customize_options_action.x = 0;
+        s_options_customize_options_action.y = 140;
+        s_options_customize_options_action.name = "customize controls";
+        s_options_customize_options_action.callback = new mcallback() {
+            public void execute(Object o) {
+                CustomizeControlsFunc(o);
+            }
+        };
+
+        s_options_defaults_action.type = MTYPE_ACTION;
+        s_options_defaults_action.x = 0;
+        s_options_defaults_action.y = 150;
+        s_options_defaults_action.name = "reset defaults";
+        s_options_defaults_action.callback = new mcallback() {
+            public void execute(Object o) {
+                ControlsResetDefaultsFunc(o);
+            }
+        };
+
+        s_options_console_action.type = MTYPE_ACTION;
+        s_options_console_action.x = 0;
+        s_options_console_action.y = 160;
+        s_options_console_action.name = "go to console";
+        s_options_console_action.callback = new mcallback() {
+            public void execute(Object o) {
+                ConsoleFunc(o);
+            }
+        };
+
+        ControlsSetMenuItemValues();
+
+        Menu_AddItem(s_options_menu, s_options_sfxvolume_slider);
+
+        Menu_AddItem(s_options_menu, s_options_cdvolume_box);
+        Menu_AddItem(s_options_menu, s_options_quality_list);
+        //             Menu_AddItem(s_options_menu, s_options_compatibility_list);
+        Menu_AddItem(s_options_menu, s_options_sensitivity_slider);
+        Menu_AddItem(s_options_menu, s_options_alwaysrun_box);
+        Menu_AddItem(s_options_menu, s_options_invertmouse_box);
+        Menu_AddItem(s_options_menu, s_options_lookspring_box);
+        Menu_AddItem(s_options_menu, s_options_lookstrafe_box);
+        Menu_AddItem(s_options_menu, s_options_freelook_box);
+        Menu_AddItem(s_options_menu, s_options_crosshair_box);
+        //             Menu_AddItem(s_options_menu, s_options_joystick_box);
+        Menu_AddItem(s_options_menu, s_options_customize_options_action);
+        Menu_AddItem(s_options_menu, s_options_defaults_action);
+        Menu_AddItem(s_options_menu, s_options_console_action);
+    }
+
+    static void Options_MenuDraw() {
+        Banner("m_banner_options");
+        Menu_AdjustCursor(s_options_menu, 1);
+        Menu_Draw(s_options_menu);
+    }
+
+    static String Options_MenuKey(int key) {
+        return Default_MenuKey(s_options_menu, key);
+    }
+
+    static void Menu_Options_f() {
+        Options_MenuInit();
+        PushMenu(new xcommand_t() {
+            public void execute() {
+                Options_MenuDraw();
+            }
+        }, new keyfunc_t() {
+            public String execute(int key) {
+                return Options_MenuKey(key);
+            }
+        });
+    }
+
+    static void Menu_Video_f() {
+        VideoDriver.MenuInit();
+        PushMenu(new xcommand_t() {
+            public void execute() {
+                VideoDriver.MenuDraw();
+            }
+        }, new keyfunc_t() {
+            public String execute(int key) {
+                return VideoDriver.MenuKey(key);
+            }
+        });
+    }
+
+    public static void Credits_MenuDraw() {
+        int i, y;
+
+        /*
+         * * draw the credits
+         */
+        for (i = 0, y = (int) (viddef.height - ((clientStaticT.realtime - credits_start_time) / 40.0F)); credits[i] != null
+                && y < viddef.height; y += 10, i++) {
+            int j, stringoffset = 0;
+            boolean bold = false;
+
+            if (y <= -8)
+                continue;
+
+            if (credits[i].length() > 0 && credits[i].charAt(0) == '+') {
+                bold = true;
+                stringoffset = 1;
+            } else {
+                bold = false;
+                stringoffset = 0;
+            }
+
+            for (j = 0; j + stringoffset < credits[i].length(); j++) {
+                int x;
+
+                x = (viddef.width - credits[i].length() * 8 - stringoffset * 8)
+                        / 2 + (j + stringoffset) * 8;
+
+                if (bold)
+                    re
+                            .DrawChar(x, y,
+                                    credits[i].charAt(j + stringoffset) + 128);
+                else
+                    re.DrawChar(x, y, credits[i].charAt(j + stringoffset));
+            }
+        }
+
+        if (y < 0)
+            credits_start_time = clientStaticT.realtime;
+    }
+
+    public static String Credits_Key(int key) {
+        switch (key) {
+            case K_ESCAPE:
+                if (creditsBuffer != null)
+                    //FS.FreeFile(creditsBuffer);
+                    ;
+                PopMenu();
+                break;
+        }
+
+        return menu_out_sound;
+
+    }
+
+    static void Menu_Credits_f() {
+        int n;
+        int isdeveloper = 0;
+
+        byte b[] = FS.LoadFile("credits");
+
+        if (b != null) {
+            creditsBuffer = new String(b);
+            String line[] = creditsBuffer.split("\r\n");
+
+            for (n = 0; n < line.length; n++) {
+                creditsIndex[n] = line[n];
+            }
+
+            creditsIndex[n] = null;
+            credits = creditsIndex;
+        } else {
+            isdeveloper = FS.Developer_searchpath(1);
+
+            if (isdeveloper == 1) // xatrix
+                credits = xatcredits;
+            else if (isdeveloper == 2) // ROGUE
+                credits = roguecredits;
+            else {
+                credits = idcredits;
+            }
+
+        }
+
+        credits_start_time = clientStaticT.realtime;
+        PushMenu(new xcommand_t() {
+            public void execute() {
+                Credits_MenuDraw();
+            }
+        }, new keyfunc_t() {
+            public String execute(int key) {
+                return Credits_Key(key);
+            }
+        });
+    }
+
+    static void StartGame() {
+        // disable updates and start the cinematic going
+        clientStateT.servercount = -1;
+        ForceMenuOff();
+        Cvar.setValue("deathmatch", 0);
+        Cvar.setValue("coop", 0);
+
+        Cvar.setValue("gamerules", 0); //PGM
+
+        CommandBuffer.AddText("loading ; killserver ; wait ; newgame\n");
+        clientStaticT.key_dest = key_game;
+    }
+
+    static void EasyGameFunc(Object data) {
+        Cvar.forceSet("skill", "0");
+        StartGame();
+    }
+
+    static void MediumGameFunc(Object data) {
+        Cvar.forceSet("skill", "1");
+        StartGame();
+    }
+
+    static void HardGameFunc(Object data) {
+        Cvar.forceSet("skill", "2");
+        StartGame();
+    }
+
+    static void LoadGameFunc(Object unused) {
+        Menu_LoadGame_f();
+    }
+
+    static void SaveGameFunc(Object unused) {
+        Menu_SaveGame_f();
+    }
+
+    static void CreditsFunc(Object unused) {
+        Menu_Credits_f();
+    }
+
+    static void Game_MenuInit() {
+
+        s_game_menu.x = (int) (viddef.width * 0.50);
+        s_game_menu.nitems = 0;
+
+        s_easy_game_action.type = MTYPE_ACTION;
+        s_easy_game_action.flags = QMF_LEFT_JUSTIFY;
+        s_easy_game_action.x = 0;
+        s_easy_game_action.y = 0;
+        s_easy_game_action.name = "easy";
+        s_easy_game_action.callback = new mcallback() {
+            public void execute(Object o) {
+                EasyGameFunc(o);
+            }
+        };
+
+        s_medium_game_action.type = MTYPE_ACTION;
+        s_medium_game_action.flags = QMF_LEFT_JUSTIFY;
+        s_medium_game_action.x = 0;
+        s_medium_game_action.y = 10;
+        s_medium_game_action.name = "medium";
+        s_medium_game_action.callback = new mcallback() {
+            public void execute(Object o) {
+                MediumGameFunc(o);
+            }
+        };
+
+        s_hard_game_action.type = MTYPE_ACTION;
+        s_hard_game_action.flags = QMF_LEFT_JUSTIFY;
+        s_hard_game_action.x = 0;
+        s_hard_game_action.y = 20;
+        s_hard_game_action.name = "hard";
+        s_hard_game_action.callback = new mcallback() {
+            public void execute(Object o) {
+                HardGameFunc(o);
+            }
+        };
+
+        s_blankline.type = MTYPE_SEPARATOR;
+
+        s_load_game_action.type = MTYPE_ACTION;
+        s_load_game_action.flags = QMF_LEFT_JUSTIFY;
+        s_load_game_action.x = 0;
+        s_load_game_action.y = 40;
+        s_load_game_action.name = "load game";
+        s_load_game_action.callback = new mcallback() {
+            public void execute(Object o) {
+                LoadGameFunc(o);
+            }
+        };
+
+        s_save_game_action.type = MTYPE_ACTION;
+        s_save_game_action.flags = QMF_LEFT_JUSTIFY;
+        s_save_game_action.x = 0;
+        s_save_game_action.y = 50;
+        s_save_game_action.name = "save game";
+        s_save_game_action.callback = new mcallback() {
+            public void execute(Object o) {
+                SaveGameFunc(o);
+            }
+        };
+
+        s_credits_action.type = MTYPE_ACTION;
+        s_credits_action.flags = QMF_LEFT_JUSTIFY;
+        s_credits_action.x = 0;
+        s_credits_action.y = 60;
+        s_credits_action.name = "credits";
+        s_credits_action.callback = new mcallback() {
+            public void execute(Object o) {
+                CreditsFunc(o);
+            }
+        };
+
+        Menu_AddItem(s_game_menu, s_easy_game_action);
+        Menu_AddItem(s_game_menu, s_medium_game_action);
+        Menu_AddItem(s_game_menu, s_hard_game_action);
+        Menu_AddItem(s_game_menu, s_blankline);
+        Menu_AddItem(s_game_menu, s_load_game_action);
+        Menu_AddItem(s_game_menu, s_save_game_action);
+        Menu_AddItem(s_game_menu, s_blankline);
+        Menu_AddItem(s_game_menu, s_credits_action);
+
+        Menu_Center(s_game_menu);
+    }
+
+    static void Game_MenuDraw() {
+        Banner("m_banner_game");
+        Menu_AdjustCursor(s_game_menu, 1);
+        Menu_Draw(s_game_menu);
+    }
+
+    //   ROGUE
+
+    static String Game_MenuKey(int key) {
+        return Default_MenuKey(s_game_menu, key);
+    }
+
+    static void Menu_Game_f() {
+        Game_MenuInit();
+        PushMenu(new xcommand_t() {
+            public void execute() {
+                Game_MenuDraw();
+            }
+        }, new keyfunc_t() {
+            public String execute(int key) {
+                return Game_MenuKey(key);
+            }
+        });
+        m_game_cursor = 1;
+    }
+
+    /**
+     * Search the save dir for saved games and their names.
+     */
+    static void Create_Savestrings() {
+        int i;
+        QuakeFile f;
+        String name;
+
+        for (i = 0; i < MAX_SAVEGAMES; i++) {
+
+            m_savestrings[i] = "<EMPTY>";
+            name = FS.Gamedir() + "/save/save" + i + "/server.ssv";
+
+            try {
+                f = new QuakeFile(name, "r");
+                String str = f.readString();
+                if (str != null)
+                    m_savestrings[i] = str;
+                f.close();
+                m_savevalid[i] = true;
+            } catch (Exception e) {
+                m_savestrings[i] = "<EMPTY>";
+                m_savevalid[i] = false;
+            }
+        }
+    }
+
+    static void LoadGameCallback(Object self) {
+        menuaction_s a = (menuaction_s) self;
+
+        if (m_savevalid[a.localdata[0]])
+            CommandBuffer.AddText("load save" + a.localdata[0] + "\n");
+        ForceMenuOff();
+    }
+
+    static void LoadGame_MenuInit() {
+        int i;
+
+        s_loadgame_menu.x = viddef.width / 2 - 120;
+        s_loadgame_menu.y = viddef.height / 2 - 58;
+        s_loadgame_menu.nitems = 0;
+
+        Create_Savestrings();
+
+        for (i = 0; i < MAX_SAVEGAMES; i++) {
+            s_loadgame_actions[i].name = m_savestrings[i];
+            s_loadgame_actions[i].flags = QMF_LEFT_JUSTIFY;
+            s_loadgame_actions[i].localdata[0] = i;
+            s_loadgame_actions[i].callback = new mcallback() {
+                public void execute(Object o) {
+                    LoadGameCallback(o);
+                }
+            };
+
+            s_loadgame_actions[i].x = 0;
+            s_loadgame_actions[i].y = (i) * 10;
+            if (i > 0) // separate from autosave
+                s_loadgame_actions[i].y += 10;
+
+            s_loadgame_actions[i].type = MTYPE_ACTION;
+
+            Menu_AddItem(s_loadgame_menu, s_loadgame_actions[i]);
+        }
+    }
+
+    static void LoadGame_MenuDraw() {
+        Banner("m_banner_load_game");
+        //             Menu_AdjustCursor( &s_loadgame_menu, 1 );
+        Menu_Draw(s_loadgame_menu);
+    }
+
+    static String LoadGame_MenuKey(int key) {
+        if (key == K_ESCAPE || key == K_ENTER) {
+            s_savegame_menu.cursor = s_loadgame_menu.cursor - 1;
+            if (s_savegame_menu.cursor < 0)
+                s_savegame_menu.cursor = 0;
+        }
+        return Default_MenuKey(s_loadgame_menu, key);
+    }
+
+    static void Menu_LoadGame_f() {
+        LoadGame_MenuInit();
+        PushMenu(new xcommand_t() {
+            public void execute() {
+                LoadGame_MenuDraw();
+            }
+        }, new keyfunc_t() {
+            public String execute(int key) {
+                return LoadGame_MenuKey(key);
+            }
+        });
+    }
+
+    static void SaveGameCallback(Object self) {
+        menuaction_s a = (menuaction_s) self;
+
+        CommandBuffer.AddText("save save" + a.localdata[0] + "\n");
+        ForceMenuOff();
+    }
+
+    static void SaveGame_MenuDraw() {
+        Banner("m_banner_save_game");
+        Menu_AdjustCursor(s_savegame_menu, 1);
+        Menu_Draw(s_savegame_menu);
+    }
+
+    static void SaveGame_MenuInit() {
+        int i;
+
+        s_savegame_menu.x = viddef.width / 2 - 120;
+        s_savegame_menu.y = viddef.height / 2 - 58;
+        s_savegame_menu.nitems = 0;
+
+        Create_Savestrings();
+
+        // don't include the autosave slot
+        for (i = 0; i < MAX_SAVEGAMES - 1; i++) {
+            s_savegame_actions[i].name = m_savestrings[i + 1];
+            s_savegame_actions[i].localdata[0] = i + 1;
+            s_savegame_actions[i].flags = QMF_LEFT_JUSTIFY;
+            s_savegame_actions[i].callback = new mcallback() {
+                public void execute(Object o) {
+                    SaveGameCallback(o);
+                }
+            };
+
+            s_savegame_actions[i].x = 0;
+            s_savegame_actions[i].y = (i) * 10;
+
+            s_savegame_actions[i].type = MTYPE_ACTION;
+
+            Menu_AddItem(s_savegame_menu, s_savegame_actions[i]);
+        }
+    }
+
+    static String SaveGame_MenuKey(int key) {
+        if (key == K_ENTER || key == K_ESCAPE) {
+            s_loadgame_menu.cursor = s_savegame_menu.cursor - 1;
+            if (s_loadgame_menu.cursor < 0)
+                s_loadgame_menu.cursor = 0;
+        }
+        return Default_MenuKey(s_savegame_menu, key);
+    }
+
+    static void Menu_SaveGame_f() {
+        if (0 == Globals.server_state)
+            return; // not playing a game
+
+        SaveGame_MenuInit();
+        PushMenu(new xcommand_t() {
+            public void execute() {
+                SaveGame_MenuDraw();
+            }
+        }, new keyfunc_t() {
+            public String execute(int key) {
+                return SaveGame_MenuKey(key);
+            }
+        });
+        Create_Savestrings();
+    }
+
+    static void AddToServerList(String info) {
+        int i;
+
+        if (m_num_servers == MAX_LOCAL_SERVERS)
+            return;
+
+        String x = info.trim();
+
+        // ignore if duplicated
+
+        for (i = 0; i < m_num_servers; i++)
+            if (x.equals(local_server_names[i]))
+                return;
+
+        local_server_netadr[m_num_servers].set(Globals.net_from);
+        local_server_names[m_num_servers] = x;
+        s_joinserver_server_actions[m_num_servers].name = x;
+        m_num_servers++;
+    }
+
+    static void JoinServerFunc(Object self) {
+        String buffer;
+        int index;
+
+        index = ((menucommon_s) self).n;
+
+        if (Lib.Q_stricmp(local_server_names[index], NO_SERVER_STRING) == 0)
+            return;
+
+        if (index >= m_num_servers)
+            return;
+
+        buffer = "connect " + NET.AdrToString(local_server_netadr[index])
+                + "\n";
+        CommandBuffer.AddText(buffer);
+        ForceMenuOff();
+    }
+
+    static void AddressBookFunc(Object self) {
+        Menu_AddressBook_f();
+    }
+
+    static void NullCursorDraw(Object self) {
+    }
+
+    static void SearchLocalGames() {
+        int i;
+
+        m_num_servers = 0;
+        for (i = 0; i < MAX_LOCAL_SERVERS; i++)
+            local_server_names[i] = NO_SERVER_STRING;
+
+        DrawTextBox(8, 120 - 48, 36, 3);
+        Print(16 + 16, 120 - 48 + 8, "Searching for local servers, this");
+        Print(16 + 16, 120 - 48 + 16, "could take up to a minute, so");
+        Print(16 + 16, 120 - 48 + 24, "please be patient.");
+
+        // the text box won't show up unless we do a buffer swap
+        re.EndFrame();
+
+        // send out info packets
+        Client.PingServers_f.execute();
+    }
+
+    static void SearchLocalGamesFunc(Object self) {
+        SearchLocalGames();
+    }
+
+    static void JoinServer_MenuInit() {
+        int i;
+
+        s_joinserver_menu.x = (int) (viddef.width * 0.50 - 120);
+        s_joinserver_menu.nitems = 0;
+
+        s_joinserver_address_book_action.type = MTYPE_ACTION;
+        s_joinserver_address_book_action.name = "address book";
+        s_joinserver_address_book_action.flags = QMF_LEFT_JUSTIFY;
+        s_joinserver_address_book_action.x = 0;
+        s_joinserver_address_book_action.y = 0;
+        s_joinserver_address_book_action.callback = new mcallback() {
+            public void execute(Object o) {
+                AddressBookFunc(o);
+            }
+        };
+
+        s_joinserver_search_action.type = MTYPE_ACTION;
+        s_joinserver_search_action.name = "refresh server list";
+        s_joinserver_search_action.flags = QMF_LEFT_JUSTIFY;
+        s_joinserver_search_action.x = 0;
+        s_joinserver_search_action.y = 10;
+        s_joinserver_search_action.callback = new mcallback() {
+            public void execute(Object o) {
+                SearchLocalGamesFunc(o);
+            }
+        };
+        s_joinserver_search_action.statusbar = "search for servers";
+
+        s_joinserver_server_title.type = MTYPE_SEPARATOR;
+        s_joinserver_server_title.name = "connect to...";
+        s_joinserver_server_title.x = 80;
+        s_joinserver_server_title.y = 30;
+
+        for (i = 0; i < MAX_LOCAL_SERVERS; i++) {
+            s_joinserver_server_actions[i].type = MTYPE_ACTION;
+            local_server_names[i] = NO_SERVER_STRING;
+            s_joinserver_server_actions[i].name = local_server_names[i];
+            s_joinserver_server_actions[i].flags = QMF_LEFT_JUSTIFY;
+            s_joinserver_server_actions[i].x = 0;
+            s_joinserver_server_actions[i].y = 40 + i * 10;
+            s_joinserver_server_actions[i].callback = new mcallback() {
+                public void execute(Object o) {
+                    JoinServerFunc(o);
+                }
+            };
+            s_joinserver_server_actions[i].statusbar = "press ENTER to connect";
+        }
+
+        Menu_AddItem(s_joinserver_menu, s_joinserver_address_book_action);
+        Menu_AddItem(s_joinserver_menu, s_joinserver_server_title);
+        Menu_AddItem(s_joinserver_menu, s_joinserver_search_action);
+
+        for (i = 0; i < 8; i++)
+            Menu_AddItem(s_joinserver_menu, s_joinserver_server_actions[i]);
+
+        Menu_Center(s_joinserver_menu);
+
+        SearchLocalGames();
+    }
+
+    static void JoinServer_MenuDraw() {
+        Banner("m_banner_join_server");
+        Menu_Draw(s_joinserver_menu);
+    }
+
+    static String JoinServer_MenuKey(int key) {
+        return Default_MenuKey(s_joinserver_menu, key);
+    }
+
+    /*
+     * =============================================================================
+     * 
+     * ADDRESS BOOK MENU
+     * 
+     * =============================================================================
+     */
+
+    static void Menu_JoinServer_f() {
+        JoinServer_MenuInit();
+        PushMenu(new xcommand_t() {
+            public void execute() {
+                JoinServer_MenuDraw();
+            }
+        }, new keyfunc_t() {
+            public String execute(int key) {
+                return JoinServer_MenuKey(key);
+            }
+        });
+    }
+
+    static void DMOptionsFunc(Object self) {
+        if (s_rules_box.curvalue == 1)
+            return;
+        Menu_DMOptions_f();
+    }
+
+    static void RulesChangeFunc(Object self) {
+        // DM
+        if (s_rules_box.curvalue == 0) {
+            s_maxclients_field.statusbar = null;
+            s_startserver_dmoptions_action.statusbar = null;
+        } else if (s_rules_box.curvalue == 1)
+        // coop // PGM
+        {
+            s_maxclients_field.statusbar = "4 maximum for cooperative";
+            if (Lib.atoi(s_maxclients_field.buffer.toString()) > 4)
+                s_maxclients_field.buffer = new StringBuffer("4");
+            s_startserver_dmoptions_action.statusbar = "N/A for cooperative";
+        }
+        //       =====
+        //       PGM
+        // ROGUE GAMES
+        else if (FS.Developer_searchpath(2) == 2) {
+            if (s_rules_box.curvalue == 2) // tag
+            {
+                s_maxclients_field.statusbar = null;
+                s_startserver_dmoptions_action.statusbar = null;
+            }
+            /*
+             * else if(s_rules_box.curvalue == 3) // deathball {
+             * s_maxclients_field.statusbar = null;
+             * s_startserver_dmoptions_action.statusbar = null; }
+             */
+        }
+        //       PGM
+        //       =====
+    }
+
+    static void StartServerActionFunc(Object self) {
+        //char startmap[1024];
+        String startmap;
+        int timelimit;
+        int fraglimit;
+        int maxclients;
+        String spot;
+
+        //strcpy(startmap, strchr(mapnames[s_startmap_list.curvalue], '\n') +
+        // 1);
+        String x = mapnames[s_startmap_list.curvalue];
+
+        int pos = x.indexOf('\n');
+        if (pos == -1)
+            startmap = x;
+        else
+            startmap = x.substring(pos + 1, x.length());
+
+        maxclients = Lib.atoi(s_maxclients_field.buffer.toString());
+        timelimit = Lib.atoi(s_timelimit_field.buffer.toString());
+        fraglimit = Lib.atoi(s_fraglimit_field.buffer.toString());
+
+        Cvar.setValue("maxclients", ClampCvar(maxclients, maxclients));
+        Cvar.setValue("timelimit", ClampCvar(timelimit, timelimit));
+        Cvar.setValue("fraglimit", ClampCvar(fraglimit, fraglimit));
+        Cvar.set("hostname", s_hostname_field.buffer.toString());
+        //             Cvar.setValue ("deathmatch", !s_rules_box.curvalue );
+        //             Cvar.setValue ("coop", s_rules_box.curvalue );
+
+        //       PGM
+        if ((s_rules_box.curvalue < 2) || (FS.Developer_searchpath(2) != 2)) {
+            Cvar.setValue("deathmatch", 1 - s_rules_box.curvalue);
+            Cvar.setValue("coop", s_rules_box.curvalue);
+            Cvar.setValue("gamerules", 0);
+        } else {
+            Cvar.setValue("deathmatch", 1);
+            // deathmatch is always true for rogue games, right?
+            Cvar.setValue("coop", 0);
+            // FIXME - this might need to depend on which game we're running
+            Cvar.setValue("gamerules", s_rules_box.curvalue);
+        }
+        //       PGM
+
+        spot = null;
+        if (s_rules_box.curvalue == 1) // PGM
+        {
+            if (Lib.Q_stricmp(startmap, "bunk1") == 0)
+                spot = "start";
+            else if (Lib.Q_stricmp(startmap, "mintro") == 0)
+                spot = "start";
+            else if (Lib.Q_stricmp(startmap, "fact1") == 0)
+                spot = "start";
+            else if (Lib.Q_stricmp(startmap, "power1") == 0)
+                spot = "pstart";
+            else if (Lib.Q_stricmp(startmap, "biggun") == 0)
+                spot = "bstart";
+            else if (Lib.Q_stricmp(startmap, "hangar1") == 0)
+                spot = "unitstart";
+            else if (Lib.Q_stricmp(startmap, "city1") == 0)
+                spot = "unitstart";
+            else if (Lib.Q_stricmp(startmap, "boss1") == 0)
+                spot = "bosstart";
+        }
+
+        if (spot != null) {
+            if (Globals.server_state != 0)
+                CommandBuffer.AddText("disconnect\n");
+            CommandBuffer.AddText("gamemap \"*" + startmap + "$" + spot + "\"\n");
+        } else {
+            CommandBuffer.AddText("map " + startmap + "\n");
+        }
+
+        ForceMenuOff();
+    }
+
+    static void StartServer_MenuInit() {
+
+        //       =======
+        //       PGM
+        //       =======
+
+        byte[] buffer = null;
+        String mapsname;
+        String s;
+        int i;
+        RandomAccessFile fp;
+
+        /*
+         * * load the list of map names
+         */
+        mapsname = FS.Gamedir() + "/maps.lst";
+
+        // Check user dir first (default ~/.lwjake2)
+        if ((fp = Lib.fopen(mapsname, "r")) == null) {
+            // Check base dir first (baseq2 folder)
+            mapsname = FS.BaseGamedir() + "/maps.lst";
+            if ((fp = Lib.fopen(mapsname, "r")) == null) {
+                // Open the pak's maplist
+                buffer = FS.LoadFile("maps.lst");
+                if (buffer == null)
+                    Com.Error(ERR_DROP, "couldn't find maps.lst\n");
+            } else {
+                try {
+                    int len = (int) fp.length();
+                    buffer = new byte[len];
+                    fp.readFully(buffer);
+                } catch (Exception e) {
+                    Com.Error(ERR_DROP, "couldn't load maps.lst\n");
+                }
+            }
+        } else {
+            try {
+                int len = (int) fp.length();
+                buffer = new byte[len];
+                fp.readFully(buffer);
+            } catch (Exception e) {
+                Com.Error(ERR_DROP, "couldn't load maps.lst\n");
+            }
+        }
+
+        s = new String(buffer);
+        String lines[] = s.split("\r\n");
+
+        nummaps = lines.length;
+
+        if (nummaps == 0)
+            Com.Error(ERR_DROP, "no maps in maps.lst\n");
+
+        mapnames = new String[nummaps];
+
+        for (i = 0; i < nummaps; i++) {
+            String shortname, longname, scratch;
+
+            Com.ParseHelp ph = new Com.ParseHelp(lines[i]);
+
+            shortname = Com.Parse(ph).toUpperCase();
+            longname = Com.Parse(ph);
+            scratch = longname + "\n" + shortname;
+            mapnames[i] = scratch;
+        }
+
+        if (fp != null) {
+            Lib.fclose(fp);
+            fp = null;
+
+        } else {
+            FS.FreeFile();
+        }
+
+        /*
+         * * initialize the menu stuff
+         */
+        s_startserver_menu.x = (int) (viddef.width * 0.50);
+        s_startserver_menu.nitems = 0;
+
+        s_startmap_list.type = MTYPE_SPINCONTROL;
+        s_startmap_list.x = 0;
+        s_startmap_list.y = 0;
+        s_startmap_list.name = "initial map";
+        s_startmap_list.itemnames = mapnames;
+
+        s_rules_box.type = MTYPE_SPINCONTROL;
+        s_rules_box.x = 0;
+        s_rules_box.y = 20;
+        s_rules_box.name = "rules";
+
+        //       PGM - rogue games only available with rogue DLL.
+        if (FS.Developer_searchpath(2) == 2)
+            s_rules_box.itemnames = dm_coop_names_rogue;
+        else
+            s_rules_box.itemnames = dm_coop_names;
+        //       PGM
+
+        if (Cvar.variableValue("coop") != 0)
+            s_rules_box.curvalue = 1;
+        else
+            s_rules_box.curvalue = 0;
+        s_rules_box.callback = new mcallback() {
+            public void execute(Object o) {
+                RulesChangeFunc(o);
+            }
+        };
+
+        s_timelimit_field.type = MTYPE_FIELD;
+        s_timelimit_field.name = "time limit";
+        s_timelimit_field.flags = QMF_NUMBERSONLY;
+        s_timelimit_field.x = 0;
+        s_timelimit_field.y = 36;
+        s_timelimit_field.statusbar = "0 = no limit";
+        s_timelimit_field.length = 3;
+        s_timelimit_field.visible_length = 3;
+        s_timelimit_field.buffer = new StringBuffer(Cvar
+                .variableString("timelimit"));
+
+        s_fraglimit_field.type = MTYPE_FIELD;
+        s_fraglimit_field.name = "frag limit";
+        s_fraglimit_field.flags = QMF_NUMBERSONLY;
+        s_fraglimit_field.x = 0;
+        s_fraglimit_field.y = 54;
+        s_fraglimit_field.statusbar = "0 = no limit";
+        s_fraglimit_field.length = 3;
+        s_fraglimit_field.visible_length = 3;
+        s_fraglimit_field.buffer = new StringBuffer(Cvar
+                .variableString("fraglimit"));
+
+        /*
+         * * maxclients determines the maximum number of players that can join *
+         * the game. If maxclients is only "1" then we should default the menu *
+         * option to 8 players, otherwise use whatever its current value is. *
+         * Clamping will be done when the server is actually started.
+         */
+        s_maxclients_field.type = MTYPE_FIELD;
+        s_maxclients_field.name = "max players";
+        s_maxclients_field.flags = QMF_NUMBERSONLY;
+        s_maxclients_field.x = 0;
+        s_maxclients_field.y = 72;
+        s_maxclients_field.statusbar = null;
+        s_maxclients_field.length = 3;
+        s_maxclients_field.visible_length = 3;
+        if (Cvar.variableValue("maxclients") == 1)
+            s_maxclients_field.buffer = new StringBuffer("8");
+        else
+            s_maxclients_field.buffer = new StringBuffer(Cvar
+                    .variableString("maxclients"));
+
+        s_hostname_field.type = MTYPE_FIELD;
+        s_hostname_field.name = "hostname";
+        s_hostname_field.flags = 0;
+        s_hostname_field.x = 0;
+        s_hostname_field.y = 90;
+        s_hostname_field.statusbar = null;
+        s_hostname_field.length = 12;
+        s_hostname_field.visible_length = 12;
+        s_hostname_field.buffer = new StringBuffer(Cvar
+                .variableString("hostname"));
+        s_hostname_field.cursor = s_hostname_field.buffer.length();
+
+        s_startserver_dmoptions_action.type = MTYPE_ACTION;
+        s_startserver_dmoptions_action.name = " deathmatch flags";
+        s_startserver_dmoptions_action.flags = QMF_LEFT_JUSTIFY;
+        s_startserver_dmoptions_action.x = 24;
+        s_startserver_dmoptions_action.y = 108;
+        s_startserver_dmoptions_action.statusbar = null;
+        s_startserver_dmoptions_action.callback = new mcallback() {
+            public void execute(Object o) {
+                DMOptionsFunc(o);
+            }
+        };
+
+        s_startserver_start_action.type = MTYPE_ACTION;
+        s_startserver_start_action.name = " begin";
+        s_startserver_start_action.flags = QMF_LEFT_JUSTIFY;
+        s_startserver_start_action.x = 24;
+        s_startserver_start_action.y = 128;
+        s_startserver_start_action.callback = new mcallback() {
+            public void execute(Object o) {
+                StartServerActionFunc(o);
+            }
+        };
+
+        Menu_AddItem(s_startserver_menu, s_startmap_list);
+        Menu_AddItem(s_startserver_menu, s_rules_box);
+        Menu_AddItem(s_startserver_menu, s_timelimit_field);
+        Menu_AddItem(s_startserver_menu, s_fraglimit_field);
+        Menu_AddItem(s_startserver_menu, s_maxclients_field);
+        Menu_AddItem(s_startserver_menu, s_hostname_field);
+        Menu_AddItem(s_startserver_menu, s_startserver_dmoptions_action);
+        Menu_AddItem(s_startserver_menu, s_startserver_start_action);
+
+        Menu_Center(s_startserver_menu);
+
+        // call this now to set proper inital state
+        RulesChangeFunc(null);
+    }
+
+    static void StartServer_MenuDraw() {
+        Menu_Draw(s_startserver_menu);
+    }
+
+    static String StartServer_MenuKey(int key) {
+        if (key == K_ESCAPE) {
+            if (mapnames != null) {
+                int i;
+
+                for (i = 0; i < nummaps; i++)
+                    mapnames[i] = null;
+
+            }
+            mapnames = null;
+            nummaps = 0;
+        }
+
+        return Default_MenuKey(s_startserver_menu, key);
+    }
+
+    static void Menu_StartServer_f() {
+        StartServer_MenuInit();
+        PushMenu(startServer_MenuDraw, startServer_MenuKey);
+    }
+
+    static void setvalue(int flags) {
+        Cvar.setValue("dmflags", flags);
+        dmoptions_statusbar = "dmflags = " + flags;
+    }
+
+    static void DMFlagCallback(Object self) {
+        menulist_s f = (menulist_s) self;
+        int flags;
+        int bit = 0;
+
+        flags = (int) Cvar.variableValue("dmflags");
+
+        if (f == s_friendlyfire_box) {
+            if (f.curvalue != 0)
+                flags &= ~DF_NO_FRIENDLY_FIRE;
+            else
+                flags |= DF_NO_FRIENDLY_FIRE;
+            setvalue(flags);
+            return;
+        } else if (f == s_falls_box) {
+            if (f.curvalue != 0)
+                flags &= ~DF_NO_FALLING;
+            else
+                flags |= DF_NO_FALLING;
+            setvalue(flags);
+            return;
+        } else if (f == s_instant_powerups_box) {
+            bit = DF_INSTANT_ITEMS;
+        } else if (f == s_allow_exit_box) {
+            bit = DF_ALLOW_EXIT;
+        } else if (f == s_powerups_box) {
+            if (f.curvalue != 0)
+                flags &= ~DF_NO_ITEMS;
+            else
+                flags |= DF_NO_ITEMS;
+            setvalue(flags);
+            return;
+        } else if (f == s_spawn_farthest_box) {
+            bit = DF_SPAWN_FARTHEST;
+        } else if (f == s_teamplay_box) {
+            if (f.curvalue == 1) {
+                flags |= DF_SKINTEAMS;
+                flags &= ~DF_MODELTEAMS;
+            } else if (f.curvalue == 2) {
+                flags |= DF_MODELTEAMS;
+                flags &= ~DF_SKINTEAMS;
+            } else {
+                flags &= ~(DF_MODELTEAMS | DF_SKINTEAMS);
+            }
+
+            setvalue(flags);
+            return;
+        } else if (f == s_samelevel_box) {
+            bit = DF_SAME_LEVEL;
+        } else if (f == s_force_respawn_box) {
+            bit = DF_FORCE_RESPAWN;
+        } else if (f == s_armor_box) {
+            if (f.curvalue != 0)
+                flags &= ~DF_NO_ARMOR;
+            else
+                flags |= DF_NO_ARMOR;
+            setvalue(flags);
+            return;
+        } else if (f == s_infinite_ammo_box) {
+            bit = DF_INFINITE_AMMO;
+        } else if (f == s_fixed_fov_box) {
+            bit = DF_FIXED_FOV;
+        } else if (f == s_quad_drop_box) {
+            bit = DF_QUAD_DROP;
+        }
+
+        //       =======
+        //       ROGUE
+        else if (FS.Developer_searchpath(2) == 2) {
+            if (f == s_no_mines_box) {
+                bit = DF_NO_MINES;
+            } else if (f == s_no_nukes_box) {
+                bit = DF_NO_NUKES;
+            } else if (f == s_stack_double_box) {
+                bit = DF_NO_STACK_DOUBLE;
+            } else if (f == s_no_spheres_box) {
+                bit = DF_NO_SPHERES;
+            }
+        }
+        //       ROGUE
+        //       =======
+
+        if (f != null) {
+            if (f.curvalue == 0)
+                flags &= ~bit;
+            else
+                flags |= bit;
+        }
+
+        Cvar.setValue("dmflags", flags);
+
+        dmoptions_statusbar = "dmflags = " + flags;
+
+    }
+
+    static void DMOptions_MenuInit() {
+
+        int dmflags = (int) Cvar.variableValue("dmflags");
+        int y = 0;
+
+        s_dmoptions_menu.x = (int) (viddef.width * 0.50);
+        s_dmoptions_menu.nitems = 0;
+
+        s_falls_box.type = MTYPE_SPINCONTROL;
+        s_falls_box.x = 0;
+        s_falls_box.y = y;
+        s_falls_box.name = "falling damage";
+        s_falls_box.callback = new mcallback() {
+            public void execute(Object o) {
+                DMFlagCallback(o);
+            }
+        };
+        s_falls_box.itemnames = yes_no_names;
+        s_falls_box.curvalue = (dmflags & DF_NO_FALLING) == 0 ? 1 : 0;
+
+        s_instant_powerups_box.type = MTYPE_SPINCONTROL;
+        s_instant_powerups_box.x = 0;
+        s_instant_powerups_box.y = y += 10;
+        s_instant_powerups_box.name = "instant powerups";
+        s_instant_powerups_box.callback = new mcallback() {
+            public void execute(Object o) {
+                DMFlagCallback(o);
+            }
+        };
+        s_instant_powerups_box.itemnames = yes_no_names;
+        s_instant_powerups_box.curvalue = (dmflags & DF_INSTANT_ITEMS) != 0 ? 1
+                : 0;
+
+        s_powerups_box.type = MTYPE_SPINCONTROL;
+        s_powerups_box.x = 0;
+        s_powerups_box.y = y += 10;
+        s_powerups_box.name = "allow powerups";
+        s_powerups_box.callback = new mcallback() {
+            public void execute(Object o) {
+                DMFlagCallback(o);
+            }
+        };
+        s_powerups_box.itemnames = yes_no_names;
+        s_powerups_box.curvalue = (dmflags & DF_NO_ITEMS) == 0 ? 1 : 0;
+
+        s_armor_box.type = MTYPE_SPINCONTROL;
+        s_armor_box.x = 0;
+        s_armor_box.y = y += 10;
+        s_armor_box.name = "allow armor";
+        s_armor_box.callback = new mcallback() {
+            public void execute(Object o) {
+                DMFlagCallback(o);
+            }
+        };
+        s_armor_box.itemnames = yes_no_names;
+        s_armor_box.curvalue = (dmflags & DF_NO_ARMOR) == 0 ? 1 : 0;
+
+        s_spawn_farthest_box.type = MTYPE_SPINCONTROL;
+        s_spawn_farthest_box.x = 0;
+        s_spawn_farthest_box.y = y += 10;
+        s_spawn_farthest_box.name = "spawn farthest";
+        s_spawn_farthest_box.callback = new mcallback() {
+            public void execute(Object o) {
+                DMFlagCallback(o);
+            }
+        };
+        s_spawn_farthest_box.itemnames = yes_no_names;
+        s_spawn_farthest_box.curvalue = (dmflags & DF_SPAWN_FARTHEST) != 0 ? 1
+                : 0;
+
+        s_samelevel_box.type = MTYPE_SPINCONTROL;
+        s_samelevel_box.x = 0;
+        s_samelevel_box.y = y += 10;
+        s_samelevel_box.name = "same map";
+        s_samelevel_box.callback = new mcallback() {
+            public void execute(Object o) {
+                DMFlagCallback(o);
+            }
+        };
+        s_samelevel_box.itemnames = yes_no_names;
+        s_samelevel_box.curvalue = (dmflags & DF_SAME_LEVEL) != 0 ? 1 : 0;
+
+        s_force_respawn_box.type = MTYPE_SPINCONTROL;
+        s_force_respawn_box.x = 0;
+        s_force_respawn_box.y = y += 10;
+        s_force_respawn_box.name = "force respawn";
+        s_force_respawn_box.callback = new mcallback() {
+            public void execute(Object o) {
+                DMFlagCallback(o);
+            }
+        };
+        s_force_respawn_box.itemnames = yes_no_names;
+        s_force_respawn_box.curvalue = (dmflags & DF_FORCE_RESPAWN) != 0 ? 1
+                : 0;
+
+        s_teamplay_box.type = MTYPE_SPINCONTROL;
+        s_teamplay_box.x = 0;
+        s_teamplay_box.y = y += 10;
+        s_teamplay_box.name = "teamplay";
+        s_teamplay_box.callback = new mcallback() {
+            public void execute(Object o) {
+                DMFlagCallback(o);
+            }
+        };
+        s_teamplay_box.itemnames = teamplay_names;
+
+        s_allow_exit_box.type = MTYPE_SPINCONTROL;
+        s_allow_exit_box.x = 0;
+        s_allow_exit_box.y = y += 10;
+        s_allow_exit_box.name = "allow exit";
+        s_allow_exit_box.callback = new mcallback() {
+            public void execute(Object o) {
+                DMFlagCallback(o);
+            }
+        };
+        s_allow_exit_box.itemnames = yes_no_names;
+        s_allow_exit_box.curvalue = (dmflags & DF_ALLOW_EXIT) != 0 ? 1 : 0;
+
+        s_infinite_ammo_box.type = MTYPE_SPINCONTROL;
+        s_infinite_ammo_box.x = 0;
+        s_infinite_ammo_box.y = y += 10;
+        s_infinite_ammo_box.name = "infinite ammo";
+        s_infinite_ammo_box.callback = new mcallback() {
+            public void execute(Object o) {
+                DMFlagCallback(o);
+            }
+        };
+        s_infinite_ammo_box.itemnames = yes_no_names;
+        s_infinite_ammo_box.curvalue = (dmflags & DF_INFINITE_AMMO) != 0 ? 1
+                : 0;
+
+        s_fixed_fov_box.type = MTYPE_SPINCONTROL;
+        s_fixed_fov_box.x = 0;
+        s_fixed_fov_box.y = y += 10;
+        s_fixed_fov_box.name = "fixed FOV";
+        s_fixed_fov_box.callback = new mcallback() {
+            public void execute(Object o) {
+                DMFlagCallback(o);
+            }
+        };
+        s_fixed_fov_box.itemnames = yes_no_names;
+        s_fixed_fov_box.curvalue = (dmflags & DF_FIXED_FOV) != 0 ? 1 : 0;
+
+        s_quad_drop_box.type = MTYPE_SPINCONTROL;
+        s_quad_drop_box.x = 0;
+        s_quad_drop_box.y = y += 10;
+        s_quad_drop_box.name = "quad drop";
+        s_quad_drop_box.callback = new mcallback() {
+            public void execute(Object o) {
+                DMFlagCallback(o);
+            }
+        };
+        s_quad_drop_box.itemnames = yes_no_names;
+        s_quad_drop_box.curvalue = (dmflags & DF_QUAD_DROP) != 0 ? 1 : 0;
+
+        s_friendlyfire_box.type = MTYPE_SPINCONTROL;
+        s_friendlyfire_box.x = 0;
+        s_friendlyfire_box.y = y += 10;
+        s_friendlyfire_box.name = "friendly fire";
+        s_friendlyfire_box.callback = new mcallback() {
+            public void execute(Object o) {
+                DMFlagCallback(o);
+            }
+        };
+        s_friendlyfire_box.itemnames = yes_no_names;
+        s_friendlyfire_box.curvalue = (dmflags & DF_NO_FRIENDLY_FIRE) == 0 ? 1
+                : 0;
+
+        //       ============
+        //       ROGUE
+        if (FS.Developer_searchpath(2) == 2) {
+            s_no_mines_box.type = MTYPE_SPINCONTROL;
+            s_no_mines_box.x = 0;
+            s_no_mines_box.y = y += 10;
+            s_no_mines_box.name = "remove mines";
+            s_no_mines_box.callback = new mcallback() {
+                public void execute(Object o) {
+                    DMFlagCallback(o);
+                }
+            };
+            s_no_mines_box.itemnames = yes_no_names;
+            s_no_mines_box.curvalue = (dmflags & DF_NO_MINES) != 0 ? 1 : 0;
+
+            s_no_nukes_box.type = MTYPE_SPINCONTROL;
+            s_no_nukes_box.x = 0;
+            s_no_nukes_box.y = y += 10;
+            s_no_nukes_box.name = "remove nukes";
+            s_no_nukes_box.callback = new mcallback() {
+                public void execute(Object o) {
+                    DMFlagCallback(o);
+                }
+            };
+            s_no_nukes_box.itemnames = yes_no_names;
+            s_no_nukes_box.curvalue = (dmflags & DF_NO_NUKES) != 0 ? 1 : 0;
+
+            s_stack_double_box.type = MTYPE_SPINCONTROL;
+            s_stack_double_box.x = 0;
+            s_stack_double_box.y = y += 10;
+            s_stack_double_box.name = "2x/4x stacking off";
+            s_stack_double_box.callback = new mcallback() {
+                public void execute(Object o) {
+                    DMFlagCallback(o);
+                }
+            };
+            s_stack_double_box.itemnames = yes_no_names;
+            s_stack_double_box.curvalue = (dmflags & DF_NO_STACK_DOUBLE);
+
+            s_no_spheres_box.type = MTYPE_SPINCONTROL;
+            s_no_spheres_box.x = 0;
+            s_no_spheres_box.y = y += 10;
+            s_no_spheres_box.name = "remove spheres";
+            s_no_spheres_box.callback = new mcallback() {
+                public void execute(Object o) {
+                    DMFlagCallback(o);
+                }
+            };
+            s_no_spheres_box.itemnames = yes_no_names;
+            s_no_spheres_box.curvalue = (dmflags & DF_NO_SPHERES) != 0 ? 1 : 0;
+
+        }
+        //       ROGUE
+        //       ============
+
+        Menu_AddItem(s_dmoptions_menu, s_falls_box);
+        Menu_AddItem(s_dmoptions_menu, s_instant_powerups_box);
+        Menu_AddItem(s_dmoptions_menu, s_powerups_box);
+        Menu_AddItem(s_dmoptions_menu, s_armor_box);
+        Menu_AddItem(s_dmoptions_menu, s_spawn_farthest_box);
+        Menu_AddItem(s_dmoptions_menu, s_samelevel_box);
+        Menu_AddItem(s_dmoptions_menu, s_force_respawn_box);
+        Menu_AddItem(s_dmoptions_menu, s_teamplay_box);
+        Menu_AddItem(s_dmoptions_menu, s_allow_exit_box);
+        Menu_AddItem(s_dmoptions_menu, s_infinite_ammo_box);
+        Menu_AddItem(s_dmoptions_menu, s_fixed_fov_box);
+        Menu_AddItem(s_dmoptions_menu, s_quad_drop_box);
+        Menu_AddItem(s_dmoptions_menu, s_friendlyfire_box);
+
+        //       =======
+        //       ROGUE
+        if (FS.Developer_searchpath(2) == 2) {
+            Menu_AddItem(s_dmoptions_menu, s_no_mines_box);
+            Menu_AddItem(s_dmoptions_menu, s_no_nukes_box);
+            Menu_AddItem(s_dmoptions_menu, s_stack_double_box);
+            Menu_AddItem(s_dmoptions_menu, s_no_spheres_box);
+        }
+        //       ROGUE
+        //       =======
+
+        Menu_Center(s_dmoptions_menu);
+
+        // set the original dmflags statusbar
+        DMFlagCallback(null);
+        Menu_SetStatusBar(s_dmoptions_menu, dmoptions_statusbar);
+    }
+
+    static void DMOptions_MenuDraw() {
+        Menu_Draw(s_dmoptions_menu);
+    }
+
+    static String DMOptions_MenuKey(int key) {
+        return Default_MenuKey(s_dmoptions_menu, key);
+    }
+
+    static void Menu_DMOptions_f() {
+        DMOptions_MenuInit();
+        PushMenu(new xcommand_t() {
+            public void execute() {
+                DMOptions_MenuDraw();
+            }
+        }, new keyfunc_t() {
+            public String execute(int key) {
+                return DMOptions_MenuKey(key);
+            }
+        });
+    }
+
+    static void DownloadCallback(Object self) {
+        menulist_s f = (menulist_s) self;
+
+        if (f == s_allow_download_box) {
+            Cvar.setValue("allow_download", f.curvalue);
+        } else if (f == s_allow_download_maps_box) {
+            Cvar.setValue("allow_download_maps", f.curvalue);
+        } else if (f == s_allow_download_models_box) {
+            Cvar.setValue("allow_download_models", f.curvalue);
+        } else if (f == s_allow_download_players_box) {
+            Cvar.setValue("allow_download_players", f.curvalue);
+        } else if (f == s_allow_download_sounds_box) {
+            Cvar.setValue("allow_download_sounds", f.curvalue);
+        }
+    }
+
+    static void DownloadOptions_MenuInit() {
+
+        int y = 0;
+
+        s_downloadoptions_menu.x = (int) (viddef.width * 0.50);
+        s_downloadoptions_menu.nitems = 0;
+
+        s_download_title.type = MTYPE_SEPARATOR;
+        s_download_title.name = "Download Options";
+        s_download_title.x = 48;
+        s_download_title.y = y;
+
+        s_allow_download_box.type = MTYPE_SPINCONTROL;
+        s_allow_download_box.x = 0;
+        s_allow_download_box.y = y += 20;
+        s_allow_download_box.name = "allow downloading";
+        s_allow_download_box.callback = new mcallback() {
+            public void execute(Object o) {
+                DownloadCallback(o);
+            }
+        };
+        s_allow_download_box.itemnames = yes_no_names;
+        s_allow_download_box.curvalue = (Cvar.variableValue("allow_download") != 0) ? 1
+                : 0;
+
+        s_allow_download_maps_box.type = MTYPE_SPINCONTROL;
+        s_allow_download_maps_box.x = 0;
+        s_allow_download_maps_box.y = y += 20;
+        s_allow_download_maps_box.name = "maps";
+        s_allow_download_maps_box.callback = new mcallback() {
+            public void execute(Object o) {
+                DownloadCallback(o);
+            }
+        };
+        s_allow_download_maps_box.itemnames = yes_no_names;
+        s_allow_download_maps_box.curvalue = (Cvar
+                .variableValue("allow_download_maps") != 0) ? 1 : 0;
+
+        s_allow_download_players_box.type = MTYPE_SPINCONTROL;
+        s_allow_download_players_box.x = 0;
+        s_allow_download_players_box.y = y += 10;
+        s_allow_download_players_box.name = "player models/skins";
+        s_allow_download_players_box.callback = new mcallback() {
+            public void execute(Object o) {
+                DownloadCallback(o);
+            }
+        };
+        s_allow_download_players_box.itemnames = yes_no_names;
+        s_allow_download_players_box.curvalue = (Cvar
+                .variableValue("allow_download_players") != 0) ? 1 : 0;
+
+        s_allow_download_models_box.type = MTYPE_SPINCONTROL;
+        s_allow_download_models_box.x = 0;
+        s_allow_download_models_box.y = y += 10;
+        s_allow_download_models_box.name = "models";
+        s_allow_download_models_box.callback = new mcallback() {
+            public void execute(Object o) {
+                DownloadCallback(o);
+            }
+        };
+        s_allow_download_models_box.itemnames = yes_no_names;
+        s_allow_download_models_box.curvalue = (Cvar
+                .variableValue("allow_download_models") != 0) ? 1 : 0;
+
+        s_allow_download_sounds_box.type = MTYPE_SPINCONTROL;
+        s_allow_download_sounds_box.x = 0;
+        s_allow_download_sounds_box.y = y += 10;
+        s_allow_download_sounds_box.name = "sounds";
+        s_allow_download_sounds_box.callback = new mcallback() {
+            public void execute(Object o) {
+                DownloadCallback(o);
+            }
+        };
+        s_allow_download_sounds_box.itemnames = yes_no_names;
+        s_allow_download_sounds_box.curvalue = (Cvar
+                .variableValue("allow_download_sounds") != 0) ? 1 : 0;
+
+        Menu_AddItem(s_downloadoptions_menu, s_download_title);
+        Menu_AddItem(s_downloadoptions_menu, s_allow_download_box);
+        Menu_AddItem(s_downloadoptions_menu, s_allow_download_maps_box);
+        Menu_AddItem(s_downloadoptions_menu, s_allow_download_players_box);
+        Menu_AddItem(s_downloadoptions_menu, s_allow_download_models_box);
+        Menu_AddItem(s_downloadoptions_menu, s_allow_download_sounds_box);
+
+        Menu_Center(s_downloadoptions_menu);
+
+        // skip over title
+        if (s_downloadoptions_menu.cursor == 0)
+            s_downloadoptions_menu.cursor = 1;
+    }
+
+    static void DownloadOptions_MenuDraw() {
+        Menu_Draw(s_downloadoptions_menu);
+    }
+
+    static String DownloadOptions_MenuKey(int key) {
+        return Default_MenuKey(s_downloadoptions_menu, key);
+    }
+
+    static void Menu_DownloadOptions_f() {
+        DownloadOptions_MenuInit();
+        PushMenu(new xcommand_t() {
+            public void execute() {
+                DownloadOptions_MenuDraw();
+            }
+        }, new keyfunc_t() {
+            public String execute(int key) {
+                return DownloadOptions_MenuKey(key);
+            }
+        });
+    }
+
+    static void AddressBook_MenuInit() {
+        s_addressbook_menu.x = viddef.width / 2 - 142;
+        s_addressbook_menu.y = viddef.height / 2 - 58;
+        s_addressbook_menu.nitems = 0;
+
+        for (int i = 0; i < NUM_ADDRESSBOOK_ENTRIES; i++) {
+            CvarT adr = Cvar.get("adr" + i, "", CVAR_ARCHIVE);
+
+            s_addressbook_fields[i].type = MTYPE_FIELD;
+            s_addressbook_fields[i].name = null;
+            s_addressbook_fields[i].callback = null;
+            s_addressbook_fields[i].x = 0;
+            s_addressbook_fields[i].y = i * 18;
+            s_addressbook_fields[i].localdata[0] = i;
+            // put the cursor to the end of text for editing
+            s_addressbook_fields[i].cursor = adr.string.length();
+            s_addressbook_fields[i].length = 60;
+            s_addressbook_fields[i].visible_length = 30;
+
+            s_addressbook_fields[i].buffer = new StringBuffer(adr.string);
+
+            Menu_AddItem(s_addressbook_menu, s_addressbook_fields[i]);
+        }
+    }
+
+    static String AddressBook_MenuKey_f(int key) {
+        if (key == K_ESCAPE) {
+            for (int index = 0; index < NUM_ADDRESSBOOK_ENTRIES; index++) {
+                Cvar.set("adr" + index, s_addressbook_fields[index].buffer.toString());
+            }
+        }
+        return Default_MenuKey(s_addressbook_menu, key);
+    }
+
+    static void AddressBook_MenuDraw_f() {
+        Banner("m_banner_addressbook");
+        Menu_Draw(s_addressbook_menu);
+    }
+
+    static void Menu_AddressBook_f() {
+        AddressBook_MenuInit();
+        PushMenu(new xcommand_t() {
+            public void execute() {
+                AddressBook_MenuDraw_f();
+            }
+        }, new keyfunc_t() {
+            public String execute(int key) {
+                return AddressBook_MenuKey_f(key);
+            }
+        });
+    }
+
+    static void DownloadOptionsFunc(Object self) {
+        Menu_DownloadOptions_f();
+    }
+
+    static void HandednessCallback(Object unused) {
+        Cvar.setValue("hand", s_player_handedness_box.curvalue);
+    }
+
+    static void RateCallback(Object unused) {
+        if (s_player_rate_box.curvalue != rate_tbl.length - 1) //sizeof(rate_tbl)
+            // / sizeof(*
+            // rate_tbl) - 1)
+            Cvar.setValue("rate", rate_tbl[s_player_rate_box.curvalue]);
+    }
+
+    static void ModelCallback(Object unused) {
+        s_player_skin_box.itemnames = s_pmi[s_player_model_box.curvalue].skindisplaynames;
+        s_player_skin_box.curvalue = 0;
+    }
+
+    static boolean IconOfSkinExists(String skin, String pcxfiles[],
+                                    int npcxfiles) {
+
+        String scratch;
+
+        //strcpy(scratch, skin);
+        scratch = skin;
+        int pos = scratch.lastIndexOf('.');
+        if (pos != -1)
+            scratch = scratch.substring(0, pos) + "_i.pcx";
+
+        else
+            scratch += "_i.pcx";
+
+        for (int i = 0; i < npcxfiles; i++) {
+            if (pcxfiles[i].equals(scratch))
+                return true;
+        }
+
+        return false;
+    }
+
+    static void PlayerConfig_ScanDirectories() {
+        //char findname[1024];
+        String findname;
+        //char scratch[1024];
+        String scratch;
+
+        int ndirs = 0, npms = 0;
+        int a, b, c;
+        String dirnames[];
+
+        String path = null;
+
+        int i;
+
+        //extern String * FS_ListFiles(String , int *, unsigned, unsigned);
+
+        s_numplayermodels = 0;
+
+        /*
+         * * get a list of directories
+         */
+        do {
+            path = FS.NextPath(path);
+            findname = path + "/players/*.*";
+
+            if ((dirnames = FS.ListFiles(findname, 0, SFF_SUBDIR)) != null) {
+                ndirs = dirnames.length;
+                break;
+            }
+        } while (path != null);
+
+        if (dirnames == null)
+            return;
+
+        /*
+         * * go through the subdirectories
+         */
+        npms = ndirs;
+        if (npms > MAX_PLAYERMODELS)
+            npms = MAX_PLAYERMODELS;
+
+        for (i = 0; i < npms; i++) {
+            int k, s;
+            //String a, b, c;
+            String pcxnames[];
+            String skinnames[];
+            int npcxfiles;
+            int nskins = 0;
+
+            if (dirnames[i] == null)
+                continue;
+
+            // verify the existence of tris.md2
+            scratch = dirnames[i];
+            scratch += "/tris.md2";
+            if (Sys.FindFirst(scratch, 0, SFF_SUBDIR | SFF_HIDDEN | SFF_SYSTEM) == null) {
+                //free(dirnames[i]);
+                dirnames[i] = null;
+                Sys.FindClose();
+                continue;
+            }
+            Sys.FindClose();
+
+            // verify the existence of at least one pcx skin
+            scratch = dirnames[i] + "/*.pcx";
+            pcxnames = FS.ListFiles(scratch, 0, 0);
+            npcxfiles = pcxnames.length;
+
+            // count valid skins, which consist of a skin with a matching "_i"
+            // icon
+            for (k = 0; k < npcxfiles - 1; k++) {
+                if (!pcxnames[k].endsWith("_i.pcx")) {
+                    //if (!strstr(pcxnames[k], "_i.pcx")) {
+                    if (IconOfSkinExists(pcxnames[k], pcxnames, npcxfiles)) {
+                        nskins++;
+                    }
+                }
+            }
+            if (nskins == 0)
+                continue;
+
+            skinnames = new String[nskins + 1]; //malloc(sizeof(String) *
+            // (nskins + 1));
+            //memset(skinnames, 0, sizeof(String) * (nskins + 1));
+
+            // copy the valid skins
+            for (s = 0, k = 0; k < npcxfiles; k++) {
+
+                if (!pcxnames[k].contains("_i.pcx")) {
+                    if (IconOfSkinExists(pcxnames[k], pcxnames, npcxfiles)) {
+                        a = pcxnames[k].lastIndexOf('/');
+                        b = pcxnames[k].lastIndexOf('\\');
+
+                        if (a > b)
+                            c = a;
+                        else
+                            c = b;
+
+                        scratch = pcxnames[k].substring(c + 1, pcxnames[k]
+                                .length());
+                        int pos = scratch.lastIndexOf('.');
+                        if (pos != -1)
+                            scratch = scratch.substring(0, pos);
+
+                        skinnames[s] = scratch;
+                        s++;
+                    }
+                }
+            }
+
+            // at this point we have a valid player model
+            if (s_pmi[s_numplayermodels] == null)
+                s_pmi[s_numplayermodels] = new playermodelinfo_s();
+
+            s_pmi[s_numplayermodels].nskins = nskins;
+            s_pmi[s_numplayermodels].skindisplaynames = skinnames;
+
+            // make short name for the model
+            a = dirnames[i].lastIndexOf('/');
+            b = dirnames[i].lastIndexOf('\\');
+
+            if (a > b)
+                c = a;
+            else
+                c = b;
+
+            s_pmi[s_numplayermodels].displayname = dirnames[i].substring(c + 1);
+            s_pmi[s_numplayermodels].directory = dirnames[i].substring(c + 1);
+
+            s_numplayermodels++;
+        }
+
+    }
+
+    static int pmicmpfnc(playermodelinfo_s a, playermodelinfo_s b) {
+
+        /*
+         * * sort by male, female, then alphabetical
+         */
+        if (a.directory.equals("male"))
+            return -1;
+        else if (b.directory.equals("male"))
+            return 1;
+
+        if (a.directory.equals("female"))
+            return -1;
+        else if (b.directory.equals("female"))
+            return 1;
+
+        return a.directory.compareTo(b.directory);
+    }
+
+    static boolean PlayerConfig_MenuInit() {
+        /*
+         * extern CvarT * name; extern CvarT * team; extern CvarT * skin;
+         */
+        //har currentdirectory[1024];
+        String currentdirectory;
+        //char currentskin[1024];
+        String currentskin;
+
+        int i = 0;
+
+        int currentdirectoryindex = 0;
+        int currentskinindex = 0;
+
+        CvarT hand = Cvar.get("hand", "0", CVAR_USERINFO | CVAR_ARCHIVE);
+
+        PlayerConfig_ScanDirectories();
+
+        if (s_numplayermodels == 0)
+            return false;
+
+        if (hand.value < 0 || hand.value > 2)
+            Cvar.setValue("hand", 0);
+
+        currentdirectory = skin.string;
+
+        if (currentdirectory.lastIndexOf('/') != -1) {
+            currentskin = Lib.rightFrom(currentdirectory, '/');
+            currentdirectory = Lib.leftFrom(currentdirectory, '/');
+        } else if (currentdirectory.lastIndexOf('\\') != -1) {
+            currentskin = Lib.rightFrom(currentdirectory, '\\');
+            currentdirectory = Lib.leftFrom(currentdirectory, '\\');
+        } else {
+            currentdirectory = "male";
+            currentskin = "grunt";
+        }
+
+        //qsort(s_pmi, s_numplayermodels, sizeof(s_pmi[0]), pmicmpfnc);
+        Arrays.sort(s_pmi, 0, s_numplayermodels, Menu::pmicmpfnc);
+
+        //memset(s_pmnames, 0, sizeof(s_pmnames));
+        s_pmnames = new String[MAX_PLAYERMODELS];
+
+        for (i = 0; i < s_numplayermodels; i++) {
+            s_pmnames[i] = s_pmi[i].displayname;
+            if (Lib.Q_stricmp(s_pmi[i].directory, currentdirectory) == 0) {
+                int j;
+
+                currentdirectoryindex = i;
+
+                for (j = 0; j < s_pmi[i].nskins; j++) {
+                    if (Lib
+                            .Q_stricmp(s_pmi[i].skindisplaynames[j],
+                                    currentskin) == 0) {
+                        currentskinindex = j;
+                        break;
+                    }
+                }
+            }
+        }
+
+        s_player_config_menu.x = viddef.width / 2 - 95;
+        s_player_config_menu.y = viddef.height / 2 - 97;
+        s_player_config_menu.nitems = 0;
+
+        s_player_name_field.type = MTYPE_FIELD;
+        s_player_name_field.name = "name";
+        s_player_name_field.callback = null;
+        s_player_name_field.x = 0;
+        s_player_name_field.y = 0;
+        s_player_name_field.length = 20;
+        s_player_name_field.visible_length = 20;
+        s_player_name_field.buffer = new StringBuffer(name.string);
+        s_player_name_field.cursor = name.string.length();
+
+        s_player_model_title.type = MTYPE_SEPARATOR;
+        s_player_model_title.name = "model";
+        s_player_model_title.x = -8;
+        s_player_model_title.y = 60;
+
+        s_player_model_box.type = MTYPE_SPINCONTROL;
+        s_player_model_box.x = -56;
+        s_player_model_box.y = 70;
+        s_player_model_box.callback = new mcallback() {
+            public void execute(Object o) {
+                ModelCallback(o);
+            }
+        };
+        s_player_model_box.cursor_offset = -48;
+        s_player_model_box.curvalue = currentdirectoryindex;
+        s_player_model_box.itemnames = s_pmnames;
+
+        s_player_skin_title.type = MTYPE_SEPARATOR;
+        s_player_skin_title.name = "skin";
+        s_player_skin_title.x = -16;
+        s_player_skin_title.y = 84;
+
+        s_player_skin_box.type = MTYPE_SPINCONTROL;
+        s_player_skin_box.x = -56;
+        s_player_skin_box.y = 94;
+        s_player_skin_box.name = null;
+        s_player_skin_box.callback = null;
+        s_player_skin_box.cursor_offset = -48;
+        s_player_skin_box.curvalue = currentskinindex;
+        s_player_skin_box.itemnames = s_pmi[currentdirectoryindex].skindisplaynames;
+
+        s_player_hand_title.type = MTYPE_SEPARATOR;
+        s_player_hand_title.name = "handedness";
+        s_player_hand_title.x = 32;
+        s_player_hand_title.y = 108;
+
+        s_player_handedness_box.type = MTYPE_SPINCONTROL;
+        s_player_handedness_box.x = -56;
+        s_player_handedness_box.y = 118;
+        s_player_handedness_box.name = null;
+        s_player_handedness_box.cursor_offset = -48;
+        s_player_handedness_box.callback = new mcallback() {
+            public void execute(Object o) {
+                HandednessCallback(o);
+            }
+        };
+        s_player_handedness_box.curvalue = (int) Cvar.variableValue("hand");
+        s_player_handedness_box.itemnames = handedness;
+
+        for (i = 0; i < rate_tbl.length - 1; i++)
+            if (Cvar.variableValue("rate") == rate_tbl[i])
+                break;
+
+        s_player_rate_title.type = MTYPE_SEPARATOR;
+        s_player_rate_title.name = "connect speed";
+        s_player_rate_title.x = 56;
+        s_player_rate_title.y = 156;
+
+        s_player_rate_box.type = MTYPE_SPINCONTROL;
+        s_player_rate_box.x = -56;
+        s_player_rate_box.y = 166;
+        s_player_rate_box.name = null;
+        s_player_rate_box.cursor_offset = -48;
+        s_player_rate_box.callback = new mcallback() {
+            public void execute(Object o) {
+                RateCallback(o);
+            }
+        };
+        s_player_rate_box.curvalue = i;
+        s_player_rate_box.itemnames = rate_names;
+
+        s_player_download_action.type = MTYPE_ACTION;
+        s_player_download_action.name = "download options";
+        s_player_download_action.flags = QMF_LEFT_JUSTIFY;
+        s_player_download_action.x = -24;
+        s_player_download_action.y = 186;
+        s_player_download_action.statusbar = null;
+        s_player_download_action.callback = new mcallback() {
+            public void execute(Object o) {
+                DownloadOptionsFunc(o);
+            }
+        };
+
+        Menu_AddItem(s_player_config_menu, s_player_name_field);
+        Menu_AddItem(s_player_config_menu, s_player_model_title);
+        Menu_AddItem(s_player_config_menu, s_player_model_box);
+        if (s_player_skin_box.itemnames != null) {
+            Menu_AddItem(s_player_config_menu, s_player_skin_title);
+            Menu_AddItem(s_player_config_menu, s_player_skin_box);
+        }
+        Menu_AddItem(s_player_config_menu, s_player_hand_title);
+        Menu_AddItem(s_player_config_menu, s_player_handedness_box);
+        Menu_AddItem(s_player_config_menu, s_player_rate_title);
+        Menu_AddItem(s_player_config_menu, s_player_rate_box);
+        Menu_AddItem(s_player_config_menu, s_player_download_action);
+
+        return true;
+    }
+
+    static void PlayerConfig_MenuDraw() {
+
+        refdef_t refdef = new refdef_t();
+        //char scratch[MAX_QPATH];
+        String scratch;
+
+        //memset(refdef, 0, sizeof(refdef));
+
+        refdef.x = viddef.width / 2;
+        refdef.y = viddef.height / 2 - 72;
+        refdef.width = 144;
+        refdef.height = 168;
+        refdef.fov_x = 40;
+        refdef.fov_y = Math3D
+                .calcFov(refdef.fov_x, refdef.width, refdef.height);
+        refdef.time = clientStaticT.realtime * 0.001f;
+
+        if (s_pmi[s_player_model_box.curvalue].skindisplaynames != null) {
+
+            entity.clear();
+
+            scratch = "players/" + s_pmi[s_player_model_box.curvalue].directory
+                    + "/tris.md2";
+
+            entity.model = re.RegisterModel(scratch);
+
+            scratch = "players/"
+                    + s_pmi[s_player_model_box.curvalue].directory
+                    + "/"
+                    + s_pmi[s_player_model_box.curvalue].skindisplaynames[s_player_skin_box.curvalue]
+                    + ".pcx";
+
+            entity.skin = re.RegisterSkin(scratch);
+            entity.flags = RF_FULLBRIGHT;
+            entity.origin[0] = 80;
+            entity.origin[1] = 0;
+            entity.origin[2] = 0;
+            Math3D.vectorCopy(entity.origin, entity.oldorigin);
+            entity.frame = 0;
+            entity.oldframe = 0;
+            entity.backlerp = 0.0f;
+            entity.angles[1] = yaw++;
+            if (++yaw > 360)
+                yaw -= 360;
+
+            refdef.areabits = null;
+            refdef.num_entities = 1;
+            refdef.entities = new entity_t[]{entity};
+            refdef.lightstyles = null;
+            refdef.rdflags = RDF_NOWORLDMODEL;
+
+            Menu_Draw(s_player_config_menu);
+
+            DrawTextBox(
+                    (int) ((refdef.x) * (320.0F / viddef.width) - 8),
+                    (int) ((viddef.height / 2) * (240.0F / viddef.height) - 77),
+                    refdef.width / 8, refdef.height / 8);
+            refdef.height += 4;
+
+            re.RenderFrame(refdef);
+
+            scratch = "/players/"
+                    + s_pmi[s_player_model_box.curvalue].directory
+                    + "/"
+                    + s_pmi[s_player_model_box.curvalue].skindisplaynames[s_player_skin_box.curvalue]
+                    + "_i.pcx";
+
+            re.DrawPic(s_player_config_menu.x - 40, refdef.y, scratch);
+        }
+    }
+
+    static String PlayerConfig_MenuKey(int key) {
+        int i;
+
+        if (key == K_ESCAPE) {
+            String scratch;
+
+            Cvar.set("name", s_player_name_field.buffer.toString());
+
+            scratch = s_pmi[s_player_model_box.curvalue].directory
+                    + "/"
+                    + s_pmi[s_player_model_box.curvalue].skindisplaynames[s_player_skin_box.curvalue];
+
+            Cvar.set("skin", scratch);
+
+            for (i = 0; i < s_numplayermodels; i++) {
+                int j;
+
+                for (j = 0; j < s_pmi[i].nskins; j++) {
+                    if (s_pmi[i].skindisplaynames[j] != null)
+                        s_pmi[i].skindisplaynames[j] = null;
+                }
+                s_pmi[i].skindisplaynames = null;
+                s_pmi[i].nskins = 0;
+            }
+        }
+        return Default_MenuKey(s_player_config_menu, key);
+    }
+
+    static void Menu_PlayerConfig_f() {
+        if (!PlayerConfig_MenuInit()) {
+            Menu_SetStatusBar(s_multiplayer_menu,
+                    "No valid player models found");
+            return;
+        }
+        Menu_SetStatusBar(s_multiplayer_menu, null);
+        PushMenu(new xcommand_t() {
+            public void execute() {
+                PlayerConfig_MenuDraw();
+            }
+        }, new keyfunc_t() {
+            public String execute(int key) {
+                return PlayerConfig_MenuKey(key);
+            }
+        });
+    }
+
+    static String Quit_Key(int key) {
+        switch (key) {
+            case K_ESCAPE:
+            case 'n':
+            case 'N':
+                PopMenu();
+                break;
+
+            case 'Y':
+            case 'y':
+                clientStaticT.key_dest = key_console;
+                Client.Quit_f.execute();
+                break;
+
+            default:
+                break;
+        }
+
+        return null;
+
+    }
+
+    static void Quit_Draw() {
+        int w, h;
+        Dimension d = new Dimension();
+        re.DrawGetPicSize(d, "quit");
+        w = d.width;
+        h = d.height;
+        re.DrawPic((viddef.width - w) / 2, (viddef.height - h) / 2, "quit");
+    }
+
+    static void Menu_Quit_f() {
+        PushMenu(new xcommand_t() {
+            public void execute() {
+                Quit_Draw();
+            }
+        }, new keyfunc_t() {
+            public String execute(int key) {
+                return Quit_Key(key);
+            }
+        });
+    }
+
+    /**
+     * Init
+     */
+    public static void Init() {
+        Cmd.AddCommand("menu_main", Menu_Main);
+        Cmd.AddCommand("menu_game", Menu_Game);
+        Cmd.AddCommand("menu_loadgame", Menu_LoadGame);
+        Cmd.AddCommand("menu_savegame", Menu_SaveGame);
+        Cmd.AddCommand("menu_joinserver", Menu_JoinServer);
+        Cmd.AddCommand("menu_addressbook", Menu_AddressBook);
+        Cmd.AddCommand("menu_startserver", Menu_StartServer);
+        Cmd.AddCommand("menu_dmoptions", Menu_DMOptions);
+        Cmd.AddCommand("menu_playerconfig", Menu_PlayerConfig);
+        Cmd.AddCommand("menu_downloadoptions", Menu_DownloadOptions);
+        Cmd.AddCommand("menu_credits", Menu_Credits);
+        Cmd.AddCommand("menu_multiplayer", Menu_Multiplayer);
+        Cmd.AddCommand("menu_video", Menu_Video);
+        Cmd.AddCommand("menu_options", Menu_Options);
+        Cmd.AddCommand("menu_keys", Menu_Keys);
+        Cmd.AddCommand("menu_quit", Menu_Quit);
+
+        for (int i = 0; i < m_layers.length; i++) {
+            m_layers[i] = new menulayer_t();
+        }
+    }
+
+    /*
+     * ================= Draw =================
+     */
+    static void Draw() {
+        if (clientStaticT.key_dest != key_menu)
+            return;
+
+        // repaint everything next frame
+        SCR.DirtyScreen();
+
+        // dim everything behind it down
+        if (clientStateT.cinematictime > 0)
+            re.DrawFill(0, 0, viddef.width, viddef.height, 0);
+        else
+            re.DrawFadeScreen();
+
+        m_drawfunc.execute();
+
+        // delay playing the enter sound until after the
+        // menu has been drawn, to avoid delay while
+        // caching images
+        if (m_entersound) {
+            S.StartLocalSound(menu_in_sound);
+            m_entersound = false;
+        }
+    }
+
+    /*
+     * ================= Keydown =================
+     */
+    static void Keydown(int key) {
+        String s;
+
+        if (m_keyfunc != null)
+            if ((s = m_keyfunc.execute(key)) != null)
+                S.StartLocalSound(s);
+    }
+
+    public static void Action_DoEnter(menuaction_s a) {
+        if (a.callback != null)
+            a.callback.execute(a);
+    }
+
+    public static void Action_Draw(menuaction_s a) {
+        if ((a.flags & QMF_LEFT_JUSTIFY) != 0) {
+            if ((a.flags & QMF_GRAYED) != 0)
+                Menu_DrawStringDark(a.x + a.parent.x + LCOLUMN_OFFSET, a.y
+                        + a.parent.y, a.name);
+            else
+                Menu_DrawString(a.x + a.parent.x + LCOLUMN_OFFSET, a.y
+                        + a.parent.y, a.name);
+        } else {
+            if ((a.flags & QMF_GRAYED) != 0)
+                Menu_DrawStringR2LDark(a.x + a.parent.x + LCOLUMN_OFFSET, a.y
+                        + a.parent.y, a.name);
+            else
+                Menu_DrawStringR2L(a.x + a.parent.x + LCOLUMN_OFFSET, a.y
+                        + a.parent.y, a.name);
+        }
+        if (a.ownerdraw != null)
+            a.ownerdraw.execute(a);
+    }
+
+    /*
+     * =======================================================================
+     * 
+     * QUIT MENU
+     * 
+     * =======================================================================
+     */
+
+    public static void Field_DoEnter(menufield_s f) {
+        if (f.callback != null) {
+            f.callback.execute(f);
+        }
+    }
+
+    public static void Field_Draw(menufield_s f) {
+        int i;
+        String tempbuffer;
+        //[128] = "";
+
+        if (f.name != null)
+            Menu_DrawStringR2LDark(f.x + f.parent.x + LCOLUMN_OFFSET, f.y
+                    + f.parent.y, f.name);
+
+        //strncpy(tempbuffer, f.buffer + f.visible_offset, f.visible_length);
+        String s = f.buffer.toString();
+        tempbuffer = s.substring(f.visible_offset, s.length());
+        re.DrawChar(f.x + f.parent.x + 16, f.y + f.parent.y - 4, 18);
+        re.DrawChar(f.x + f.parent.x + 16, f.y + f.parent.y + 4, 24);
+
+        re.DrawChar(f.x + f.parent.x + 24 + f.visible_length * 8, f.y
+                + f.parent.y - 4, 20);
+        re.DrawChar(f.x + f.parent.x + 24 + f.visible_length * 8, f.y
+                + f.parent.y + 4, 26);
+
+        for (i = 0; i < f.visible_length; i++) {
+            re
+                    .DrawChar(f.x + f.parent.x + 24 + i * 8, f.y + f.parent.y
+                            - 4, 19);
+            re
+                    .DrawChar(f.x + f.parent.x + 24 + i * 8, f.y + f.parent.y
+                            + 4, 25);
+        }
+
+        Menu_DrawString(f.x + f.parent.x + 24, f.y + f.parent.y, tempbuffer);
+
+        if (Menu_ItemAtCursor(f.parent) == f) {
+            int offset;
+
+            if (f.visible_offset != 0)
+                offset = f.visible_length;
+            else
+                offset = f.cursor;
+
+            if ((Timer.getCurrentTimeMillis() / 250 & 1) != 0) {
+                re.DrawChar(f.x + f.parent.x + (offset + 2) * 8 + 8, f.y
+                        + f.parent.y, 11);
+            } else {
+                re.DrawChar(f.x + f.parent.x + (offset + 2) * 8 + 8, f.y
+                        + f.parent.y, ' ');
+            }
+        }
+    }
+
+    public static boolean Field_Key(menufield_s f, int k) {
+        char key = (char) k;
+
+        switch (key) {
+            case K_KP_SLASH:
+                key = '/';
+                break;
+            case K_KP_MINUS:
+                key = '-';
+                break;
+            case K_KP_PLUS:
+                key = '+';
+                break;
+            case K_KP_HOME:
+                key = '7';
+                break;
+            case K_KP_UPARROW:
+                key = '8';
+                break;
+            case K_KP_PGUP:
+                key = '9';
+                break;
+            case K_KP_LEFTARROW:
+                key = '4';
+                break;
+            case K_KP_5:
+                key = '5';
+                break;
+            case K_KP_RIGHTARROW:
+                key = '6';
+                break;
+            case K_KP_END:
+                key = '1';
+                break;
+            case K_KP_DOWNARROW:
+                key = '2';
+                break;
+            case K_KP_PGDN:
+                key = '3';
+                break;
+            case K_KP_INS:
+                key = '0';
+                break;
+            case K_KP_DEL:
+                key = '.';
+                break;
+        }
+
+        if (key > 127) {
+            switch (key) {
+                case K_DEL:
+                default:
+                    return false;
+            }
+        }
+
+        /*
+         * * support pasting from the clipboard
+         */
+        if ((Character.toUpperCase(key) == 'V' && keydown[K_CTRL])
+                || (((key == K_INS) || (key == K_KP_INS)) && keydown[K_SHIFT])) {
+            String cbd;
+
+            if ((cbd = Sys.GetClipboardData()) != null) {
+                //strtok(cbd, "\n\r\b");
+                String lines[] = cbd.split("\r\n");
+                if (lines.length > 0 && lines[0].length() != 0) {
+                    //strncpy(f.buffer, cbd, f.length - 1);
+                    f.buffer = new StringBuffer(lines[0]);
+                    f.cursor = f.buffer.length();
+
+                    f.visible_offset = f.cursor - f.visible_length;
+
+                    if (f.visible_offset < 0)
+                        f.visible_offset = 0;
+                }
+            }
+            return true;
+        }
+
+        switch (key) {
+            case K_KP_LEFTARROW:
+            case K_LEFTARROW:
+            case K_BACKSPACE:
+                if (f.cursor > 0) {
+                    f.buffer.deleteCharAt(f.cursor - 1);
+                    //memmove(f.buffer[f.cursor - 1], f.buffer[f.cursor], strlen(&
+                    // f.buffer[f.cursor]) + 1);
+                    f.cursor--;
+
+                    if (f.visible_offset != 0) {
+                        f.visible_offset--;
+                    }
+                }
+                break;
+
+            case K_KP_DEL:
+            case K_DEL:
+                //memmove(& f.buffer[f.cursor], & f.buffer[f.cursor + 1], strlen(&
+                // f.buffer[f.cursor + 1]) + 1);
+                f.buffer.deleteCharAt(f.cursor);
+                break;
+
+            case K_KP_ENTER:
+            case K_ENTER:
+            case K_ESCAPE:
+            case K_TAB:
+                return false;
+
+            case K_SPACE:
+            default:
+                if (!Character.isDigit(key) && (f.flags & QMF_NUMBERSONLY) != 0)
+                    return false;
+
+                if (f.cursor < f.length) {
+                    f.buffer.append(key);
+                    f.cursor++;
+
+                    if (f.cursor > f.visible_length) {
+                        f.visible_offset++;
+                    }
+                }
+        }
+
+        return true;
+    }
+
+    public static void Menu_AddItem(menuframework_s menu, menucommon_s item) {
+        if (menu.nitems == 0)
+            menu.nslots = 0;
+
+        if (menu.nitems < MAXMENUITEMS) {
+            menu.items[menu.nitems] = item;
+            menu.items[menu.nitems].parent = menu;
+            menu.nitems++;
+        }
+
+        menu.nslots = Menu_TallySlots(menu);
+    }
+
+    //   =============================================================================
+    /* Menu Subsystem */
+
+    /*
+     * * Menu_AdjustCursor * * This function takes the given menu, the
+     * direction, and attempts * to adjust the menu's cursor so that it's at the
+     * next available * slot.
+     */
+    public static void Menu_AdjustCursor(menuframework_s m, int dir) {
+        menucommon_s citem;
+
+        /*
+         * * see if it's in a valid spot
+         */
+        if (m.cursor >= 0 && m.cursor < m.nitems) {
+            if ((citem = Menu_ItemAtCursor(m)) != null) {
+                if (citem.type != MTYPE_SEPARATOR)
+                    return;
+            }
+        }
+
+        /*
+         * * it's not in a valid spot, so crawl in the direction indicated until
+         * we * find a valid spot
+         */
+        if (dir == 1) {
+            while (true) {
+                citem = Menu_ItemAtCursor(m);
+                if (citem != null)
+                    if (citem.type != MTYPE_SEPARATOR)
+                        break;
+                m.cursor += dir;
+                if (m.cursor >= m.nitems)
+                    m.cursor = 0;
+            }
+        } else {
+            while (true) {
+                citem = Menu_ItemAtCursor(m);
+                if (citem != null)
+                    if (citem.type != MTYPE_SEPARATOR)
+                        break;
+                m.cursor += dir;
+                if (m.cursor < 0)
+                    m.cursor = m.nitems - 1;
+            }
+        }
+    }
+
+    public static void Menu_Center(menuframework_s menu) {
+        int height;
+
+        height = menu.items[menu.nitems - 1].y;
+        height += 10;
+
+        menu.y = (viddef.height - height) / 2;
+    }
+
+    public static void Menu_Draw(menuframework_s menu) {
+        int i;
+        menucommon_s item;
+
+        /*
+         * * draw contents
+         */
+        for (i = 0; i < menu.nitems; i++) {
+            switch (menu.items[i].type) {
+                case MTYPE_FIELD:
+                    Field_Draw((menufield_s) menu.items[i]);
+                    break;
+                case MTYPE_SLIDER:
+                    Slider_Draw((menuslider_s) menu.items[i]);
+                    break;
+                case MTYPE_LIST:
+                    MenuList_Draw((menulist_s) menu.items[i]);
+                    break;
+                case MTYPE_SPINCONTROL:
+                    SpinControl_Draw((menulist_s) menu.items[i]);
+                    break;
+                case MTYPE_ACTION:
+                    Action_Draw((menuaction_s) menu.items[i]);
+                    break;
+                case MTYPE_SEPARATOR:
+                    Separator_Draw((menuseparator_s) menu.items[i]);
+                    break;
+            }
+        }
+
+        item = Menu_ItemAtCursor(menu);
+
+        if (item != null && item.cursordraw != null) {
+            item.cursordraw.execute(item);
+        } else if (menu.cursordraw != null) {
+            menu.cursordraw.execute(menu);
+        } else if (item != null && item.type != MTYPE_FIELD) {
+            if ((item.flags & QMF_LEFT_JUSTIFY) != 0) {
+                re.DrawChar(menu.x + item.x - 24 + item.cursor_offset, menu.y
+                        + item.y, 12 + (Timer.getCurrentTimeMillis() / 250 & 1));
+            } else {
+                re.DrawChar(menu.x + item.cursor_offset, menu.y + item.y,
+                        12 + (Timer.getCurrentTimeMillis() / 250 & 1));
+            }
+        }
+
+        if (item != null) {
+            if (item.statusbarfunc != null)
+                item.statusbarfunc.execute(item);
+            else if (item.statusbar != null)
+                Menu_DrawStatusBar(item.statusbar);
+            else
+                Menu_DrawStatusBar(menu.statusbar);
+
+        } else {
+            Menu_DrawStatusBar(menu.statusbar);
+        }
+    }
+
+    public static void Menu_DrawStatusBar(String string) {
+        if (string != null) {
+            int l = string.length();
+            int maxcol = viddef.width / 8;
+            int col = maxcol / 2 - l / 2;
+
+            re.DrawFill(0, viddef.height - 8, viddef.width, 8, 4);
+            Menu_DrawString(col * 8, viddef.height - 8, string);
+        } else {
+            re.DrawFill(0, viddef.height - 8, viddef.width, 8, 0);
+        }
+    }
+
+    public static void Menu_DrawString(int x, int y, String string) {
+        int i;
+
+        for (i = 0; i < string.length(); i++) {
+            re.DrawChar((x + i * 8), y, string.charAt(i));
+        }
+    }
+
+    public static void Menu_DrawStringDark(int x, int y, String string) {
+        int i;
+
+        for (i = 0; i < string.length(); i++) {
+            re.DrawChar((x + i * 8), y, string.charAt(i) + 128);
+        }
+    }
+
+    public static void Menu_DrawStringR2L(int x, int y, String string) {
+        int i;
+
+        int l = string.length();
+        for (i = 0; i < l; i++) {
+            re.DrawChar((x - i * 8), y, string.charAt(l - i - 1));
+        }
+    }
+
+    public static void Menu_DrawStringR2LDark(int x, int y, String string) {
+        int i;
+
+        int l = string.length();
+        for (i = 0; i < l; i++) {
+            re.DrawChar((x - i * 8), y, string.charAt(l - i - 1) + 128);
+        }
+    }
+
+    public static menucommon_s Menu_ItemAtCursor(menuframework_s m) {
+        if (m.cursor < 0 || m.cursor >= m.nitems)
+            return null;
+
+        return m.items[m.cursor];
+    }
+
+    static void Menu_SelectItem(menuframework_s s) {
+        menucommon_s item = Menu_ItemAtCursor(s);
+
+        if (item != null) {
+            switch (item.type) {
+                case MTYPE_FIELD:
+                    Field_DoEnter((menufield_s) item);
+                    return;
+                case MTYPE_ACTION:
+                    Action_DoEnter((menuaction_s) item);
+                    return;
+                case MTYPE_LIST:
+                    //                 Menulist_DoEnter( ( menulist_s ) item );
+                    return;
+                case MTYPE_SPINCONTROL:
+                    //                 SpinControl_DoEnter( ( menulist_s ) item );
+            }
+        }
+    }
+
+    public static void Menu_SetStatusBar(menuframework_s m, String string) {
+        m.statusbar = string;
+    }
+
+    public static void Menu_SlideItem(menuframework_s s, int dir) {
+        menucommon_s item = Menu_ItemAtCursor(s);
+
+        if (item != null) {
+            switch (item.type) {
+                case MTYPE_SLIDER:
+                    Slider_DoSlide((menuslider_s) item, dir);
+                    break;
+                case MTYPE_SPINCONTROL:
+                    SpinControl_DoSlide((menulist_s) item, dir);
+                    break;
+            }
+        }
+    }
+
+    public static int Menu_TallySlots(menuframework_s menu) {
+        int i;
+        int total = 0;
+
+        for (i = 0; i < menu.nitems; i++) {
+            if (menu.items[i].type == MTYPE_LIST) {
+                int nitems = 0;
+                String n[] = ((menulist_s) menu.items[i]).itemnames;
+
+                while (n[nitems] != null)
+                    nitems++;
+
+                total += nitems;
+            } else {
+                total++;
+            }
+        }
+
+        return total;
+    }
+
+    public static void Menulist_DoEnter(menulist_s l) {
+        int start;
+
+        start = l.y / 10 + 1;
+
+        l.curvalue = l.parent.cursor - start;
+
+        if (l.callback != null)
+            l.callback.execute(l);
+    }
+
+    public static void MenuList_Draw(menulist_s l) {
+        String n[];
+        int y = 0;
+
+        Menu_DrawStringR2LDark(l.x + l.parent.x + LCOLUMN_OFFSET, l.y
+                + l.parent.y, l.name);
+
+        n = l.itemnames;
+
+        re.DrawFill(l.x - 112 + l.parent.x, l.parent.y + l.y + l.curvalue * 10
+                + 10, 128, 10, 16);
+        int i = 0;
+
+        while (n[i] != null) {
+            Menu_DrawStringR2LDark(l.x + l.parent.x + LCOLUMN_OFFSET, l.y
+                    + l.parent.y + y + 10, n[i]);
+
+            i++;
+            y += 10;
+        }
+    }
+
+    public static void Separator_Draw(menuseparator_s s) {
+        if (s.name != null)
+            Menu_DrawStringR2LDark(s.x + s.parent.x, s.y + s.parent.y, s.name);
+    }
+
+    public static void Slider_DoSlide(menuslider_s s, int dir) {
+        s.curvalue += dir;
+
+        if (s.curvalue > s.maxvalue)
+            s.curvalue = s.maxvalue;
+        else if (s.curvalue < s.minvalue)
+            s.curvalue = s.minvalue;
+
+        if (s.callback != null)
+            s.callback.execute(s);
+    }
+
+    public static void Slider_Draw(menuslider_s s) {
+        int i;
+
+        Menu_DrawStringR2LDark(s.x + s.parent.x + LCOLUMN_OFFSET, s.y
+                + s.parent.y, s.name);
+
+        s.range = (s.curvalue - s.minvalue) / (s.maxvalue - s.minvalue);
+
+        if (s.range < 0)
+            s.range = 0;
+        if (s.range > 1)
+            s.range = 1;
+        re.DrawChar(s.x + s.parent.x + RCOLUMN_OFFSET, s.y + s.parent.y, 128);
+        for (i = 0; i < SLIDER_RANGE; i++)
+            re.DrawChar(RCOLUMN_OFFSET + s.x + i * 8 + s.parent.x + 8, s.y
+                    + s.parent.y, 129);
+        re.DrawChar(RCOLUMN_OFFSET + s.x + i * 8 + s.parent.x + 8, s.y
+                + s.parent.y, 130);
+        re
+                .DrawChar(
+                        (int) (8 + RCOLUMN_OFFSET + s.parent.x + s.x + (SLIDER_RANGE - 1)
+                                * 8 * s.range), s.y + s.parent.y, 131);
+    }
+
+    public static void SpinControl_DoEnter(menulist_s s) {
+        s.curvalue++;
+        if (s.itemnames[s.curvalue] == null)
+            s.curvalue = 0;
+
+        if (s.callback != null)
+            s.callback.execute(s);
+    }
+
+    public static void SpinControl_DoSlide(menulist_s s, int dir) {
+        s.curvalue += dir;
+
+        if (s.curvalue < 0)
+            s.curvalue = 0;
+        else if (s.curvalue >= s.itemnames.length || s.itemnames[s.curvalue] == null)
+            s.curvalue--;
+
+        if (s.callback != null)
+            s.callback.execute(s);
+    }
+
+    public static void SpinControl_Draw(menulist_s s) {
+        //char buffer[100];
+
+        if (s.name != null) {
+            Menu_DrawStringR2LDark(s.x + s.parent.x + LCOLUMN_OFFSET, s.y
+                    + s.parent.y, s.name);
+        }
+
+        if (s.itemnames[s.curvalue].indexOf('\n') == -1) {
+            Menu_DrawString(RCOLUMN_OFFSET + s.x + s.parent.x,
+                    s.y + s.parent.y, s.itemnames[s.curvalue]);
+        } else {
+            String line1, line2;
+            line1 = Lib.leftFrom(s.itemnames[s.curvalue], '\n');
+            Menu_DrawString(RCOLUMN_OFFSET + s.x + s.parent.x,
+                    s.y + s.parent.y, line1);
+
+            line2 = Lib.rightFrom(s.itemnames[s.curvalue], '\n');
+
+            int pos = line2.indexOf('\n');
+            if (pos != -1)
+                line2 = line2.substring(0, pos);
+
+            Menu_DrawString(RCOLUMN_OFFSET + s.x + s.parent.x, s.y + s.parent.y
+                    + 10, line2);
+        }
+    }
+
+    public static class menulayer_t {
+        xcommand_t draw;
+
+        keyfunc_t key;
+    }
+
+    static class menuframework_s {
+        final menucommon_s[] items = new menucommon_s[64];
+        int x, y;
+        int cursor;
+        int nitems;
+        int nslots;
+        String statusbar;
+
+        //void (*cursordraw)( struct _tag_menuframework *m );
+        mcallback cursordraw;
+
+    }
+
+    abstract static class mcallback {
+        abstract public void execute(Object self);
+    }
+
+    static class menucommon_s {
+        final int[] localdata = {0, 0, 0, 0};
+        int type;
+        String name = "";
+        int x, y;
+        menuframework_s parent;
+        int cursor_offset;
+        int flags;
+
+        int n = -1; //position in an array.
+
+        String statusbar;
+
+        mcallback callback;
+
+        mcallback statusbarfunc;
+
+        mcallback ownerdraw;
+
+        mcallback cursordraw;
+    }
+
+    static class menufield_s extends menucommon_s {
+        //char buffer[80];
+        StringBuffer buffer; //allow deletion.
+
+        int cursor;
+
+        int length;
+
+        int visible_length;
+
+        int visible_offset;
+    }
+
+    static class menuslider_s extends menucommon_s {
+
+        float minvalue;
+
+        float maxvalue;
+
+        float curvalue;
+
+        float range;
+    }
+
+    static class menulist_s extends menucommon_s {
+        int curvalue;
+
+        String itemnames[];
+    }
+
+    static class menuaction_s extends menucommon_s {
+
+    }
+
+    static class menuseparator_s extends menucommon_s {
+
+    }
+
+    static class playermodelinfo_s {
+        int nskins;
+
+        String skindisplaynames[];
+
+        //char displayname[MAX_DISPLAYNAME];
+        String displayname;
+
+        //char directory[MAX_QPATH];
+        String directory;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/client/SCR.java b/src/main/java/lwjake2/client/SCR.java
new file mode 100644 (file)
index 0000000..25e4d57
--- /dev/null
@@ -0,0 +1,1767 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.Cmd;
+import lwjake2.game.CvarT;
+import lwjake2.qcommon.*;
+import lwjake2.sound.S;
+import lwjake2.sys.Timer;
+import lwjake2.util.Lib;
+import lwjake2.util.Vargs;
+
+import java.awt.*;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+/**
+ * SCR
+ */
+public final class SCR extends Globals {
+
+    // cl_scrn.c -- master for refresh, status bar, console, chat, notify, etc
+
+    static final int STAT_MINUS = 10; // num frame for '-' stats digit
+
+    /*
+     * full screen console put up loading plaque blanked background with loading
+     * plaque blanked background with menu cinematics full screen image for quit
+     * and victory
+     * 
+     * end of unit intermissions
+     */
+    static final int ICON_WIDTH = 24;
+    static final int ICON_HEIGHT = 24;
+    static final int CHAR_WIDTH = 16;
+    static final int ICON_SPACE = 8;
+
+    // scr_vrect ist in Globals definiert
+    // position of render window on screen
+    /*
+     * ================ SCR_DrawLayout
+     *
+     * ================
+     */
+    static final int STAT_LAYOUTS = 13;
+    static final String[][] sb_nums = {
+            {"num_0", "num_1", "num_2", "num_3", "num_4", "num_5", "num_6",
+                    "num_7", "num_8", "num_9", "num_minus"},
+            {"anum_0", "anum_1", "anum_2", "anum_3", "anum_4", "anum_5",
+                    "anum_6", "anum_7", "anum_8", "anum_9", "anum_minus"}};
+    static final dirty_t scr_dirty = new dirty_t();
+    static final dirty_t[] scr_old_dirty = {new dirty_t(), new dirty_t()};
+    static final graphsamp_t[] values = new graphsamp_t[1024];
+    static final dirty_t clear = new dirty_t();
+    /*
+     * ================== SCR_UpdateScreen
+     *
+     * This is called every frame, and can also be called explicitly to flush
+     * text to the screen. ==================
+     */
+    private static final float[] separation = {0, 0};
+    private static final cinematics_t cin = new cinematics_t();
+    private static final byte[] compressed = new byte[0x20000];
+    public static CvarT fps = new CvarT();
+    static float scr_con_current; // aproaches scr_conlines at scr_conspeed
+    static float scr_conlines; // 0.0 to 1.0 lines of console to display
+    static boolean scr_initialized; // ready to draw
+    static int scr_draw_loading;
+    static CvarT scr_viewsize;
+    static CvarT scr_conspeed;
+    static CvarT scr_centertime;
+    static CvarT scr_showturtle;
+    static CvarT scr_showpause;
+    static CvarT scr_printspeed;
+    static CvarT scr_netgraph;
+    static CvarT scr_timegraph;
+    static CvarT scr_debuggraph;
+    static CvarT scr_graphheight;
+    static CvarT scr_graphscale;
+    /*
+     * ===============================================================================
+     *
+     * BAR GRAPHS
+     *
+     * ===============================================================================
+     */
+    static CvarT scr_graphshift;
+    static CvarT scr_drawall;
+    static String crosshair_pic;
+    static int crosshair_width, crosshair_height;
+    /*
+     * ===============================================================================
+     *
+     * CENTER PRINTING
+     *
+     * ===============================================================================
+     */
+    static int current;
+    // char scr_centerstring[1024];
+    static String scr_centerstring;
+    static float scr_centertime_start; // for slow victory printing
+    static float scr_centertime_off;
+    static int scr_center_lines;
+    static int scr_erase_center;
+    // =============================================================================
+    private static int lastframes = 0;
+    private static int lasttime = 0;
+    private static String fpsvalue = "";
+    // ============================================================================
+    private static final xcommand_t updateScreenCallback = new xcommand_t() {
+        public void execute() {
+            UpdateScreen2();
+        }
+    };
+
+    static {
+        for (int n = 0; n < 1024; n++)
+            values[n] = new graphsamp_t();
+    }
+
+    /*
+     * ============== SCR_DebugGraph ==============
+     */
+    public static void DebugGraph(float value, int color) {
+        values[current & 1023].value = value;
+        values[current & 1023].color = color;
+        current++;
+    }
+
+    // =============================================================================
+
+    /*
+     * ============== SCR_DrawDebugGraph ==============
+     */
+    static void DrawDebugGraph() {
+        int a, x, y, w, i, h;
+        float v;
+        int color;
+
+        // draw the graph
+
+        w = scr_vrect.width;
+
+        x = scr_vrect.x;
+        y = scr_vrect.y + scr_vrect.height;
+        re.DrawFill(x, (int) (y - scr_graphheight.value), w,
+                (int) scr_graphheight.value, 8);
+
+        for (a = 0; a < w; a++) {
+            i = (current - 1 - a + 1024) & 1023;
+            v = values[i].value;
+            color = values[i].color;
+            v = v * scr_graphscale.value + scr_graphshift.value;
+
+            if (v < 0)
+                v += scr_graphheight.value
+                        * (1 + (int) (-v / scr_graphheight.value));
+            h = (int) v % (int) scr_graphheight.value;
+            re.DrawFill(x + w - 1 - a, y - h, 1, h, color);
+        }
+    }
+
+    /*
+     * ============== SCR_CenterPrint
+     *
+     * Called for important messages that should stay in the center of the
+     * screen for a few moments ==============
+     */
+    static void CenterPrint(String str) {
+        //char *s;
+        int s;
+        StringBuilder line = new StringBuilder(64);
+        int i, j, l;
+
+        //strncpy (scr_centerstring, str, sizeof(scr_centerstring)-1);
+        scr_centerstring = str;
+        scr_centertime_off = scr_centertime.value;
+        scr_centertime_start = clientStateT.time;
+
+        // count the number of lines for centering
+        scr_center_lines = 1;
+        s = 0;
+        while (s < str.length()) {
+            if (str.charAt(s) == '\n')
+                scr_center_lines++;
+            s++;
+        }
+
+        // echo it to the console
+        Com
+                .Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n");
+
+        s = 0;
+
+        if (str.length() != 0) {
+            do {
+                // scan the width of the line
+
+                for (l = 0; l < 40 && (l + s) < str.length(); l++)
+                    if (str.charAt(s + l) == '\n' || str.charAt(s + l) == 0)
+                        break;
+                for (i = 0; i < (40 - l) / 2; i++)
+                    line.append(' ');
+
+                for (j = 0; j < l; j++) {
+                    line.append(str.charAt(s + j));
+                }
+
+                line.append('\n');
+
+                Com.Printf(line.toString());
+
+                while (s < str.length() && str.charAt(s) != '\n')
+                    s++;
+
+                if (s == str.length())
+                    break;
+                s++; // skip the \n
+            } while (true);
+        }
+        Com
+                .Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n\n");
+        Console.ClearNotify();
+    }
+
+    // =============================================================================
+
+    static void DrawCenterString() {
+        String cs = scr_centerstring + "\0";
+        int start;
+        int l;
+        int j;
+        int x, y;
+        int remaining;
+
+        if (cs == null || cs.length() == 0)
+            return;
+
+        // the finale prints the characters one at a time
+        remaining = 9999;
+
+        scr_erase_center = 0;
+        start = 0;
+
+        if (scr_center_lines <= 4)
+            y = (int) (viddef.height * 0.35);
+        else
+            y = 48;
+
+        do {
+            // scan the width of the line
+            for (l = 0; l < 40; l++)
+                if (start + l == cs.length() - 1
+                        || cs.charAt(start + l) == '\n')
+                    break;
+            x = (viddef.width - l * 8) / 2;
+            SCR.AddDirtyPoint(x, y);
+            for (j = 0; j < l; j++, x += 8) {
+                re.DrawChar(x, y, cs.charAt(start + j));
+                if (remaining == 0)
+                    return;
+                remaining--;
+            }
+            SCR.AddDirtyPoint(x, y + 8);
+
+            y += 8;
+
+            while (start < cs.length() && cs.charAt(start) != '\n')
+                start++;
+
+            if (start == cs.length())
+                break;
+            start++; // skip the \n
+        } while (true);
+    }
+
+    static void CheckDrawCenterString() {
+        scr_centertime_off -= clientStaticT.frametime;
+
+        if (scr_centertime_off <= 0)
+            return;
+
+        DrawCenterString();
+    }
+
+    /*
+     * ================= SCR_CalcVrect
+     *
+     * Sets scr_vrect, the coordinates of the rendered window =================
+     */
+    static void CalcVrect() {
+        int size;
+
+        // bound viewsize
+        if (scr_viewsize.value < 40)
+            Cvar.set("viewsize", "40");
+        if (scr_viewsize.value > 100)
+            Cvar.set("viewsize", "100");
+
+        size = (int) scr_viewsize.value;
+
+        scr_vrect.width = viddef.width * size / 100;
+        scr_vrect.width &= ~7;
+
+        scr_vrect.height = viddef.height * size / 100;
+        scr_vrect.height &= ~1;
+
+        scr_vrect.x = (viddef.width - scr_vrect.width) / 2;
+        scr_vrect.y = (viddef.height - scr_vrect.height) / 2;
+    }
+
+    /*
+     * ================= SCR_SizeUp_f
+     *
+     * Keybinding command =================
+     */
+    static void SizeUp_f() {
+        Cvar.setValue("viewsize", scr_viewsize.value + 10);
+    }
+
+    /*
+     * ================= SCR_SizeDown_f
+     *
+     * Keybinding command =================
+     */
+    static void SizeDown_f() {
+        Cvar.setValue("viewsize", scr_viewsize.value - 10);
+    }
+
+    /*
+     * ============== SCR_TileClear
+     * 
+     * Clear any parts of the tiled background that were drawn on last frame
+     * ==============
+     */
+
+    /*
+     * ================= SCR_Sky_f
+     *
+     * Set a specific sky and rotation speed =================
+     */
+    static void Sky_f() {
+        float rotate;
+        float[] axis = {0, 0, 0};
+
+        if (Cmd.Argc() < 2) {
+            Com.Printf("Usage: sky <basename> <rotate> <axis x y z>\n");
+            return;
+        }
+        if (Cmd.Argc() > 2)
+            rotate = Float.parseFloat(Cmd.Argv(2));
+        else
+            rotate = 0;
+        if (Cmd.Argc() == 6) {
+            axis[0] = Float.parseFloat(Cmd.Argv(3));
+            axis[1] = Float.parseFloat(Cmd.Argv(4));
+            axis[2] = Float.parseFloat(Cmd.Argv(5));
+        } else {
+            axis[0] = 0;
+            axis[1] = 0;
+            axis[2] = 1;
+        }
+
+        re.SetSky(Cmd.Argv(1), rotate, axis);
+    }
+
+    /*
+     * ================== SCR_Init ==================
+     */
+    static void Init() {
+        scr_viewsize = Cvar.get("viewsize", "100", CVAR_ARCHIVE);
+        scr_conspeed = Cvar.get("scr_conspeed", "3", 0);
+        scr_showturtle = Cvar.get("scr_showturtle", "0", 0);
+        scr_showpause = Cvar.get("scr_showpause", "1", 0);
+        scr_centertime = Cvar.get("scr_centertime", "2.5", 0);
+        scr_printspeed = Cvar.get("scr_printspeed", "8", 0);
+        scr_netgraph = Cvar.get("netgraph", "1", 0);
+        scr_timegraph = Cvar.get("timegraph", "1", 0);
+        scr_debuggraph = Cvar.get("debuggraph", "1", 0);
+        scr_graphheight = Cvar.get("graphheight", "32", 0);
+        scr_graphscale = Cvar.get("graphscale", "1", 0);
+        scr_graphshift = Cvar.get("graphshift", "0", 0);
+        scr_drawall = Cvar.get("scr_drawall", "1", 0);
+        fps = Cvar.get("fps", "0", 0);
+
+        //
+        // register our commands
+        //
+        Cmd.AddCommand("timerefresh", new xcommand_t() {
+            public void execute() {
+                TimeRefresh_f();
+            }
+        });
+        Cmd.AddCommand("loading", new xcommand_t() {
+            public void execute() {
+                Loading_f();
+            }
+        });
+        Cmd.AddCommand("sizeup", new xcommand_t() {
+            public void execute() {
+                SizeUp_f();
+            }
+        });
+        Cmd.AddCommand("sizedown", new xcommand_t() {
+            public void execute() {
+                SizeDown_f();
+            }
+        });
+        Cmd.AddCommand("sky", new xcommand_t() {
+            public void execute() {
+                Sky_f();
+            }
+        });
+
+        scr_initialized = true;
+    }
+
+    // ===============================================================
+
+    /*
+     * ============== SCR_DrawNet ==============
+     */
+    static void DrawNet() {
+        if (clientStaticT.netchan.outgoing_sequence - clientStaticT.netchan.incoming_acknowledged < CMD_BACKUP - 1)
+            return;
+
+        re.DrawPic(scr_vrect.x + 64, scr_vrect.y, "net");
+    }
+
+    /*
+     * ============== SCR_DrawPause ==============
+     */
+    static void DrawPause() {
+        Dimension dim = new Dimension();
+
+        if (scr_showpause.value == 0) // turn off for screenshots
+            return;
+
+        if (cl_paused.value == 0)
+            return;
+
+        re.DrawGetPicSize(dim, "pause");
+        re.DrawPic((viddef.width - dim.width) / 2, viddef.height / 2 + 8,
+                "pause");
+    }
+
+    /*
+     * ============== SCR_DrawLoading ==============
+     */
+    static void DrawLoading() {
+        Dimension dim = new Dimension();
+
+        if (scr_draw_loading == 0)
+            return;
+
+        scr_draw_loading = 0;
+        re.DrawGetPicSize(dim, "loading");
+        re.DrawPic((viddef.width - dim.width) / 2,
+                (viddef.height - dim.height) / 2, "loading");
+    }
+
+    /*
+     * ================== SCR_RunConsole
+     *
+     * Scroll it up or down ==================
+     */
+    static void RunConsole() {
+        // decide on the height of the console
+        if (clientStaticT.key_dest == key_console)
+            scr_conlines = 0.5f; // half screen
+        else
+            scr_conlines = 0; // none visible
+
+        if (scr_conlines < scr_con_current) {
+            scr_con_current -= scr_conspeed.value * clientStaticT.frametime;
+            if (scr_conlines > scr_con_current)
+                scr_con_current = scr_conlines;
+
+        } else if (scr_conlines > scr_con_current) {
+            scr_con_current += scr_conspeed.value * clientStaticT.frametime;
+            if (scr_conlines < scr_con_current)
+                scr_con_current = scr_conlines;
+        }
+    }
+
+    /*
+     * ================== SCR_DrawConsole ==================
+     */
+    static void DrawConsole() {
+        Console.CheckResize();
+
+        if (clientStaticT.state == ca_disconnected || clientStaticT.state == ca_connecting) { // forced
+            // full
+            // screen
+            // console
+            Console.DrawConsole(1.0f);
+            return;
+        }
+
+        if (clientStaticT.state != ca_active || !clientStateT.refresh_prepped) { // connected, but
+            // can't render
+            Console.DrawConsole(0.5f);
+            re.DrawFill(0, viddef.height / 2, viddef.width, viddef.height / 2,
+                    0);
+            return;
+        }
+
+        if (scr_con_current != 0) {
+            Console.DrawConsole(scr_con_current);
+        } else {
+            if (clientStaticT.key_dest == key_game || clientStaticT.key_dest == key_message)
+                Console.DrawNotify(); // only draw notify in game
+        }
+    }
+
+    /*
+     * ================ SCR_BeginLoadingPlaque ================
+     */
+    public static void BeginLoadingPlaque() {
+        S.StopAllSounds();
+        clientStateT.sound_prepped = false; // don't play ambients
+
+        if (clientStaticT.disable_screen != 0)
+            return;
+        if (developer.value != 0)
+            return;
+        if (clientStaticT.state == ca_disconnected)
+            return; // if at console, don't bring up the plaque
+        if (clientStaticT.key_dest == key_console)
+            return;
+        if (clientStateT.cinematictime > 0)
+            scr_draw_loading = 2; // clear to balack first
+        else
+            scr_draw_loading = 1;
+
+        UpdateScreen();
+        clientStaticT.disable_screen = Timer.getCurrentTimeMillis();
+        clientStaticT.disable_servercount = clientStateT.servercount;
+    }
+
+    /*
+     * ================ SCR_EndLoadingPlaque ================
+     */
+    public static void EndLoadingPlaque() {
+        clientStaticT.disable_screen = 0;
+        Console.ClearNotify();
+    }
+
+    /*
+     * ================ SCR_Loading_f ================
+     */
+    static void Loading_f() {
+        BeginLoadingPlaque();
+    }
+
+    /*
+     * ================ SCR_TimeRefresh_f ================
+     */
+    static void TimeRefresh_f() {
+        int i;
+        int start, stop;
+        float time;
+
+        if (clientStaticT.state != ca_active)
+            return;
+
+        start = Timer.getCurrentTimeMillis();
+
+        if (Cmd.Argc() == 2) { // run without page flipping
+            re.BeginFrame(0);
+            for (i = 0; i < 128; i++) {
+                clientStateT.refdef.viewangles[1] = i / 128.0f * 360.0f;
+                re.RenderFrame(clientStateT.refdef);
+            }
+            re.EndFrame();
+        } else {
+            for (i = 0; i < 128; i++) {
+                clientStateT.refdef.viewangles[1] = i / 128.0f * 360.0f;
+
+                re.BeginFrame(0);
+                re.RenderFrame(clientStateT.refdef);
+                re.EndFrame();
+            }
+        }
+
+        stop = Timer.getCurrentTimeMillis();
+        time = (stop - start) / 1000.0f;
+        Com.Printf("%f seconds (%f fps)\n", new Vargs(2).add(time).add(
+                128.0f / time));
+    }
+
+    static void DirtyScreen() {
+        AddDirtyPoint(0, 0);
+        AddDirtyPoint(viddef.width - 1, viddef.height - 1);
+    }
+
+    static void TileClear() {
+        int i;
+        int top, bottom, left, right;
+        clear.clear();
+
+        if (scr_drawall.value != 0)
+            DirtyScreen(); // for power vr or broken page flippers...
+
+        if (scr_con_current == 1.0f)
+            return; // full screen console
+        if (scr_viewsize.value == 100)
+            return; // full screen rendering
+        if (clientStateT.cinematictime > 0)
+            return; // full screen cinematic
+
+        // erase rect will be the union of the past three frames
+        // so tripple buffering works properly
+        clear.set(scr_dirty);
+        for (i = 0; i < 2; i++) {
+            if (scr_old_dirty[i].x1 < clear.x1)
+                clear.x1 = scr_old_dirty[i].x1;
+            if (scr_old_dirty[i].x2 > clear.x2)
+                clear.x2 = scr_old_dirty[i].x2;
+            if (scr_old_dirty[i].y1 < clear.y1)
+                clear.y1 = scr_old_dirty[i].y1;
+            if (scr_old_dirty[i].y2 > clear.y2)
+                clear.y2 = scr_old_dirty[i].y2;
+        }
+
+        scr_old_dirty[1].set(scr_old_dirty[0]);
+        scr_old_dirty[0].set(scr_dirty);
+
+        scr_dirty.x1 = 9999;
+        scr_dirty.x2 = -9999;
+        scr_dirty.y1 = 9999;
+        scr_dirty.y2 = -9999;
+
+        // don't bother with anything convered by the console)
+        top = (int) (scr_con_current * viddef.height);
+        if (top >= clear.y1)
+            clear.y1 = top;
+
+        if (clear.y2 <= clear.y1)
+            return; // nothing disturbed
+
+        top = scr_vrect.y;
+        bottom = top + scr_vrect.height - 1;
+        left = scr_vrect.x;
+        right = left + scr_vrect.width - 1;
+
+        if (clear.y1 < top) { // clear above view screen
+            i = clear.y2 < top - 1 ? clear.y2 : top - 1;
+            re.DrawTileClear(clear.x1, clear.y1, clear.x2 - clear.x1 + 1, i
+                    - clear.y1 + 1, "backtile");
+            clear.y1 = top;
+        }
+        if (clear.y2 > bottom) { // clear below view screen
+            i = clear.y1 > bottom + 1 ? clear.y1 : bottom + 1;
+            re.DrawTileClear(clear.x1, i, clear.x2 - clear.x1 + 1, clear.y2 - i
+                    + 1, "backtile");
+            clear.y2 = bottom;
+        }
+        if (clear.x1 < left) { // clear left of view screen
+            i = clear.x2 < left - 1 ? clear.x2 : left - 1;
+            re.DrawTileClear(clear.x1, clear.y1, i - clear.x1 + 1, clear.y2
+                    - clear.y1 + 1, "backtile");
+            clear.x1 = left;
+        }
+        if (clear.x2 > right) { // clear left of view screen
+            i = clear.x1 > right + 1 ? clear.x1 : right + 1;
+            re.DrawTileClear(i, clear.y1, clear.x2 - i + 1, clear.y2 - clear.y1
+                    + 1, "backtile");
+            clear.x2 = right;
+        }
+
+    }
+
+    /*
+     * ================ SizeHUDString
+     *
+     * Allow embedded \n in the string ================
+     */
+    static void SizeHUDString(String string, Dimension dim) {
+        int lines, width, current;
+
+        lines = 1;
+        width = 0;
+
+        current = 0;
+        for (int i = 0; i < string.length(); i++) {
+            if (string.charAt(i) == '\n') {
+                lines++;
+                current = 0;
+            } else {
+                current++;
+                if (current > width)
+                    width = current;
+            }
+
+        }
+
+        dim.width = width * 8;
+        dim.height = lines * 8;
+    }
+
+    static void DrawHUDString(String string, int x, int y,
+                              int xor) {
+        int margin;
+        //char line[1024];
+        StringBuffer line = new StringBuffer(1024);
+        int i;
+
+        margin = x;
+
+        for (int l = 0; l < string.length(); ) {
+            // scan out one line of text from the string
+            line = new StringBuffer(1024);
+            while (l < string.length() && string.charAt(l) != '\n') {
+                line.append(string.charAt(l));
+                l++;
+            }
+
+            if (320 != 0)
+                x = margin + (320 - line.length() * 8) / 2;
+            else
+                x = margin;
+            for (i = 0; i < line.length(); i++) {
+                re.DrawChar(x, y, line.charAt(i) ^ xor);
+                x += 8;
+            }
+            if (l < string.length()) {
+                l++; // skip the \n
+                x = margin;
+                y += 8;
+            }
+        }
+    }
+
+    // =======================================================
+
+    /*
+     * ============== SCR_DrawField ==============
+     */
+    static void DrawField(int x, int y, int color, int width, int value) {
+        char ptr;
+        String num;
+        int l;
+        int frame;
+
+        if (width < 1)
+            return;
+
+        // draw number string
+        if (width > 5)
+            width = 5;
+
+        AddDirtyPoint(x, y);
+        AddDirtyPoint(x + width * CHAR_WIDTH + 2, y + 23);
+
+        num = "" + value;
+        l = num.length();
+        if (l > width)
+            l = width;
+        x += 2 + CHAR_WIDTH * (width - l);
+
+        ptr = num.charAt(0);
+
+        for (int i = 0; i < l; i++) {
+            ptr = num.charAt(i);
+            if (ptr == '-')
+                frame = STAT_MINUS;
+            else
+                frame = ptr - '0';
+
+            re.DrawPic(x, y, sb_nums[color][frame]);
+            x += CHAR_WIDTH;
+        }
+    }
+
+    /*
+     * =============== SCR_TouchPics
+     *
+     * Allows rendering code to cache all needed sbar graphics ===============
+     */
+    static void TouchPics() {
+        int i, j;
+
+        for (i = 0; i < 2; i++)
+            for (j = 0; j < 11; j++)
+                re.RegisterPic(sb_nums[i][j]);
+
+        if (crosshair.value != 0.0f) {
+            if (crosshair.value > 3.0f || crosshair.value < 0.0f)
+                crosshair.value = 3.0f;
+
+            crosshair_pic = "ch" + (int) crosshair.value;
+            Dimension dim = new Dimension();
+            re.DrawGetPicSize(dim, crosshair_pic);
+            crosshair_width = dim.width;
+            crosshair_height = dim.height;
+            if (crosshair_width == 0)
+                crosshair_pic = "";
+        }
+    }
+
+    /*
+     * ================ SCR_ExecuteLayoutString
+     *
+     * ================
+     */
+    static void ExecuteLayoutString(String s) {
+        int x, y;
+        int value;
+        String token;
+        int width;
+        int index;
+        ClientInfo ci;
+
+        if (clientStaticT.state != ca_active || !clientStateT.refresh_prepped)
+            return;
+
+        //             if (!s[0])
+        if (s == null || s.length() == 0)
+            return;
+
+        x = 0;
+        y = 0;
+        width = 3;
+
+        Com.ParseHelp ph = new Com.ParseHelp(s);
+
+        while (!ph.isEof()) {
+            token = Com.Parse(ph);
+            if (token.equals("xl")) {
+                token = Com.Parse(ph);
+                x = Lib.atoi(token);
+                continue;
+            }
+            if (token.equals("xr")) {
+                token = Com.Parse(ph);
+                x = viddef.width + Lib.atoi(token);
+                continue;
+            }
+            if (token.equals("xv")) {
+                token = Com.Parse(ph);
+                x = viddef.width / 2 - 160 + Lib.atoi(token);
+                continue;
+            }
+
+            if (token.equals("yt")) {
+                token = Com.Parse(ph);
+                y = Lib.atoi(token);
+                continue;
+            }
+            if (token.equals("yb")) {
+                token = Com.Parse(ph);
+                y = viddef.height + Lib.atoi(token);
+                continue;
+            }
+            if (token.equals("yv")) {
+                token = Com.Parse(ph);
+                y = viddef.height / 2 - 120 + Lib.atoi(token);
+                continue;
+            }
+
+            if (token.equals("pic")) { // draw a pic from a stat number
+                token = Com.Parse(ph);
+                value = clientStateT.frame.playerstate.stats[Lib.atoi(token)];
+                if (value >= MAX_IMAGES)
+                    Com.Error(ERR_DROP, "Pic >= MAX_IMAGES");
+                if (clientStateT.configstrings[CS_IMAGES + value] != null) {
+                    AddDirtyPoint(x, y);
+                    AddDirtyPoint(x + 23, y + 23);
+                    re.DrawPic(x, y, clientStateT.configstrings[CS_IMAGES + value]);
+                }
+                continue;
+            }
+
+            if (token.equals("client")) { // draw a deathmatch client block
+                int score, ping, time;
+
+                token = Com.Parse(ph);
+                x = viddef.width / 2 - 160 + Lib.atoi(token);
+                token = Com.Parse(ph);
+                y = viddef.height / 2 - 120 + Lib.atoi(token);
+                AddDirtyPoint(x, y);
+                AddDirtyPoint(x + 159, y + 31);
+
+                token = Com.Parse(ph);
+                value = Lib.atoi(token);
+                if (value >= MAX_CLIENTS || value < 0)
+                    Com.Error(ERR_DROP, "client >= MAX_CLIENTS");
+                ci = clientStateT.clientinfo[value];
+
+                token = Com.Parse(ph);
+                score = Lib.atoi(token);
+
+                token = Com.Parse(ph);
+                ping = Lib.atoi(token);
+
+                token = Com.Parse(ph);
+                time = Lib.atoi(token);
+
+                Console.DrawAltString(x + 32, y, ci.name);
+                Console.DrawString(x + 32, y + 8, "Score: ");
+                Console.DrawAltString(x + 32 + 7 * 8, y + 8, "" + score);
+                Console.DrawString(x + 32, y + 16, "Ping:  " + ping);
+                Console.DrawString(x + 32, y + 24, "Time:  " + time);
+
+                if (ci.icon == null)
+                    ci = clientStateT.baseclientinfo;
+                re.DrawPic(x, y, ci.iconname);
+                continue;
+            }
+
+            if (token.equals("ctf")) { // draw a ctf client block
+                int score, ping;
+
+                token = Com.Parse(ph);
+                x = viddef.width / 2 - 160 + Lib.atoi(token);
+                token = Com.Parse(ph);
+                y = viddef.height / 2 - 120 + Lib.atoi(token);
+                AddDirtyPoint(x, y);
+                AddDirtyPoint(x + 159, y + 31);
+
+                token = Com.Parse(ph);
+                value = Lib.atoi(token);
+                if (value >= MAX_CLIENTS || value < 0)
+                    Com.Error(ERR_DROP, "client >= MAX_CLIENTS");
+                ci = clientStateT.clientinfo[value];
+
+                token = Com.Parse(ph);
+                score = Lib.atoi(token);
+
+                token = Com.Parse(ph);
+                ping = Lib.atoi(token);
+                if (ping > 999)
+                    ping = 999;
+
+                // sprintf(block, "%3d %3d %-12.12s", score, ping, ci->name);
+                String block = Com.sprintf("%3d %3d %-12.12s", new Vargs(3)
+                        .add(score).add(ping).add(ci.name));
+
+                if (value == clientStateT.playernum)
+                    Console.DrawAltString(x, y, block);
+                else
+                    Console.DrawString(x, y, block);
+                continue;
+            }
+
+            if (token.equals("picn")) { // draw a pic from a name
+                token = Com.Parse(ph);
+                AddDirtyPoint(x, y);
+                AddDirtyPoint(x + 23, y + 23);
+                re.DrawPic(x, y, token);
+                continue;
+            }
+
+            if (token.equals("num")) { // draw a number
+                token = Com.Parse(ph);
+                width = Lib.atoi(token);
+                token = Com.Parse(ph);
+                value = clientStateT.frame.playerstate.stats[Lib.atoi(token)];
+                DrawField(x, y, 0, width, value);
+                continue;
+            }
+
+            if (token.equals("hnum")) { // health number
+                int color;
+
+                width = 3;
+                value = clientStateT.frame.playerstate.stats[STAT_HEALTH];
+                if (value > 25)
+                    color = 0; // green
+                else if (value > 0)
+                    color = (clientStateT.frame.serverframe >> 2) & 1; // flash
+                else
+                    color = 1;
+
+                if ((clientStateT.frame.playerstate.stats[STAT_FLASHES] & 1) != 0)
+                    re.DrawPic(x, y, "field_3");
+
+                DrawField(x, y, color, width, value);
+                continue;
+            }
+
+
+            if (token.equals("stat_string")) {
+                token = Com.Parse(ph);
+                index = Lib.atoi(token);
+                if (index < 0 || index >= MAX_CONFIGSTRINGS)
+                    Com.Error(ERR_DROP, "Bad stat_string index");
+                index = clientStateT.frame.playerstate.stats[index];
+                if (index < 0 || index >= MAX_CONFIGSTRINGS)
+                    Com.Error(ERR_DROP, "Bad stat_string index");
+                Console.DrawString(x, y, clientStateT.configstrings[index]);
+                continue;
+            }
+
+            if (token.equals("cstring")) {
+                token = Com.Parse(ph);
+                DrawHUDString(token, x, y, 0);
+                continue;
+            }
+
+            if (token.equals("string")) {
+                token = Com.Parse(ph);
+                Console.DrawString(x, y, token);
+                continue;
+            }
+
+            if (token.equals("cstring2")) {
+                token = Com.Parse(ph);
+                DrawHUDString(token, x, y, 0x80);
+                continue;
+            }
+
+            if (token.equals("string2")) {
+                token = Com.Parse(ph);
+                Console.DrawAltString(x, y, token);
+                continue;
+            }
+
+            if (token.equals("if")) { // draw a number
+                token = Com.Parse(ph);
+                value = clientStateT.frame.playerstate.stats[Lib.atoi(token)];
+                if (value == 0) {
+                    // skip to endif
+                    while (!ph.isEof() && !(token = Com.Parse(ph)).equals("endif")) ;
+                }
+            }
+
+        }
+    }
+
+    /*
+     * ================ SCR_DrawStats
+     *
+     * The status bar is a small layout program that is based on the stats array
+     * ================
+     */
+    static void DrawStats() {
+        //TODO:
+        SCR.ExecuteLayoutString(clientStateT.configstrings[CS_STATUSBAR]);
+    }
+
+    static void DrawLayout() {
+        if (clientStateT.frame.playerstate.stats[STAT_LAYOUTS] != 0)
+            SCR.ExecuteLayoutString(clientStateT.layout);
+    }
+
+    static void UpdateScreen2() {
+        int numframes;
+        int i;
+        // if the screen is disabled (loading plaque is up, or vid mode
+        // changing)
+        // do nothing at all
+        if (clientStaticT.disable_screen != 0) {
+            if (Timer.getCurrentTimeMillis() - clientStaticT.disable_screen > 120000) {
+                clientStaticT.disable_screen = 0;
+                Com.Printf("Loading plaque timed out.\n");
+            }
+            return;
+        }
+
+        if (!scr_initialized || !con.initialized)
+            return; // not initialized yet
+
+        /*
+         * * range check cl_camera_separation so we don't inadvertently fry
+         * someone's * brain
+         */
+        if (cl_stereo_separation.value > 1.0)
+            Cvar.setValue("cl_stereo_separation", 1.0f);
+        else if (cl_stereo_separation.value < 0)
+            Cvar.setValue("cl_stereo_separation", 0.0f);
+
+        if (cl_stereo.value != 0) {
+            numframes = 2;
+            separation[0] = -cl_stereo_separation.value / 2;
+            separation[1] = cl_stereo_separation.value / 2;
+        } else {
+            separation[0] = 0;
+            separation[1] = 0;
+            numframes = 1;
+        }
+
+        for (i = 0; i < numframes; i++) {
+            re.BeginFrame(separation[i]);
+
+            if (scr_draw_loading == 2) { //  loading plaque over black screen
+                Dimension dim = new Dimension();
+
+                re.CinematicSetPalette(null);
+                scr_draw_loading = 0; // false
+                re.DrawGetPicSize(dim, "loading");
+                re.DrawPic((viddef.width - dim.width) / 2,
+                        (viddef.height - dim.height) / 2, "loading");
+            }
+            // if a cinematic is supposed to be running, handle menus
+            // and console specially
+            else if (clientStateT.cinematictime > 0) {
+                if (clientStaticT.key_dest == key_menu) {
+                    if (clientStateT.cinematicpalette_active) {
+                        re.CinematicSetPalette(null);
+                        clientStateT.cinematicpalette_active = false;
+                    }
+                    Menu.Draw();
+                } else if (clientStaticT.key_dest == key_console) {
+                    if (clientStateT.cinematicpalette_active) {
+                        re.CinematicSetPalette(null);
+                        clientStateT.cinematicpalette_active = false;
+                    }
+                    DrawConsole();
+                } else {
+                    // TODO implement cinematics completely
+                    DrawCinematic();
+                }
+            } else {
+                // make sure the game palette is active
+                if (clientStateT.cinematicpalette_active) {
+                    re.CinematicSetPalette(null);
+                    clientStateT.cinematicpalette_active = false;
+                }
+
+                // do 3D refresh drawing, and then update the screen
+                CalcVrect();
+
+                // clear any dirty part of the background
+                TileClear();
+
+                V.RenderView(separation[i]);
+
+                DrawStats();
+
+                if ((clientStateT.frame.playerstate.stats[STAT_LAYOUTS] & 1) != 0)
+                    DrawLayout();
+                if ((clientStateT.frame.playerstate.stats[STAT_LAYOUTS] & 2) != 0)
+                    CL_inv.DrawInventory();
+
+                DrawNet();
+                CheckDrawCenterString();
+                DrawFPS();
+
+                //
+                //                             if (scr_timegraph->value)
+                //                                     SCR_DebugGraph (cls.frametime*300, 0);
+                //
+                //                             if (scr_debuggraph->value || scr_timegraph->value ||
+                // scr_netgraph->value)
+                //                                     SCR_DrawDebugGraph ();
+                //
+                DrawPause();
+                DrawConsole();
+                Menu.Draw();
+                DrawLoading();
+            }
+        }
+
+        Globals.re.EndFrame();
+    }
+
+    /*
+     * ================= SCR_DrawCrosshair =================
+     */
+    static void DrawCrosshair() {
+        if (crosshair.value == 0.0f)
+            return;
+
+        if (crosshair.modified) {
+            crosshair.modified = false;
+            SCR.TouchPics();
+        }
+
+        if (crosshair_pic.length() == 0)
+            return;
+
+        re.DrawPic(scr_vrect.x + ((scr_vrect.width - crosshair_width) >> 1),
+                scr_vrect.y + ((scr_vrect.height - crosshair_height) >> 1),
+                crosshair_pic);
+    }
+
+    // wird anstelle von der richtigen UpdateScreen benoetigt
+    public static void UpdateScreen() {
+        Globals.re.updateScreen(updateScreenCallback);
+    }
+
+    /*
+     * ================= SCR_AddDirtyPoint =================
+     */
+    static void AddDirtyPoint(int x, int y) {
+        if (x < scr_dirty.x1)
+            scr_dirty.x1 = x;
+        if (x > scr_dirty.x2)
+            scr_dirty.x2 = x;
+        if (y < scr_dirty.y1)
+            scr_dirty.y1 = y;
+        if (y > scr_dirty.y2)
+            scr_dirty.y2 = y;
+    }
+
+    static void DrawFPS() {
+        if (fps.value > 0.0f) {
+            if (fps.modified) {
+                fps.modified = false;
+                Cvar.setValue("cl_maxfps", 1000);
+            }
+
+            int diff = clientStaticT.realtime - lasttime;
+            if (diff > (int) (fps.value * 1000)) {
+                fpsvalue = (clientStaticT.framecount - lastframes) * 100000 / diff
+                        / 100.0f + " fps";
+                lastframes = clientStaticT.framecount;
+                lasttime = clientStaticT.realtime;
+            }
+            int x = viddef.width - 8 * fpsvalue.length() - 2;
+            for (int i = 0; i < fpsvalue.length(); i++) {
+                re.DrawChar(x, 2, fpsvalue.charAt(i));
+                x += 8;
+            }
+        } else if (fps.modified) {
+            fps.modified = false;
+            Cvar.setValue("cl_maxfps", 90);
+        }
+    }
+
+    /*
+     * =================================================================
+     * 
+     * cl_cin.c
+     * 
+     * Play Cinematics
+     * 
+     * =================================================================
+     */
+
+    /**
+     * LoadPCX
+     */
+    static int LoadPCX(String filename, byte[] palette) {
+        qfiles.pcx_t pcx;
+
+        // load the file
+        ByteBuffer raw = FS.LoadMappedFile(filename);
+
+        if (raw == null) {
+            VideoDriver.Printf(Defines.PRINT_DEVELOPER, "Bad pcx file " + filename
+                    + '\n');
+            return 0;
+        }
+
+        // parse the PCX file
+        pcx = new qfiles.pcx_t(raw);
+
+        if (pcx.manufacturer != 0x0a || pcx.version != 5 || pcx.encoding != 1
+                || pcx.bits_per_pixel != 8 || pcx.xmax >= 640
+                || pcx.ymax >= 480) {
+
+            VideoDriver.Printf(Defines.PRINT_ALL, "Bad pcx file " + filename + '\n');
+            return 0;
+        }
+
+        int width = pcx.xmax - pcx.xmin + 1;
+        int height = pcx.ymax - pcx.ymin + 1;
+
+        byte[] pix = new byte[width * height];
+
+        if (palette != null) {
+            raw.position(raw.limit() - 768);
+            raw.get(palette);
+        }
+
+        if (SCR.cin != null) {
+            SCR.cin.pic = pix;
+            SCR.cin.width = width;
+            SCR.cin.height = height;
+        }
+
+        //
+        // decode pcx
+        //
+        int count = 0;
+        byte dataByte = 0;
+        int runLength = 0;
+        int x, y;
+
+        // simple counter for buffer indexing
+        int p = 0;
+
+        for (y = 0; y < height; y++) {
+            for (x = 0; x < width; ) {
+
+                dataByte = pcx.data.get(p++);
+
+                if ((dataByte & 0xC0) == 0xC0) {
+                    runLength = dataByte & 0x3F;
+                    dataByte = pcx.data.get(p++);
+                    // write runLength pixel
+                    while (runLength-- > 0) {
+                        pix[count++] = dataByte;
+                        x++;
+                    }
+                } else {
+                    // write one pixel
+                    pix[count++] = dataByte;
+                    x++;
+                }
+            }
+        }
+        return width * height;
+    }
+
+    /**
+     * StopCinematic
+     */
+    static void StopCinematic() {
+        if (cin.restart_sound) {
+            // done
+            clientStateT.cinematictime = 0;
+            cin.pic = null;
+            cin.pic_pending = null;
+            if (clientStateT.cinematicpalette_active) {
+                re.CinematicSetPalette(null);
+                clientStateT.cinematicpalette_active = false;
+            }
+            if (clientStateT.cinematic_file != null) {
+                // free the mapped byte buffer
+                clientStateT.cinematic_file = null;
+            }
+            if (cin.hnodes1 != null) {
+                cin.hnodes1 = null;
+            }
+
+            S.disableStreaming();
+            cin.restart_sound = false;
+        }
+    }
+
+    /**
+     * FinishCinematic
+     * <p>
+     * Called when either the cinematic completes, or it is aborted
+     */
+    static void FinishCinematic() {
+        // tell the server to advance to the next map / cinematic
+        MSG.WriteByte(clientStaticT.netchan.message, clc_stringcmd);
+        SZ.Print(clientStaticT.netchan.message, "nextserver " + clientStateT.servercount + '\n');
+    }
+
+    /**
+     * SmallestNode1
+     */
+    private static int SmallestNode1(int numhnodes) {
+
+        int best = 99999999;
+        int bestnode = -1;
+        for (int i = 0; i < numhnodes; i++) {
+            if (cin.h_used[i] != 0)
+                continue;
+            if (cin.h_count[i] == 0)
+                continue;
+            if (cin.h_count[i] < best) {
+                best = cin.h_count[i];
+                bestnode = i;
+            }
+        }
+
+        if (bestnode == -1)
+            return -1;
+
+        cin.h_used[bestnode] = 1; // true
+        return bestnode;
+    }
+
+    /**
+     * Huff1TableInit
+     * <p>
+     * Reads the 64k counts table and initializes the node trees.
+     */
+    private static void Huff1TableInit() {
+        int[] node;
+        byte[] counts = new byte[256];
+        int numhnodes;
+
+        cin.hnodes1 = new int[256 * 256 * 2];
+        Arrays.fill(cin.hnodes1, 0);
+
+        for (int prev = 0; prev < 256; prev++) {
+            Arrays.fill(cin.h_count, 0);
+            Arrays.fill(cin.h_used, 0);
+
+            // read a row of counts
+            clientStateT.cinematic_file.get(counts);
+            for (int j = 0; j < 256; j++)
+                cin.h_count[j] = counts[j] & 0xFF;
+
+            // build the nodes
+            numhnodes = 256;
+            int nodebase = prev * 256 * 2;
+            int index = 0;
+            node = cin.hnodes1;
+            while (numhnodes != 511) {
+                index = nodebase + (numhnodes - 256) * 2;
+
+                // pick two lowest counts
+                node[index] = SmallestNode1(numhnodes);
+                if (node[index] == -1)
+                    break; // no more
+
+                node[index + 1] = SmallestNode1(numhnodes);
+                if (node[index + 1] == -1)
+                    break;
+
+                cin.h_count[numhnodes] = cin.h_count[node[index]] + cin.h_count[node[index + 1]];
+                numhnodes++;
+            }
+
+            cin.numhnodes1[prev] = numhnodes - 1;
+        }
+    }
+
+    // ==========================================================================
+
+    /**
+     * Huff1Decompress
+     */
+    private static byte[] Huff1Decompress(int size) {
+        // get decompressed count
+        int count = (SCR.compressed[0] & 0xFF) | ((SCR.compressed[1] & 0xFF) << 8) | ((SCR.compressed[2] & 0xFF) << 16) | ((SCR.compressed[3] & 0xFF) << 24);
+        // used as index for in[];
+        int input = 4;
+        byte[] out = new byte[count];
+        // used as index for out[];
+        int out_p = 0;
+
+        // read bits
+
+        int hnodesbase = -256 * 2; // nodes 0-255 aren't stored
+        int index = hnodesbase;
+        int[] hnodes = cin.hnodes1;
+        int nodenum = cin.numhnodes1[0];
+        int inbyte;
+        while (count != 0) {
+            inbyte = SCR.compressed[input++] & 0xFF;
+
+            if (nodenum < 256) {
+                index = hnodesbase + (nodenum << 9);
+                out[out_p++] = (byte) nodenum;
+                if (--count == 0)
+                    break;
+                nodenum = cin.numhnodes1[nodenum];
+            }
+            nodenum = hnodes[index + nodenum * 2 + (inbyte & 1)];
+            inbyte >>= 1;
+
+            if (nodenum < 256) {
+                index = hnodesbase + (nodenum << 9);
+                out[out_p++] = (byte) nodenum;
+                if (--count == 0)
+                    break;
+                nodenum = cin.numhnodes1[nodenum];
+            }
+            nodenum = hnodes[index + nodenum * 2 + (inbyte & 1)];
+            inbyte >>= 1;
+
+            if (nodenum < 256) {
+                index = hnodesbase + (nodenum << 9);
+                out[out_p++] = (byte) nodenum;
+                if (--count == 0)
+                    break;
+                nodenum = cin.numhnodes1[nodenum];
+            }
+            nodenum = hnodes[index + nodenum * 2 + (inbyte & 1)];
+            inbyte >>= 1;
+
+            if (nodenum < 256) {
+                index = hnodesbase + (nodenum << 9);
+                out[out_p++] = (byte) nodenum;
+                if (--count == 0)
+                    break;
+                nodenum = cin.numhnodes1[nodenum];
+            }
+            nodenum = hnodes[index + nodenum * 2 + (inbyte & 1)];
+            inbyte >>= 1;
+
+            if (nodenum < 256) {
+                index = hnodesbase + (nodenum << 9);
+                out[out_p++] = (byte) nodenum;
+                if (--count == 0)
+                    break;
+                nodenum = cin.numhnodes1[nodenum];
+            }
+            nodenum = hnodes[index + nodenum * 2 + (inbyte & 1)];
+            inbyte >>= 1;
+
+            if (nodenum < 256) {
+                index = hnodesbase + (nodenum << 9);
+                out[out_p++] = (byte) nodenum;
+                if (--count == 0)
+                    break;
+                nodenum = cin.numhnodes1[nodenum];
+            }
+            nodenum = hnodes[index + nodenum * 2 + (inbyte & 1)];
+            inbyte >>= 1;
+
+            if (nodenum < 256) {
+                index = hnodesbase + (nodenum << 9);
+                out[out_p++] = (byte) nodenum;
+                if (--count == 0)
+                    break;
+                nodenum = cin.numhnodes1[nodenum];
+            }
+            nodenum = hnodes[index + nodenum * 2 + (inbyte & 1)];
+            inbyte >>= 1;
+
+            if (nodenum < 256) {
+                index = hnodesbase + (nodenum << 9);
+                out[out_p++] = (byte) nodenum;
+                if (--count == 0)
+                    break;
+                nodenum = cin.numhnodes1[nodenum];
+            }
+            nodenum = hnodes[index + nodenum * 2 + (inbyte & 1)];
+            inbyte >>= 1;
+        }
+
+        if (input != size && input != size + 1) {
+            Com.Printf("Decompression overread by " + (input - size));
+        }
+
+        return out;
+    }
+
+    /**
+     * ReadNextFrame
+     */
+    static byte[] ReadNextFrame() {
+
+        ByteBuffer file = clientStateT.cinematic_file;
+
+        // read the next frame
+        int command = file.getInt();
+
+        if (command == 2) {
+            // last frame marker
+            return null;
+        }
+
+        if (command == 1) {
+            // read palette
+            file.get(clientStateT.cinematicpalette);
+            // dubious.... exposes an edge case
+            clientStateT.cinematicpalette_active = false;
+        }
+        // decompress the next frame
+        int size = file.getInt();
+        if (size > compressed.length || size < 1)
+            Com.Error(ERR_DROP, "Bad compressed frame size:" + size);
+
+        file.get(compressed, 0, size);
+
+        // read sound
+        int start = clientStateT.cinematicframe * cin.s_rate / 14;
+        int end = (clientStateT.cinematicframe + 1) * cin.s_rate / 14;
+        int count = end - start;
+
+        S.RawSamples(count, cin.s_rate, cin.s_width, cin.s_channels, file.slice());
+        // skip the sound samples
+        file.position(file.position() + count * cin.s_width * cin.s_channels);
+
+        byte[] pic = Huff1Decompress(size);
+        clientStateT.cinematicframe++;
+
+        return pic;
+    }
+
+    /**
+     * RunCinematic
+     */
+    static void RunCinematic() {
+        if (clientStateT.cinematictime <= 0) {
+            StopCinematic();
+            return;
+        }
+
+        if (clientStateT.cinematicframe == -1) {
+            // static image
+            return;
+        }
+
+        if (clientStaticT.key_dest != key_game) {
+            // pause if menu or console is up
+            clientStateT.cinematictime = clientStaticT.realtime - clientStateT.cinematicframe * 1000 / 14;
+            return;
+        }
+
+        int frame = (int) ((clientStaticT.realtime - clientStateT.cinematictime) * 14.0f / 1000);
+
+        if (frame <= clientStateT.cinematicframe)
+            return;
+
+        if (frame > clientStateT.cinematicframe + 1) {
+            Com.Println("Dropped frame: " + frame + " > "
+                    + (clientStateT.cinematicframe + 1));
+            clientStateT.cinematictime = clientStaticT.realtime - clientStateT.cinematicframe * 1000 / 14;
+        }
+
+        cin.pic = cin.pic_pending;
+        cin.pic_pending = ReadNextFrame();
+
+        if (cin.pic_pending == null) {
+            StopCinematic();
+            FinishCinematic();
+            // hack to get the black screen behind loading
+            clientStateT.cinematictime = 1;
+            BeginLoadingPlaque();
+            clientStateT.cinematictime = 0;
+        }
+    }
+
+    /**
+     * DrawCinematic
+     * <p>
+     * Returns true if a cinematic is active, meaning the view rendering should
+     * be skipped.
+     */
+    static void DrawCinematic() {
+        if (clientStateT.cinematictime <= 0) {
+            return;
+        }
+
+        if (clientStaticT.key_dest == key_menu) {
+            // blank screen and pause if menu is up
+            Globals.re.CinematicSetPalette(null);
+            clientStateT.cinematicpalette_active = false;
+            return;
+        }
+
+        if (!clientStateT.cinematicpalette_active) {
+            re.CinematicSetPalette(clientStateT.cinematicpalette);
+            clientStateT.cinematicpalette_active = true;
+        }
+
+        if (cin.pic == null)
+            return;
+
+        Globals.re.DrawStretchRaw(0, 0, viddef.width, viddef.height, cin.width, cin.height, cin.pic);
+
+    }
+
+    /**
+     * PlayCinematic
+     */
+    static void PlayCinematic(String arg) {
+
+        // make sure CD isn't playing music
+        //CDAudio.Stop();
+
+        clientStateT.cinematicframe = 0;
+        if (arg.endsWith(".pcx")) {
+            // static pcx image
+            String name = "pics/" + arg;
+            int size = LoadPCX(name, clientStateT.cinematicpalette);
+            clientStateT.cinematicframe = -1;
+            clientStateT.cinematictime = 1;
+            EndLoadingPlaque();
+            clientStaticT.state = ca_active;
+            if (size == 0 || cin.pic == null) {
+                Com.Println(name + " not found.");
+                clientStateT.cinematictime = 0;
+            }
+            return;
+        }
+
+        String name = "video/" + arg;
+        clientStateT.cinematic_file = FS.LoadMappedFile(name);
+        if (clientStateT.cinematic_file == null) {
+            //Com.Error(ERR_DROP, "Cinematic " + name + " not found.\n");
+            FinishCinematic();
+            // done
+            clientStateT.cinematictime = 0;
+            return;
+        }
+
+        EndLoadingPlaque();
+
+        clientStaticT.state = ca_active;
+
+        clientStateT.cinematic_file.order(ByteOrder.LITTLE_ENDIAN);
+        ByteBuffer file = clientStateT.cinematic_file;
+        cin.width = file.getInt();
+        cin.height = file.getInt();
+        cin.s_rate = file.getInt();
+        cin.s_width = file.getInt();
+        cin.s_channels = file.getInt();
+
+        Huff1TableInit();
+
+        cin.restart_sound = true;
+        clientStateT.cinematicframe = 0;
+        cin.pic = ReadNextFrame();
+        clientStateT.cinematictime = Timer.getCurrentTimeMillis();
+    }
+
+    static class dirty_t {
+        int x1;
+
+        int x2;
+
+        int y1;
+
+        int y2;
+
+        void set(dirty_t src) {
+            x1 = src.x1;
+            x2 = src.x2;
+            y1 = src.y1;
+            y2 = src.y2;
+        }
+
+        void clear() {
+            x1 = x2 = y1 = y2 = 0;
+        }
+    }
+
+    // typedef struct
+    // {
+    //         float value;
+    //         int color;
+    // } graphsamp_t;
+    static class graphsamp_t {
+        float value;
+
+        int color;
+    }
+
+    private static class cinematics_t {
+        final int[] numhnodes1 = new int[256];
+        final int[] h_used = new int[512];
+        final int[] h_count = new int[512];
+        boolean restart_sound;
+        int s_rate;
+        int s_width;
+        int s_channels;
+        int width;
+        int height;
+        byte[] pic;
+        byte[] pic_pending;
+        // order 1 huffman stuff
+        int[] hnodes1; // [256][256][2];
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/client/V.java b/src/main/java/lwjake2/client/V.java
new file mode 100644 (file)
index 0000000..9292a0f
--- /dev/null
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.Globals;
+import lwjake2.game.Cmd;
+import lwjake2.game.CvarT;
+import lwjake2.qcommon.Com;
+import lwjake2.qcommon.Cvar;
+import lwjake2.qcommon.xcommand_t;
+import lwjake2.sys.Timer;
+import lwjake2.util.Math3D;
+import lwjake2.util.Vargs;
+
+import java.nio.FloatBuffer;
+
+/**
+ * V
+ */
+public final class V extends Globals {
+
+    static final dlight_t[] r_dlights = new dlight_t[MAX_DLIGHTS];
+    static final entity_t[] r_entities = new entity_t[MAX_ENTITIES];
+    static final lightstyle_t[] r_lightstyles = new lightstyle_t[MAX_LIGHTSTYLES];
+    /*
+     * ============= V_Viewpos_f =============
+     */
+    static final xcommand_t Viewpos_f = new xcommand_t() {
+        public void execute() {
+            Com.Printf("(%i %i %i) : %i\n", new Vargs(4).add(
+                    (int) clientStateT.refdef.vieworg[0]).add((int) clientStateT.refdef.vieworg[1])
+                    .add((int) clientStateT.refdef.vieworg[2]).add(
+                            (int) clientStateT.refdef.viewangles[YAW]));
+        }
+    };
+    // stack variable
+    private static final float[] origin = {0, 0, 0};
+    static CvarT cl_testblend;
+    static CvarT cl_testparticles;
+    static CvarT cl_testentities;
+    static CvarT cl_testlights;
+    static CvarT cl_stats;
+    static int r_numdlights;
+    static int r_numentities;
+    //static particle_t[] r_particles = new particle_t[MAX_PARTICLES];
+    static int r_numparticles;
+
+    static {
+        for (int i = 0; i < r_dlights.length; i++)
+            r_dlights[i] = new dlight_t();
+        for (int i = 0; i < r_entities.length; i++)
+            r_entities[i] = new entity_t();
+        for (int i = 0; i < r_lightstyles.length; i++)
+            r_lightstyles[i] = new lightstyle_t();
+    }
+
+    /*
+     * ==================== V_ClearScene
+     *
+     * Specifies the model that will be used as the world ====================
+     */
+    static void ClearScene() {
+        r_numdlights = 0;
+        r_numentities = 0;
+        r_numparticles = 0;
+    }
+
+    /*
+     * ===================== V_AddEntity
+     *
+     * =====================
+     */
+    static void AddEntity(entity_t ent) {
+        if (r_numentities >= MAX_ENTITIES)
+            return;
+        r_entities[r_numentities++].set(ent);
+    }
+
+    /*
+     * ===================== V_AddParticle
+     *
+     * =====================
+     */
+    static void AddParticle(float[] org, int color, float alpha) {
+        if (r_numparticles >= MAX_PARTICLES)
+            return;
+
+        int i = r_numparticles++;
+
+        int c = particle_t.colorTable[color];
+        c |= (int) (alpha * 255) << 24;
+        particle_t.colorArray.put(i, c);
+
+        i *= 3;
+        FloatBuffer vertexBuf = particle_t.vertexArray;
+        vertexBuf.put(i++, org[0]);
+        vertexBuf.put(i++, org[1]);
+        vertexBuf.put(i++, org[2]);
+    }
+
+    /*
+     * ===================== V_AddLight
+     *
+     * =====================
+     */
+    static void AddLight(float[] org, float intensity, float r, float g, float b) {
+        dlight_t dl;
+
+        if (r_numdlights >= MAX_DLIGHTS)
+            return;
+        dl = r_dlights[r_numdlights++];
+        Math3D.vectorCopy(org, dl.origin);
+        dl.intensity = intensity;
+        dl.color[0] = r;
+        dl.color[1] = g;
+        dl.color[2] = b;
+    }
+
+    /*
+     * ===================== V_AddLightStyle
+     *
+     * =====================
+     */
+    static void AddLightStyle(int style, float r, float g, float b) {
+        lightstyle_t ls;
+
+        if (style < 0 || style > MAX_LIGHTSTYLES)
+            Com.Error(ERR_DROP, "Bad light style " + style);
+        ls = r_lightstyles[style];
+
+        ls.white = r + g + b;
+        ls.rgb[0] = r;
+        ls.rgb[1] = g;
+        ls.rgb[2] = b;
+    }
+
+    /*
+     * ================ V_TestParticles
+     *
+     * If cl_testparticles is set, create 4096 particles in the view
+     * ================
+     */
+    static void TestParticles() {
+        int i, j;
+        float d, r, u;
+
+        r_numparticles = 0;
+        for (i = 0; i < MAX_PARTICLES; i++) {
+            d = i * 0.25f;
+            r = 4 * ((i & 7) - 3.5f);
+            u = 4 * (((i >> 3) & 7) - 3.5f);
+
+            for (j = 0; j < 3; j++)
+                origin[j] = clientStateT.refdef.vieworg[j] + clientStateT.v_forward[j] * d
+                        + clientStateT.v_right[j] * r + clientStateT.v_up[j] * u;
+
+            AddParticle(origin, 8, cl_testparticles.value);
+        }
+    }
+
+    /*
+     * ================ V_TestEntities
+     *
+     * If cl_testentities is set, create 32 player models ================
+     */
+    static void TestEntities() {
+        int i, j;
+        float f, r;
+        entity_t ent;
+
+        r_numentities = 32;
+        //memset (r_entities, 0, sizeof(r_entities));
+        for (i = 0; i < r_entities.length; i++)
+            r_entities[i].clear();
+
+        for (i = 0; i < r_numentities; i++) {
+            ent = r_entities[i];
+
+            r = 64 * ((i % 4) - 1.5f);
+            f = 64 * (i / 4) + 128;
+
+            for (j = 0; j < 3; j++)
+                ent.origin[j] = clientStateT.refdef.vieworg[j] + clientStateT.v_forward[j] * f
+                        + clientStateT.v_right[j] * r;
+
+            ent.model = clientStateT.baseclientinfo.model;
+            ent.skin = clientStateT.baseclientinfo.skin;
+        }
+    }
+
+    /*
+     * ================ V_TestLights
+     *
+     * If cl_testlights is set, create 32 lights models ================
+     */
+    static void TestLights() {
+        int i, j;
+        float f, r;
+        dlight_t dl;
+
+        r_numdlights = 32;
+        //memset (r_dlights, 0, sizeof(r_dlights));
+        for (i = 0; i < r_dlights.length; i++)
+            r_dlights[i] = new dlight_t();
+
+        for (i = 0; i < r_numdlights; i++) {
+            dl = r_dlights[i];
+
+            r = 64 * ((i % 4) - 1.5f);
+            f = 64 * (i / 4) + 128;
+
+            for (j = 0; j < 3; j++)
+                dl.origin[j] = clientStateT.refdef.vieworg[j] + clientStateT.v_forward[j] * f
+                        + clientStateT.v_right[j] * r;
+            dl.color[0] = ((i % 6) + 1) & 1;
+            dl.color[1] = (((i % 6) + 1) & 2) >> 1;
+            dl.color[2] = (((i % 6) + 1) & 4) >> 2;
+            dl.intensity = 200;
+        }
+    }
+
+    /*
+     * ================== V_RenderView
+     *
+     * ==================
+     */
+    static void RenderView(float stereo_separation) {
+        //             extern int entitycmpfnc( const entity_t *, const entity_t * );
+        //
+        if (clientStaticT.state != ca_active)
+            return;
+
+        if (!clientStateT.refresh_prepped)
+            return; // still loading
+
+        if (cl_timedemo.value != 0.0f) {
+            if (clientStateT.timedemo_start == 0)
+                clientStateT.timedemo_start = Timer.getCurrentTimeMillis();
+            clientStateT.timedemo_frames++;
+        }
+
+        // an invalid frame will just use the exact previous refdef
+        // we can't use the old frame if the video mode has changed, though...
+        if (clientStateT.frame.valid && (clientStateT.force_refdef || cl_paused.value == 0.0f)) {
+            clientStateT.force_refdef = false;
+
+            V.ClearScene();
+
+            // build a refresh entity list and calc cl.sim*
+            // this also calls CL_CalcViewValues which loads
+            // v_forward, etc.
+            CL_ents.AddEntities();
+
+            if (cl_testparticles.value != 0.0f)
+                TestParticles();
+            if (cl_testentities.value != 0.0f)
+                TestEntities();
+            if (cl_testlights.value != 0.0f)
+                TestLights();
+            if (cl_testblend.value != 0.0f) {
+                clientStateT.refdef.blend[0] = 1.0f;
+                clientStateT.refdef.blend[1] = 0.5f;
+                clientStateT.refdef.blend[2] = 0.25f;
+                clientStateT.refdef.blend[3] = 0.5f;
+            }
+
+            // offset vieworg appropriately if we're doing stereo separation
+            if (stereo_separation != 0) {
+                float[] tmp = new float[3];
+
+                Math3D.vectorScale(clientStateT.v_right, stereo_separation, tmp);
+                Math3D.vectorAdd(clientStateT.refdef.vieworg, tmp, clientStateT.refdef.vieworg);
+            }
+
+            // never let it sit exactly on a node line, because a water plane
+            // can
+            // dissapear when viewed with the eye exactly on it.
+            // the server protocol only specifies to 1/8 pixel, so add 1/16 in
+            // each axis
+            clientStateT.refdef.vieworg[0] += 1.0 / 16;
+            clientStateT.refdef.vieworg[1] += 1.0 / 16;
+            clientStateT.refdef.vieworg[2] += 1.0 / 16;
+
+            clientStateT.refdef.x = scr_vrect.x;
+            clientStateT.refdef.y = scr_vrect.y;
+            clientStateT.refdef.width = scr_vrect.width;
+            clientStateT.refdef.height = scr_vrect.height;
+            clientStateT.refdef.fov_y = Math3D.calcFov(clientStateT.refdef.fov_x, clientStateT.refdef.width,
+                    clientStateT.refdef.height);
+            clientStateT.refdef.time = clientStateT.time * 0.001f;
+
+            clientStateT.refdef.areabits = clientStateT.frame.areabits;
+
+            if (cl_add_entities.value == 0.0f)
+                r_numentities = 0;
+            if (cl_add_particles.value == 0.0f)
+                r_numparticles = 0;
+            if (cl_add_lights.value == 0.0f)
+                r_numdlights = 0;
+            if (cl_add_blend.value == 0) {
+                Math3D.vectorClear(clientStateT.refdef.blend);
+            }
+
+            clientStateT.refdef.num_entities = r_numentities;
+            clientStateT.refdef.entities = r_entities;
+            clientStateT.refdef.num_particles = r_numparticles;
+            clientStateT.refdef.num_dlights = r_numdlights;
+            clientStateT.refdef.dlights = r_dlights;
+            clientStateT.refdef.lightstyles = r_lightstyles;
+
+            clientStateT.refdef.rdflags = clientStateT.frame.playerstate.rdflags;
+
+            // sort entities for better cache locality
+            // !!! useless in Java !!!
+            //Arrays.sort(cl.refdef.entities, entitycmpfnc);
+        }
+
+        re.RenderFrame(clientStateT.refdef);
+
+        if (cl_stats.value != 0.0f)
+            Com.Printf("ent:%i  lt:%i  part:%i\n", new Vargs(3).add(
+                    r_numentities).add(r_numdlights).add(r_numparticles));
+
+        SCR.AddDirtyPoint(scr_vrect.x, scr_vrect.y);
+        SCR.AddDirtyPoint(scr_vrect.x + scr_vrect.width - 1, scr_vrect.y
+                + scr_vrect.height - 1);
+
+        SCR.DrawCrosshair();
+    }
+
+    public static void Init() {
+
+        Cmd.AddCommand("viewpos", Viewpos_f);
+
+        crosshair = Cvar.get("crosshair", "0", CVAR_ARCHIVE);
+
+        cl_testblend = Cvar.get("cl_testblend", "0", 0);
+        cl_testparticles = Cvar.get("cl_testparticles", "0", 0);
+        cl_testentities = Cvar.get("cl_testentities", "0", 0);
+        cl_testlights = Cvar.get("cl_testlights", "0", 0);
+
+        cl_stats = Cvar.get("cl_stats", "0", 0);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/client/VideoDriver.java b/src/main/java/lwjake2/client/VideoDriver.java
new file mode 100644 (file)
index 0000000..3468806
--- /dev/null
@@ -0,0 +1,717 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.Cmd;
+import lwjake2.game.CvarT;
+import lwjake2.qcommon.Com;
+import lwjake2.qcommon.Cvar;
+import lwjake2.qcommon.xcommand_t;
+import lwjake2.render.Renderer;
+import lwjake2.sound.S;
+import lwjake2.sys.UserInputHandler;
+import lwjake2.util.Vargs;
+
+import java.awt.*;
+
+/**
+ * VideoDriver is a video driver.
+ * <p>
+ * source: client/vid.h linux/vid_so.c
+ *
+ * @author cwei
+ */
+public class VideoDriver extends Globals {
+    //    Main windowed and fullscreen graphics interface module. This module
+    //    is used for both the software and OpenGL rendering versions of the
+    //    Quake refresh engine.
+
+    // Global variables used internally by this module
+    // Globals.viddef
+    // global video state; used by other modules
+
+    // Structure containing functions exported from refresh DLL
+    // Globals.re;
+
+    static final String[] resolutions =
+            {
+                    "[320 240  ]",
+                    "[400 300  ]",
+                    "[512 384  ]",
+                    "[640 480  ]",
+                    "[800 600  ]",
+                    "[960 720  ]",
+                    "[1024 768 ]",
+                    "[1152 864 ]",
+                    "[1280 1024]",
+                    "[1600 1200]",
+                    "[2048 1536]",
+                    "user mode",
+            };
+    static final String[] yesno_names =
+            {
+                    "no",
+                    "yes",
+            };
+    /*
+    ** VID_GetModeInfo
+    */
+    static final vidmode_t[] vid_modes =
+            {
+                    new vidmode_t("Mode 0: 320x240", 320, 240, 0),
+                    new vidmode_t("Mode 1: 400x300", 400, 300, 1),
+                    new vidmode_t("Mode 2: 512x384", 512, 384, 2),
+                    new vidmode_t("Mode 3: 640x480", 640, 480, 3),
+                    new vidmode_t("Mode 4: 800x600", 800, 600, 4),
+                    new vidmode_t("Mode 5: 960x720", 960, 720, 5),
+                    new vidmode_t("Mode 6: 1024x768", 1024, 768, 6),
+                    new vidmode_t("Mode 7: 1152x864", 1152, 864, 7),
+                    new vidmode_t("Mode 8: 1280x1024", 1280, 1024, 8),
+                    new vidmode_t("Mode 9: 1600x1200", 1600, 1200, 9),
+                    new vidmode_t("Mode 10: 2048x1536", 2048, 1536, 10),
+                    new vidmode_t("Mode 11: user", 640, 480, 11)};
+    static final Menu.menuframework_s s_opengl_menu = new Menu.menuframework_s();
+    static final Menu.menulist_s s_mode_list = new Menu.menulist_s();
+    // const char so_file[] = "/etc/quake2.conf";
+    static final Menu.menulist_s s_ref_list = new Menu.menulist_s();
+    static final Menu.menuslider_s s_tq_slider = new Menu.menuslider_s();
+    static final Menu.menuslider_s s_screensize_slider = new Menu.menuslider_s();
+    static final Menu.menuslider_s s_brightness_slider = new Menu.menuslider_s();
+    static final Menu.menulist_s s_fs_box = new Menu.menulist_s();
+    /*
+    ====================================================================
+
+    MENU INTERACTION
+
+    ====================================================================
+    */
+    static final Menu.menulist_s s_stipple_box = new Menu.menulist_s();
+    static final Menu.menulist_s s_paletted_texture_box = new Menu.menulist_s();
+    static final Menu.menulist_s s_windowed_mouse = new Menu.menulist_s();
+    static final Menu.menuaction_s s_apply_action = new Menu.menuaction_s();
+    static final Menu.menuaction_s s_defaults_action = new Menu.menuaction_s();
+    // Console variables that we need to access from this module
+    static CvarT vid_gamma;
+    static CvarT vid_ref;            // Name of Refresh DLL loaded
+    static CvarT vid_xpos;            // X coordinate of window position
+    /*
+    ==========================================================================
+
+    DLL GLUE
+
+    ==========================================================================
+    */
+    static CvarT vid_ypos;            // Y coordinate of window position
+    static CvarT vid_width;
+    // ==========================================================================
+    static CvarT vid_height;
+    static CvarT vid_fullscreen;
+    // Global variables used internally by this module
+    // void *reflib_library;           // Handle to refresh DLL
+    static boolean reflib_active = false;
+    static vidmode_t fs_modes[];
+    static CvarT gl_mode;
+    static CvarT gl_driver;
+    static CvarT gl_picmip;
+    static CvarT gl_ext_palettedtexture;
+    static CvarT sw_mode;
+    // ==========================================================================
+    //
+    // vid_menu.c
+    //
+    // ==========================================================================
+    static CvarT sw_stipplealpha;
+    static CvarT _windowed_mouse;
+    static Menu.menuframework_s s_current_menu; // referenz
+    static String[] fs_resolutions;
+    static int mode_x;
+    static String[] refs;
+    static String[] drivers;
+
+    public static void Printf(int print_level, String fmt) {
+        Printf(print_level, fmt, null);
+    }
+
+    public static void Printf(int print_level, String fmt, Vargs vargs) {
+        // static qboolean inupdate;
+        if (print_level == Defines.PRINT_ALL)
+            Com.Printf(fmt, vargs);
+        else
+            Com.DPrintf(fmt, vargs);
+    }
+
+    /*
+    ============
+    VID_Restart_f
+
+    Console command to re-start the video mode and refresh DLL. We do this
+    simply by setting the modified flag for the vid_ref variable, which will
+    cause the entire video mode and refresh DLL to be reset on the next frame.
+    ============
+    */
+    static void Restart_f() {
+        vid_modes[11].width = (int) vid_width.value;
+        vid_modes[11].height = (int) vid_height.value;
+
+        vid_ref.modified = true;
+    }
+
+    public static boolean GetModeInfo(Dimension dim, int mode) {
+        if (fs_modes == null) initModeList();
+
+        vidmode_t[] modes = vid_modes;
+        if (vid_fullscreen.value != 0.0f) modes = fs_modes;
+
+        if (mode < 0 || mode >= modes.length)
+            return false;
+
+        dim.width = modes[mode].width;
+        dim.height = modes[mode].height;
+
+        return true;
+    }
+
+    /*
+    ** VID_NewWindow
+    */
+    public static void NewWindow(int width, int height) {
+        Globals.viddef.width = width;
+        Globals.viddef.height = height;
+    }
+
+    static void FreeReflib() {
+        if (Globals.re != null) {
+            Globals.re.getKeyboardHandler().Close();
+            UserInputHandler.Shutdown();
+        }
+
+        Globals.re = null;
+        reflib_active = false;
+    }
+
+    /*
+    ==============
+    VID_LoadRefresh
+    ==============
+    */
+    static boolean LoadRefresh(String name) {
+
+        if (reflib_active) {
+            Globals.re.getKeyboardHandler().Close();
+            UserInputHandler.Shutdown();
+
+            Globals.re.Shutdown();
+            FreeReflib();
+        }
+
+        Com.Printf("------- Loading " + name + " -------\n");
+
+        boolean found = false;
+
+        String[] driverNames = Renderer.getDriverNames();
+        for (String driverName : driverNames) {
+            if (driverName.equals(name)) {
+                found = true;
+                break;
+            }
+        }
+
+        if (!found) {
+            Com.Printf("LoadLibrary(\"" + name + "\") failed\n");
+            return false;
+        }
+
+        Com.Printf("LoadLibrary(\"" + name + "\")\n");
+        Globals.re = Renderer.getDriver(name);
+
+        if (Globals.re == null) {
+            Com.Error(Defines.ERR_FATAL, name + " can't load but registered");
+        }
+
+        if (Globals.re.apiVersion() != Defines.API_VERSION) {
+            FreeReflib();
+            Com.Error(Defines.ERR_FATAL, name + " has incompatible api_version");
+        }
+
+        UserInputHandler.Real_IN_Init();
+
+        if (!Globals.re.Init((int) vid_xpos.value, (int) vid_ypos.value)) {
+            Globals.re.Shutdown();
+            FreeReflib();
+            return false;
+        }
+
+               /* Init KBD */
+        Globals.re.getKeyboardHandler().Init();
+
+        Com.Printf("------------------------------------\n");
+        reflib_active = true;
+        return true;
+    }
+
+    /*
+    ============
+    VID_CheckChanges
+
+    This function gets called once just before drawing each frame, and it's sole purpose in life
+    is to check to see if any of the video mode parameters have changed, and if they have to
+    update the rendering DLL and/or video mode to match.
+    ============
+    */
+    public static void CheckChanges() {
+        CvarT gl_mode;
+
+        if (vid_ref.modified) {
+            S.StopAllSounds();
+        }
+
+        while (vid_ref.modified) {
+            /*
+            ** refresh has changed
+                       */
+            vid_ref.modified = false;
+            vid_fullscreen.modified = true;
+            Globals.clientStateT.refresh_prepped = false;
+            Globals.clientStaticT.disable_screen = 1.0f; // true;
+
+
+            if (!LoadRefresh(vid_ref.string)) {
+                String renderer;
+                if (vid_ref.string.equals(Renderer.getPreferedName())) {
+                    // try the default renderer as fallback after prefered
+                    renderer = Renderer.getDefaultName();
+                } else {
+                    // try the prefered renderer as first fallback
+                    renderer = Renderer.getPreferedName();
+                }
+                if (vid_ref.string.equals(Renderer.getDefaultName())) {
+                    renderer = vid_ref.string;
+                    Com.Printf("Refresh failed\n");
+                    gl_mode = Cvar.get("gl_mode", "0", 0);
+                    if (gl_mode.value != 0.0f) {
+                        Com.Printf("Trying mode 0\n");
+                        Cvar.setValue("gl_mode", 0);
+                        if (!LoadRefresh(vid_ref.string))
+                            Com.Error(Defines.ERR_FATAL, "Couldn't fall back to " + renderer + " refresh!");
+                    } else
+                        Com.Error(Defines.ERR_FATAL, "Couldn't fall back to " + renderer + " refresh!");
+                }
+
+                Cvar.set("vid_ref", renderer);
+
+                               /*
+                 * drop the console if we fail to load a refresh
+                                */
+                if (Globals.clientStaticT.key_dest != Defines.key_console) {
+                    try {
+                        Console.ToggleConsole_f.execute();
+                    } catch (Exception ignored) {
+                    }
+                }
+            }
+            Globals.clientStaticT.disable_screen = 0.0f; //false;
+        }
+    }
+
+    /*
+    ============
+    VID_Init
+    ============
+    */
+    public static void Init() {
+        /* Create the video variables so we know how to start the graphics drivers */
+        vid_ref = Cvar.get("vid_ref", Renderer.getPreferedName(), CVAR_ARCHIVE);
+        vid_xpos = Cvar.get("vid_xpos", "3", CVAR_ARCHIVE);
+        vid_ypos = Cvar.get("vid_ypos", "22", CVAR_ARCHIVE);
+        vid_width = Cvar.get("vid_width", "640", CVAR_ARCHIVE);
+        vid_height = Cvar.get("vid_height", "480", CVAR_ARCHIVE);
+        vid_fullscreen = Cvar.get("vid_fullscreen", "0", CVAR_ARCHIVE);
+        vid_gamma = Cvar.get("vid_gamma", "1", CVAR_ARCHIVE);
+
+        vid_modes[11].width = (int) vid_width.value;
+        vid_modes[11].height = (int) vid_height.value;
+
+               /* Add some console commands that we want to handle */
+        Cmd.AddCommand("vid_restart", new xcommand_t() {
+            public void execute() {
+                Restart_f();
+            }
+        });
+
+               /* Disable the 3Dfx splash screen */
+        // putenv("FX_GLIDE_NO_SPLASH=0");
+
+               /* Start the graphics mode and load refresh DLL */
+        CheckChanges();
+    }
+
+    /*
+    ============
+    VID_Shutdown
+    ============
+    */
+    public static void Shutdown() {
+        if (reflib_active) {
+            Globals.re.getKeyboardHandler().Close();
+            UserInputHandler.Shutdown();
+
+            Globals.re.Shutdown();
+            FreeReflib();
+        }
+    }
+
+    static void DriverCallback(Object unused) {
+        s_current_menu = s_opengl_menu; // s_software_menu;
+    }
+
+    static void ScreenSizeCallback(Object s) {
+        Menu.menuslider_s slider = (Menu.menuslider_s) s;
+
+        Cvar.setValue("viewsize", slider.curvalue * 10);
+    }
+
+    static void BrightnessCallback(Object s) {
+        Menu.menuslider_s slider = (Menu.menuslider_s) s;
+
+        // if ( stricmp( vid_ref.string, "soft" ) == 0 ||
+        //     stricmp( vid_ref.string, "softx" ) == 0 )
+        if (vid_ref.string.equalsIgnoreCase("soft") ||
+                vid_ref.string.equalsIgnoreCase("softx")) {
+            float gamma = (0.8f - (slider.curvalue / 10.0f - 0.5f)) + 0.5f;
+
+            Cvar.setValue("vid_gamma", gamma);
+        }
+    }
+
+    static void ResetDefaults(Object unused) {
+        MenuInit();
+    }
+
+    static void ApplyChanges(Object unused) {
+
+               /*
+        ** invert sense so greater = brighter, and scale to a range of 0.5 to 1.3
+               */
+        // the original was modified, because on CRTs it was too dark.
+        // the slider range is [5; 13]
+        // gamma: [1.1; 0.7]
+        float gamma = (0.4f - (s_brightness_slider.curvalue / 20.0f - 0.25f)) + 0.7f;
+        // modulate:  [1.0; 2.6]
+        float modulate = s_brightness_slider.curvalue * 0.2f;
+
+        Cvar.setValue("vid_gamma", gamma);
+        Cvar.setValue("gl_modulate", modulate);
+        Cvar.setValue("sw_stipplealpha", s_stipple_box.curvalue);
+        Cvar.setValue("gl_picmip", 3 - s_tq_slider.curvalue);
+        Cvar.setValue("vid_fullscreen", s_fs_box.curvalue);
+        Cvar.setValue("gl_ext_palettedtexture", s_paletted_texture_box.curvalue);
+        Cvar.setValue("gl_mode", s_mode_list.curvalue);
+        Cvar.setValue("_windowed_mouse", s_windowed_mouse.curvalue);
+
+        Cvar.set("vid_ref", drivers[s_ref_list.curvalue]);
+        Cvar.set("gl_driver", drivers[s_ref_list.curvalue]);
+        if (gl_driver.modified)
+            vid_ref.modified = true;
+
+        Menu.ForceMenuOff();
+    }
+
+    static void initModeList() {
+        DisplayMode[] modes = re.getModeList();
+        fs_resolutions = new String[modes.length];
+        fs_modes = new vidmode_t[modes.length];
+        for (int i = 0; i < modes.length; i++) {
+            DisplayMode m = modes[i];
+            StringBuilder sb = new StringBuilder(18);
+            sb.append('[');
+            sb.append(m.getWidth());
+            sb.append(' ');
+            sb.append(m.getHeight());
+            while (sb.length() < 10) sb.append(' ');
+            sb.append(']');
+            fs_resolutions[i] = sb.toString();
+            sb.setLength(0);
+            sb.append("Mode ");
+            sb.append(i);
+            sb.append(':');
+            sb.append(m.getWidth());
+            sb.append('x');
+            sb.append(m.getHeight());
+            fs_modes[i] = new vidmode_t(sb.toString(), m.getWidth(), m.getHeight(), i);
+        }
+    }
+
+    private static void initRefs() {
+        drivers = Renderer.getDriverNames();
+        refs = new String[drivers.length];
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < drivers.length; i++) {
+            sb.setLength(0);
+            sb.append("[OpenGL ").append(drivers[i]);
+            while (sb.length() < 16) sb.append(" ");
+            sb.append("]");
+            refs[i] = sb.toString();
+        }
+    }
+
+    /*
+    ** VID_MenuInit
+    */
+    public static void MenuInit() {
+
+        initRefs();
+
+        if (gl_driver == null)
+            gl_driver = Cvar.get("gl_driver", Renderer.getPreferedName(), 0);
+        if (gl_picmip == null)
+            gl_picmip = Cvar.get("gl_picmip", "0", 0);
+        if (gl_mode == null)
+            gl_mode = Cvar.get("gl_mode", "3", 0);
+        if (sw_mode == null)
+            sw_mode = Cvar.get("sw_mode", "0", 0);
+        if (gl_ext_palettedtexture == null)
+            gl_ext_palettedtexture = Cvar.get("gl_ext_palettedtexture", "1", CVAR_ARCHIVE);
+
+        if (sw_stipplealpha == null)
+            sw_stipplealpha = Cvar.get("sw_stipplealpha", "0", CVAR_ARCHIVE);
+
+        if (_windowed_mouse == null)
+            _windowed_mouse = Cvar.get("_windowed_mouse", "0", CVAR_ARCHIVE);
+
+        s_mode_list.curvalue = (int) gl_mode.value;
+        if (vid_fullscreen.value != 0.0f) {
+            s_mode_list.itemnames = fs_resolutions;
+            if (s_mode_list.curvalue >= fs_resolutions.length - 1) {
+                s_mode_list.curvalue = 0;
+            }
+            mode_x = fs_modes[s_mode_list.curvalue].width;
+        } else {
+            s_mode_list.itemnames = resolutions;
+            if (s_mode_list.curvalue >= resolutions.length - 1) {
+                s_mode_list.curvalue = 0;
+            }
+            mode_x = vid_modes[s_mode_list.curvalue].width;
+        }
+
+        if (SCR.scr_viewsize == null)
+            SCR.scr_viewsize = Cvar.get("viewsize", "100", CVAR_ARCHIVE);
+
+        s_screensize_slider.curvalue = (int) (SCR.scr_viewsize.value / 10);
+
+        for (int i = 0; i < drivers.length; i++) {
+            if (vid_ref.string.equals(drivers[i])) {
+                s_ref_list.curvalue = i;
+            }
+        }
+
+        s_opengl_menu.x = (int) (viddef.width * 0.50f);
+        s_opengl_menu.nitems = 0;
+
+        s_ref_list.type = MTYPE_SPINCONTROL;
+        s_ref_list.name = "driver";
+        s_ref_list.x = 0;
+        s_ref_list.y = 0;
+        s_ref_list.callback = new Menu.mcallback() {
+            public void execute(Object self) {
+                DriverCallback(self);
+            }
+        };
+        s_ref_list.itemnames = refs;
+
+        s_mode_list.type = MTYPE_SPINCONTROL;
+        s_mode_list.name = "video mode";
+        s_mode_list.x = 0;
+        s_mode_list.y = 10;
+
+        s_screensize_slider.type = MTYPE_SLIDER;
+        s_screensize_slider.x = 0;
+        s_screensize_slider.y = 20;
+        s_screensize_slider.name = "screen size";
+        s_screensize_slider.minvalue = 3;
+        s_screensize_slider.maxvalue = 12;
+        s_screensize_slider.callback = new Menu.mcallback() {
+            public void execute(Object self) {
+                ScreenSizeCallback(self);
+            }
+        };
+        s_brightness_slider.type = MTYPE_SLIDER;
+        s_brightness_slider.x = 0;
+        s_brightness_slider.y = 30;
+        s_brightness_slider.name = "brightness";
+        s_brightness_slider.callback = new Menu.mcallback() {
+            public void execute(Object self) {
+                BrightnessCallback(self);
+            }
+        };
+        s_brightness_slider.minvalue = 5;
+        s_brightness_slider.maxvalue = 13;
+        s_brightness_slider.curvalue = (1.3f - vid_gamma.value + 0.5f) * 10;
+
+        s_fs_box.type = MTYPE_SPINCONTROL;
+        s_fs_box.x = 0;
+        s_fs_box.y = 40;
+        s_fs_box.name = "fullscreen";
+        s_fs_box.itemnames = yesno_names;
+        s_fs_box.curvalue = (int) vid_fullscreen.value;
+        s_fs_box.callback = new Menu.mcallback() {
+            public void execute(Object o) {
+                int fs = ((Menu.menulist_s) o).curvalue;
+                if (fs == 0) {
+                    s_mode_list.itemnames = resolutions;
+                    int i = vid_modes.length - 2;
+                    while (i > 0 && vid_modes[i].width > mode_x) i--;
+                    s_mode_list.curvalue = i;
+                } else {
+                    s_mode_list.itemnames = fs_resolutions;
+                    int i = fs_modes.length - 1;
+                    while (i > 0 && fs_modes[i].width > mode_x) i--;
+                    s_mode_list.curvalue = i;
+                }
+            }
+        };
+
+        s_defaults_action.type = MTYPE_ACTION;
+        s_defaults_action.name = "reset to default";
+        s_defaults_action.x = 0;
+        s_defaults_action.y = 90;
+        s_defaults_action.callback = new Menu.mcallback() {
+            public void execute(Object self) {
+                ResetDefaults(self);
+            }
+        };
+
+        s_apply_action.type = MTYPE_ACTION;
+        s_apply_action.name = "apply";
+        s_apply_action.x = 0;
+        s_apply_action.y = 100;
+        s_apply_action.callback = new Menu.mcallback() {
+            public void execute(Object self) {
+                ApplyChanges(self);
+            }
+        };
+
+
+        s_stipple_box.type = MTYPE_SPINCONTROL;
+        s_stipple_box.x = 0;
+        s_stipple_box.y = 60;
+        s_stipple_box.name = "stipple alpha";
+        s_stipple_box.curvalue = (int) sw_stipplealpha.value;
+        s_stipple_box.itemnames = yesno_names;
+
+        s_windowed_mouse.type = MTYPE_SPINCONTROL;
+        s_windowed_mouse.x = 0;
+        s_windowed_mouse.y = 72;
+        s_windowed_mouse.name = "windowed mouse";
+        s_windowed_mouse.curvalue = (int) _windowed_mouse.value;
+        s_windowed_mouse.itemnames = yesno_names;
+
+        s_tq_slider.type = MTYPE_SLIDER;
+        s_tq_slider.x = 0;
+        s_tq_slider.y = 60;
+        s_tq_slider.name = "texture quality";
+        s_tq_slider.minvalue = 0;
+        s_tq_slider.maxvalue = 3;
+        s_tq_slider.curvalue = 3 - gl_picmip.value;
+
+        s_paletted_texture_box.type = MTYPE_SPINCONTROL;
+        s_paletted_texture_box.x = 0;
+        s_paletted_texture_box.y = 70;
+        s_paletted_texture_box.name = "8-bit textures";
+        s_paletted_texture_box.itemnames = yesno_names;
+        s_paletted_texture_box.curvalue = (int) gl_ext_palettedtexture.value;
+
+        Menu.Menu_AddItem(s_opengl_menu, s_ref_list);
+        Menu.Menu_AddItem(s_opengl_menu, s_mode_list);
+        Menu.Menu_AddItem(s_opengl_menu, s_screensize_slider);
+        Menu.Menu_AddItem(s_opengl_menu, s_brightness_slider);
+        Menu.Menu_AddItem(s_opengl_menu, s_fs_box);
+        Menu.Menu_AddItem(s_opengl_menu, s_tq_slider);
+        Menu.Menu_AddItem(s_opengl_menu, s_paletted_texture_box);
+
+        Menu.Menu_AddItem(s_opengl_menu, s_defaults_action);
+        Menu.Menu_AddItem(s_opengl_menu, s_apply_action);
+
+        Menu.Menu_Center(s_opengl_menu);
+        s_opengl_menu.x -= 8;
+    }
+
+    /*
+    ================
+    VID_MenuDraw
+    ================
+    */
+    static void MenuDraw() {
+        s_current_menu = s_opengl_menu;
+
+               /*
+        ** draw the banner
+               */
+        Dimension dim = new Dimension();
+        re.DrawGetPicSize(dim, "m_banner_video");
+        re.DrawPic(viddef.width / 2 - dim.width / 2, viddef.height / 2 - 110, "m_banner_video");
+
+               /*
+        ** move cursor to a reasonable starting position
+               */
+        Menu.Menu_AdjustCursor(s_current_menu, 1);
+
+               /*
+               ** draw the menu
+               */
+        Menu.Menu_Draw(s_current_menu);
+    }
+
+    /*
+    ================
+    VID_MenuKey
+    ================
+    */
+    static String MenuKey(int key) {
+        Menu.menuframework_s m = s_current_menu;
+        final String sound = "misc/menu1.wav";
+
+        switch (key) {
+            case K_ESCAPE:
+                Menu.PopMenu();
+                return null;
+            case K_UPARROW:
+                m.cursor--;
+                Menu.Menu_AdjustCursor(m, -1);
+                break;
+            case K_DOWNARROW:
+                m.cursor++;
+                Menu.Menu_AdjustCursor(m, 1);
+                break;
+            case K_LEFTARROW:
+                Menu.Menu_SlideItem(m, -1);
+                break;
+            case K_RIGHTARROW:
+                Menu.Menu_SlideItem(m, 1);
+                break;
+            case K_ENTER:
+                Menu.Menu_SelectItem(m);
+                break;
+        }
+
+        return sound;
+    }
+
+}
diff --git a/src/main/java/lwjake2/client/centity_t.java b/src/main/java/lwjake2/client/centity_t.java
new file mode 100644 (file)
index 0000000..a11c983
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.game.entity_state_t;
+
+public class centity_t {
+    public final entity_state_t current = new entity_state_t(null);
+    final entity_state_t baseline = new entity_state_t(null); // delta from this if not from a previous frame
+    final entity_state_t prev = new entity_state_t(null); // will always be valid, but might just be a copy of current
+    final float[] lerp_origin = {0, 0, 0}; // for trails (variable hz)
+    int serverframe; // if not current, this ent isn't in the frame
+    int trailcount; // for diminishing grenade trails
+    int fly_stoptime;
+}
diff --git a/src/main/java/lwjake2/client/cl_sustain_t.java b/src/main/java/lwjake2/client/cl_sustain_t.java
new file mode 100644 (file)
index 0000000..0257b01
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+/**
+ * cl_sustain_t
+ */
+public class cl_sustain_t {
+    final float[] org = new float[3];
+    final float[] dir = new float[3];
+    int id;
+    int type;
+    int endtime;
+    int nextthink;
+    int thinkinterval;
+    int color;
+    int count;
+    int magnitude;
+    ThinkAdapter think;
+
+    void clear() {
+        org[0] = org[1] = org[2] =
+                dir[0] = dir[1] = dir[2] =
+                        id = type = endtime = nextthink = thinkinterval = color = count = magnitude = 0;
+        think = null;
+    }
+
+    static abstract class ThinkAdapter {
+        abstract void think(cl_sustain_t self);
+    }
+}
diff --git a/src/main/java/lwjake2/client/console_t.java b/src/main/java/lwjake2/client/console_t.java
new file mode 100644 (file)
index 0000000..fcc8c67
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.Defines;
+
+/**
+ * console_t
+ */
+public final class console_t {
+    final byte[] text = new byte[Defines.CON_TEXTSIZE];
+    final float[] times = new float[Defines.NUM_CON_TIMES];    // cls.realtime time the line was generated
+    boolean initialized;
+    int current;    // line where next message will be printed
+    int x;            // offset in current line for next print
+    int display;    // bottom of console displays this line
+    int ormask;        // high bit mask for colored characters
+    int linewidth;    // characters across screen
+    int totallines;    // total lines in console scrollback
+    float cursorspeed;
+    int vislines;
+    // for transparent notify lines
+}
diff --git a/src/main/java/lwjake2/client/cparticle_t.java b/src/main/java/lwjake2/client/cparticle_t.java
new file mode 100644 (file)
index 0000000..a6ea9db
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+/**
+ * cparticle_t
+ *
+ * @author cwei
+ */
+public class cparticle_t {
+
+    public final float[] org = {0, 0, 0}; // vec3_t
+    public final float[] vel = {0, 0, 0}; // vec3_t
+    public final float[] accel = {0, 0, 0}; // vec3_t
+    public cparticle_t next;
+    public float time;
+    public float color;
+    //public float colorvel;
+    public float alpha;
+    public float alphavel;
+}
diff --git a/src/main/java/lwjake2/client/dlight_t.java b/src/main/java/lwjake2/client/dlight_t.java
new file mode 100644 (file)
index 0000000..6cea0a3
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+public class dlight_t {
+    public final float[] origin = {0, 0, 0};
+    public final float[] color = {0, 0, 0};
+    public float intensity;
+}
diff --git a/src/main/java/lwjake2/client/entity_t.java b/src/main/java/lwjake2/client/entity_t.java
new file mode 100644 (file)
index 0000000..892adba
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.render.Image;
+import lwjake2.render.Model;
+import lwjake2.util.Math3D;
+
+public class entity_t implements Cloneable {
+    public final float[] angles = {0, 0, 0};
+    /*
+    ** most recent data
+    */
+    public final float[] origin = {0, 0, 0}; // also used as RF_BEAM's "from"
+    /*
+    ** previous data for lerping
+    */
+    public final float[] oldorigin = {0, 0, 0}; // also used as RF_BEAM's "to"
+    //ptr
+    public Model model; // opaque type outside refresh
+    public int frame; // also used as RF_BEAM's diameter
+    public int oldframe;
+
+    /*
+    ** misc
+    */
+    public float backlerp; // 0.0 = current, 1.0 = old
+    public int skinnum; // also used as RF_BEAM's palette index
+
+    public int lightstyle; // for flashing entities
+    public float alpha; // ignore if RF_TRANSLUCENT isn't set
+
+    // reference
+    public Image skin; // NULL for inline skin
+    public int flags;
+
+
+    public void set(entity_t src) {
+        this.model = src.model;
+        Math3D.vectorCopy(src.angles, this.angles);
+        Math3D.vectorCopy(src.origin, this.origin);
+        this.frame = src.frame;
+        Math3D.vectorCopy(src.oldorigin, this.oldorigin);
+        this.oldframe = src.oldframe;
+        this.backlerp = src.backlerp;
+        this.skinnum = src.skinnum;
+        this.lightstyle = src.lightstyle;
+        this.alpha = src.alpha;
+        this.skin = src.skin;
+        this.flags = src.flags;
+    }
+
+    public void clear() {
+        model = null;
+        Math3D.vectorClear(angles);
+        Math3D.vectorClear(origin);
+        frame = 0;
+        Math3D.vectorClear(oldorigin);
+        oldframe = 0;
+        backlerp = 0;
+        skinnum = 0;
+        lightstyle = 0;
+        alpha = 0;
+        skin = null;
+        flags = 0;
+    }
+
+}
diff --git a/src/main/java/lwjake2/client/frame_t.java b/src/main/java/lwjake2/client/frame_t.java
new file mode 100644 (file)
index 0000000..4e6fa2d
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.game.player_state_t;
+
+import java.util.Arrays;
+
+public class frame_t {
+
+    public static final int MAX_MAP_AREAS = 256;
+    public final player_state_t playerstate = new player_state_t(); // mem
+    final byte[] areabits = new byte[MAX_MAP_AREAS / 8];        // portalarea visibility bits
+    public int servertime;        // server time the message is valid for (in msec)
+    public int num_entities;
+    public int parse_entities;    // non-masked index into cl_parse_entities array
+    boolean valid;            // cleared if delta parsing was invalid
+    int serverframe;
+    int deltaframe;
+
+    public void set(frame_t from) {
+        valid = from.valid;
+        serverframe = from.serverframe;
+        deltaframe = from.deltaframe;
+        num_entities = from.num_entities;
+        parse_entities = from.parse_entities;
+        System.arraycopy(from.areabits, 0, areabits, 0, areabits.length);
+        playerstate.set(from.playerstate);
+    }
+
+    public void reset() {
+        valid = false;
+        serverframe = servertime = deltaframe = 0;
+        Arrays.fill(areabits, (byte) 0);
+        playerstate.clear();
+        num_entities = parse_entities = 0;
+    }
+}
diff --git a/src/main/java/lwjake2/client/kbutton_t.java b/src/main/java/lwjake2/client/kbutton_t.java
new file mode 100644 (file)
index 0000000..b86afd4
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+/**
+ * kbutton_t
+ */
+public class kbutton_t {
+    final int[] down = new int[2];    // key nums holding it down
+    public int state;
+    long downtime;                // msec timestamp
+    long msec;                    // msec down this frame
+}
diff --git a/src/main/java/lwjake2/client/lightstyle_t.java b/src/main/java/lwjake2/client/lightstyle_t.java
new file mode 100644 (file)
index 0000000..28dff1c
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+public class lightstyle_t {
+    public final float[] rgb = {0, 0, 0}; // 0.0 - 2.0
+    public float white; // highest of rgb
+}
diff --git a/src/main/java/lwjake2/client/particle_t.java b/src/main/java/lwjake2/client/particle_t.java
new file mode 100644 (file)
index 0000000..c731c16
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.Defines;
+import lwjake2.util.Lib;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+public class particle_t {
+
+    public static final FloatBuffer vertexArray = Lib.newFloatBuffer(Defines.MAX_PARTICLES * 3);
+    public static final int[] colorTable = new int[256];
+    // lwjgl renderer needs a ByteBuffer
+    private static final ByteBuffer colorByteArray = Lib.newByteBuffer(Defines.MAX_PARTICLES * Lib.SIZEOF_INT, ByteOrder.LITTLE_ENDIAN);
+    public static final IntBuffer colorArray = colorByteArray.asIntBuffer();
+
+
+    public static void setColorPalette(int[] palette) {
+        for (int i = 0; i < 256; i++) {
+            colorTable[i] = palette[i] & 0x00FFFFFF;
+        }
+    }
+
+    public static ByteBuffer getColorAsByteBuffer() {
+        return colorByteArray;
+    }
+}
diff --git a/src/main/java/lwjake2/client/refdef_t.java b/src/main/java/lwjake2/client/refdef_t.java
new file mode 100644 (file)
index 0000000..181945c
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+public class refdef_t {
+    public final float[] vieworg = {0, 0, 0};
+    public final float[] viewangles = {0, 0, 0};
+    public final float[] blend = {0, 0, 0, 0};            // rgba 0-1 full screen blend
+    public int x, y, width, height;// in virtual screen coordinates
+    public float fov_x, fov_y;
+    public float time;                // time is uesed to auto animate
+    public int rdflags;            // RDF_UNDERWATER, etc
+
+    public byte areabits[];            // if not NULL, only areas with set bits will be drawn
+
+    public lightstyle_t lightstyles[];    // [MAX_LIGHTSTYLES]
+
+    public int num_entities;
+    public entity_t entities[];
+
+    public int num_dlights;
+    public dlight_t dlights[];
+
+    public int num_particles;
+    //public particle_t        particles[];
+}
diff --git a/src/main/java/lwjake2/client/refexport_t.java b/src/main/java/lwjake2/client/refexport_t.java
new file mode 100644 (file)
index 0000000..3a02124
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+import lwjake2.qcommon.xcommand_t;
+import lwjake2.render.Image;
+import lwjake2.render.Model;
+import lwjake2.sys.KBD;
+
+import java.awt.*;
+
+/**
+ * refexport_t
+ *
+ * @author cwei
+ */
+public interface refexport_t {
+    // ============================================================================
+    // public interface for Renderer implementations
+    //
+    // ref.h, refexport_t
+    // ============================================================================
+    //
+    // these are the functions exported by the refresh module
+    //
+    // called when the library is loaded
+    boolean Init(int vid_xpos, int vid_ypos);
+
+    // called before the library is unloaded
+    void Shutdown();
+
+    // All data that will be used in a level should be
+    // registered before rendering any frames to prevent disk hits,
+    // but they can still be registered at a later time
+    // if necessary.
+    //
+    // EndRegistration will free any remaining data that wasn't registered.
+    // Any model_s or skin_s pointers from before the BeginRegistration
+    // are no longer valid after EndRegistration.
+    //
+    // Skins and images need to be differentiated, because skins
+    // are flood filled to eliminate mip map edge errors, and pics have
+    // an implicit "pics/" prepended to the name. (a pic name that starts with a
+    // slash will not use the "pics/" prefix or the ".pcx" postfix)
+    void BeginRegistration(String map);
+
+    Model RegisterModel(String name);
+
+    Image RegisterSkin(String name);
+
+    Image RegisterPic(String name);
+
+    void SetSky(String name, float rotate, /* vec3_t */
+                float[] axis);
+
+    void EndRegistration();
+
+    void RenderFrame(refdef_t fd);
+
+    void DrawGetPicSize(Dimension dim /* int *w, *h */, String name);
+
+    // will return 0 0 if not found
+    void DrawPic(int x, int y, String name);
+
+    void DrawStretchPic(int x, int y, int w, int h, String name);
+
+    void DrawChar(int x, int y, int num); // num is 8 bit ASCII
+
+    void DrawTileClear(int x, int y, int w, int h, String name);
+
+    void DrawFill(int x, int y, int w, int h, int c);
+
+    void DrawFadeScreen();
+
+    // Draw images for cinematic rendering (which can have a different palette). Note that calls
+    void DrawStretchRaw(int x, int y, int w, int h, int cols, int rows, byte[] data);
+
+    /*
+    ** video mode and refresh state management entry points
+    */
+    /* 256 r,g,b values;       null = game palette, size = 768 bytes */
+    void CinematicSetPalette(final byte[] palette);
+
+    void BeginFrame(float camera_separation);
+
+    void EndFrame();
+
+    void AppActivate(boolean activate);
+
+    /**
+     *
+     *
+     */
+    void updateScreen(xcommand_t callback);
+
+    int apiVersion();
+
+    DisplayMode[] getModeList();
+
+    KBD getKeyboardHandler();
+}
diff --git a/src/main/java/lwjake2/client/viddef_t.java b/src/main/java/lwjake2/client/viddef_t.java
new file mode 100644 (file)
index 0000000..0dd3cef
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+public class viddef_t {
+    public int width, height;
+}
diff --git a/src/main/java/lwjake2/client/vidmode_t.java b/src/main/java/lwjake2/client/vidmode_t.java
new file mode 100644 (file)
index 0000000..af85d67
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+/**
+ * vidmode_t
+ *
+ * @author cwei
+ */
+public class vidmode_t {
+    final String description;
+    final int mode;
+    int width, height;
+
+    vidmode_t(String description, int width, int height, int mode) {
+        this.description = description;
+        this.width = width;
+        this.height = height;
+        this.mode = mode;
+    }
+}
diff --git a/src/main/java/lwjake2/client/vrect_t.java b/src/main/java/lwjake2/client/vrect_t.java
new file mode 100644 (file)
index 0000000..bbbaf91
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.client;
+
+/**
+ * vrect_t
+ *
+ * @author cwei
+ */
+public class vrect_t {
+    public int x;
+    public int y;
+    public int width;
+    public int height;
+    vrect_t pnext;
+}
diff --git a/src/main/java/lwjake2/game/Client.java b/src/main/java/lwjake2/game/Client.java
new file mode 100644 (file)
index 0000000..1b7cf0f
--- /dev/null
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.util.QuakeFile;
+
+import java.io.IOException;
+
+public class Client {
+
+    // known to server
+    public final player_state_t ps = new player_state_t(); // communicated by server to clients
+    public final int index;
+    // this structure is cleared on each PutClientInServer(),
+    // except for 'client->pers'
+    public int ping;
+    // private to game
+    public client_persistant_t pers = new client_persistant_t();
+    public client_respawn_t resp = new client_respawn_t();
+    public pmove_state_t old_pmove = new pmove_state_t(); // for detecting out-of-pmove changes
+    public boolean showscores; // set layout stat
+    public boolean showinventory; // set layout stat
+    public boolean showhelp;
+    public boolean showhelpicon;
+    public int buttons;
+    public int oldbuttons;
+    public int latched_buttons;
+    // sum up damage over an entire frame, so
+    // shotgun blasts give a single big kick
+    public float v_dmg_roll, v_dmg_pitch, v_dmg_time; // damage kicks
+    public float fall_time, fall_value; // for view drop on fall
+    public float[] damage_blend = {0, 0, 0};
+    public float[] v_angle = {0, 0, 0}; // aiming direction
+    public float bobtime; // so off-ground doesn't change it
+    public float[] oldviewangles = {0, 0, 0};
+    public float[] oldvelocity = {0, 0, 0};
+    public float next_drown_time;
+    public int old_waterlevel;
+    public int breather_sound;
+    public int machinegun_shots; // for weapon raising
+    // animation vars
+    public int anim_end;
+    public int anim_priority;
+    public boolean anim_duck;
+    public boolean anim_run;
+    // powerup timers
+    public float quad_framenum;
+    public float invincible_framenum;
+    public float enviro_framenum;
+    public float pickup_msg_time;
+    public float flood_locktill; // locked from talking
+    public float flood_when[] = new float[10]; // when messages were said
+    public int flood_whenhead; // head pointer for when said
+    public float respawn_time; // can respawn when time > this
+    public EDict chase_target; // player we are chasing
+    public boolean update_chase; // need to update chase info?
+
+    public Client(int index) {
+        this.index = index;
+    }
+
+    /**
+     * Clears the game client structure.
+     */
+    public void clear() {
+        ping = 0;
+
+        pers = new client_persistant_t();
+        resp = new client_respawn_t();
+        old_pmove = new pmove_state_t();
+
+        showscores = false; // set layout stat
+        showinventory = false; // set layout stat
+        showhelp = false;
+        showhelpicon = false;
+
+
+        buttons = oldbuttons = latched_buttons = 0;
+        v_dmg_roll = v_dmg_pitch = v_dmg_time = 0;
+        fall_time = fall_value = 0;
+        damage_blend = new float[3];
+        v_angle = new float[3];
+        bobtime = 0;
+
+        oldviewangles = new float[3];
+
+        oldvelocity = new float[3];
+
+        next_drown_time = 0;
+
+        old_waterlevel = 0;
+
+        breather_sound = 0;
+        machinegun_shots = 0;
+
+        anim_end = 0;
+        anim_priority = 0;
+        anim_duck = false;
+        anim_run = false;
+
+        // powerup timers
+        quad_framenum = 0;
+        invincible_framenum = 0;
+        enviro_framenum = 0;
+
+        pickup_msg_time = 0;
+
+        flood_locktill = 0; // locked from talking
+        flood_when = new float[10]; // when messages were said
+        flood_whenhead = 0; // head pointer for when said
+
+        respawn_time = 0; // can respawn when time > this
+
+        chase_target = null; // player we are chasing
+        update_chase = false; // need to update chase info?
+    }
+
+    /**
+     * Reads a game client from the file.
+     */
+    public void read(QuakeFile f) throws IOException {
+
+        ps.load(f);
+
+        ping = f.readInt();
+
+        pers.read(f);
+        resp.read(f);
+
+        old_pmove.load(f);
+
+        showscores = f.readInt() != 0;
+        showinventory = f.readInt() != 0;
+        showhelp = f.readInt() != 0;
+        showhelpicon = f.readInt() != 0;
+
+        buttons = f.readInt();
+        oldbuttons = f.readInt();
+        latched_buttons = f.readInt();
+
+        v_dmg_roll = f.readFloat();
+        v_dmg_pitch = f.readFloat();
+        v_dmg_time = f.readFloat();
+        fall_time = f.readFloat();
+        fall_value = f.readFloat();
+
+        damage_blend[0] = f.readFloat();
+        damage_blend[1] = f.readFloat();
+        damage_blend[2] = f.readFloat();
+
+        v_angle[0] = f.readFloat();
+        v_angle[1] = f.readFloat();
+        v_angle[2] = f.readFloat();
+
+        bobtime = f.readFloat();
+
+        oldviewangles[0] = f.readFloat();
+        oldviewangles[1] = f.readFloat();
+        oldviewangles[2] = f.readFloat();
+
+        oldvelocity[0] = f.readFloat();
+        oldvelocity[1] = f.readFloat();
+        oldvelocity[2] = f.readFloat();
+
+        next_drown_time = f.readFloat();
+
+        old_waterlevel = f.readInt();
+        breather_sound = f.readInt();
+        machinegun_shots = f.readInt();
+        anim_end = f.readInt();
+        anim_priority = f.readInt();
+        anim_duck = f.readInt() != 0;
+        anim_run = f.readInt() != 0;
+
+        quad_framenum = f.readFloat();
+        invincible_framenum = f.readFloat();
+        enviro_framenum = f.readFloat();
+
+        pickup_msg_time = f.readFloat();
+        flood_locktill = f.readFloat();
+        flood_when[0] = f.readFloat();
+        flood_when[1] = f.readFloat();
+        flood_when[2] = f.readFloat();
+        flood_when[3] = f.readFloat();
+        flood_when[4] = f.readFloat();
+        flood_when[5] = f.readFloat();
+        flood_when[6] = f.readFloat();
+        flood_when[7] = f.readFloat();
+        flood_when[8] = f.readFloat();
+        flood_when[9] = f.readFloat();
+        flood_whenhead = f.readInt();
+        respawn_time = f.readFloat();
+        chase_target = f.readEdictRef();
+        update_chase = f.readInt() != 0;
+
+        if (f.readInt() != 8765)
+            System.err.println("game client load failed for num=" + index);
+    }
+
+    /**
+     * Writes a game_client_t (a player) to a file.
+     */
+    public void write(QuakeFile f) throws IOException {
+        ps.write(f);
+
+        f.writeInt(ping);
+
+        pers.write(f);
+        resp.write(f);
+
+        old_pmove.write(f);
+
+        f.writeInt(showscores ? 1 : 0);
+        f.writeInt(showinventory ? 1 : 0);
+        f.writeInt(showhelp ? 1 : 0);
+        f.writeInt(showhelpicon ? 1 : 0);
+
+        f.writeInt(buttons);
+        f.writeInt(oldbuttons);
+        f.writeInt(latched_buttons);
+
+        f.writeFloat(v_dmg_roll);
+        f.writeFloat(v_dmg_pitch);
+        f.writeFloat(v_dmg_time);
+        f.writeFloat(fall_time);
+        f.writeFloat(fall_value);
+
+        f.writeFloat(damage_blend[0]);
+        f.writeFloat(damage_blend[1]);
+        f.writeFloat(damage_blend[2]);
+
+        f.writeFloat(v_angle[0]);
+        f.writeFloat(v_angle[1]);
+        f.writeFloat(v_angle[2]);
+
+        f.writeFloat(bobtime);
+
+        f.writeFloat(oldviewangles[0]);
+        f.writeFloat(oldviewangles[1]);
+        f.writeFloat(oldviewangles[2]);
+
+        f.writeFloat(oldvelocity[0]);
+        f.writeFloat(oldvelocity[1]);
+        f.writeFloat(oldvelocity[2]);
+
+        f.writeFloat(next_drown_time);
+
+        f.writeInt(old_waterlevel);
+        f.writeInt(breather_sound);
+        f.writeInt(machinegun_shots);
+        f.writeInt(anim_end);
+        f.writeInt(anim_priority);
+        f.writeInt(anim_duck ? 1 : 0);
+        f.writeInt(anim_run ? 1 : 0);
+
+        f.writeFloat(quad_framenum);
+        f.writeFloat(invincible_framenum);
+        f.writeFloat(enviro_framenum);
+
+        f.writeFloat(pickup_msg_time);
+        f.writeFloat(flood_locktill);
+        f.writeFloat(flood_when[0]);
+        f.writeFloat(flood_when[1]);
+        f.writeFloat(flood_when[2]);
+        f.writeFloat(flood_when[3]);
+        f.writeFloat(flood_when[4]);
+        f.writeFloat(flood_when[5]);
+        f.writeFloat(flood_when[6]);
+        f.writeFloat(flood_when[7]);
+        f.writeFloat(flood_when[8]);
+        f.writeFloat(flood_when[9]);
+        f.writeInt(flood_whenhead);
+        f.writeFloat(respawn_time);
+        f.writeEdictRef(chase_target);
+        f.writeInt(update_chase ? 1 : 0);
+
+        f.writeInt(8765);
+    }
+}
diff --git a/src/main/java/lwjake2/game/Cmd.java b/src/main/java/lwjake2/game/Cmd.java
new file mode 100644 (file)
index 0000000..e555117
--- /dev/null
@@ -0,0 +1,991 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.qcommon.*;
+import lwjake2.server.SV_GAME;
+import lwjake2.util.Lib;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Vector;
+
+/**
+ * Cmd
+ */
+public final class Cmd {
+    private static final int ALIAS_LOOP_COUNT = 16;
+    private static final xcommand_t Wait_f = new xcommand_t() {
+        public void execute() {
+            Globals.cmd_wait = true;
+        }
+    };
+    private static final String[] cmd_argv = new String[Defines.MAX_STRING_TOKENS];
+    private static final Comparator<Integer> PlayerSort = (o1, o2) -> {
+
+        int anum1 = GameBase.game.clients[o1].ps.stats[Defines.STAT_FRAGS];
+        int bnum1 = GameBase.game.clients[o2].ps.stats[Defines.STAT_FRAGS];
+
+        return Integer.compare(anum1, bnum1);
+    };
+    private static final char[] expanded = new char[Defines.MAX_STRING_CHARS];
+    private static final char[] temporary = new char[Defines.MAX_STRING_CHARS];
+    private static cmd_function_t cmd_functions = null;
+    private static final xcommand_t List_f = new xcommand_t() {
+        public void execute() {
+            cmd_function_t cmd = Cmd.cmd_functions;
+            int i = 0;
+
+            while (cmd != null) {
+                Com.Printf(cmd.name + '\n');
+                i++;
+                cmd = cmd.next;
+            }
+            Com.Printf(i + " commands\n");
+        }
+    };
+    private static int cmd_argc;
+    private static final xcommand_t Exec_f = new xcommand_t() {
+        public void execute() {
+            if (Cmd.Argc() != 2) {
+                Com.Printf("exec <filename> : execute a script file\n");
+                return;
+            }
+
+            byte[] f = null;
+            f = FS.LoadFile(Cmd.Argv(1));
+            if (f == null) {
+                Com.Printf("couldn't exec " + Cmd.Argv(1) + "\n");
+                return;
+            }
+            Com.Printf("execing " + Cmd.Argv(1) + "\n");
+
+            CommandBuffer.InsertText(new String(f));
+
+            FS.FreeFile();
+        }
+    };
+    private static final xcommand_t Echo_f = new xcommand_t() {
+        public void execute() {
+            for (int i = 1; i < Cmd.Argc(); i++) {
+                Com.Printf(Cmd.Argv(i) + " ");
+            }
+            Com.Printf("'\n");
+        }
+    };
+    private static final xcommand_t Alias_f = new xcommand_t() {
+        public void execute() {
+            cmdalias_t a = null;
+            if (Cmd.Argc() == 1) {
+                Com.Printf("Current alias commands:\n");
+                for (a = Globals.cmd_alias; a != null; a = a.next) {
+                    Com.Printf(a.name + " : " + a.value);
+                }
+                return;
+            }
+
+            String s = Cmd.Argv(1);
+            if (s.length() > Defines.MAX_ALIAS_NAME) {
+                Com.Printf("Alias name is too long\n");
+                return;
+            }
+
+            // if the alias already exists, reuse it
+            for (a = Globals.cmd_alias; a != null; a = a.next) {
+                if (s.equalsIgnoreCase(a.name)) {
+                    a.value = null;
+                    break;
+                }
+            }
+
+            if (a == null) {
+                a = new cmdalias_t();
+                a.next = Globals.cmd_alias;
+                Globals.cmd_alias = a;
+            }
+            a.name = s;
+
+            // copy the rest of the command line
+            StringBuilder cmd = new StringBuilder();
+            int c = Cmd.Argc();
+            for (int i = 2; i < c; i++) {
+                cmd.append(Cmd.Argv(i));
+                if (i != (c - 1))
+                    cmd.append(" ");
+            }
+            cmd.append("\n");
+
+            a.value = cmd.toString();
+        }
+    };
+    private static String cmd_args;
+
+    /**
+     * Register our commands.
+     */
+    public static void Init() {
+
+        Cmd.AddCommand("exec", Exec_f);
+        Cmd.AddCommand("echo", Echo_f);
+        Cmd.AddCommand("cmdlist", List_f);
+        Cmd.AddCommand("alias", Alias_f);
+        Cmd.AddCommand("wait", Wait_f);
+    }
+
+    /**
+     * Cmd_MacroExpandString.
+     */
+    private static char[] MacroExpandString(char text[], int len) {
+        int i, j, count;
+        boolean inquote;
+
+        char scan[];
+
+        String token;
+        inquote = false;
+
+        scan = text;
+
+        if (len >= Defines.MAX_STRING_CHARS) {
+            Com.Printf("Line exceeded " + Defines.MAX_STRING_CHARS
+                    + " chars, discarded.\n");
+            return null;
+        }
+
+        count = 0;
+
+        for (i = 0; i < len; i++) {
+            if (scan[i] == '"')
+                inquote = !inquote;
+
+            if (inquote)
+                continue; // don't expand inside quotes
+
+            if (scan[i] != '$')
+                continue;
+
+            // scan out the complete macro, without $
+            Com.ParseHelp ph = new Com.ParseHelp(text, i + 1);
+            token = Com.Parse(ph);
+
+            if (ph.data == null)
+                continue;
+
+            token = Cvar.variableString(token);
+
+            j = token.length();
+
+            len += j;
+
+            if (len >= Defines.MAX_STRING_CHARS) {
+                Com.Printf("Expanded line exceeded " + Defines.MAX_STRING_CHARS
+                        + " chars, discarded.\n");
+                return null;
+            }
+
+            System.arraycopy(scan, 0, temporary, 0, i);
+            System.arraycopy(token.toCharArray(), 0, temporary, i, token.length());
+            System.arraycopy(ph.data, ph.index, temporary, i + j, len - ph.index - j);
+
+            System.arraycopy(temporary, 0, expanded, 0, 0);
+            scan = expanded;
+            i--;
+            if (++count == 100) {
+                Com.Printf("Macro expansion loop, discarded.\n");
+                return null;
+            }
+        }
+
+        if (inquote) {
+            Com.Printf("Line has unmatched quote, discarded.\n");
+            return null;
+        }
+
+        return scan;
+    }
+
+    /**
+     * Cmd_TokenizeString
+     * <p>
+     * Parses the given string into command line tokens. $Cvars will be expanded
+     * unless they are in a quoted token.
+     */
+    public static void TokenizeString(char text[], boolean macroExpand) {
+        String com_token;
+
+        cmd_argc = 0;
+        cmd_args = "";
+
+        int len = Lib.strlen(text);
+
+        // macro expand the text
+        if (macroExpand)
+            text = MacroExpandString(text, len);
+
+        if (text == null)
+            return;
+
+        len = Lib.strlen(text);
+
+        Com.ParseHelp ph = new Com.ParseHelp(text);
+
+        while (true) {
+
+            // skip whitespace up to a /n
+            char c = ph.skipwhitestoeol();
+
+            if (c == '\n') { // a newline seperates commands in the buffer
+                c = ph.nextchar();
+                break;
+            }
+
+            if (c == 0)
+                return;
+
+            // set cmd_args to everything after the first arg
+            if (cmd_argc == 1) {
+                cmd_args = new String(text, ph.index, len - ph.index);
+                cmd_args.trim();
+            }
+
+            com_token = Com.Parse(ph);
+
+            if (ph.data == null)
+                return;
+
+            if (cmd_argc < Defines.MAX_STRING_TOKENS) {
+                cmd_argv[cmd_argc] = com_token;
+                cmd_argc++;
+            }
+        }
+    }
+
+    public static void AddCommand(String cmd_name, xcommand_t function) {
+        cmd_function_t cmd;
+        //Com.DPrintf("Cmd_AddCommand: " + cmd_name + "\n");
+        // fail if the command is a variable name
+        if ((Cvar.variableString(cmd_name)).length() > 0) {
+            Com.Printf("Cmd_AddCommand: " + cmd_name
+                    + " already defined as a var\n");
+            return;
+        }
+
+        // fail if the command already exists
+        for (cmd = cmd_functions; cmd != null; cmd = cmd.next) {
+            if (cmd_name.equals(cmd.name)) {
+                Com
+                        .Printf("Cmd_AddCommand: " + cmd_name
+                                + " already defined\n");
+                return;
+            }
+        }
+
+        cmd = new cmd_function_t();
+        cmd.name = cmd_name;
+
+        cmd.function = function;
+        cmd.next = cmd_functions;
+        cmd_functions = cmd;
+    }
+
+    /**
+     * Cmd_RemoveCommand
+     */
+    public static void RemoveCommand(String cmd_name) {
+        cmd_function_t cmd, back = null;
+
+        back = cmd = cmd_functions;
+
+        while (true) {
+
+            if (cmd == null) {
+                Com.Printf("Cmd_RemoveCommand: " + cmd_name + " not added\n");
+                return;
+            }
+            if (0 == Lib.strcmp(cmd_name, cmd.name)) {
+                if (cmd == cmd_functions)
+                    cmd_functions = cmd.next;
+                else
+                    back.next = cmd.next;
+                return;
+            }
+            back = cmd;
+            cmd = cmd.next;
+        }
+    }
+
+    public static int Argc() {
+        return cmd_argc;
+    }
+
+    public static String Argv(int i) {
+        if (i < 0 || i >= cmd_argc)
+            return "";
+        return cmd_argv[i];
+    }
+
+    public static String Args() {
+        return cmd_args;
+    }
+
+    /**
+     * Cmd_ExecuteString
+     * <p>
+     * A complete command line has been parsed, so try to execute it
+     * FIXME: lookupnoadd the token to speed search?
+     */
+    public static void ExecuteString(String text) {
+
+        cmd_function_t cmd;
+        cmdalias_t a;
+
+        TokenizeString(text.toCharArray(), true);
+
+        // execute the command line
+        if (Argc() == 0)
+            return; // no tokens
+
+        // check functions
+        for (cmd = cmd_functions; cmd != null; cmd = cmd.next) {
+            if (cmd_argv[0].equalsIgnoreCase(cmd.name)) {
+                if (null == cmd.function) { // forward to server command
+                    Cmd.ExecuteString("cmd " + text);
+                } else {
+                    cmd.function.execute();
+                }
+                return;
+            }
+        }
+
+        // check alias
+        for (a = Globals.cmd_alias; a != null; a = a.next) {
+
+            if (cmd_argv[0].equalsIgnoreCase(a.name)) {
+
+                if (++Globals.alias_count == ALIAS_LOOP_COUNT) {
+                    Com.Printf("ALIAS_LOOP_COUNT\n");
+                    return;
+                }
+                CommandBuffer.InsertText(a.value);
+                return;
+            }
+        }
+
+        // check cvars
+        if (Cvar.command())
+            return;
+
+        // send it as a server command if we are connected
+        Cmd.ForwardToServer();
+    }
+
+    /**
+     * Cmd_Give_f
+     * <p>
+     * Give items to a client.
+     */
+    private static void Give_f(EDict ent) {
+        String name;
+        gitem_t it;
+        int index;
+        int i;
+        boolean give_all;
+        EDict it_ent;
+
+        if (GameBase.deathmatch.value != 0 && GameBase.sv_cheats.value == 0) {
+            SV_GAME.PF_cprintfhigh(ent,
+                    "You must run the server with '+set cheats 1' to enable this command.\n");
+            return;
+        }
+
+        name = Cmd.Args();
+
+        give_all = 0 == Lib.Q_stricmp(name, "all");
+
+        if (give_all || 0 == Lib.Q_stricmp(Cmd.Argv(1), "health")) {
+            if (Cmd.Argc() == 3)
+                ent.health = Lib.atoi(Cmd.Argv(2));
+            else
+                ent.health = ent.max_health;
+            if (!give_all)
+                return;
+        }
+
+        if (give_all || 0 == Lib.Q_stricmp(name, "weapons")) {
+            if (!give_all) {
+            }
+        }
+
+    }
+
+    /**
+     * Cmd_God_f
+     * <p>
+     * Sets client to godmode
+     * <p>
+     * argv(0) god
+     */
+    private static void God_f(EDict ent) {
+        String msg;
+
+        if (GameBase.deathmatch.value != 0 && GameBase.sv_cheats.value == 0) {
+            SV_GAME.PF_cprintfhigh(ent,
+                    "You must run the server with '+set cheats 1' to enable this command.\n");
+            return;
+        }
+
+        ent.flags ^= Defines.FL_GODMODE;
+        if (0 == (ent.flags & Defines.FL_GODMODE))
+            msg = "godmode OFF\n";
+        else
+            msg = "godmode ON\n";
+
+        SV_GAME.PF_cprintf(ent, Defines.PRINT_HIGH, msg);
+    }
+
+    /**
+     * Cmd_Notarget_f
+     * <p>
+     * Sets client to notarget
+     * <p>
+     * argv(0) notarget.
+     */
+    private static void Notarget_f(EDict ent) {
+        String msg;
+
+        if (GameBase.deathmatch.value != 0 && GameBase.sv_cheats.value == 0) {
+            SV_GAME.PF_cprintfhigh(ent,
+                    "You must run the server with '+set cheats 1' to enable this command.\n");
+            return;
+        }
+
+        ent.flags ^= Defines.FL_NOTARGET;
+        if (0 == (ent.flags & Defines.FL_NOTARGET))
+            msg = "notarget OFF\n";
+        else
+            msg = "notarget ON\n";
+
+        SV_GAME.PF_cprintfhigh(ent, msg);
+    }
+
+    /**
+     * Cmd_Noclip_f
+     * <p>
+     * argv(0) noclip.
+     */
+    private static void Noclip_f(EDict ent) {
+        String msg;
+
+        if (GameBase.deathmatch.value != 0 && GameBase.sv_cheats.value == 0) {
+            SV_GAME.PF_cprintfhigh(ent,
+                    "You must run the server with '+set cheats 1' to enable this command.\n");
+            return;
+        }
+
+        if (ent.movetype == Defines.MOVETYPE_NOCLIP) {
+            ent.movetype = Defines.MOVETYPE_WALK;
+            msg = "noclip OFF\n";
+        } else {
+            ent.movetype = Defines.MOVETYPE_NOCLIP;
+            msg = "noclip ON\n";
+        }
+
+        SV_GAME.PF_cprintfhigh(ent, msg);
+    }
+
+    /**
+     * Cmd_Use_f
+     * <p>
+     * Use an inventory item.
+     */
+    private static void Use_f(EDict ent) {
+
+    }
+
+    /**
+     * Cmd_Drop_f
+     * <p>
+     * Drop an inventory item.
+     */
+    private static void Drop_f(EDict ent) {
+    }
+
+    /**
+     * Cmd_Inven_f.
+     */
+    private static void Inven_f(EDict ent) {
+        int i;
+        Client cl;
+
+        cl = ent.client;
+
+        cl.showscores = false;
+        cl.showhelp = false;
+
+        if (cl.showinventory) {
+            cl.showinventory = false;
+            return;
+        }
+
+        cl.showinventory = true;
+
+        GameBase.gi.WriteByte(Defines.svc_inventory);
+        for (i = 0; i < Defines.MAX_ITEMS; i++) {
+            GameBase.gi.WriteShort(cl.pers.inventory[i]);
+        }
+        GameBase.gi.unicast(ent, true);
+    }
+
+    /**
+     * Cmd_InvUse_f.
+     */
+    private static void InvUse_f(EDict ent) {
+    }
+
+    /**
+     * Cmd_WeapPrev_f.
+     */
+    private static void WeapPrev_f(EDict ent) {
+    }
+
+    /**
+     * Cmd_WeapNext_f.
+     */
+    private static void WeapNext_f(EDict ent) {
+    }
+
+    /**
+     * Cmd_WeapLast_f.
+     */
+    private static void WeapLast_f(EDict ent) {
+
+    }
+
+    /**
+     * Cmd_InvDrop_f
+     */
+    private static void InvDrop_f(EDict ent) {
+    }
+
+    /**
+     * Cmd_Score_f
+     * <p>
+     * Display the scoreboard.
+     */
+    private static void Score_f(EDict ent) {
+        ent.client.showinventory = false;
+        ent.client.showhelp = false;
+
+        if (0 == GameBase.deathmatch.value && 0 == GameBase.coop.value)
+            return;
+
+        if (ent.client.showscores) {
+            ent.client.showscores = false;
+            return;
+        }
+
+        ent.client.showscores = true;
+        PlayerHud.DeathmatchScoreboard(ent);
+    }
+
+    /**
+     * Cmd_Help_f
+     * <p>
+     * Display the current help message.
+     */
+    private static void Help_f(EDict ent) {
+        // this is for backwards compatability
+        if (GameBase.deathmatch.value != 0) {
+            Score_f(ent);
+            return;
+        }
+
+        ent.client.showinventory = false;
+        ent.client.showscores = false;
+
+        if (ent.client.showhelp
+                && (ent.client.pers.game_helpchanged == GameBase.game.helpchanged)) {
+            ent.client.showhelp = false;
+            return;
+        }
+
+        ent.client.showhelp = true;
+        ent.client.pers.helpchanged = 0;
+        PlayerHud.HelpComputer(ent);
+    }
+
+
+    /**
+     * Cmd_PutAway_f
+     */
+    private static void PutAway_f(EDict ent) {
+        ent.client.showscores = false;
+        ent.client.showhelp = false;
+        ent.client.showinventory = false;
+    }
+
+    /**
+     * Cmd_Players_f
+     */
+    private static void Players_f(EDict ent) {
+        int i;
+        int count;
+        String small;
+        StringBuilder large;
+
+        Integer index[] = new Integer[256];
+
+        count = 0;
+        for (i = 0; i < GameBase.maxclients.value; i++) {
+            if (GameBase.game.clients[i].pers.connected) {
+                index[count] = i;
+                count++;
+            }
+        }
+
+        // sort by frags
+        Arrays.sort(index, 0, count - 1, Cmd.PlayerSort);
+
+        // print information
+        large = new StringBuilder();
+
+        for (i = 0; i < count; i++) {
+            small = GameBase.game.clients[index[i]].ps.stats[Defines.STAT_FRAGS]
+                    + " "
+                    + GameBase.game.clients[index[i]].pers.netname
+                    + "\n";
+
+            if (small.length() + large.length() > 1024 - 100) {
+                // can't print all of them in one packet
+                large.append("...\n");
+                break;
+            }
+            large.append(small);
+        }
+
+        SV_GAME.PF_cprintfhigh(ent, large + "\n" + count + " players\n");
+    }
+
+    /**
+     * Cmd_Wave_f
+     */
+    private static void Wave_f(EDict ent) {
+        int i;
+
+        i = Lib.atoi(Cmd.Argv(1));
+
+        // can't wave when ducked
+        if ((ent.client.ps.pmove.pm_flags & Move.PMF_DUCKED) != 0)
+            return;
+
+        if (ent.client.anim_priority > Defines.ANIM_WAVE)
+            return;
+
+        ent.client.anim_priority = Defines.ANIM_WAVE;
+
+        switch (i) {
+            case 0:
+                SV_GAME.PF_cprintfhigh(ent, "flipoff\n");
+                ent.s.frame = M_Player.FRAME_flip01 - 1;
+                ent.client.anim_end = M_Player.FRAME_flip12;
+                break;
+            case 1:
+                SV_GAME.PF_cprintfhigh(ent, "salute\n");
+                ent.s.frame = M_Player.FRAME_salute01 - 1;
+                ent.client.anim_end = M_Player.FRAME_salute11;
+                break;
+            case 2:
+                SV_GAME.PF_cprintfhigh(ent, "taunt\n");
+                ent.s.frame = M_Player.FRAME_taunt01 - 1;
+                ent.client.anim_end = M_Player.FRAME_taunt17;
+                break;
+            case 3:
+                SV_GAME.PF_cprintfhigh(ent, "wave\n");
+                ent.s.frame = M_Player.FRAME_wave01 - 1;
+                ent.client.anim_end = M_Player.FRAME_wave11;
+                break;
+            case 4:
+            default:
+                SV_GAME.PF_cprintfhigh(ent, "point\n");
+                ent.s.frame = M_Player.FRAME_point01 - 1;
+                ent.client.anim_end = M_Player.FRAME_point12;
+                break;
+        }
+    }
+
+    /**
+     * Command to print the players own position.
+     */
+    private static void ShowPosition_f(EDict ent) {
+        SV_GAME.PF_cprintfhigh(ent, "pos=" + Lib.vtofsbeaty(ent.s.origin) + "\n");
+    }
+
+    /**
+     * Cmd_Say_f
+     */
+    private static void Say_f(EDict ent, boolean team, boolean arg0) {
+
+        int i, j;
+        EDict other;
+        String text;
+        Client cl;
+
+        if (Cmd.Argc() < 2 && !arg0)
+            return;
+
+        if (0 == ((int) (GameBase.dmflags.value) & (Defines.DF_MODELTEAMS | Defines.DF_SKINTEAMS)))
+            team = false;
+
+        if (team)
+            text = "(" + ent.client.pers.netname + "): ";
+        else
+            text = "" + ent.client.pers.netname + ": ";
+
+        if (arg0) {
+            text += Cmd.Argv(0);
+            text += " ";
+            text += Cmd.Args();
+        } else {
+            if (Cmd.Args().startsWith("\""))
+                text += Cmd.Args().substring(1, Cmd.Args().length() - 1);
+            else
+                text += Cmd.Args();
+        }
+
+        // don't let text be too long for malicious reasons
+        if (text.length() > 150)
+            //text[150] = 0;
+            text = text.substring(0, 150);
+
+        text += "\n";
+
+        if (GameBase.flood_msgs.value != 0) {
+            cl = ent.client;
+
+            if (GameBase.level.time < cl.flood_locktill) {
+                SV_GAME.PF_cprintfhigh(ent, "You can't talk for "
+                        + (int) (cl.flood_locktill - GameBase.level.time)
+                        + " more seconds\n");
+                return;
+            }
+            i = (int) (cl.flood_whenhead - GameBase.flood_msgs.value + 1);
+            if (i < 0)
+                i = (10) + i;
+            if (cl.flood_when[i] != 0
+                    && GameBase.level.time - cl.flood_when[i] < GameBase.flood_persecond.value) {
+                cl.flood_locktill = GameBase.level.time + GameBase.flood_waitdelay.value;
+                SV_GAME.PF_cprintf(ent, Defines.PRINT_CHAT,
+                        "Flood protection:  You can't talk for "
+                                + (int) GameBase.flood_waitdelay.value
+                                + " seconds.\n");
+                return;
+            }
+
+            cl.flood_whenhead = (cl.flood_whenhead + 1) % 10;
+            cl.flood_when[cl.flood_whenhead] = GameBase.level.time;
+        }
+
+        if (Globals.dedicated.value != 0)
+            SV_GAME.PF_cprintf(null, Defines.PRINT_CHAT, "" + text + "");
+
+        for (j = 1; j <= GameBase.game.maxclients; j++) {
+            other = GameBase.g_edicts[j];
+            if (!other.inuse)
+                continue;
+            if (other.client == null)
+                continue;
+            if (team) {
+                if (!GameUtil.OnSameTeam(ent, other))
+                    continue;
+            }
+            SV_GAME.PF_cprintf(other, Defines.PRINT_CHAT, "" + text + "");
+        }
+
+    }
+
+    /**
+     * Returns the playerlist. TODO: The list is badly formatted at the moment.
+     */
+    private static void PlayerList_f(EDict ent) {
+        int i;
+        String st;
+        StringBuilder text;
+        EDict e2;
+
+        // connect time, ping, score, name
+        text = new StringBuilder();
+
+        for (i = 0; i < GameBase.maxclients.value; i++) {
+            e2 = GameBase.g_edicts[1 + i];
+            if (!e2.inuse)
+                continue;
+
+            st = ""
+                    + (GameBase.level.framenum - e2.client.resp.enterframe)
+                    / 600
+                    + ":"
+                    + ((GameBase.level.framenum - e2.client.resp.enterframe) % 600)
+                    / 10 + " " + e2.client.ping + " " + e2.client.resp.score
+                    + " " + e2.client.pers.netname + " "
+                    + (e2.client.resp.spectator ? " (spectator)" : "") + "\n";
+
+            if (text.length() + st.length() > 1024 - 50) {
+                text.append("And more...\n");
+                SV_GAME.PF_cprintfhigh(ent, "" + text + "");
+                return;
+            }
+            text.append(st);
+        }
+        SV_GAME.PF_cprintfhigh(ent, text.toString());
+    }
+
+    /**
+     * Adds the current command line as a clc_stringcmd to the client message.
+     * things like godmode, noclip, etc, are commands directed to the server, so
+     * when they are typed in at the console, they will need to be forwarded.
+     */
+    private static void ForwardToServer() {
+        String cmd;
+
+        cmd = Cmd.Argv(0);
+        if (Globals.clientStaticT.state <= Defines.ca_connected || cmd.charAt(0) == '-'
+                || cmd.charAt(0) == '+') {
+            Com.Printf("Unknown command \"" + cmd + "\"\n");
+            return;
+        }
+
+        MSG.WriteByte(Globals.clientStaticT.netchan.message, Defines.clc_stringcmd);
+        SZ.Print(Globals.clientStaticT.netchan.message, cmd);
+        if (Cmd.Argc() > 1) {
+            SZ.Print(Globals.clientStaticT.netchan.message, " ");
+            SZ.Print(Globals.clientStaticT.netchan.message, Cmd.Args());
+        }
+    }
+
+    /**
+     * Cmd_CompleteCommand.
+     */
+    public static Vector<String> CompleteCommand(String partial) {
+        Vector<String> cmds = new Vector<>();
+
+        // check for match
+        for (cmd_function_t cmd = cmd_functions; cmd != null; cmd = cmd.next)
+            if (cmd.name.startsWith(partial))
+                cmds.add(cmd.name);
+        for (cmdalias_t a = Globals.cmd_alias; a != null; a = a.next)
+            if (a.name.startsWith(partial))
+                cmds.add(a.name);
+
+        return cmds;
+    }
+
+    /**
+     * Processes the commands the player enters in the quake console.
+     */
+    public static void ClientCommand(EDict ent) {
+        String cmd;
+
+        if (ent.client == null)
+            return; // not fully in game yet
+
+        cmd = GameBase.gi.argv(0).toLowerCase();
+
+        if (cmd.equals("players")) {
+            Players_f(ent);
+            return;
+        }
+        if (cmd.equals("say")) {
+            Say_f(ent, false, false);
+            return;
+        }
+        if (cmd.equals("say_team")) {
+            Say_f(ent, true, false);
+            return;
+        }
+        if (cmd.equals("score")) {
+            Score_f(ent);
+            return;
+        }
+        if (cmd.equals("help")) {
+            Help_f(ent);
+            return;
+        }
+
+        if (GameBase.level.intermissiontime != 0)
+            return;
+
+        switch (cmd) {
+            case "use":
+                Use_f(ent);
+                break;
+            case "drop":
+                Drop_f(ent);
+                break;
+            case "give":
+                Give_f(ent);
+                break;
+            case "god":
+                God_f(ent);
+                break;
+            case "notarget":
+                Notarget_f(ent);
+                break;
+            case "noclip":
+                Noclip_f(ent);
+                break;
+            case "inven":
+                Inven_f(ent);
+                break;
+            case "invuse":
+                InvUse_f(ent);
+                break;
+            case "invdrop":
+                InvDrop_f(ent);
+                break;
+            case "weapprev":
+                WeapPrev_f(ent);
+                break;
+            case "weapnext":
+                WeapNext_f(ent);
+                break;
+            case "weaplast":
+                WeapLast_f(ent);
+                break;
+            case "putaway":
+                PutAway_f(ent);
+                break;
+            case "wave":
+                Wave_f(ent);
+                break;
+            case "playerlist":
+                PlayerList_f(ent);
+                break;
+            case "showposition":
+                ShowPosition_f(ent);
+                break;
+            default:
+                // anything that doesn't match a command will be a chat
+                Say_f(ent, false, true);
+                break;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/CvarT.java b/src/main/java/lwjake2/game/CvarT.java
new file mode 100644 (file)
index 0000000..e280fa4
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+/**
+ * CvarT implements the struct CvarT of the C version
+ */
+public final class CvarT {
+    public String name;
+    public String string;
+    public String latched_string;
+    public int flags = 0;
+    public boolean modified = false;
+    public float value = 0.0f;
+    public CvarT next = null;
+}
diff --git a/src/main/java/lwjake2/game/EDict.java b/src/main/java/lwjake2/game/EDict.java
new file mode 100644 (file)
index 0000000..5edd919
--- /dev/null
@@ -0,0 +1,674 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.Defines;
+import lwjake2.qcommon.Com;
+import lwjake2.util.Lib;
+import lwjake2.util.QuakeFile;
+
+import java.io.IOException;
+
+public class EDict {
+
+    /**
+     * common integrated data blocks.
+     */
+    public final moveinfo_t moveinfo = new moveinfo_t();
+    public final monsterinfo_t monsterinfo = new monsterinfo_t();
+    /**
+     * Integrated entity state.
+     */
+    public entity_state_t s = new entity_state_t(this);
+    public boolean inuse;
+    public int linkcount;
+    /**
+     * FIXME: move these fields to a server private sv_entity_t. linked to a
+     * division node or leaf.
+     */
+    public link_t area = new link_t(this);
+    /**
+     * if -1, use headnode instead.
+     */
+    public int num_clusters;
+    public int clusternums[] = new int[Defines.MAX_ENT_CLUSTERS];
+    /**
+     * unused if num_clusters != -1.
+     */
+    public int headnode;
+    public int areanum, areanum2;
+    /**
+     * SVF_NOCLIENT, SVF_DEADMONSTER, SVF_MONSTER, etc.
+     */
+    public int svflags;
+    public float[] mins = {0, 0, 0};
+    //================================
+    public float[] maxs = {0, 0, 0};
+    public float[] absmin = {0, 0, 0};
+    public float[] absmax = {0, 0, 0};
+    public float[] size = {0, 0, 0};
+    public int solid;
+    public int clipmask;
+    //================================
+    public int movetype;
+    public int flags;
+    public String model = null;
+    /**
+     * sv.time when the object was freed.
+     */
+    public float freetime;
+    //
+    // only used locally in game, not by server
+    //
+    public String message = null;
+    public String classname = "";
+    public int spawnflags;
+    public float timestamp;
+    /**
+     * set in qe3, -1 = up, -2 = down
+     */
+    public float angle;
+    public String target = null;
+    public String targetname = null;
+    public String team = null;
+    public String pathtarget = null;
+    public EDict target_ent = null;
+    public float speed, accel, decel;
+    public float[] movedir = {0, 0, 0};
+    public float[] pos1 = {0, 0, 0};
+    public float[] pos2 = {0, 0, 0};
+    public float[] velocity = {0, 0, 0};
+    public float[] avelocity = {0, 0, 0};
+    public int mass;
+    public float air_finished;
+    /**
+     * per entity gravity multiplier (1.0 is normal).
+     */
+    public float gravity;
+    /**
+     * use for lowgrav artifact, flares.
+     */
+
+    public float yaw_speed;
+    public float ideal_yaw;
+    public float nextthink;
+    public EntThinkAdapter prethink = null;
+    public EntThinkAdapter think = null;
+    public EntBlockedAdapter blocked = null;
+    public EntTouchAdapter touch = null;
+    public EntUseAdapter use = null;
+    /**
+     * Are all these legit? do we need more/less of them?
+     */
+    public float touch_debounce_time;
+    public float pain_debounce_time;
+    public float damage_debounce_time;
+    /**
+     * Move to clientinfo.
+     */
+    public float fly_sound_debounce_time;
+    public float last_move_time;
+    public int health;
+    public int max_health;
+    public int gib_health;
+    public int show_hostile;
+    public float powerarmor_time;
+    /**
+     * target_changelevel.
+     */
+    public String map = null;
+    /**
+     * Height above origin where eyesight is determined.
+     */
+    public int viewheight;
+    public int dmg;
+    public int radius_dmg;
+    public float dmg_radius;
+    /**
+     * make this a spawntemp var?
+     */
+    public int sounds;
+    public int count;
+    public EDict chain = null;
+    public EDict enemy = null;
+    public EDict oldenemy = null;
+    public EDict activator = null;
+    public EDict groundentity = null;
+    public int groundentity_linkcount;
+    public EDict teamchain = null;
+    public EDict teammaster = null;
+    /**
+     * can go in client only.
+     */
+    public EDict mynoise = null;
+    public EDict mynoise2 = null;
+    public int noise_index;
+    public int noise_index2;
+    public float volume;
+    public float attenuation;
+    /**
+     * Timing variables.
+     */
+    public float wait;
+    /**
+     * before firing targets...
+     */
+    public float delay;
+    public float random;
+    public float teleport_time;
+    public int watertype;
+    public int waterlevel;
+    public float[] move_origin = {0, 0, 0};
+    public float[] move_angles = {0, 0, 0};
+    /**
+     * move this to clientinfo? .
+     */
+    public int light_level;
+    /**
+     * also used as areaportal number.
+     */
+    public int style;
+    public gitem_t item; // for bonus items
+    public Client client;
+    public EDict owner;
+    /**
+     * Introduced by rst.
+     */
+    public int index;
+
+    /**
+     * Constructor.
+     */
+    public EDict(int i) {
+        s.number = i;
+        index = i;
+    }
+
+    /**
+     * Used during level loading.
+     */
+    public void cleararealinks() {
+        area = new link_t(this);
+    }
+
+    /////////////////////////////////////////////////
+
+    public boolean setField(String key, String value) {
+
+        if (key.equals("classname")) {
+            classname = GameSpawn.ED_NewString(value);
+            return true;
+        } // F_LSTRING),
+
+        if (key.equals("model")) {
+            model = GameSpawn.ED_NewString(value);
+            return true;
+        } // F_LSTRING),
+
+        if (key.equals("spawnflags")) {
+            spawnflags = Lib.atoi(value);
+            return true;
+        } // F_INT),
+
+        if (key.equals("speed")) {
+            speed = Lib.atof(value);
+            return true;
+        } // F_FLOAT),
+
+        if (key.equals("accel")) {
+            accel = Lib.atof(value);
+            return true;
+        } // F_FLOAT),
+
+        if (key.equals("decel")) {
+            decel = Lib.atof(value);
+            return true;
+        } // F_FLOAT),
+
+        if (key.equals("target")) {
+            target = GameSpawn.ED_NewString(value);
+            return true;
+        } // F_LSTRING),
+
+        if (key.equals("targetname")) {
+            targetname = GameSpawn.ED_NewString(value);
+            return true;
+        } // F_LSTRING),
+
+        if (key.equals("pathtarget")) {
+            pathtarget = GameSpawn.ED_NewString(value);
+            return true;
+        } // F_LSTRING),
+
+        if (key.equals("message")) {
+            message = GameSpawn.ED_NewString(value);
+            return true;
+        } // F_LSTRING),
+
+        if (key.equals("team")) {
+            team = GameSpawn.ED_NewString(value);
+            Com.dprintln("Monster Team:" + team);
+            return true;
+        } // F_LSTRING),
+
+        if (key.equals("wait")) {
+            wait = Lib.atof(value);
+            return true;
+        } // F_FLOAT),
+
+        if (key.equals("delay")) {
+            delay = Lib.atof(value);
+            return true;
+        } // F_FLOAT),
+
+        if (key.equals("random")) {
+            random = Lib.atof(value);
+            return true;
+        } // F_FLOAT),
+
+        if (key.equals("move_origin")) {
+            move_origin = Lib.atov(value);
+            return true;
+        } // F_VECTOR),
+
+        if (key.equals("move_angles")) {
+            move_angles = Lib.atov(value);
+            return true;
+        } // F_VECTOR),
+
+        if (key.equals("style")) {
+            style = Lib.atoi(value);
+            return true;
+        } // F_INT),
+
+        if (key.equals("count")) {
+            count = Lib.atoi(value);
+            return true;
+        } // F_INT),
+
+        if (key.equals("health")) {
+            health = Lib.atoi(value);
+            return true;
+        } // F_INT),
+
+        if (key.equals("sounds")) {
+            sounds = Lib.atoi(value);
+            return true;
+        } // F_INT),
+
+        if (key.equals("light")) {
+            return true;
+        } // F_IGNORE),
+
+        if (key.equals("dmg")) {
+            dmg = Lib.atoi(value);
+            return true;
+        } // F_INT),
+
+        if (key.equals("mass")) {
+            mass = Lib.atoi(value);
+            return true;
+        } // F_INT),
+
+        if (key.equals("volume")) {
+            volume = Lib.atof(value);
+            return true;
+        } // F_FLOAT),
+
+        if (key.equals("attenuation")) {
+            attenuation = Lib.atof(value);
+            return true;
+        } // F_FLOAT),
+
+        if (key.equals("map")) {
+            map = GameSpawn.ED_NewString(value);
+            return true;
+        } // F_LSTRING),
+
+        if (key.equals("origin")) {
+            s.origin = Lib.atov(value);
+            return true;
+        } // F_VECTOR),
+
+        if (key.equals("angles")) {
+            s.angles = Lib.atov(value);
+            return true;
+        } // F_VECTOR),
+
+        if (key.equals("angle")) {
+            s.angles = new float[]{0, Lib.atof(value), 0};
+            return true;
+        } // F_ANGLEHACK),
+
+        if (key.equals("item")) {
+            GameBase.gi.error("ent.set(\"item\") called.");
+            return true;
+        } // F_ITEM)
+
+        return false;
+    }
+
+    /**
+     * Writes the entity to the file.
+     */
+    public void write(QuakeFile f) throws IOException {
+
+        s.write(f);
+        f.writeBoolean(inuse);
+        f.writeInt(linkcount);
+        f.writeInt(num_clusters);
+
+        f.writeInt(9999);
+
+        if (clusternums == null)
+            f.writeInt(-1);
+        else {
+            f.writeInt(Defines.MAX_ENT_CLUSTERS);
+            for (int n = 0; n < Defines.MAX_ENT_CLUSTERS; n++)
+                f.writeInt(clusternums[n]);
+
+        }
+        f.writeInt(headnode);
+        f.writeInt(areanum);
+        f.writeInt(areanum2);
+        f.writeInt(svflags);
+        f.writeVector(mins);
+        f.writeVector(maxs);
+        f.writeVector(absmin);
+        f.writeVector(absmax);
+        f.writeVector(size);
+        f.writeInt(solid);
+        f.writeInt(clipmask);
+
+        f.writeInt(movetype);
+        f.writeInt(flags);
+
+        f.writeString(model);
+        f.writeFloat(freetime);
+        f.writeString(message);
+        f.writeString(classname);
+        f.writeInt(spawnflags);
+        f.writeFloat(timestamp);
+
+        f.writeFloat(angle);
+
+        f.writeString(target);
+        f.writeString(targetname);
+        f.writeString(team);
+        f.writeString(pathtarget);
+
+        f.writeEdictRef(target_ent);
+
+        f.writeFloat(speed);
+        f.writeFloat(accel);
+        f.writeFloat(decel);
+
+        f.writeVector(movedir);
+
+        f.writeVector(pos1);
+        f.writeVector(pos2);
+
+        f.writeVector(velocity);
+        f.writeVector(avelocity);
+
+        f.writeInt(mass);
+        f.writeFloat(air_finished);
+
+        f.writeFloat(gravity);
+
+        f.writeFloat(yaw_speed);
+        f.writeFloat(ideal_yaw);
+
+        f.writeFloat(nextthink);
+
+        f.writeAdapter(prethink);
+        f.writeAdapter(think);
+        f.writeAdapter(blocked);
+
+        f.writeAdapter(touch);
+        f.writeAdapter(use);
+
+        f.writeFloat(touch_debounce_time);
+        f.writeFloat(pain_debounce_time);
+        f.writeFloat(damage_debounce_time);
+
+        f.writeFloat(fly_sound_debounce_time);
+        f.writeFloat(last_move_time);
+
+        f.writeInt(health);
+        f.writeInt(max_health);
+
+        f.writeInt(gib_health);
+        f.writeInt(show_hostile);
+
+        f.writeFloat(powerarmor_time);
+
+        f.writeString(map);
+
+        f.writeInt(viewheight);
+        f.writeInt(dmg);
+        f.writeInt(radius_dmg);
+        f.writeFloat(dmg_radius);
+
+        f.writeInt(sounds);
+        f.writeInt(count);
+
+        f.writeEdictRef(chain);
+        f.writeEdictRef(enemy);
+        f.writeEdictRef(oldenemy);
+        f.writeEdictRef(activator);
+        f.writeEdictRef(groundentity);
+        f.writeInt(groundentity_linkcount);
+        f.writeEdictRef(teamchain);
+        f.writeEdictRef(teammaster);
+
+        f.writeEdictRef(mynoise);
+        f.writeEdictRef(mynoise2);
+
+        f.writeInt(noise_index);
+        f.writeInt(noise_index2);
+
+        f.writeFloat(volume);
+        f.writeFloat(attenuation);
+        f.writeFloat(wait);
+        f.writeFloat(delay);
+        f.writeFloat(random);
+
+        f.writeFloat(teleport_time);
+
+        f.writeInt(watertype);
+        f.writeInt(waterlevel);
+        f.writeVector(move_origin);
+        f.writeVector(move_angles);
+
+        f.writeInt(light_level);
+        f.writeInt(style);
+
+        f.writeItem(item);
+
+        moveinfo.write(f);
+        monsterinfo.write(f);
+        if (client == null)
+            f.writeInt(-1);
+        else
+            f.writeInt(client.index);
+
+        f.writeEdictRef(owner);
+
+        // rst's checker :-)
+        f.writeInt(9876);
+    }
+
+    /**
+     * Reads the entity from the file.
+     */
+    public void read(QuakeFile f) throws IOException {
+        s.read(f);
+        inuse = f.readBoolean();
+        linkcount = f.readInt();
+        num_clusters = f.readInt();
+
+        if (f.readInt() != 9999)
+            new Throwable("wrong read pos!").printStackTrace();
+
+        int len = f.readInt();
+
+        if (len == -1)
+            clusternums = null;
+        else {
+            clusternums = new int[Defines.MAX_ENT_CLUSTERS];
+            for (int n = 0; n < Defines.MAX_ENT_CLUSTERS; n++)
+                clusternums[n] = f.readInt();
+        }
+
+        headnode = f.readInt();
+        areanum = f.readInt();
+        areanum2 = f.readInt();
+        svflags = f.readInt();
+        mins = f.readVector();
+        maxs = f.readVector();
+        absmin = f.readVector();
+        absmax = f.readVector();
+        size = f.readVector();
+        solid = f.readInt();
+        clipmask = f.readInt();
+
+        movetype = f.readInt();
+        flags = f.readInt();
+
+        model = f.readString();
+        freetime = f.readFloat();
+        message = f.readString();
+        classname = f.readString();
+        spawnflags = f.readInt();
+        timestamp = f.readFloat();
+
+        angle = f.readFloat();
+
+        target = f.readString();
+        targetname = f.readString();
+        team = f.readString();
+        pathtarget = f.readString();
+
+        target_ent = f.readEdictRef();
+
+        speed = f.readFloat();
+        accel = f.readFloat();
+        decel = f.readFloat();
+
+        movedir = f.readVector();
+
+        pos1 = f.readVector();
+        pos2 = f.readVector();
+
+        velocity = f.readVector();
+        avelocity = f.readVector();
+
+        mass = f.readInt();
+        air_finished = f.readFloat();
+
+        gravity = f.readFloat();
+
+        yaw_speed = f.readFloat();
+        ideal_yaw = f.readFloat();
+
+        nextthink = f.readFloat();
+
+        prethink = (EntThinkAdapter) f.readAdapter();
+        think = (EntThinkAdapter) f.readAdapter();
+        blocked = (EntBlockedAdapter) f.readAdapter();
+
+        touch = (EntTouchAdapter) f.readAdapter();
+        use = (EntUseAdapter) f.readAdapter();
+
+        touch_debounce_time = f.readFloat();
+        pain_debounce_time = f.readFloat();
+        damage_debounce_time = f.readFloat();
+
+        fly_sound_debounce_time = f.readFloat();
+        last_move_time = f.readFloat();
+
+        health = f.readInt();
+        max_health = f.readInt();
+
+        gib_health = f.readInt();
+        show_hostile = f.readInt();
+
+        powerarmor_time = f.readFloat();
+
+        map = f.readString();
+
+        viewheight = f.readInt();
+        dmg = f.readInt();
+        radius_dmg = f.readInt();
+        dmg_radius = f.readFloat();
+
+        sounds = f.readInt();
+        count = f.readInt();
+
+        chain = f.readEdictRef();
+        enemy = f.readEdictRef();
+
+        oldenemy = f.readEdictRef();
+        activator = f.readEdictRef();
+        groundentity = f.readEdictRef();
+
+        groundentity_linkcount = f.readInt();
+        teamchain = f.readEdictRef();
+        teammaster = f.readEdictRef();
+
+        mynoise = f.readEdictRef();
+        mynoise2 = f.readEdictRef();
+
+        noise_index = f.readInt();
+        noise_index2 = f.readInt();
+
+        volume = f.readFloat();
+        attenuation = f.readFloat();
+        wait = f.readFloat();
+        delay = f.readFloat();
+        random = f.readFloat();
+
+        teleport_time = f.readFloat();
+
+        watertype = f.readInt();
+        waterlevel = f.readInt();
+        move_origin = f.readVector();
+        move_angles = f.readVector();
+
+        light_level = f.readInt();
+        style = f.readInt();
+
+
+        moveinfo.read(f);
+        monsterinfo.read(f);
+
+        int ndx = f.readInt();
+        if (ndx == -1)
+            client = null;
+        else
+            client = GameBase.game.clients[ndx];
+
+        owner = f.readEdictRef();
+
+        // rst's checker :-)
+        if (f.readInt() != 9876)
+            System.err.println("ent load check failed for num " + index);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/EdictFindFilter.java b/src/main/java/lwjake2/game/EdictFindFilter.java
new file mode 100644 (file)
index 0000000..de31128
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+/**
+ * Helps for filtering the iteration over the gedicts[] array, see GFind(). RST.
+ */
+
+public class EdictFindFilter {
+    boolean matches(EDict e, String s) {
+        return false;
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/EdictIterator.java b/src/main/java/lwjake2/game/EdictIterator.java
new file mode 100644 (file)
index 0000000..6e2295f
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+/**
+ * Helps for iterating over the gedicts[] array. RST.
+ */
+
+public class EdictIterator {
+    public EDict o;
+    int i;
+
+    EdictIterator() {
+        this.i = 0;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/EndianHandler.java b/src/main/java/lwjake2/game/EndianHandler.java
new file mode 100644 (file)
index 0000000..adb26f9
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+public abstract class EndianHandler {
+    private static final int mask = 0xFF;
+
+
+    public static int swapInt(int i) {
+
+        int a = i & mask;
+        i >>>= 8;
+
+        a <<= 24;
+
+        int b = i & mask;
+
+        i >>>= 8;
+        b <<= 16;
+
+        int c = i & mask;
+        i >>>= 8;
+        c <<= 8;
+
+        return i | c | b | a;
+    }
+
+
+}
diff --git a/src/main/java/lwjake2/game/EntBlockedAdapter.java b/src/main/java/lwjake2/game/EntBlockedAdapter.java
new file mode 100644 (file)
index 0000000..2286dfe
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+public abstract class EntBlockedAdapter extends SuperAdapter {
+    // move to moveinfo?
+    public abstract void blocked(EDict self, EDict other);
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/EntDodgeAdapter.java b/src/main/java/lwjake2/game/EntDodgeAdapter.java
new file mode 100644 (file)
index 0000000..d2495b1
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+public abstract class EntDodgeAdapter extends SuperAdapter {
+}
diff --git a/src/main/java/lwjake2/game/EntInteractAdapter.java b/src/main/java/lwjake2/game/EntInteractAdapter.java
new file mode 100644 (file)
index 0000000..28c1030
--- /dev/null
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+public abstract class EntInteractAdapter extends SuperAdapter {
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/EntThinkAdapter.java b/src/main/java/lwjake2/game/EntThinkAdapter.java
new file mode 100644 (file)
index 0000000..b663b05
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+public abstract class EntThinkAdapter extends SuperAdapter {
+    public abstract void think(EDict self);
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/EntTouchAdapter.java b/src/main/java/lwjake2/game/EntTouchAdapter.java
new file mode 100644 (file)
index 0000000..04d8c9b
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+public abstract class EntTouchAdapter extends SuperAdapter {
+    public abstract void touch(EDict self, EDict other, cplane_t plane, csurface_t surf);
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/EntUseAdapter.java b/src/main/java/lwjake2/game/EntUseAdapter.java
new file mode 100644 (file)
index 0000000..cd79e31
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+public abstract class EntUseAdapter extends SuperAdapter {
+    public abstract void use(EDict self, EDict other, EDict activator);
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/GameAI.java b/src/main/java/lwjake2/game/GameAI.java
new file mode 100644 (file)
index 0000000..8a8ec4b
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.Defines;
+
+
+class GameAI {
+
+    /**
+     * Called once each frame to set level.sight_client to the player to be
+     * checked for in findtarget.
+     * <p>
+     * If all clients are either dead or in notarget, sight_client will be null.
+     * <p>
+     * In coop games, sight_client will cycle between the clients.
+     */
+    static void AI_SetSightClient() {
+        EDict ent;
+        int start, check;
+
+        if (GameBase.level.sight_client == null)
+            start = 1;
+        else
+            start = GameBase.level.sight_client.index;
+
+        check = start;
+        while (true) {
+            check++;
+            if (check > GameBase.game.maxclients)
+                check = 1;
+            ent = GameBase.g_edicts[check];
+
+            if (ent.inuse && ent.health > 0
+                    && (ent.flags & Defines.FL_NOTARGET) == 0) {
+                GameBase.level.sight_client = ent;
+                return; // got one
+            }
+            if (check == start) {
+                GameBase.level.sight_client = null;
+                return; // nobody to see
+            }
+        }
+    }
+
+
+}
diff --git a/src/main/java/lwjake2/game/GameBase.java b/src/main/java/lwjake2/game/GameBase.java
new file mode 100644 (file)
index 0000000..ca1c79c
--- /dev/null
@@ -0,0 +1,599 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+/**
+ * Father of all GameObjects.
+ */
+
+package lwjake2.game;
+
+import lwjake2.Defines;
+import lwjake2.client.M;
+import lwjake2.qcommon.Com;
+import lwjake2.server.SV;
+import lwjake2.server.SV_WORLD;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+
+import java.util.StringTokenizer;
+
+public class GameBase {
+    public static final cplane_t dummyplane = new cplane_t();
+    public static final game_locals_t game = new game_locals_t();
+    public static final pushed_t[] pushed = new pushed_t[Defines.MAX_EDICTS];
+    public static final int STEPSIZE = 18;
+    public static final EdictFindFilter findByTarget = new EdictFindFilter() {
+        public boolean matches(EDict e, String s) {
+            return e.targetname != null && e.targetname.equalsIgnoreCase(s);
+        }
+    };
+    public static final EdictFindFilter findByClass = new EdictFindFilter() {
+        public boolean matches(EDict e, String s) {
+            return e.classname.equalsIgnoreCase(s);
+        }
+    };
+    private final static float STOP_EPSILON = 0.1f;
+    /**
+     * Searches all active entities for the next one that holds the matching
+     * string at fieldofs (use the FOFS() macro) in the structure.
+     * <p>
+     * Searches beginning at the edict after from, or the beginning if null null
+     * will be returned if the end of the list is reached.
+     */
+
+    private static final int MAXCHOICES = 8;
+    private static final float[] VEC_UP = {0, -1, 0};
+    private static final float[] MOVEDIR_UP = {0, 0, 1};
+    private static final float[] VEC_DOWN = {0, -2, 0};
+    private static final float[] MOVEDIR_DOWN = {0, 0, -1};
+    /**
+     * G_TouchTriggers
+     */
+
+    private static final EDict[] touch = new EDict[Defines.MAX_EDICTS];
+    public static level_locals_t level = new level_locals_t();
+    public static game_import_t gi = new game_import_t();
+    public static spawn_temp_t st = new spawn_temp_t();
+    public static int sm_meat_index;
+    public static int snd_fry;
+    public static int meansOfDeath;
+    public static int num_edicts;
+    public static EDict g_edicts[] = new EDict[Defines.MAX_EDICTS];
+    public static CvarT deathmatch = new CvarT();
+    public static CvarT coop = new CvarT();
+    public static CvarT dmflags = new CvarT();
+    public static CvarT fraglimit = new CvarT();
+    public static CvarT timelimit = new CvarT();
+    public static CvarT password = new CvarT();
+    public static CvarT spectator_password = new CvarT();
+    public static CvarT needpass = new CvarT();
+    public static CvarT maxclients = new CvarT();
+    public static CvarT maxspectators = new CvarT();
+    public static CvarT maxentities = new CvarT();
+    public static CvarT g_select_empty = new CvarT();
+    public static CvarT filterban = new CvarT();
+    public static CvarT sv_maxvelocity = new CvarT();
+    public static CvarT sv_gravity = new CvarT();
+    public static CvarT sv_rollspeed = new CvarT();
+    public static CvarT sv_rollangle = new CvarT();
+    public static CvarT gun_x = new CvarT();
+    public static CvarT gun_y = new CvarT();
+    public static CvarT gun_z = new CvarT();
+    public static CvarT run_pitch = new CvarT();
+    public static CvarT run_roll = new CvarT();
+    public static CvarT bob_up = new CvarT();
+    public static CvarT bob_pitch = new CvarT();
+    public static CvarT bob_roll = new CvarT();
+    public static CvarT sv_cheats = new CvarT();
+    public static CvarT flood_msgs = new CvarT();
+    public static CvarT flood_persecond = new CvarT();
+    public static CvarT flood_waitdelay = new CvarT();
+    public static CvarT sv_maplist = new CvarT();
+    public static int pushed_p;
+    public static EDict obstacle;
+    public static int c_yes, c_no;
+
+    static {
+        for (int n = 0; n < Defines.MAX_EDICTS; n++)
+            g_edicts[n] = new EDict(n);
+    }
+
+    static {
+        for (int n = 0; n < Defines.MAX_EDICTS; n++)
+            pushed[n] = new pushed_t();
+    }
+
+    /**
+     * Slide off of the impacting object returns the blocked flags (1 = floor, 2 =
+     * step / wall).
+     */
+    public static void ClipVelocity(float[] in, float[] normal, float[] out,
+                                    float overbounce) {
+        float backoff;
+        float change;
+        int i, blocked;
+
+        blocked = 0;
+        if (normal[2] > 0)
+            blocked |= 1; // floor
+        if (normal[2] == 0.0f)
+            blocked |= 2; // step
+
+        backoff = Math3D.dotProduct(in, normal) * overbounce;
+
+        for (i = 0; i < 3; i++) {
+            change = normal[i] * backoff;
+            out[i] = in[i] - change;
+            if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON)
+                out[i] = 0;
+        }
+
+    }
+
+    /**
+     * Searches all active entities for the next one that holds the matching
+     * string at fieldofs (use the FOFS() macro) in the structure.
+     * <p>
+     * Searches beginning at the edict after from, or the beginning if null null
+     * will be returned if the end of the list is reached.
+     */
+
+    public static EdictIterator G_Find(EdictIterator from, EdictFindFilter eff,
+                                       String s) {
+
+        if (from == null)
+            from = new EdictIterator();
+        else
+            from.i++;
+
+        for (; from.i < num_edicts; from.i++) {
+            from.o = g_edicts[from.i];
+            if (from.o.classname == null) {
+                Com.Printf("edict with classname = null" + from.o.index);
+            }
+
+            if (!from.o.inuse)
+                continue;
+
+            if (eff.matches(from.o, s))
+                return from;
+        }
+
+        return null;
+    }
+
+    // comfort version (rst)
+    public static EDict G_FindEdict(EdictIterator from, EdictFindFilter eff,
+                                    String s) {
+        EdictIterator ei = G_Find(from, eff, s);
+        if (ei == null)
+            return null;
+        else
+            return ei.o;
+    }
+
+    /**
+     * Returns entities that have origins within a spherical area.
+     */
+    public static EdictIterator findradius(EdictIterator from, float[] org,
+                                           float rad) {
+        float[] eorg = {0, 0, 0};
+        int j;
+
+        if (from == null)
+            from = new EdictIterator();
+        else
+            from.i++;
+
+        for (; from.i < num_edicts; from.i++) {
+            from.o = g_edicts[from.i];
+            if (!from.o.inuse)
+                continue;
+
+            if (from.o.solid == Defines.SOLID_NOT)
+                continue;
+
+            for (j = 0; j < 3; j++)
+                eorg[j] = org[j]
+                        - (from.o.s.origin[j] + (from.o.mins[j] + from.o.maxs[j]) * 0.5f);
+
+            if (Math3D.vectorLength(eorg) > rad)
+                continue;
+            return from;
+        }
+
+        return null;
+    }
+
+    public static EDict G_PickTarget(String targetname) {
+        int num_choices = 0;
+        EDict choice[] = new EDict[MAXCHOICES];
+
+        if (targetname == null) {
+            gi.dprintf("G_PickTarget called with null targetname\n");
+            return null;
+        }
+
+        EdictIterator es = null;
+
+        while ((es = G_Find(es, findByTarget, targetname)) != null) {
+            choice[num_choices++] = es.o;
+            if (num_choices == MAXCHOICES)
+                break;
+        }
+
+        if (num_choices == 0) {
+            gi.dprintf("G_PickTarget: target " + targetname + " not found\n");
+            return null;
+        }
+
+        return choice[Lib.rand() % num_choices];
+    }
+
+    public static void G_SetMovedir(float[] angles, float[] movedir) {
+        if (Math3D.vectorEquals(angles, VEC_UP)) {
+            Math3D.vectorCopy(MOVEDIR_UP, movedir);
+        } else if (Math3D.vectorEquals(angles, VEC_DOWN)) {
+            Math3D.vectorCopy(MOVEDIR_DOWN, movedir);
+        } else {
+            Math3D.angleVectors(angles, movedir, null, null);
+        }
+
+        Math3D.vectorClear(angles);
+    }
+
+    public static String G_CopyString(String in) {
+        return in;
+    }
+
+    public static void G_TouchTriggers(EDict ent) {
+        int i, num;
+        EDict hit;
+
+        // dead things don't activate triggers!
+        if ((ent.client != null || (ent.svflags & Defines.SVF_MONSTER) != 0)
+                && (ent.health <= 0))
+            return;
+
+        num = gi.BoxEdicts(ent.absmin, ent.absmax, touch, Defines.MAX_EDICTS,
+                Defines.AREA_TRIGGERS);
+
+        // be careful, it is possible to have an entity in this
+        // list removed before we get to it (killtriggered)
+        for (i = 0; i < num; i++) {
+            hit = touch[i];
+
+            if (!hit.inuse)
+                continue;
+
+            if (hit.touch == null)
+                continue;
+
+            hit.touch.touch(hit, ent, dummyplane, null);
+        }
+    }
+
+    /**
+     * G_RunEntity
+     */
+    private static void G_RunEntity(EDict ent) {
+
+        if (ent.prethink != null)
+            ent.prethink.think(ent);
+
+        switch (ent.movetype) {
+            case Defines.MOVETYPE_PUSH:
+            case Defines.MOVETYPE_STOP:
+                SV.SV_Physics_Pusher(ent);
+                break;
+            case Defines.MOVETYPE_NONE:
+                SV.SV_Physics_None(ent);
+                break;
+            case Defines.MOVETYPE_NOCLIP:
+                SV.SV_Physics_Noclip(ent);
+                break;
+            case Defines.MOVETYPE_STEP:
+                SV.SV_Physics_Step(ent);
+                break;
+            case Defines.MOVETYPE_TOSS:
+            case Defines.MOVETYPE_BOUNCE:
+            case Defines.MOVETYPE_FLY:
+            case Defines.MOVETYPE_FLYMISSILE:
+                SV.SV_Physics_Toss(ent);
+                break;
+            default:
+                gi.error("SV_Physics: bad movetype " + ent.movetype);
+        }
+    }
+
+    public static void ClearBounds(float[] mins, float[] maxs) {
+        mins[0] = mins[1] = mins[2] = 99999;
+        maxs[0] = maxs[1] = maxs[2] = -99999;
+    }
+
+    public static void AddPointToBounds(float[] v, float[] mins, float[] maxs) {
+        int i;
+        float val;
+
+        for (i = 0; i < 3; i++) {
+            val = v[i];
+            if (val < mins[i])
+                mins[i] = val;
+            if (val > maxs[i])
+                maxs[i] = val;
+        }
+    }
+
+    public static void ShutdownGame() {
+        gi.dprintf("==== ShutdownGame ====\n");
+    }
+
+    /**
+     * ClientEndServerFrames.
+     */
+    private static void ClientEndServerFrames() {
+        int i;
+        EDict ent;
+
+        // calc the player views now that all pushing
+        // and damage has been added
+        for (i = 0; i < maxclients.value; i++) {
+            ent = g_edicts[1 + i];
+            if (!ent.inuse || null == ent.client)
+                continue;
+            PlayerView.ClientEndServerFrame(ent);
+        }
+
+    }
+
+    /**
+     * Returns the created target changelevel.
+     */
+    private static EDict CreateTargetChangeLevel(String map) {
+        EDict ent;
+
+        ent = GameUtil.G_Spawn();
+        ent.classname = "target_changelevel";
+        level.nextmap = map;
+        ent.map = level.nextmap;
+        return ent;
+    }
+
+    /**
+     * The timelimit or fraglimit has been exceeded.
+     */
+    private static void EndDMLevel() {
+        EDict ent;
+        //char * s, * t, * f;
+        //static const char * seps = " ,\n\r";
+        String s, t, f;
+        String seps = " ,\n\r";
+
+        // stay on same level flag
+        if (((int) dmflags.value & Defines.DF_SAME_LEVEL) != 0) {
+            PlayerHud.BeginIntermission(CreateTargetChangeLevel(level.mapname));
+            return;
+        }
+
+        // see if it's in the map list
+        if (sv_maplist.string.length() > 0) {
+            s = sv_maplist.string;
+            f = null;
+            StringTokenizer tk = new StringTokenizer(s, seps);
+
+            while (tk.hasMoreTokens()) {
+                t = tk.nextToken();
+
+                // store first map
+                if (f == null)
+                    f = t;
+
+                if (t.equalsIgnoreCase(level.mapname)) {
+                    // it's in the list, go to the next one
+                    if (!tk.hasMoreTokens()) {
+                        // end of list, go to first one
+                        if (f == null) // there isn't a first one, same level
+                            PlayerHud.BeginIntermission(CreateTargetChangeLevel(level.mapname));
+                        else
+                            PlayerHud.BeginIntermission(CreateTargetChangeLevel(f));
+                    } else
+                        PlayerHud.BeginIntermission(CreateTargetChangeLevel(tk.nextToken()));
+                    return;
+                }
+            }
+        }
+
+        //not in the map list
+        if (level.nextmap.length() > 0) // go to a specific map
+            PlayerHud.BeginIntermission(CreateTargetChangeLevel(level.nextmap));
+        else { // search for a changelevel
+            EdictIterator edit = null;
+            edit = G_Find(edit, findByClass, "target_changelevel");
+            if (edit == null) { // the map designer didn't include a
+                // changelevel,
+                // so create a fake ent that goes back to the same level
+                PlayerHud.BeginIntermission(CreateTargetChangeLevel(level.mapname));
+                return;
+            }
+            ent = edit.o;
+            PlayerHud.BeginIntermission(ent);
+        }
+    }
+
+    /**
+     * CheckNeedPass.
+     */
+    private static void CheckNeedPass() {
+        int need;
+
+        // if password or spectator_password has changed, update needpass
+        // as needed
+        if (password.modified || spectator_password.modified) {
+            password.modified = spectator_password.modified = false;
+
+            need = 0;
+
+            if ((password.string.length() > 0)
+                    && 0 != Lib.Q_stricmp(password.string, "none"))
+                need |= 1;
+            if ((spectator_password.string.length() > 0)
+                    && 0 != Lib.Q_stricmp(spectator_password.string, "none"))
+                need |= 2;
+
+            gi.cvar_set("needpass", "" + need);
+        }
+    }
+
+    /**
+     * CheckDMRules.
+     */
+    private static void CheckDMRules() {
+        int i;
+        Client cl;
+
+        if (level.intermissiontime != 0)
+            return;
+
+        if (0 == deathmatch.value)
+            return;
+
+        if (timelimit.value != 0) {
+            if (level.time >= timelimit.value * 60) {
+                gi.bprintf(Defines.PRINT_HIGH, "Timelimit hit.\n");
+                EndDMLevel();
+                return;
+            }
+        }
+
+        if (fraglimit.value != 0) {
+            for (i = 0; i < maxclients.value; i++) {
+                cl = game.clients[i];
+                if (!g_edicts[i + 1].inuse)
+                    continue;
+
+                if (cl.resp.score >= fraglimit.value) {
+                    gi.bprintf(Defines.PRINT_HIGH, "Fraglimit hit.\n");
+                    EndDMLevel();
+                    return;
+                }
+            }
+        }
+    }
+
+    /**
+     * Exits a level.
+     */
+    private static void ExitLevel() {
+        int i;
+        EDict ent;
+
+        String command = "gamemap \"" + level.changemap + "\"\n";
+        gi.AddCommandString(command);
+        level.changemap = null;
+        level.exitintermission = false;
+        level.intermissiontime = 0;
+        ClientEndServerFrames();
+
+        // clear some things before going to next level
+        for (i = 0; i < maxclients.value; i++) {
+            ent = g_edicts[1 + i];
+            if (!ent.inuse)
+                continue;
+            if (ent.health > ent.client.pers.max_health)
+                ent.health = ent.client.pers.max_health;
+        }
+    }
+
+    /**
+     * G_RunFrame
+     * <p>
+     * Advances the world by Defines.FRAMETIME (0.1) seconds.
+     */
+    public static void G_RunFrame() {
+        int i;
+        EDict ent;
+
+        level.framenum++;
+        level.time = level.framenum * Defines.FRAMETIME;
+
+        // choose a client for monsters to target this frame
+        GameAI.AI_SetSightClient();
+
+        // exit intermissions
+
+        if (level.exitintermission) {
+            ExitLevel();
+            return;
+        }
+
+        //
+        // treat each object in turn
+        // even the world gets a chance to think
+        //
+
+        for (i = 0; i < num_edicts; i++) {
+            ent = g_edicts[i];
+            if (!ent.inuse)
+                continue;
+
+            level.current_entity = ent;
+
+            Math3D.vectorCopy(ent.s.origin, ent.s.old_origin);
+
+            // if the ground entity moved, make sure we are still on it
+            if ((ent.groundentity != null)
+                    && (ent.groundentity.linkcount != ent.groundentity_linkcount)) {
+                ent.groundentity = null;
+                if (0 == (ent.flags & (Defines.FL_SWIM | Defines.FL_FLY))
+                        && (ent.svflags & Defines.SVF_MONSTER) != 0) {
+                    M.M_CheckGround(ent);
+                }
+            }
+
+            if (i > 0 && i <= maxclients.value) {
+                PlayerClient.clientBeginServerFrame(ent);
+                continue;
+            }
+
+            G_RunEntity(ent);
+        }
+
+        // see if it is time to end a deathmatch
+        CheckDMRules();
+
+        // see if needpass needs updated
+        CheckNeedPass();
+
+        // build the playerstate_t structures for all players
+        ClientEndServerFrames();
+    }
+
+    /**
+     * This return a pointer to the structure with all entry points and global
+     * variables.
+     */
+
+    public static void GetGameApi(game_import_t imp) {
+        gi = imp;
+        gi.pointcontents = new Move.PointContentsAdapter() {
+            public int pointcontents(float[] o) {
+                return SV_WORLD.SV_PointContents(o);
+            }
+        };
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/GameFunc.java b/src/main/java/lwjake2/game/GameFunc.java
new file mode 100644 (file)
index 0000000..1dc23d2
--- /dev/null
@@ -0,0 +1,1069 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+
+class GameFunc {
+
+    /**
+     * PLATS
+     * <p>
+     * movement options:
+     * <p>
+     * linear smooth start, hard stop smooth start, smooth stop
+     * <p>
+     * start end acceleration speed deceleration begin sound end sound target
+     * fired when reaching end wait at end
+     * <p>
+     * object characteristics that use move segments
+     * --------------------------------------------- movetype_push, or
+     * movetype_stop action when touched action when blocked action when used
+     * disabled? auto trigger spawning
+     */
+
+    private final static int PLAT_LOW_TRIGGER = 1;
+    private final static int STATE_TOP = 0;
+    private final static int STATE_BOTTOM = 1;
+    private final static int STATE_UP = 2;
+    private final static int STATE_DOWN = 3;
+
+    private final static int TRAIN_START_ON = 1;
+    private final static int TRAIN_TOGGLE = 2;
+    private final static int TRAIN_BLOCK_STOPS = 4;
+    private static final EntThinkAdapter Move_Done = new EntThinkAdapter() {
+        public String getID() {
+            return "move_done";
+        }
+
+        public void think(EDict ent) {
+            Math3D.vectorClear(ent.velocity);
+            ent.moveinfo.endfunc.think(ent);
+        }
+    };
+    private static final EntThinkAdapter Move_Final = new EntThinkAdapter() {
+        public String getID() {
+            return "move_final";
+        }
+
+        public void think(EDict ent) {
+
+            if (ent.moveinfo.remaining_distance == 0) {
+                Move_Done.think(ent);
+                return;
+            }
+
+            Math3D.vectorScale(ent.moveinfo.dir,
+                    ent.moveinfo.remaining_distance / Defines.FRAMETIME,
+                    ent.velocity);
+
+            ent.think = Move_Done;
+            ent.nextthink = GameBase.level.time + Defines.FRAMETIME;
+        }
+    };
+    private static final EntThinkAdapter Move_Begin = new EntThinkAdapter() {
+        public String getID() {
+            return "move_begin";
+        }
+
+        public void think(EDict ent) {
+
+            float frames;
+
+            if ((ent.moveinfo.speed * Defines.FRAMETIME) >= ent.moveinfo.remaining_distance) {
+                Move_Final.think(ent);
+                return;
+            }
+            Math3D.vectorScale(ent.moveinfo.dir, ent.moveinfo.speed,
+                    ent.velocity);
+            frames = (float) Math
+                    .floor((ent.moveinfo.remaining_distance / ent.moveinfo.speed)
+                            / Defines.FRAMETIME);
+            ent.moveinfo.remaining_distance -= frames * ent.moveinfo.speed
+                    * Defines.FRAMETIME;
+            ent.nextthink = GameBase.level.time + (frames * Defines.FRAMETIME);
+            ent.think = Move_Final;
+        }
+    };
+    private static final EntThinkAdapter AngleMove_Done = new EntThinkAdapter() {
+        public String getID() {
+            return "agnle_move_done";
+        }
+
+        public void think(EDict ent) {
+            Math3D.vectorClear(ent.avelocity);
+            ent.moveinfo.endfunc.think(ent);
+        }
+    };
+    private static final EntThinkAdapter AngleMove_Final = new EntThinkAdapter() {
+        public String getID() {
+            return "angle_move_final";
+        }
+
+        public void think(EDict ent) {
+            float[] move = {0, 0, 0};
+
+            if (ent.moveinfo.state == STATE_UP)
+                Math3D.vectorSubtract(ent.moveinfo.end_angles, ent.s.angles,
+                        move);
+            else
+                Math3D.vectorSubtract(ent.moveinfo.start_angles, ent.s.angles,
+                        move);
+
+            if (Math3D.vectorEquals(move, Globals.vec3_origin)) {
+                AngleMove_Done.think(ent);
+                return;
+            }
+
+            Math3D.vectorScale(move, 1.0f / Defines.FRAMETIME, ent.avelocity);
+
+            ent.think = AngleMove_Done;
+            ent.nextthink = GameBase.level.time + Defines.FRAMETIME;
+        }
+    };
+    static final EntThinkAdapter AngleMove_Begin = new EntThinkAdapter() {
+        public String getID() {
+            return "angle_move_begin";
+        }
+
+        public void think(EDict ent) {
+            float[] destdelta = {0, 0, 0};
+            float len;
+            float traveltime;
+            float frames;
+
+            // set destdelta to the vector needed to move
+            if (ent.moveinfo.state == STATE_UP)
+                Math3D.vectorSubtract(ent.moveinfo.end_angles, ent.s.angles,
+                        destdelta);
+            else
+                Math3D.vectorSubtract(ent.moveinfo.start_angles, ent.s.angles,
+                        destdelta);
+
+            // calculate length of vector
+            len = Math3D.vectorLength(destdelta);
+
+            // divide by speed to get time to reach dest
+            traveltime = len / ent.moveinfo.speed;
+
+            if (traveltime < Defines.FRAMETIME) {
+                AngleMove_Final.think(ent);
+                return;
+            }
+
+            frames = (float) (Math.floor(traveltime / Defines.FRAMETIME));
+
+            // scale the destdelta vector by the time spent traveling to get
+            // velocity
+            Math3D.vectorScale(destdelta, 1.0f / traveltime, ent.avelocity);
+
+            // set nextthink to trigger a think when dest is reached
+            ent.nextthink = GameBase.level.time + frames * Defines.FRAMETIME;
+            ent.think = AngleMove_Final;
+        }
+    };
+    private static final EntThinkAdapter Think_AccelMove = new EntThinkAdapter() {
+        public String getID() {
+            return "thinc_accelmove";
+        }
+
+        public void think(EDict ent) {
+            ent.moveinfo.remaining_distance -= ent.moveinfo.current_speed;
+
+            if (ent.moveinfo.current_speed == 0) // starting or blocked
+                plat_CalcAcceleratedMove(ent.moveinfo);
+
+            plat_Accelerate(ent.moveinfo);
+
+            // will the entire move complete on next frame?
+            if (ent.moveinfo.remaining_distance <= ent.moveinfo.current_speed) {
+                Move_Final.think(ent);
+                return;
+            }
+
+            Math3D.vectorScale(ent.moveinfo.dir,
+                    ent.moveinfo.current_speed * 10, ent.velocity);
+            ent.nextthink = GameBase.level.time + Defines.FRAMETIME;
+            ent.think = Think_AccelMove;
+        }
+    };
+    private static final EntThinkAdapter plat_hit_bottom = new EntThinkAdapter() {
+        public String getID() {
+            return "plat_hit_bottom";
+        }
+
+        public void think(EDict ent) {
+
+            if (0 == (ent.flags & Defines.FL_TEAMSLAVE)) {
+                if (ent.moveinfo.sound_end != 0)
+                    GameBase.gi.sound(ent, Defines.CHAN_NO_PHS_ADD
+                                    + Defines.CHAN_VOICE, ent.moveinfo.sound_end, 1,
+                            Defines.ATTN_STATIC, 0);
+                ent.s.sound = 0;
+            }
+            ent.moveinfo.state = STATE_BOTTOM;
+        }
+    };
+
+    //
+    //    Support routines for movement (changes in origin using velocity)
+    //
+    private static final EntThinkAdapter plat_go_down = new EntThinkAdapter() {
+        public String getID() {
+            return "plat_go_down";
+        }
+
+        public void think(EDict ent) {
+            if (0 == (ent.flags & Defines.FL_TEAMSLAVE)) {
+                if (ent.moveinfo.sound_start != 0)
+                    GameBase.gi.sound(ent, Defines.CHAN_NO_PHS_ADD
+                                    + Defines.CHAN_VOICE, ent.moveinfo.sound_start, 1,
+                            Defines.ATTN_STATIC, 0);
+                ent.s.sound = ent.moveinfo.sound_middle;
+            }
+            ent.moveinfo.state = STATE_DOWN;
+            Move_Calc(ent, ent.moveinfo.end_origin, plat_hit_bottom);
+        }
+    };
+    private static final EntThinkAdapter plat_hit_top = new EntThinkAdapter() {
+        public String getID() {
+            return "plat_hit_top";
+        }
+
+        public void think(EDict ent) {
+            if (0 == (ent.flags & Defines.FL_TEAMSLAVE)) {
+                if (ent.moveinfo.sound_end != 0)
+                    GameBase.gi.sound(ent, Defines.CHAN_NO_PHS_ADD
+                                    + Defines.CHAN_VOICE, ent.moveinfo.sound_end, 1,
+                            Defines.ATTN_STATIC, 0);
+                ent.s.sound = 0;
+            }
+            ent.moveinfo.state = STATE_TOP;
+
+            ent.think = plat_go_down;
+            ent.nextthink = GameBase.level.time + 3;
+        }
+    };
+    private static final EntBlockedAdapter plat_blocked = new EntBlockedAdapter() {
+        public String getID() {
+            return "plat_blocked";
+        }
+
+        public void blocked(EDict self, EDict other) {
+            if (0 == (other.svflags & Defines.SVF_MONSTER)
+                    && (null == other.client)) {
+                // if it's still there, nuke it
+                if (other != null)
+                    GameMisc.BecomeExplosion1(other);
+                return;
+            }
+
+            if (self.moveinfo.state == STATE_UP)
+                plat_go_down.think(self);
+            else if (self.moveinfo.state == STATE_DOWN)
+                plat_go_up(self);
+
+        }
+    };
+
+    //
+    //    Support routines for angular movement (changes in angle using avelocity)
+    //
+    private static final EntUseAdapter Use_Plat = new EntUseAdapter() {
+        public String getID() {
+            return "use_plat";
+        }
+
+        public void use(EDict ent, EDict other, EDict activator) {
+            if (ent.think != null)
+                return; // already down
+            plat_go_down.think(ent);
+        }
+    };
+    private static final EntTouchAdapter Touch_Plat_Center = new EntTouchAdapter() {
+        public String getID() {
+            return "touch_plat_center";
+        }
+
+        public void touch(EDict ent, EDict other, cplane_t plane,
+                          csurface_t surf) {
+            if (other.client == null)
+                return;
+
+            if (other.health <= 0)
+                return;
+
+            ent = ent.enemy; // now point at the plat, not the trigger
+            if (ent.moveinfo.state == STATE_BOTTOM)
+                plat_go_up(ent);
+            else if (ent.moveinfo.state == STATE_TOP) {
+                ent.nextthink = GameBase.level.time + 1; // the player is still
+                // on the plat, so
+                // delay going down
+            }
+        }
+    };
+
+
+    /*
+     * ======================================================================
+     * 
+     * BUTTONS
+     * 
+     * ======================================================================
+     */
+
+    private static final EntBlockedAdapter train_blocked = new EntBlockedAdapter() {
+        public String getID() {
+            return "train_blocked";
+        }
+
+        public void blocked(EDict self, EDict other) {
+            if (0 == (other.svflags & Defines.SVF_MONSTER)
+                    && (null == other.client)) {
+                // if it's still there, nuke it
+                if (other != null)
+                    GameMisc.BecomeExplosion1(other);
+                return;
+            }
+
+            if (GameBase.level.time < self.touch_debounce_time)
+                return;
+
+            if (self.dmg == 0)
+                return;
+            self.touch_debounce_time = GameBase.level.time + 0.5f;
+        }
+    };
+    private static final EntThinkAdapter train_wait = new EntThinkAdapter() {
+        public String getID() {
+            return "train_wait";
+        }
+
+        public void think(EDict self) {
+            if (self.target_ent.pathtarget != null) {
+                String savetarget;
+                EDict ent;
+
+                ent = self.target_ent;
+                savetarget = ent.target;
+                ent.target = ent.pathtarget;
+                GameUtil.G_UseTargets(ent, self.activator);
+                ent.target = savetarget;
+
+                // make sure we didn't get killed by a killtarget
+                if (!self.inuse)
+                    return;
+            }
+
+            if (self.moveinfo.wait != 0) {
+                if (self.moveinfo.wait > 0) {
+                    self.nextthink = GameBase.level.time + self.moveinfo.wait;
+                    self.think = train_next;
+                } else if (0 != (self.spawnflags & TRAIN_TOGGLE)) // && wait < 0
+                {
+                    train_next.think(self);
+                    self.spawnflags &= ~TRAIN_START_ON;
+                    Math3D.vectorClear(self.velocity);
+                    self.nextthink = 0;
+                }
+
+                if (0 == (self.flags & Defines.FL_TEAMSLAVE)) {
+                    if (self.moveinfo.sound_end != 0)
+                        GameBase.gi.sound(self, Defines.CHAN_NO_PHS_ADD
+                                        + Defines.CHAN_VOICE, self.moveinfo.sound_end,
+                                1, Defines.ATTN_STATIC, 0);
+                    self.s.sound = 0;
+                }
+            } else {
+                train_next.think(self);
+            }
+        }
+    };
+    private static final EntThinkAdapter train_next = new EntThinkAdapter() {
+        public String getID() {
+            return "train_next";
+        }
+
+        public void think(EDict self) {
+            EDict ent = null;
+            float[] dest = {0, 0, 0};
+            boolean first;
+
+            first = true;
+
+            boolean dogoto = true;
+            while (dogoto) {
+                if (null == self.target) {
+                    //                 gi.dprintf ("train_next: no next target\n");
+                    return;
+                }
+
+                ent = GameBase.G_PickTarget(self.target);
+                if (null == ent) {
+                    GameBase.gi.dprintf("train_next: bad target " + self.target
+                            + "\n");
+                    return;
+                }
+
+                self.target = ent.target;
+                dogoto = false;
+                // check for a teleport path_corner
+                if ((ent.spawnflags & 1) != 0) {
+                    if (!first) {
+                        GameBase.gi
+                                .dprintf("connected teleport path_corners, see "
+                                        + ent.classname
+                                        + " at "
+                                        + Lib.vtos(ent.s.origin) + "\n");
+                        return;
+                    }
+                    first = false;
+                    Math3D.vectorSubtract(ent.s.origin, self.mins,
+                            self.s.origin);
+                    Math3D.vectorCopy(self.s.origin, self.s.old_origin);
+                    self.s.event = Defines.EV_OTHER_TELEPORT;
+                    GameBase.gi.linkentity(self);
+                    dogoto = true;
+                }
+            }
+            self.moveinfo.wait = ent.wait;
+            self.target_ent = ent;
+
+            if (0 == (self.flags & Defines.FL_TEAMSLAVE)) {
+                if (self.moveinfo.sound_start != 0)
+                    GameBase.gi.sound(self, Defines.CHAN_NO_PHS_ADD
+                                    + Defines.CHAN_VOICE, self.moveinfo.sound_start, 1,
+                            Defines.ATTN_STATIC, 0);
+                self.s.sound = self.moveinfo.sound_middle;
+            }
+
+            Math3D.vectorSubtract(ent.s.origin, self.mins, dest);
+            self.moveinfo.state = STATE_TOP;
+            Math3D.vectorCopy(self.s.origin, self.moveinfo.start_origin);
+            Math3D.vectorCopy(dest, self.moveinfo.end_origin);
+            Move_Calc(self, dest, train_wait);
+            self.spawnflags |= TRAIN_START_ON;
+        }
+    };
+    public static final EntUseAdapter train_use = new EntUseAdapter() {
+        public String getID() {
+            return "train_use";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            self.activator = activator;
+
+            if ((self.spawnflags & TRAIN_START_ON) != 0) {
+                if (0 == (self.spawnflags & TRAIN_TOGGLE))
+                    return;
+                self.spawnflags &= ~TRAIN_START_ON;
+                Math3D.vectorClear(self.velocity);
+                self.nextthink = 0;
+            } else {
+                if (self.target_ent != null)
+                    train_resume(self);
+                else
+                    train_next.think(self);
+            }
+        }
+    };
+    public static final EntThinkAdapter func_train_find = new EntThinkAdapter() {
+        public String getID() {
+            return "func_train_find";
+        }
+
+        public void think(EDict self) {
+            EDict ent;
+
+            if (null == self.target) {
+                GameBase.gi.dprintf("train_find: no target\n");
+                return;
+            }
+            ent = GameBase.G_PickTarget(self.target);
+            if (null == ent) {
+                GameBase.gi.dprintf("train_find: target " + self.target
+                        + " not found\n");
+                return;
+            }
+            self.target = ent.target;
+
+            Math3D.vectorSubtract(ent.s.origin, self.mins, self.s.origin);
+            GameBase.gi.linkentity(self);
+
+            // if not triggered, start immediately
+            if (null == self.targetname)
+                self.spawnflags |= TRAIN_START_ON;
+
+            if ((self.spawnflags & TRAIN_START_ON) != 0) {
+                self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+                self.think = train_next;
+                self.activator = self;
+            }
+        }
+    };
+    /*
+     * QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8)
+     */
+    private static final EntUseAdapter trigger_elevator_use = new EntUseAdapter() {
+        public String getID() {
+            return "trigger_elevator_use";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            EDict target;
+
+
+            if (null == other.pathtarget) {
+                GameBase.gi.dprintf("elevator used with no pathtarget\n");
+                return;
+            }
+
+            target = GameBase.G_PickTarget(other.pathtarget);
+            if (null == target) {
+                GameBase.gi.dprintf("elevator used with bad pathtarget: "
+                        + other.pathtarget + "\n");
+            }
+
+        }
+    };
+    private static final EntThinkAdapter trigger_elevator_init = new EntThinkAdapter() {
+        public String getID() {
+            return "trigger_elevator_init";
+        }
+
+        public void think(EDict self) {
+            if (null == self.target) {
+                GameBase.gi.dprintf("trigger_elevator has no target\n");
+                return;
+            }
+
+            self.use = trigger_elevator_use;
+            self.svflags = Defines.SVF_NOCLIENT;
+        }
+    };
+
+    /*
+     * QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER
+     * NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS TOGGLE causes the door to wait in
+     * both the start and end states for a trigger event.
+     * 
+     * START_OPEN the door to moves to its destination when spawned, and operate
+     * in reverse. It is used to temporarily or permanently close off an area
+     * when triggered (not useful for touch or takedamage doors). NOMONSTER
+     * monsters will not trigger this door
+     * 
+     * You need to have an origin brush as part of this entity. The center of
+     * that brush will be the point around which it is rotated. It will rotate
+     * around the Z axis by default. You can check either the X_AXIS or Y_AXIS
+     * box to change that.
+     * 
+     * "distance" is how many degrees the door will be rotated. "speed"
+     * determines how fast the door moves; default value is 100.
+     * 
+     * REVERSE will cause the door to rotate in the opposite direction.
+     * 
+     * "message" is printed when the door is touched if it is a trigger door and
+     * it hasn't been fired yet "angle" determines the opening direction
+     * "targetname" if set, no touch field will be spawned and a remote button
+     * or trigger field activates the door. "health" if set, door must be shot
+     * open "speed" movement speed (100 default) "wait" wait before returning (3
+     * default, -1 = never return) "dmg" damage to inflict when blocked (2
+     * default) "sounds" 1) silent 2) light 3) medium 4) heavy
+     */
+    static final EntThinkAdapter SP_trigger_elevator = new EntThinkAdapter() {
+        public String getID() {
+            return "sp_trigger_elevator";
+        }
+
+        public void think(EDict self) {
+            self.think = trigger_elevator_init;
+            self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+        }
+    };
+    private static final EntThinkAdapter func_timer_think = new EntThinkAdapter() {
+        public String getID() {
+            return "func_timer_think";
+        }
+
+        public void think(EDict self) {
+            GameUtil.G_UseTargets(self, self.activator);
+            self.nextthink = GameBase.level.time + self.wait + Lib.crandom()
+                    * self.random;
+        }
+    };
+    private static final EntUseAdapter func_timer_use = new EntUseAdapter() {
+        public String getID() {
+            return "func_timer_use";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            self.activator = activator;
+
+            // if on, turn it off
+            if (self.nextthink != 0) {
+                self.nextthink = 0;
+                return;
+            }
+
+            // turn it on
+            if (self.delay != 0)
+                self.nextthink = GameBase.level.time + self.delay;
+            else
+                func_timer_think.think(self);
+        }
+    };
+    private static final EntUseAdapter func_conveyor_use = new EntUseAdapter() {
+        public String getID() {
+            return "func_conveyor_use";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            if ((self.spawnflags & 1) != 0) {
+                self.speed = 0;
+                self.spawnflags &= ~1;
+            } else {
+                self.speed = self.count;
+                self.spawnflags |= 1;
+            }
+
+            if (0 == (self.spawnflags & 2))
+                self.count = 0;
+        }
+    };
+
+    /*
+     * QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS Trains are
+     * moving platforms that players can ride. The targets origin specifies the
+     * min point of the train at each corner. The train spawns at the first
+     * target it is pointing at. If the train is the target of a button or
+     * trigger, it will not begin moving until activated. speed default 100 dmg
+     * default 2 noise looping sound to play when the train is in motion
+     *  
+     */
+    static final EntThinkAdapter SP_func_conveyor = new EntThinkAdapter() {
+        public String getID() {
+            return "sp_func_conveyor";
+        }
+
+        public void think(EDict self) {
+
+            if (0 == self.speed)
+                self.speed = 100;
+
+            if (0 == (self.spawnflags & 1)) {
+                self.count = (int) self.speed;
+                self.speed = 0;
+            }
+
+            self.use = func_conveyor_use;
+
+            GameBase.gi.setmodel(self, self.model);
+            self.solid = Defines.SOLID_BSP;
+            GameBase.gi.linkentity(self);
+        }
+    };
+
+
+    private static void Move_Calc(EDict ent, float[] dest, EntThinkAdapter func) {
+        Math3D.vectorClear(ent.velocity);
+        Math3D.vectorSubtract(dest, ent.s.origin, ent.moveinfo.dir);
+        ent.moveinfo.remaining_distance = Math3D
+                .vectorNormalize(ent.moveinfo.dir);
+
+        ent.moveinfo.endfunc = func;
+
+        if (ent.moveinfo.speed == ent.moveinfo.accel
+                && ent.moveinfo.speed == ent.moveinfo.decel) {
+            if (GameBase.level.current_entity == ((ent.flags & Defines.FL_TEAMSLAVE) != 0 ? ent.teammaster
+                    : ent)) {
+                Move_Begin.think(ent);
+            } else {
+                ent.nextthink = GameBase.level.time + Defines.FRAMETIME;
+                ent.think = Move_Begin;
+            }
+        } else {
+            // accelerative
+            ent.moveinfo.current_speed = 0;
+            ent.think = Think_AccelMove;
+            ent.nextthink = GameBase.level.time + Defines.FRAMETIME;
+        }
+    }
+
+    /**
+     * Think_AccelMove
+     * <p>
+     * The team has completed a frame of movement, so change the speed for the
+     * next frame.
+     */
+    private static float AccelerationDistance(float target, float rate) {
+        return target * ((target / rate) + 1) / 2;
+    }
+
+    private static void plat_CalcAcceleratedMove(moveinfo_t moveinfo) {
+        float accel_dist;
+        float decel_dist;
+
+        moveinfo.move_speed = moveinfo.speed;
+
+        if (moveinfo.remaining_distance < moveinfo.accel) {
+            moveinfo.current_speed = moveinfo.remaining_distance;
+            return;
+        }
+
+        accel_dist = AccelerationDistance(moveinfo.speed, moveinfo.accel);
+        decel_dist = AccelerationDistance(moveinfo.speed, moveinfo.decel);
+
+        if ((moveinfo.remaining_distance - accel_dist - decel_dist) < 0) {
+            float f;
+
+            f = (moveinfo.accel + moveinfo.decel)
+                    / (moveinfo.accel * moveinfo.decel);
+            moveinfo.move_speed = (float) ((-2 + Math.sqrt(4 - 4 * f
+                    * (-2 * moveinfo.remaining_distance))) / (2 * f));
+            decel_dist = AccelerationDistance(moveinfo.move_speed,
+                    moveinfo.decel);
+        }
+
+        moveinfo.decel_distance = decel_dist;
+    }
+
+    private static void plat_Accelerate(moveinfo_t moveinfo) {
+        // are we decelerating?
+        if (moveinfo.remaining_distance <= moveinfo.decel_distance) {
+            if (moveinfo.remaining_distance < moveinfo.decel_distance) {
+                if (moveinfo.next_speed != 0) {
+                    moveinfo.current_speed = moveinfo.next_speed;
+                    moveinfo.next_speed = 0;
+                    return;
+                }
+                if (moveinfo.current_speed > moveinfo.decel)
+                    moveinfo.current_speed -= moveinfo.decel;
+            }
+            return;
+        }
+
+        // are we at full speed and need to start decelerating during this move?
+        if (moveinfo.current_speed == moveinfo.move_speed)
+            if ((moveinfo.remaining_distance - moveinfo.current_speed) < moveinfo.decel_distance) {
+                float p1_distance;
+                float p2_distance;
+                float distance;
+
+                p1_distance = moveinfo.remaining_distance
+                        - moveinfo.decel_distance;
+                p2_distance = moveinfo.move_speed
+                        * (1.0f - (p1_distance / moveinfo.move_speed));
+                distance = p1_distance + p2_distance;
+                moveinfo.current_speed = moveinfo.move_speed;
+                moveinfo.next_speed = moveinfo.move_speed - moveinfo.decel
+                        * (p2_distance / distance);
+                return;
+            }
+
+        // are we accelerating?
+        if (moveinfo.current_speed < moveinfo.speed) {
+            float old_speed;
+            float p1_distance;
+            float p1_speed;
+            float p2_distance;
+            float distance;
+
+            old_speed = moveinfo.current_speed;
+
+            // figure simple acceleration up to move_speed
+            moveinfo.current_speed += moveinfo.accel;
+            if (moveinfo.current_speed > moveinfo.speed)
+                moveinfo.current_speed = moveinfo.speed;
+
+            // are we accelerating throughout this entire move?
+            if ((moveinfo.remaining_distance - moveinfo.current_speed) >= moveinfo.decel_distance)
+                return;
+
+            // during this move we will accelrate from current_speed to
+            // move_speed
+            // and cross over the decel_distance; figure the average speed for
+            // the
+            // entire move
+            p1_distance = moveinfo.remaining_distance - moveinfo.decel_distance;
+            p1_speed = (old_speed + moveinfo.move_speed) / 2.0f;
+            p2_distance = moveinfo.move_speed
+                    * (1.0f - (p1_distance / p1_speed));
+            distance = p1_distance + p2_distance;
+            moveinfo.current_speed = (p1_speed * (p1_distance / distance))
+                    + (moveinfo.move_speed * (p2_distance / distance));
+            moveinfo.next_speed = moveinfo.move_speed - moveinfo.decel
+                    * (p2_distance / distance);
+        }
+
+        // we are at constant velocity (move_speed)
+    }
+
+    private static void plat_go_up(EDict ent) {
+        if (0 == (ent.flags & Defines.FL_TEAMSLAVE)) {
+            if (ent.moveinfo.sound_start != 0)
+                GameBase.gi.sound(ent, Defines.CHAN_NO_PHS_ADD
+                                + Defines.CHAN_VOICE, ent.moveinfo.sound_start, 1,
+                        Defines.ATTN_STATIC, 0);
+            ent.s.sound = ent.moveinfo.sound_middle;
+        }
+        ent.moveinfo.state = STATE_UP;
+        Move_Calc(ent, ent.moveinfo.start_origin, plat_hit_top);
+    }
+
+    private static void plat_spawn_inside_trigger(EDict ent) {
+        EDict trigger;
+        float[] tmin = {0, 0, 0}, tmax = {0, 0, 0};
+
+        //
+        //        middle trigger
+        //
+        trigger = GameUtil.G_Spawn();
+        trigger.touch = Touch_Plat_Center;
+        trigger.movetype = Defines.MOVETYPE_NONE;
+        trigger.solid = Defines.SOLID_TRIGGER;
+        trigger.enemy = ent;
+
+        tmin[0] = ent.mins[0] + 25;
+        tmin[1] = ent.mins[1] + 25;
+        tmin[2] = ent.mins[2];
+
+        tmax[0] = ent.maxs[0] - 25;
+        tmax[1] = ent.maxs[1] - 25;
+        tmax[2] = ent.maxs[2] + 8;
+
+        tmin[2] = tmax[2] - (ent.pos1[2] - ent.pos2[2] + GameBase.st.lip);
+
+        if ((ent.spawnflags & PLAT_LOW_TRIGGER) != 0)
+            tmax[2] = tmin[2] + 8;
+
+        if (tmax[0] - tmin[0] <= 0) {
+            tmin[0] = (ent.mins[0] + ent.maxs[0]) * 0.5f;
+            tmax[0] = tmin[0] + 1;
+        }
+        if (tmax[1] - tmin[1] <= 0) {
+            tmin[1] = (ent.mins[1] + ent.maxs[1]) * 0.5f;
+            tmax[1] = tmin[1] + 1;
+        }
+
+        Math3D.vectorCopy(tmin, trigger.mins);
+        Math3D.vectorCopy(tmax, trigger.maxs);
+
+        GameBase.gi.linkentity(trigger);
+    }
+
+    /**
+     * QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER speed default 150
+     * <p>
+     * Plats are always drawn in the extended position, so they will light
+     * correctly.
+     * <p>
+     * If the plat is the target of another trigger or button, it will start out
+     * disabled in the extended position until it is trigger, when it will lower
+     * and become a normal plat.
+     * <p>
+     * "speed" overrides default 200. "accel" overrides default 500 "lip"
+     * overrides default 8 pixel lip
+     * <p>
+     * If the "height" key is set, that will determine the amount the plat
+     * moves, instead of being implicitly determoveinfoned by the model's
+     * height.
+     * <p>
+     * Set "sounds" to one of the following: 1) base fast 2) chain slow
+     */
+    static void SP_func_plat(EDict ent) {
+        Math3D.vectorClear(ent.s.angles);
+        ent.solid = Defines.SOLID_BSP;
+        ent.movetype = Defines.MOVETYPE_PUSH;
+
+        GameBase.gi.setmodel(ent, ent.model);
+
+        ent.blocked = plat_blocked;
+
+        if (0 == ent.speed)
+            ent.speed = 20;
+        else
+            ent.speed *= 0.1;
+
+        if (ent.accel == 0)
+            ent.accel = 5;
+        else
+            ent.accel *= 0.1;
+
+        if (ent.decel == 0)
+            ent.decel = 5;
+        else
+            ent.decel *= 0.1;
+
+        if (ent.dmg == 0)
+            ent.dmg = 2;
+
+        if (GameBase.st.lip == 0)
+            GameBase.st.lip = 8;
+
+        // pos1 is the top position, pos2 is the bottom
+        Math3D.vectorCopy(ent.s.origin, ent.pos1);
+        Math3D.vectorCopy(ent.s.origin, ent.pos2);
+        if (GameBase.st.height != 0)
+            ent.pos2[2] -= GameBase.st.height;
+        else
+            ent.pos2[2] -= (ent.maxs[2] - ent.mins[2]) - GameBase.st.lip;
+
+        ent.use = Use_Plat;
+
+        plat_spawn_inside_trigger(ent); // the "start moving" trigger
+
+        if (ent.targetname != null) {
+            ent.moveinfo.state = STATE_UP;
+        } else {
+            Math3D.vectorCopy(ent.pos2, ent.s.origin);
+            GameBase.gi.linkentity(ent);
+            ent.moveinfo.state = STATE_BOTTOM;
+        }
+
+        ent.moveinfo.speed = ent.speed;
+        ent.moveinfo.accel = ent.accel;
+        ent.moveinfo.decel = ent.decel;
+        ent.moveinfo.wait = ent.wait;
+        Math3D.vectorCopy(ent.pos1, ent.moveinfo.start_origin);
+        Math3D.vectorCopy(ent.s.angles, ent.moveinfo.start_angles);
+        Math3D.vectorCopy(ent.pos2, ent.moveinfo.end_origin);
+        Math3D.vectorCopy(ent.s.angles, ent.moveinfo.end_angles);
+
+        ent.moveinfo.sound_start = GameBase.gi.soundindex("plats/pt1_strt.wav");
+        ent.moveinfo.sound_middle = GameBase.gi.soundindex("plats/pt1_mid.wav");
+        ent.moveinfo.sound_end = GameBase.gi.soundindex("plats/pt1_end.wav");
+    }
+
+    /**
+     * QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED
+     * TOGGLE ANIMATED_FAST TOGGLE wait in both the start and end states for a
+     * trigger event. START_OPEN the door to moves to its destination when
+     * spawned, and operate in reverse. It is used to temporarily or permanently
+     * close off an area when triggered (not useful for touch or takedamage
+     * doors). NOMONSTER monsters will not trigger this door
+     * <p>
+     * "message" is printed when the door is touched if it is a trigger door and
+     * it hasn't been fired yet "angle" determines the opening direction
+     * "targetname" if set, no touch field will be spawned and a remote button
+     * or trigger field activates the door. "health" if set, door must be shot
+     * open "speed" movement speed (100 default) "wait" wait before returning (3
+     * default, -1 = never return) "lip" lip remaining at end of move (8
+     * default) "dmg" damage to inflict when blocked (2 default) "sounds" 1)
+     * silent 2) light 3) medium 4) heavy
+     */
+
+    static void door_use_areaportals(EDict self) {
+        EDict t = null;
+
+        if (self.target == null)
+            return;
+
+        EdictIterator edit = null;
+
+        while ((edit = GameBase
+                .G_Find(edit, GameBase.findByTarget, self.target)) != null) {
+            t = edit.o;
+            if (Lib.Q_stricmp(t.classname, "func_areaportal") == 0) {
+                GameBase.gi.SetAreaPortalState(t.style, false);
+            }
+        }
+    }
+
+
+    private static void train_resume(EDict self) {
+        EDict ent;
+        float[] dest = {0, 0, 0};
+
+        ent = self.target_ent;
+
+        Math3D.vectorSubtract(ent.s.origin, self.mins, dest);
+        self.moveinfo.state = STATE_TOP;
+        Math3D.vectorCopy(self.s.origin, self.moveinfo.start_origin);
+        Math3D.vectorCopy(dest, self.moveinfo.end_origin);
+        Move_Calc(self, dest, train_wait);
+        self.spawnflags |= TRAIN_START_ON;
+
+    }
+
+    static void SP_func_train(EDict self) {
+        self.movetype = Defines.MOVETYPE_PUSH;
+
+        Math3D.vectorClear(self.s.angles);
+        self.blocked = train_blocked;
+        if ((self.spawnflags & TRAIN_BLOCK_STOPS) != 0)
+            self.dmg = 0;
+        else {
+            if (0 == self.dmg)
+                self.dmg = 100;
+        }
+        self.solid = Defines.SOLID_BSP;
+        GameBase.gi.setmodel(self, self.model);
+
+        if (GameBase.st.noise != null)
+            self.moveinfo.sound_middle = GameBase.gi
+                    .soundindex(GameBase.st.noise);
+
+        if (0 == self.speed)
+            self.speed = 100;
+
+        self.moveinfo.speed = self.speed;
+        self.moveinfo.accel = self.moveinfo.decel = self.moveinfo.speed;
+
+        self.use = train_use;
+
+        GameBase.gi.linkentity(self);
+
+        if (self.target != null) {
+            // start trains on the second frame, to make sure their targets have
+            // had
+            // a chance to spawn
+            self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+            self.think = func_train_find;
+        } else {
+            GameBase.gi.dprintf("func_train without a target at "
+                    + Lib.vtos(self.absmin) + "\n");
+        }
+    }
+
+    static void SP_func_timer(EDict self) {
+        if (0 == self.wait)
+            self.wait = 1.0f;
+
+        self.use = func_timer_use;
+        self.think = func_timer_think;
+
+        if (self.random >= self.wait) {
+            self.random = self.wait - Defines.FRAMETIME;
+            GameBase.gi.dprintf("func_timer at " + Lib.vtos(self.s.origin)
+                    + " has random >= wait\n");
+        }
+
+        if ((self.spawnflags & 1) != 0) {
+            self.nextthink = GameBase.level.time + 1.0f + GameBase.st.pausetime
+                    + self.delay + self.wait + Lib.crandom() * self.random;
+            self.activator = self;
+        }
+
+        self.svflags = Defines.SVF_NOCLIENT;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/GameMisc.java b/src/main/java/lwjake2/game/GameMisc.java
new file mode 100644 (file)
index 0000000..63317ea
--- /dev/null
@@ -0,0 +1,1051 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+
+import java.util.Calendar;
+
+public class GameMisc {
+    public static final int START_OFF = 1;
+    public static final EntUseAdapter Use_Areaportal = new EntUseAdapter() {
+        public String getID() {
+            return "use_areaportal";
+        }
+
+        public void use(EDict ent, EDict other, EDict activator) {
+            ent.count ^= 1; // toggle state
+            // gi.dprintf ("portalstate: %i = %i\n", ent.style, ent.count);
+            GameBase.gi.SetAreaPortalState(ent.style, ent.count != 0);
+        }
+    };
+    /**
+     * QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT Target: next
+     * path corner Pathtarget: gets used when an entity that has this
+     * path_corner targeted touches it
+     */
+    public static final EntTouchAdapter path_corner_touch = new EntTouchAdapter() {
+        public String getID() {
+            return "path_corner_touch";
+        }
+
+        public void touch(EDict self, EDict other, cplane_t plane,
+                          csurface_t surf) {
+            float[] v = {0, 0, 0};
+            EDict next;
+
+            if (other.enemy != null)
+                return;
+
+            if (self.pathtarget != null) {
+                String savetarget;
+
+                savetarget = self.target;
+                self.target = self.pathtarget;
+                GameUtil.G_UseTargets(self, other);
+                self.target = savetarget;
+            }
+
+            if (self.target != null)
+                next = GameBase.G_PickTarget(self.target);
+            else
+                next = null;
+
+            if ((next != null) && (next.spawnflags & 1) != 0) {
+                Math3D.vectorCopy(next.s.origin, v);
+                v[2] += next.mins[2];
+                v[2] -= other.mins[2];
+                Math3D.vectorCopy(v, other.s.origin);
+                next = GameBase.G_PickTarget(next.target);
+                other.s.event = Defines.EV_OTHER_TELEPORT;
+            }
+
+
+        }
+    };
+    /*
+     * QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold Makes this the
+     * target of a monster and it will head here when first activated before
+     * going after the activator. If hold is selected, it will stay here.
+     */
+    public static final EntTouchAdapter point_combat_touch = new EntTouchAdapter() {
+        public String getID() {
+            return "point_combat_touch";
+        }
+
+        public void touch(EDict self, EDict other, cplane_t plane,
+                          csurface_t surf) {
+            EDict activator;
+
+
+        }
+    };
+    /*
+     * QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8) Just for the debugging
+     * level. Don't use
+     */
+    public static final EntThinkAdapter TH_viewthing = new EntThinkAdapter() {
+        public String getID() {
+            return "th_viewthing";
+        }
+
+        public void think(EDict ent) {
+            ent.s.frame = (ent.s.frame + 1) % 7;
+            ent.nextthink = GameBase.level.time + Defines.FRAMETIME;
+        }
+    };
+    public static final EntUseAdapter light_use = new EntUseAdapter() {
+        public String getID() {
+            return "light_use";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            if ((self.spawnflags & START_OFF) != 0) {
+                GameBase.gi.configstring(Defines.CS_LIGHTS + self.style, "m");
+                self.spawnflags &= ~START_OFF;
+            } else {
+                GameBase.gi.configstring(Defines.CS_LIGHTS + self.style, "a");
+                self.spawnflags |= START_OFF;
+            }
+        }
+    };
+    public static final EntThinkAdapter commander_body_think = new EntThinkAdapter() {
+        public String getID() {
+            return "commander_body_think";
+        }
+
+        public void think(EDict self) {
+            if (++self.s.frame < 24)
+                self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+            else
+                self.nextthink = 0;
+
+            if (self.s.frame == 22)
+                GameBase.gi.sound(self, Defines.CHAN_BODY, GameBase.gi
+                        .soundindex("tank/thud.wav"), 1, Defines.ATTN_NORM, 0);
+        }
+    };
+    public static final EntThinkAdapter func_clock_think = new EntThinkAdapter() {
+        public String getID() {
+            return "func_clock_think";
+        }
+
+        public void think(EDict self) {
+            if (null == self.enemy) {
+
+                EdictIterator es = null;
+
+                es = GameBase.G_Find(es, GameBase.findByTarget, self.target);
+                if (es != null)
+                    self.enemy = es.o;
+                if (self.enemy == null)
+                    return;
+            }
+
+            if ((self.spawnflags & 1) != 0) {
+                func_clock_format_countdown(self);
+                self.health++;
+            } else if ((self.spawnflags & 2) != 0) {
+                func_clock_format_countdown(self);
+                self.health--;
+            } else {
+                Calendar c = Calendar.getInstance();
+                self.message = "" + c.get(Calendar.HOUR_OF_DAY) + ":"
+                        + c.get(Calendar.MINUTE) + ":" + c.get(Calendar.SECOND);
+
+                /*
+                 * struct tm * ltime; time_t gmtime;
+                 *
+                 * time(& gmtime); ltime = localtime(& gmtime);
+                 * Com_sprintf(self.message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i",
+                 * ltime.tm_hour, ltime.tm_min, ltime.tm_sec); if
+                 * (self.message[3] == ' ') self.message[3] = '0'; if
+                 * (self.message[6] == ' ') self.message[6] = '0';
+                 */
+            }
+
+            self.enemy.message = self.message;
+            self.enemy.use.use(self.enemy, self, self);
+
+            if (((self.spawnflags & 1) != 0 && (self.health > self.wait))
+                    || ((self.spawnflags & 2) != 0 && (self.health < self.wait))) {
+                if (self.pathtarget != null) {
+                    String savetarget;
+                    String savemessage;
+
+                    savetarget = self.target;
+                    savemessage = self.message;
+                    self.target = self.pathtarget;
+                    self.message = null;
+                    GameUtil.G_UseTargets(self, self.activator);
+                    self.target = savetarget;
+                    self.message = savemessage;
+                }
+
+                if (0 == (self.spawnflags & 8))
+                    return;
+
+                func_clock_reset(self);
+
+                if ((self.spawnflags & 4) != 0)
+                    return;
+            }
+
+            self.nextthink = GameBase.level.time + 1;
+
+        }
+    };
+    public static final EntUseAdapter func_clock_use = new EntUseAdapter() {
+        public String getID() {
+            return "func_clock_use";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            if (0 == (self.spawnflags & 8))
+                self.use = null;
+            if (self.activator != null)
+                return;
+            self.activator = activator;
+            self.think.think(self);
+        }
+    };
+    public static final EntThinkAdapter SP_misc_teleporter_dest = new EntThinkAdapter() {
+        public String getID() {
+            return "SP_misc_teleporter_dest";
+        }
+
+        public void think(EDict ent) {
+            GameBase.gi.setmodel(ent, "models/objects/dmspot/tris.md2");
+            ent.s.skinnum = 0;
+            ent.solid = Defines.SOLID_BBOX;
+            // ent.s.effects |= EF_FLIES;
+            Math3D.vectorSet(ent.mins, -32, -32, -24);
+            Math3D.vectorSet(ent.maxs, 32, 32, -16);
+            GameBase.gi.linkentity(ent);
+        }
+    };
+    public static final EntThinkAdapter gib_think = new EntThinkAdapter() {
+        public String getID() {
+            return "gib_think";
+        }
+
+        public void think(EDict self) {
+            self.s.frame++;
+            self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+
+            if (self.s.frame == 10) {
+                self.think = GameUtil.G_FreeEdictA;
+                self.nextthink = GameBase.level.time + 8
+                        + Globals.rnd.nextFloat() * 10;
+            }
+        }
+    };
+    /**
+     * QUAKED func_areaportal (0 0 0) ?
+     * <p>
+     * This is a non-visible object that divides the world into areas that are
+     * seperated when this portal is not activated. Usually enclosed in the
+     * middle of a door.
+     */
+
+    static final EntThinkAdapter SP_func_areaportal = new EntThinkAdapter() {
+        public String getID() {
+            return "sp_func_areaportal";
+        }
+
+        public void think(EDict ent) {
+            ent.use = Use_Areaportal;
+            ent.count = 0; // always start closed;
+        }
+    };
+    static final EntUseAdapter misc_blackhole_use = new EntUseAdapter() {
+        public String getID() {
+            return "misc_blavkhole_use";
+        }
+
+        public void use(EDict ent, EDict other, EDict activator) {
+            /*
+             * gi.WriteByte (svc_temp_entity); gi.WriteByte (TE_BOSSTPORT);
+             * gi.WritePosition (ent.s.origin); gi.multicast (ent.s.origin,
+             * MULTICAST_PVS);
+             */
+            GameUtil.G_FreeEdict(ent);
+        }
+    };
+    // don't let field width of any clock messages change, or it
+    // could cause an overwrite after a game load
+    static final EntThinkAdapter misc_blackhole_think = new EntThinkAdapter() {
+        public String getID() {
+            return "misc_blackhole_think";
+        }
+
+        public void think(EDict self) {
+
+            if (++self.s.frame < 19)
+                self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+            else {
+                self.s.frame = 0;
+                self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+            }
+        }
+    };
+    static final EntThinkAdapter misc_eastertank_think = new EntThinkAdapter() {
+        public String getID() {
+            return "misc_eastertank_think";
+        }
+
+        public void think(EDict self) {
+            if (++self.s.frame < 293)
+                self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+            else {
+                self.s.frame = 254;
+                self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+            }
+        }
+    };
+    static final EntThinkAdapter misc_easterchick_think = new EntThinkAdapter() {
+        public String getID() {
+            return "misc_easterchick_think";
+        }
+
+        public void think(EDict self) {
+            if (++self.s.frame < 247)
+                self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+            else {
+                self.s.frame = 208;
+                self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+            }
+        }
+    };
+    /*
+     * QUAKED misc_easterchick2 (1 .5 0) (-32 -32 0) (32 32 32)
+     */
+    static final EntThinkAdapter misc_easterchick2_think = new EntThinkAdapter() {
+        public String getID() {
+            return "misc_easterchick2_think";
+        }
+
+        public void think(EDict self) {
+            if (++self.s.frame < 287)
+                self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+            else {
+                self.s.frame = 248;
+                self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+            }
+        }
+    };
+
+
+    //=====================================================
+    /*
+     * QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4) The origin is the bottom
+     * of the banner. The banner is 128 tall.
+     */
+    static final EntThinkAdapter misc_banner_think = new EntThinkAdapter() {
+        public String getID() {
+            return "misc_banner_think";
+        }
+
+        public void think(EDict ent) {
+            ent.s.frame = (ent.s.frame + 1) % 16;
+            ent.nextthink = GameBase.level.time + Defines.FRAMETIME;
+        }
+    };
+    static final EntUseAdapter misc_viper_use = new EntUseAdapter() {
+        public String getID() {
+            return "misc_viper_use";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            self.svflags &= ~Defines.SVF_NOCLIENT;
+            self.use = GameFunc.train_use;
+            GameFunc.train_use.use(self, other, activator);
+        }
+    };
+    /*
+     * QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8) "dmg" how much boom
+     * should the bomb make?
+     */
+    static final EntTouchAdapter misc_viper_bomb_touch = new EntTouchAdapter() {
+        public String getID() {
+            return "misc_viper_bomb_touch";
+        }
+
+        public void touch(EDict self, EDict other, cplane_t plane,
+                          csurface_t surf) {
+            GameUtil.G_UseTargets(self, self.activator);
+
+            self.s.origin[2] = self.absmin[2] + 1;
+            BecomeExplosion2(self);
+        }
+    };
+    static final EntThinkAdapter misc_viper_bomb_prethink = new EntThinkAdapter() {
+        public String getID() {
+            return "misc_viper_bomb_prethink";
+        }
+
+        public void think(EDict self) {
+
+            float[] v = {0, 0, 0};
+            float diff;
+
+            self.groundentity = null;
+
+            diff = self.timestamp - GameBase.level.time;
+            if (diff < -1.0)
+                diff = -1.0f;
+
+            Math3D.vectorScale(self.moveinfo.dir, 1.0f + diff, v);
+            v[2] = diff;
+
+            diff = self.s.angles[2];
+            Math3D.vecToAngles(v, self.s.angles);
+            self.s.angles[2] = diff + 10;
+
+        }
+    };
+    static final EntUseAdapter misc_viper_bomb_use = new EntUseAdapter() {
+        public String getID() {
+            return "misc_viper_bomb_use";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            EDict viper = null;
+
+            self.solid = Defines.SOLID_BBOX;
+            self.svflags &= ~Defines.SVF_NOCLIENT;
+            self.s.effects |= Defines.EF_ROCKET;
+            self.use = null;
+            self.movetype = Defines.MOVETYPE_TOSS;
+            self.prethink = misc_viper_bomb_prethink;
+            self.touch = misc_viper_bomb_touch;
+            self.activator = activator;
+
+            EdictIterator es = null;
+
+            es = GameBase.G_Find(es, GameBase.findByClass, "misc_viper");
+            if (es != null)
+                viper = es.o;
+
+            Math3D.vectorScale(viper.moveinfo.dir, viper.moveinfo.speed,
+                    self.velocity);
+
+            self.timestamp = GameBase.level.time;
+            Math3D.vectorCopy(viper.moveinfo.dir, self.moveinfo.dir);
+        }
+    };
+    static final EntUseAdapter misc_strogg_ship_use = new EntUseAdapter() {
+        public String getID() {
+            return "misc_strogg_ship_use";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            self.svflags &= ~Defines.SVF_NOCLIENT;
+            self.use = GameFunc.train_use;
+            GameFunc.train_use.use(self, other, activator);
+        }
+    };
+    /*
+     * QUAKED misc_satellite_dish (1 .5 0) (-64 -64 0) (64 64 128)
+     */
+    static final EntThinkAdapter misc_satellite_dish_think = new EntThinkAdapter() {
+        public String getID() {
+            return "misc_satellite_dish_think";
+        }
+
+        public void think(EDict self) {
+            self.s.frame++;
+            if (self.s.frame < 38)
+                self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+        }
+    };
+    static final EntUseAdapter misc_satellite_dish_use = new EntUseAdapter() {
+        public String getID() {
+            return "misc_satellite_dish_use";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            self.s.frame = 0;
+            self.think = misc_satellite_dish_think;
+            self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+        }
+    };
+    static final EntUseAdapter target_string_use = new EntUseAdapter() {
+        public String getID() {
+            return "target_string_use";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            EDict e;
+            int n, l;
+            char c;
+
+            l = self.message.length();
+            for (e = self.teammaster; e != null; e = e.teamchain) {
+                if (e.count == 0)
+                    continue;
+                n = e.count - 1;
+                if (n >= l) {
+                    e.s.frame = 12;
+                    continue;
+                }
+
+                c = self.message.charAt(n);
+                if (c >= '0' && c <= '9')
+                    e.s.frame = c - '0';
+                else if (c == '-')
+                    e.s.frame = 10;
+                else if (c == ':')
+                    e.s.frame = 11;
+                else
+                    e.s.frame = 12;
+            }
+        }
+    };
+    public static EntUseAdapter func_explosive_use = new EntUseAdapter() {
+        public String getID() {
+            return "func_explosive_use";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+        }
+    };
+    public static EntTouchAdapter barrel_touch = new EntTouchAdapter() {
+        public String getID() {
+            return "barrel_touch";
+        }
+
+        public void touch(EDict self, EDict other, cplane_t plane,
+                          csurface_t surf) {
+            float ratio;
+            float[] v = {0, 0, 0};
+
+            if ((null == other.groundentity) || (other.groundentity == self))
+                return;
+
+            ratio = (float) other.mass / (float) self.mass;
+            Math3D.vectorSubtract(self.s.origin, other.s.origin, v);
+        }
+    };
+    public static EntUseAdapter commander_body_use = new EntUseAdapter() {
+        public String getID() {
+            return "commander_body_use";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            self.think = commander_body_think;
+            self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+            GameBase.gi.sound(self, Defines.CHAN_BODY, GameBase.gi
+                    .soundindex("tank/pain.wav"), 1, Defines.ATTN_NORM, 0);
+        }
+    };
+    public static EntThinkAdapter commander_body_drop = new EntThinkAdapter() {
+        public String getID() {
+            return "commander_body_group";
+        }
+
+        public void think(EDict self) {
+            self.movetype = Defines.MOVETYPE_TOSS;
+            self.s.origin[2] += 2;
+        }
+    };
+    public static EntTouchAdapter gib_touch = new EntTouchAdapter() {
+        public String getID() {
+            return "gib_touch";
+        }
+
+        public void touch(EDict self, EDict other, cplane_t plane,
+                          csurface_t surf) {
+            float[] normal_angles = {0, 0, 0}, right = {0, 0, 0};
+
+            if (null == self.groundentity)
+                return;
+
+            self.touch = null;
+
+            if (plane != null) {
+                GameBase.gi.sound(self, Defines.CHAN_VOICE, GameBase.gi
+                        .soundindex("misc/fhit3.wav"), 1, Defines.ATTN_NORM, 0);
+
+                Math3D.vecToAngles(plane.normal, normal_angles);
+                Math3D.angleVectors(normal_angles, null, right, null);
+                Math3D.vecToAngles(right, self.s.angles);
+
+                if (self.s.modelindex == GameBase.sm_meat_index) {
+                    self.s.frame++;
+                    self.think = gib_think;
+                    self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+                }
+            }
+        }
+    };
+
+    public static void SP_path_corner(EDict self) {
+        if (self.targetname == null) {
+            GameBase.gi.dprintf("path_corner with no targetname at "
+                    + Lib.vtos(self.s.origin) + "\n");
+            GameUtil.G_FreeEdict(self);
+            return;
+        }
+
+        self.solid = Defines.SOLID_TRIGGER;
+        self.touch = path_corner_touch;
+        Math3D.vectorSet(self.mins, -8, -8, -8);
+        Math3D.vectorSet(self.maxs, 8, 8, 8);
+        self.svflags |= Defines.SVF_NOCLIENT;
+        GameBase.gi.linkentity(self);
+    }
+
+    public static void SP_point_combat(EDict self) {
+        if (GameBase.deathmatch.value != 0) {
+            GameUtil.G_FreeEdict(self);
+            return;
+        }
+        self.solid = Defines.SOLID_TRIGGER;
+        self.touch = point_combat_touch;
+        Math3D.vectorSet(self.mins, -8, -8, -16);
+        Math3D.vectorSet(self.maxs, 8, 8, 16);
+        self.svflags = Defines.SVF_NOCLIENT;
+        GameBase.gi.linkentity(self);
+    }
+
+    /*
+     * QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF Non-displayed light.
+     * Default light value is 300. Default style is 0. If targeted, will toggle
+     * between on and off. Default _cone value is 10 (used to set size of light
+     * for spotlights)
+     */
+
+    public static void SP_viewthing(EDict ent) {
+        GameBase.gi.dprintf("viewthing spawned\n");
+
+        ent.movetype = Defines.MOVETYPE_NONE;
+        ent.solid = Defines.SOLID_BBOX;
+        ent.s.renderfx = Defines.RF_FRAMELERP;
+        Math3D.vectorSet(ent.mins, -16, -16, -24);
+        Math3D.vectorSet(ent.maxs, 16, 16, 32);
+        ent.s.modelindex = GameBase.gi
+                .modelindex("models/objects/banner/tris.md2");
+        GameBase.gi.linkentity(ent);
+        ent.nextthink = GameBase.level.time + 0.5f;
+        ent.think = TH_viewthing;
+    }
+
+    /*
+     * QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) Used as a positional target
+     * for spotlights, etc.
+     */
+    public static void SP_info_null(EDict self) {
+        GameUtil.G_FreeEdict(self);
+    }
+
+    /*
+     * QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED
+     * ANIMATED_FAST This is just a solid wall if not inhibited
+     * 
+     * TRIGGER_SPAWN the wall will not be present until triggered it will then
+     * blink in to existance; it will kill anything that was in it's way
+     * 
+     * TOGGLE only valid for TRIGGER_SPAWN walls this allows the wall to be
+     * turned on and off
+     * 
+     * START_ON only valid for TRIGGER_SPAWN walls the wall will initially be
+     * present
+     */
+
+    /*
+     * QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) Used as a positional
+     * target for lightning.
+     */
+    public static void SP_info_notnull(EDict self) {
+        Math3D.vectorCopy(self.s.origin, self.absmin);
+        Math3D.vectorCopy(self.s.origin, self.absmax);
+    }
+
+    public static void SP_light(EDict self) {
+        // no targeted lights in deathmatch, because they cause global messages
+        if (null == self.targetname || GameBase.deathmatch.value != 0) {
+            GameUtil.G_FreeEdict(self);
+            return;
+        }
+
+        if (self.style >= 32) {
+            self.use = light_use;
+            if ((self.spawnflags & START_OFF) != 0)
+                GameBase.gi.configstring(Defines.CS_LIGHTS + self.style, "a");
+            else
+                GameBase.gi.configstring(Defines.CS_LIGHTS + self.style, "m");
+        }
+    }
+
+
+    public static void SP_misc_blackhole(EDict ent) {
+        ent.movetype = Defines.MOVETYPE_NONE;
+        ent.solid = Defines.SOLID_NOT;
+        Math3D.vectorSet(ent.mins, -64, -64, 0);
+        Math3D.vectorSet(ent.maxs, 64, 64, 8);
+        ent.s.modelindex = GameBase.gi
+                .modelindex("models/objects/black/tris.md2");
+        ent.s.renderfx = Defines.RF_TRANSLUCENT;
+        ent.use = misc_blackhole_use;
+        ent.think = misc_blackhole_think;
+        ent.nextthink = GameBase.level.time + 2 * Defines.FRAMETIME;
+        GameBase.gi.linkentity(ent);
+    }
+
+    /*
+     * QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40) Large exploding
+     * box. You can override its mass (100), health (80), and dmg (150).
+     */
+
+    public static void SP_misc_eastertank(EDict ent) {
+        ent.movetype = Defines.MOVETYPE_NONE;
+        ent.solid = Defines.SOLID_BBOX;
+        Math3D.vectorSet(ent.mins, -32, -32, -16);
+        Math3D.vectorSet(ent.maxs, 32, 32, 32);
+        ent.s.modelindex = GameBase.gi
+                .modelindex("models/monsters/tank/tris.md2");
+        ent.s.frame = 254;
+        ent.think = misc_eastertank_think;
+        ent.nextthink = GameBase.level.time + 2 * Defines.FRAMETIME;
+        GameBase.gi.linkentity(ent);
+    }
+
+    public static void SP_misc_easterchick(EDict ent) {
+        ent.movetype = Defines.MOVETYPE_NONE;
+        ent.solid = Defines.SOLID_BBOX;
+        Math3D.vectorSet(ent.mins, -32, -32, 0);
+        Math3D.vectorSet(ent.maxs, 32, 32, 32);
+        ent.s.modelindex = GameBase.gi
+                .modelindex("models/monsters/bitch/tris.md2");
+        ent.s.frame = 208;
+        ent.think = misc_easterchick_think;
+        ent.nextthink = GameBase.level.time + 2 * Defines.FRAMETIME;
+        GameBase.gi.linkentity(ent);
+    }
+
+    public static void SP_misc_easterchick2(EDict ent) {
+        ent.movetype = Defines.MOVETYPE_NONE;
+        ent.solid = Defines.SOLID_BBOX;
+        Math3D.vectorSet(ent.mins, -32, -32, 0);
+        Math3D.vectorSet(ent.maxs, 32, 32, 32);
+        ent.s.modelindex = GameBase.gi
+                .modelindex("models/monsters/bitch/tris.md2");
+        ent.s.frame = 248;
+        ent.think = misc_easterchick2_think;
+        ent.nextthink = GameBase.level.time + 2 * Defines.FRAMETIME;
+        GameBase.gi.linkentity(ent);
+    }
+
+    //
+    // miscellaneous specialty items
+    //
+
+
+    public static void SP_misc_banner(EDict ent) {
+        ent.movetype = Defines.MOVETYPE_NONE;
+        ent.solid = Defines.SOLID_NOT;
+        ent.s.modelindex = GameBase.gi
+                .modelindex("models/objects/banner/tris.md2");
+        ent.s.frame = Lib.rand() % 16;
+        GameBase.gi.linkentity(ent);
+
+        ent.think = misc_banner_think;
+        ent.nextthink = GameBase.level.time + Defines.FRAMETIME;
+    }
+
+
+
+    /*
+     * QUAKED misc_easterchick (1 .5 0) (-32 -32 0) (32 32 32)
+     */
+
+    public static void SP_misc_viper(EDict ent) {
+        if (null == ent.target) {
+            GameBase.gi.dprintf("misc_viper without a target at "
+                    + Lib.vtos(ent.absmin) + "\n");
+            GameUtil.G_FreeEdict(ent);
+            return;
+        }
+
+        if (0 == ent.speed)
+            ent.speed = 300;
+
+        ent.movetype = Defines.MOVETYPE_PUSH;
+        ent.solid = Defines.SOLID_NOT;
+        ent.s.modelindex = GameBase.gi
+                .modelindex("models/ships/viper/tris.md2");
+        Math3D.vectorSet(ent.mins, -16, -16, 0);
+        Math3D.vectorSet(ent.maxs, 16, 16, 32);
+
+        ent.think = GameFunc.func_train_find;
+        ent.nextthink = GameBase.level.time + Defines.FRAMETIME;
+        ent.use = misc_viper_use;
+        ent.svflags |= Defines.SVF_NOCLIENT;
+        ent.moveinfo.accel = ent.moveinfo.decel = ent.moveinfo.speed = ent.speed;
+
+        GameBase.gi.linkentity(ent);
+    }
+
+    /*
+     * QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72) This is a
+     * large stationary viper as seen in Paul's intro
+     */
+    public static void SP_misc_bigviper(EDict ent) {
+        ent.movetype = Defines.MOVETYPE_NONE;
+        ent.solid = Defines.SOLID_BBOX;
+        Math3D.vectorSet(ent.mins, -176, -120, -24);
+        Math3D.vectorSet(ent.maxs, 176, 120, 72);
+        ent.s.modelindex = GameBase.gi
+                .modelindex("models/ships/bigviper/tris.md2");
+        GameBase.gi.linkentity(ent);
+    }
+
+    /*
+     * QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48) Not really
+     * a monster, this is the Tank Commander's decapitated body. There should be
+     * a item_commander_head that has this as it's target.
+     */
+
+    public static void SP_misc_viper_bomb(EDict self) {
+        self.movetype = Defines.MOVETYPE_NONE;
+        self.solid = Defines.SOLID_NOT;
+        Math3D.vectorSet(self.mins, -8, -8, -8);
+        Math3D.vectorSet(self.maxs, 8, 8, 8);
+
+        self.s.modelindex = GameBase.gi
+                .modelindex("models/objects/bomb/tris.md2");
+
+        if (self.dmg == 0)
+            self.dmg = 1000;
+
+        self.use = misc_viper_bomb_use;
+        self.svflags |= Defines.SVF_NOCLIENT;
+
+        GameBase.gi.linkentity(self);
+    }
+
+    public static void SP_misc_strogg_ship(EDict ent) {
+        if (null == ent.target) {
+            GameBase.gi.dprintf(ent.classname + " without a target at "
+                    + Lib.vtos(ent.absmin) + "\n");
+            GameUtil.G_FreeEdict(ent);
+            return;
+        }
+
+        if (0 == ent.speed)
+            ent.speed = 300;
+
+        ent.movetype = Defines.MOVETYPE_PUSH;
+        ent.solid = Defines.SOLID_NOT;
+        ent.s.modelindex = GameBase.gi
+                .modelindex("models/ships/strogg1/tris.md2");
+        Math3D.vectorSet(ent.mins, -16, -16, 0);
+        Math3D.vectorSet(ent.maxs, 16, 16, 32);
+
+        ent.think = GameFunc.func_train_find;
+        ent.nextthink = GameBase.level.time + Defines.FRAMETIME;
+        ent.use = misc_strogg_ship_use;
+        ent.svflags |= Defines.SVF_NOCLIENT;
+        ent.moveinfo.accel = ent.moveinfo.decel = ent.moveinfo.speed = ent.speed;
+
+        GameBase.gi.linkentity(ent);
+    }
+
+    public static void SP_misc_satellite_dish(EDict ent) {
+        ent.movetype = Defines.MOVETYPE_NONE;
+        ent.solid = Defines.SOLID_BBOX;
+        Math3D.vectorSet(ent.mins, -64, -64, 0);
+        Math3D.vectorSet(ent.maxs, 64, 64, 128);
+        ent.s.modelindex = GameBase.gi
+                .modelindex("models/objects/satellite/tris.md2");
+        ent.use = misc_satellite_dish_use;
+        GameBase.gi.linkentity(ent);
+    }
+
+    /*
+     * QUAKED light_mine1 (0 1 0) (-2 -2 -12) (2 2 12)
+     */
+    public static void SP_light_mine1(EDict ent) {
+        ent.movetype = Defines.MOVETYPE_NONE;
+        ent.solid = Defines.SOLID_BBOX;
+        ent.s.modelindex = GameBase.gi
+                .modelindex("models/objects/minelite/light1/tris.md2");
+        GameBase.gi.linkentity(ent);
+    }
+
+    /*
+     * QUAKED light_mine2 (0 1 0) (-2 -2 -12) (2 2 12)
+     */
+    public static void SP_light_mine2(EDict ent) {
+        ent.movetype = Defines.MOVETYPE_NONE;
+        ent.solid = Defines.SOLID_BBOX;
+        ent.s.modelindex = GameBase.gi
+                .modelindex("models/objects/minelite/light2/tris.md2");
+        GameBase.gi.linkentity(ent);
+    }
+
+    /*
+     * QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32) This is the Viper for
+     * the flyby bombing. It is trigger_spawned, so you must have something use
+     * it for it to show up. There must be a path for it to follow once it is
+     * activated.
+     * 
+     * "speed" How fast the Viper should fly
+     */
+
+
+    public static void SP_target_character(EDict self) {
+        self.movetype = Defines.MOVETYPE_PUSH;
+        GameBase.gi.setmodel(self, self.model);
+        self.solid = Defines.SOLID_BSP;
+        self.s.frame = 12;
+        GameBase.gi.linkentity(self);
+    }
+
+    /*
+     * QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32) This is a Storgg
+     * ship for the flybys. It is trigger_spawned, so you must have something
+     * use it for it to show up. There must be a path for it to follow once it
+     * is activated.
+     * 
+     * "speed" How fast it should fly
+     */
+
+    public static void SP_target_string(EDict self) {
+        if (self.message == null)
+            self.message = "";
+        self.use = target_string_use;
+    }
+
+    public static void func_clock_reset(EDict self) {
+        self.activator = null;
+        if ((self.spawnflags & 1) != 0) {
+            self.health = 0;
+            self.wait = self.count;
+        } else if ((self.spawnflags & 2) != 0) {
+            self.health = self.count;
+            self.wait = 0;
+        }
+    }
+
+    public static void func_clock_format_countdown(EDict self) {
+        if (self.style == 0) {
+            self.message = "" + self.health;
+            //Com_sprintf(self.message, CLOCK_MESSAGE_SIZE, "%2i",
+            // self.health);
+            return;
+        }
+
+        if (self.style == 1) {
+            self.message = "" + self.health / 60 + ":" + self.health % 60;
+            //Com_sprintf(self.message, CLOCK_MESSAGE_SIZE, "%2i:%2i",
+            // self.health / 60, self.health % 60);
+            /*
+             * if (self.message.charAt(3) == ' ') self.message.charAt(3) = '0';
+             */
+            return;
+        }
+
+        if (self.style == 2) {
+            self.message = "" + self.health / 3600 + ":"
+                    + (self.health - (self.health / 3600) * 3600) / 60 + ":"
+                    + self.health % 60;
+            /*
+             * Com_sprintf( self.message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i",
+             * self.health / 3600, (self.health - (self.health / 3600) * 3600) /
+             * 60, self.health % 60); if (self.message[3] == ' ')
+             * self.message[3] = '0'; if (self.message[6] == ' ')
+             * self.message[6] = '0';
+             */
+        }
+    }
+
+    /*
+     * QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8)
+     */
+
+    public static void SP_func_clock(EDict self) {
+        if (self.target == null) {
+            GameBase.gi.dprintf(self.classname + " with no target at "
+                    + Lib.vtos(self.s.origin) + "\n");
+            GameUtil.G_FreeEdict(self);
+            return;
+        }
+
+        if ((self.spawnflags & 2) != 0 && (0 == self.count)) {
+            GameBase.gi.dprintf(self.classname + " with no count at "
+                    + Lib.vtos(self.s.origin) + "\n");
+            GameUtil.G_FreeEdict(self);
+            return;
+        }
+
+        if ((self.spawnflags & 1) != 0 && (0 == self.count))
+            self.count = 60 * 60;
+
+        func_clock_reset(self);
+
+        self.message = "";
+
+        self.think = func_clock_think;
+
+        if ((self.spawnflags & 4) != 0)
+            self.use = func_clock_use;
+        else
+            self.nextthink = GameBase.level.time + 1;
+    }
+
+    /*
+     * QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN
+     * START_OFF MULTI_USE target a target_string with this
+     * 
+     * The default is to be a time of day clock
+     * 
+     * TIMER_UP and TIMER_DOWN run for "count" seconds and the fire "pathtarget"
+     * If START_OFF, this entity must be used before it starts
+     * 
+     * "style" 0 "xx" 1 "xx:xx" 2 "xx:xx:xx"
+     */
+
+
+    public static void BecomeExplosion1(EDict self) {
+        GameBase.gi.WriteByte(Defines.svc_temp_entity);
+        GameBase.gi.WriteByte(Defines.TE_EXPLOSION1);
+        GameBase.gi.WritePosition(self.s.origin);
+        GameBase.gi.multicast(self.s.origin, Defines.MULTICAST_PVS);
+
+        GameUtil.G_FreeEdict(self);
+    }
+
+    //=================================================================================
+
+    public static void BecomeExplosion2(EDict self) {
+        GameBase.gi.WriteByte(Defines.svc_temp_entity);
+        GameBase.gi.WriteByte(Defines.TE_EXPLOSION2);
+        GameBase.gi.WritePosition(self.s.origin);
+        GameBase.gi.multicast(self.s.origin, Defines.MULTICAST_PVS);
+
+        GameUtil.G_FreeEdict(self);
+    }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/GameSVCmds.java b/src/main/java/lwjake2/game/GameSVCmds.java
new file mode 100644 (file)
index 0000000..a0cee9c
--- /dev/null
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.Defines;
+import lwjake2.qcommon.Com;
+import lwjake2.util.Lib;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.util.StringTokenizer;
+
+public class GameSVCmds {
+
+    private static final int MAX_IPFILTERS = 1024;
+    private static final GameSVCmds.ipfilter_t[] ipfilters = new GameSVCmds.ipfilter_t[MAX_IPFILTERS];
+    private static int numipfilters;
+
+    static {
+        for (int n = 0; n < GameSVCmds.MAX_IPFILTERS; n++)
+            GameSVCmds.ipfilters[n] = new ipfilter_t();
+    }
+
+    private static void Svcmd_Test_f() {
+        GameBase.gi.cprintf(null, Defines.PRINT_HIGH, "Svcmd_Test_f()\n");
+    }
+
+    /**
+     * StringToFilter.
+     */
+    private static boolean StringToFilter(String s, GameSVCmds.ipfilter_t f) {
+
+        byte b[] = {0, 0, 0, 0};
+        byte m[] = {0, 0, 0, 0};
+
+        try {
+            StringTokenizer tk = new StringTokenizer(s, ". ");
+
+            for (int n = 0; n < 4; n++) {
+                b[n] = (byte) Lib.atoi(tk.nextToken());
+                if (b[n] != 0)
+                    m[n] = -1;
+            }
+
+            f.mask = ByteBuffer.wrap(m).getInt();
+            f.compare = ByteBuffer.wrap(b).getInt();
+        } catch (Exception e) {
+            GameBase.gi.cprintf(null, Defines.PRINT_HIGH,
+                    "Bad filter address: " + s + "\n");
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * SV_FilterPacket.
+     */
+    static boolean SV_FilterPacket(String from) {
+        int i;
+        int in;
+        int m[] = {0, 0, 0, 0};
+
+        int p = 0;
+        char c;
+
+        i = 0;
+
+        while (p < from.length() && i < 4) {
+            m[i] = 0;
+
+            c = from.charAt(p);
+            while (c >= '0' && c <= '9') {
+                m[i] = m[i] * 10 + (c - '0');
+                c = from.charAt(p++);
+            }
+            if (p == from.length() || c == ':')
+                break;
+
+            i++;
+            p++;
+        }
+
+        in = (m[0] & 0xff) | ((m[1] & 0xff) << 8) | ((m[2] & 0xff) << 16)
+                | ((m[3] & 0xff) << 24);
+
+        for (i = 0; i < numipfilters; i++)
+            if ((in & ipfilters[i].mask) == ipfilters[i].compare)
+                return ((int) GameBase.filterban.value) != 0;
+
+        return (1 - GameBase.filterban.value) != 0;
+    }
+
+    /**
+     * SV_AddIP_f.
+     */
+    private static void SVCmd_AddIP_f() {
+        int i;
+
+        if (GameBase.gi.argc() < 3) {
+            GameBase.gi.cprintf(null, Defines.PRINT_HIGH,
+                    "Usage:  addip <ip-mask>\n");
+            return;
+        }
+
+        for (i = 0; i < numipfilters; i++)
+            if (ipfilters[i].compare == 0xffffffff)
+                break; // free spot
+        if (i == numipfilters) {
+            if (numipfilters == MAX_IPFILTERS) {
+                GameBase.gi.cprintf(null, Defines.PRINT_HIGH,
+                        "IP filter list is full\n");
+                return;
+            }
+            numipfilters++;
+        }
+
+        if (!StringToFilter(GameBase.gi.argv(2), ipfilters[i]))
+            ipfilters[i].compare = 0xffffffff;
+    }
+
+    /**
+     * SV_RemoveIP_f.
+     */
+    private static void SVCmd_RemoveIP_f() {
+        GameSVCmds.ipfilter_t f = new GameSVCmds.ipfilter_t();
+        int i, j;
+
+        if (GameBase.gi.argc() < 3) {
+            GameBase.gi.cprintf(null, Defines.PRINT_HIGH,
+                    "Usage:  sv removeip <ip-mask>\n");
+            return;
+        }
+
+        if (!StringToFilter(GameBase.gi.argv(2), f))
+            return;
+
+        for (i = 0; i < numipfilters; i++)
+            if (ipfilters[i].mask == f.mask
+                    && ipfilters[i].compare == f.compare) {
+                for (j = i + 1; j < numipfilters; j++)
+                    ipfilters[j - 1] = ipfilters[j];
+                numipfilters--;
+                GameBase.gi.cprintf(null, Defines.PRINT_HIGH, "Removed.\n");
+                return;
+            }
+        GameBase.gi.cprintf(null, Defines.PRINT_HIGH, "Didn't find "
+                + GameBase.gi.argv(2) + ".\n");
+    }
+
+    /**
+     * SV_ListIP_f.
+     */
+    private static void SVCmd_ListIP_f() {
+        int i;
+        byte b[];
+
+        GameBase.gi.cprintf(null, Defines.PRINT_HIGH, "Filter list:\n");
+        for (i = 0; i < numipfilters; i++) {
+            b = Lib.getIntBytes(ipfilters[i].compare);
+            GameBase.gi
+                    .cprintf(null, Defines.PRINT_HIGH, (b[0] & 0xff) + "."
+                            + (b[1] & 0xff) + "." + (b[2] & 0xff) + "."
+                            + (b[3] & 0xff));
+        }
+    }
+
+    /**
+     * SV_WriteIP_f.
+     */
+    private static void SVCmd_WriteIP_f() {
+        RandomAccessFile f;
+        //char name[MAX_OSPATH];
+        String name;
+        byte b[];
+
+        int i;
+        CvarT game;
+
+        game = GameBase.gi.cvar("game", "", 0);
+
+        if (game.string == null)
+            name = Defines.GAMEVERSION + "/listip.cfg";
+        else
+            name = game.string + "/listip.cfg";
+
+        GameBase.gi.cprintf(null, Defines.PRINT_HIGH, "Writing " + name + ".\n");
+
+        f = Lib.fopen(name, "rw");
+        if (f == null) {
+            GameBase.gi.cprintf(null, Defines.PRINT_HIGH, "Couldn't open "
+                    + name + "\n");
+            return;
+        }
+
+        try {
+            f.writeChars("set filterban " + (int) GameBase.filterban.value
+                    + "\n");
+
+            for (i = 0; i < numipfilters; i++) {
+                b = Lib.getIntBytes(ipfilters[i].compare);
+                f.writeChars("sv addip " + (b[0] & 0xff) + "." + (b[1] & 0xff)
+                        + "." + (b[2] & 0xff) + "." + (b[3] & 0xff) + "\n");
+            }
+
+        } catch (IOException e) {
+            Com.Printf("IOError in SVCmd_WriteIP_f:" + e);
+        }
+
+        Lib.fclose(f);
+    }
+
+    /**
+     * ServerCommand
+     * <p>
+     * ServerCommand will be called when an "sv" command is issued. The game can
+     * issue gi.argc() / gi.argv() commands to get the rest of the parameters
+     */
+    public static void ServerCommand() {
+        String cmd;
+
+        cmd = GameBase.gi.argv(1);
+        if (Lib.Q_stricmp(cmd, "test") == 0)
+            Svcmd_Test_f();
+        else if (Lib.Q_stricmp(cmd, "addip") == 0)
+            SVCmd_AddIP_f();
+        else if (Lib.Q_stricmp(cmd, "removeip") == 0)
+            SVCmd_RemoveIP_f();
+        else if (Lib.Q_stricmp(cmd, "listip") == 0)
+            SVCmd_ListIP_f();
+        else if (Lib.Q_stricmp(cmd, "writeip") == 0)
+            SVCmd_WriteIP_f();
+        else
+            GameBase.gi.cprintf(null, Defines.PRINT_HIGH,
+                    "Unknown server command \"" + cmd + "\"\n");
+    }
+
+    /**
+     * PACKET FILTERING
+     * <p>
+     * <p>
+     * You can add or remove addresses from the filter list with:
+     * <p>
+     * addip <ip> removeip <ip>
+     * <p>
+     * The ip address is specified in dot format, and any unspecified digits
+     * will match any value, so you can specify an entire class C network with
+     * "addip 192.246.40".
+     * <p>
+     * Removeip will only remove an address specified exactly the same way. You
+     * cannot addip a subnet, then removeip a single host.
+     * <p>
+     * listip Prints the current list of filters.
+     * <p>
+     * writeip Dumps "addip <ip>" commands to listip.cfg so it can be execed at
+     * a later date. The filter lists are not saved and restored by default,
+     * because I beleive it would cause too much confusion.
+     * <p>
+     * filterban <0 or 1>
+     * <p>
+     * If 1 (the default), then ip addresses matching the current list will be
+     * prohibited from entering the game. This is the default setting.
+     * <p>
+     * If 0, then only addresses matching the list will be allowed. This lets
+     * you easily set up a private game, or a game that only allows players from
+     * your local network.
+     */
+
+    static class ipfilter_t {
+        int mask;
+
+        int compare;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/GameSave.java b/src/main/java/lwjake2/game/GameSave.java
new file mode 100644 (file)
index 0000000..d3b2bd9
--- /dev/null
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.util.Lib;
+import lwjake2.util.QuakeFile;
+
+public class GameSave {
+
+    private static void CreateEdicts() {
+        GameBase.g_edicts = new EDict[GameBase.game.maxentities];
+        for (int i = 0; i < GameBase.game.maxentities; i++)
+            GameBase.g_edicts[i] = new EDict(i);
+    }
+
+    private static void CreateClients() {
+        GameBase.game.clients = new Client[GameBase.game.maxclients];
+        for (int i = 0; i < GameBase.game.maxclients; i++)
+            GameBase.game.clients[i] = new Client(i);
+
+    }
+
+    /**
+     * InitGame
+     * <p>
+     * This will be called when the dll is first loaded, which only happens when
+     * a new game is started or a save game is loaded.
+     */
+    public static void InitGame() {
+        GameBase.gi.dprintf("==== InitGame ====\n");
+
+
+        GameBase.gun_x = GameBase.gi.cvar("gun_x", "0", 0);
+        GameBase.gun_y = GameBase.gi.cvar("gun_y", "0", 0);
+        GameBase.gun_z = GameBase.gi.cvar("gun_z", "0", 0);
+
+        //FIXME: sv_ prefix are wrong names for these variables 
+        GameBase.sv_rollspeed = GameBase.gi.cvar("sv_rollspeed", "200", 0);
+        GameBase.sv_rollangle = GameBase.gi.cvar("sv_rollangle", "2", 0);
+        GameBase.sv_maxvelocity = GameBase.gi.cvar("sv_maxvelocity", "2000", 0);
+        GameBase.sv_gravity = GameBase.gi.cvar("sv_gravity", "800", 0);
+
+        // noset vars
+        Globals.dedicated = GameBase.gi.cvar("dedicated", "0",
+                Defines.CVAR_NOSET);
+
+        // latched vars
+        GameBase.sv_cheats = GameBase.gi.cvar("cheats", "0",
+                Defines.CVAR_SERVERINFO | Defines.CVAR_LATCH);
+        GameBase.gi.cvar("gamename", Defines.GAMEVERSION,
+                Defines.CVAR_SERVERINFO | Defines.CVAR_LATCH);
+        GameBase.gi.cvar("gamedate", Globals.__DATE__, Defines.CVAR_SERVERINFO
+                | Defines.CVAR_LATCH);
+
+        GameBase.maxclients = GameBase.gi.cvar("maxclients", "4",
+                Defines.CVAR_SERVERINFO | Defines.CVAR_LATCH);
+        GameBase.maxspectators = GameBase.gi.cvar("maxspectators", "4",
+                Defines.CVAR_SERVERINFO);
+        GameBase.deathmatch = GameBase.gi.cvar("deathmatch", "0",
+                Defines.CVAR_LATCH);
+        GameBase.coop = GameBase.gi.cvar("coop", "0", Defines.CVAR_LATCH);
+        GameBase.maxentities = GameBase.gi.cvar("maxentities", "1024",
+                Defines.CVAR_LATCH);
+
+        // change anytime vars
+        GameBase.dmflags = GameBase.gi.cvar("dmflags", "0",
+                Defines.CVAR_SERVERINFO);
+        GameBase.fraglimit = GameBase.gi.cvar("fraglimit", "0",
+                Defines.CVAR_SERVERINFO);
+        GameBase.timelimit = GameBase.gi.cvar("timelimit", "0",
+                Defines.CVAR_SERVERINFO);
+        GameBase.password = GameBase.gi.cvar("password", "",
+                Defines.CVAR_USERINFO);
+        GameBase.spectator_password = GameBase.gi.cvar("spectator_password",
+                "", Defines.CVAR_USERINFO);
+        GameBase.needpass = GameBase.gi.cvar("needpass", "0",
+                Defines.CVAR_SERVERINFO);
+        GameBase.filterban = GameBase.gi.cvar("filterban", "1", 0);
+
+        GameBase.g_select_empty = GameBase.gi.cvar("g_select_empty", "0",
+                Defines.CVAR_ARCHIVE);
+
+        GameBase.run_pitch = GameBase.gi.cvar("run_pitch", "0.002", 0);
+        GameBase.run_roll = GameBase.gi.cvar("run_roll", "0.005", 0);
+        GameBase.bob_up = GameBase.gi.cvar("bob_up", "0.005", 0);
+        GameBase.bob_pitch = GameBase.gi.cvar("bob_pitch", "0.002", 0);
+        GameBase.bob_roll = GameBase.gi.cvar("bob_roll", "0.002", 0);
+
+        // flood control
+        GameBase.flood_msgs = GameBase.gi.cvar("flood_msgs", "4", 0);
+        GameBase.flood_persecond = GameBase.gi.cvar("flood_persecond", "4", 0);
+        GameBase.flood_waitdelay = GameBase.gi.cvar("flood_waitdelay", "10", 0);
+
+        // dm map list
+        GameBase.sv_maplist = GameBase.gi.cvar("sv_maplist", "", 0);
+
+        GameBase.game.helpmessage1 = "";
+        GameBase.game.helpmessage2 = "";
+
+        // initialize all entities for this game
+        GameBase.game.maxentities = (int) GameBase.maxentities.value;
+        CreateEdicts();
+
+        // initialize all clients for this game
+        GameBase.game.maxclients = (int) GameBase.maxclients.value;
+
+        CreateClients();
+
+        GameBase.num_edicts = GameBase.game.maxclients + 1;
+    }
+
+    /**
+     * WriteGame
+     * <p>
+     * This will be called whenever the game goes to a new level, and when the
+     * user explicitly saves the game.
+     * <p>
+     * Game information include cross level data, like multi level triggers,
+     * help computer info, and all client states.
+     * <p>
+     * A single player death will automatically restore from the last save
+     * position.
+     */
+    public static void WriteGame(String filename, boolean autosave) {
+        try {
+            QuakeFile f;
+
+            if (!autosave)
+                PlayerClient.SaveClientData();
+
+            f = new QuakeFile(filename, "rw");
+
+            GameBase.game.autosaved = autosave;
+            GameBase.game.write(f);
+            GameBase.game.autosaved = false;
+
+            for (int i = 0; i < GameBase.game.maxclients; i++)
+                GameBase.game.clients[i].write(f);
+
+            Lib.fclose(f);
+        } catch (Exception e) {
+            e.printStackTrace();
+            GameBase.gi.error("Couldn't write to " + filename);
+        }
+    }
+
+    public static void ReadGame(String filename) {
+
+        QuakeFile f = null;
+
+        try {
+
+            f = new QuakeFile(filename, "r");
+            CreateEdicts();
+
+            GameBase.game.load(f);
+
+            for (int i = 0; i < GameBase.game.maxclients; i++) {
+                GameBase.game.clients[i] = new Client(i);
+                GameBase.game.clients[i].read(f);
+            }
+
+            f.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * WriteLevel
+     */
+    public static void WriteLevel(String filename) {
+        try {
+            int i;
+            EDict ent;
+            QuakeFile f;
+
+            f = new QuakeFile(filename, "rw");
+
+            // write out level_locals_t
+            GameBase.level.write(f);
+
+            // write out all the entities
+            for (i = 0; i < GameBase.num_edicts; i++) {
+                ent = GameBase.g_edicts[i];
+                if (!ent.inuse)
+                    continue;
+                f.writeInt(i);
+                ent.write(f);
+            }
+
+            i = -1;
+            f.writeInt(-1);
+
+            f.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+            GameBase.gi.error("Couldn't open for writing: " + filename);
+        }
+    }
+
+    /**
+     * ReadLevel
+     * <p>
+     * SpawnEntities will allready have been called on the level the same way it
+     * was when the level was saved.
+     * <p>
+     * That is necessary to get the baselines set up identically.
+     * <p>
+     * The server will have cleared all of the world links before calling
+     * ReadLevel.
+     * <p>
+     * No clients are connected yet.
+     */
+    public static void ReadLevel(String filename) {
+        try {
+            EDict ent;
+
+            QuakeFile f = new QuakeFile(filename, "r");
+
+            // wipe all the entities
+            CreateEdicts();
+
+            GameBase.num_edicts = (int) GameBase.maxclients.value + 1;
+
+            // load the level locals
+            GameBase.level.read(f);
+
+            // load all the entities
+            while (true) {
+                int entnum = f.readInt();
+                if (entnum == -1)
+                    break;
+
+                if (entnum >= GameBase.num_edicts)
+                    GameBase.num_edicts = entnum + 1;
+
+                ent = GameBase.g_edicts[entnum];
+                ent.read(f);
+                ent.cleararealinks();
+                GameBase.gi.linkentity(ent);
+            }
+
+            Lib.fclose(f);
+
+            // mark all clients as unconnected
+            for (int i = 0; i < GameBase.maxclients.value; i++) {
+                ent = GameBase.g_edicts[i + 1];
+                ent.client = GameBase.game.clients[i];
+                ent.client.pers.connected = false;
+            }
+
+            // do any load time things at this point
+            for (int i = 0; i < GameBase.num_edicts; i++) {
+                ent = GameBase.g_edicts[i];
+
+                if (!ent.inuse)
+                    continue;
+
+                // fire any cross-level triggers
+                if (ent.classname != null)
+                    if (Lib.strcmp(ent.classname, "target_crosslevel_target") == 0)
+                        ent.nextthink = GameBase.level.time + ent.delay;
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            GameBase.gi.error("Couldn't read level file " + filename);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/GameSpawn.java b/src/main/java/lwjake2/game/GameSpawn.java
new file mode 100644 (file)
index 0000000..9eb69d3
--- /dev/null
@@ -0,0 +1,922 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.Defines;
+import lwjake2.qcommon.Com;
+import lwjake2.util.Lib;
+
+public class GameSpawn {
+
+
+    static final EntThinkAdapter SP_info_player_start = new EntThinkAdapter() {
+        public String getID() {
+            return "SP_info_player_start";
+        }
+
+        public void think(EDict ent) {
+            PlayerClient.SP_info_player_start(ent);
+        }
+    };
+
+    static final EntThinkAdapter SP_info_player_deathmatch = new EntThinkAdapter() {
+        public String getID() {
+            return "SP_info_player_deathmatch";
+        }
+
+        public void think(EDict ent) {
+            PlayerClient.SP_info_player_deathmatch(ent);
+        }
+    };
+
+    static final EntThinkAdapter SP_info_player_coop = new EntThinkAdapter() {
+        public String getID() {
+            return "SP_info_player_coop";
+        }
+
+        public void think(EDict ent) {
+            PlayerClient.SP_info_player_coop(ent);
+        }
+    };
+
+    static final EntThinkAdapter SP_info_player_intermission = new EntThinkAdapter() {
+        public String getID() {
+            return "SP_info_player_intermission";
+        }
+
+        public void think(EDict ent) {
+            PlayerClient.SP_info_player_intermission();
+        }
+    };
+
+    static final EntThinkAdapter SP_func_plat = new EntThinkAdapter() {
+        public String getID() {
+            return "SP_func_plat";
+        }
+
+        public void think(EDict ent) {
+            GameFunc.SP_func_plat(ent);
+        }
+    };
+
+
+    static final EntThinkAdapter SP_func_train = new EntThinkAdapter() {
+        public String getID() {
+            return "SP_func_train";
+        }
+
+        public void think(EDict ent) {
+            GameFunc.SP_func_train(ent);
+        }
+    };
+
+    static final EntThinkAdapter SP_func_clock = new EntThinkAdapter() {
+        public String getID() {
+            return "SP_func_clock";
+        }
+
+        public void think(EDict ent) {
+            GameMisc.SP_func_clock(ent);
+        }
+    };
+    static final String single_statusbar = "yb -24 " //           health
+            + "xv      0 " + "hnum " + "xv     50 " + "pic 0 " //         ammo
+            + "if 2 " + "      xv      100 " + "       anum " + "      xv      150 " + "       pic 2 "
+            + "endif " //         armor
+            + "if 4 " + "      xv      200 " + "       rnum " + "      xv      250 " + "       pic 4 "
+            + "endif " //         selected item
+            + "if 6 " + "      xv      296 " + "       pic 6 " + "endif " + "yb        -50 " //           picked
+            // up
+            // item
+            + "if 7 " + "      xv      0 " + " pic 7 " + "     xv      26 " + "        yb      -42 "
+            + "        stat_string 8 " + "     yb      -50 " + "endif "
+            //    timer
+            + "if 9 " + "      xv      262 " + "       num     2       10 " + "        xv      296 " + "       pic     9 "
+            + "endif "
+            //         help / weapon icon
+            + "if 11 " + "     xv      148 " + "       pic     11 " + "endif ";
+    static final String dm_statusbar = "yb     -24 " //           health
+            + "xv      0 " + "hnum " + "xv     50 " + "pic 0 " //         ammo
+            + "if 2 " + "      xv      100 " + "       anum " + "      xv      150 " + "       pic 2 "
+            + "endif " //         armor
+            + "if 4 " + "      xv      200 " + "       rnum " + "      xv      250 " + "       pic 4 "
+            + "endif " //         selected item
+            + "if 6 " + "      xv      296 " + "       pic 6 " + "endif " + "yb        -50 " //           picked
+            // up
+            // item
+            + "if 7 " + "      xv      0 " + " pic 7 " + "     xv      26 " + "        yb      -42 "
+            + "        stat_string 8 " + "     yb      -50 " + "endif "
+            //    timer
+            + "if 9 " + "      xv      246 " + "       num     2       10 " + "        xv      296 " + "       pic     9 "
+            + "endif "
+            //         help / weapon icon
+            + "if 11 " + "     xv      148 " + "       pic     11 " + "endif " //              frags
+            + "xr      -50 " + "yt 2 " + "num 3 14 " //           spectator
+            + "if 17 " + "xv 0 " + "yb -58 " + "string2 \"SPECTATOR MODE\" "
+            + "endif " //         chase camera
+            + "if 16 " + "xv 0 " + "yb -68 " + "string \"Chasing\" " + "xv 64 "
+            + "stat_string 16 " + "endif ";
+    /**
+     * QUAKED worldspawn (0 0 0) ?
+     * <p>
+     * Only used for the world. "sky" environment map name "skyaxis" vector axis
+     * for rotating sky "skyrotate" speed of rotation in degrees/second "sounds"
+     * music cd track number "gravity" 800 is default gravity "message" text to
+     * print at user logon
+     */
+
+    static final EntThinkAdapter SP_worldspawn = new EntThinkAdapter() {
+        public String getID() {
+            return "SP_worldspawn";
+        }
+
+        public void think(EDict ent) {
+            ent.movetype = Defines.MOVETYPE_PUSH;
+            ent.solid = Defines.SOLID_BSP;
+            ent.inuse = true;
+            // since the world doesn't use G_Spawn()
+            ent.s.modelindex = 1;
+            // world model is always index 1
+            //---------------
+            // reserve some spots for dead player bodies for coop / deathmatch
+            PlayerClient.InitBodyQue();
+            // set configstrings for items
+            if (GameBase.st.nextmap != null)
+                GameBase.level.nextmap = GameBase.st.nextmap;
+            // make some data visible to the server
+            if (ent.message != null && ent.message.length() > 0) {
+                GameBase.gi.configstring(Defines.CS_NAME, ent.message);
+                GameBase.level.level_name = ent.message;
+            } else
+                GameBase.level.level_name = GameBase.level.mapname;
+            if (GameBase.st.sky != null && GameBase.st.sky.length() > 0)
+                GameBase.gi.configstring(Defines.CS_SKY, GameBase.st.sky);
+            else
+                GameBase.gi.configstring(Defines.CS_SKY, "unit1_");
+            GameBase.gi.configstring(Defines.CS_SKYROTATE, ""
+                    + GameBase.st.skyrotate);
+            GameBase.gi.configstring(Defines.CS_SKYAXIS, Lib
+                    .vtos(GameBase.st.skyaxis));
+            GameBase.gi.configstring(Defines.CS_CDTRACK, "" + ent.sounds);
+            GameBase.gi.configstring(Defines.CS_MAXCLIENTS, ""
+                    + (int) (GameBase.maxclients.value));
+            // status bar program
+            if (GameBase.deathmatch.value != 0)
+                GameBase.gi.configstring(Defines.CS_STATUSBAR, "" + dm_statusbar);
+            else
+                GameBase.gi.configstring(Defines.CS_STATUSBAR, "" + single_statusbar);
+            //---------------
+            // help icon for statusbar
+            GameBase.gi.imageindex("i_help");
+            GameBase.level.pic_health = GameBase.gi.imageindex("i_health");
+            GameBase.gi.imageindex("help");
+            GameBase.gi.imageindex("field_3");
+            if ("".equals(GameBase.st.gravity))
+                GameBase.gi.cvar_set("sv_gravity", "800");
+            else
+                GameBase.gi.cvar_set("sv_gravity", GameBase.st.gravity);
+            GameBase.snd_fry = GameBase.gi.soundindex("player/fry.wav");
+            // gibs
+            GameBase.gi.soundindex("items/respawn1.wav");
+            // drowning damage
+            GameBase.gi.soundindex("*gurp2.wav");
+            GameBase.gi.soundindex("*jump1.wav");
+            // THIS ORDER MUST MATCH THE DEFINES IN g_local.h
+            //-------------------
+            GameBase.gi.soundindex("player/gasp1.wav");
+            // gasping for air
+            GameBase.gi.soundindex("player/gasp2.wav");
+            // bonus item pickup
+            GameBase.gi.soundindex("world/land.wav");
+            // landing thud
+            GameBase.gi.soundindex("misc/h2ohit1.wav");
+            // landing splash
+            //
+            // Setup light animation tables. 'a' is total darkness, 'z' is
+            // doublebright.
+            //
+            // 0 normal
+            GameBase.gi.configstring(Defines.CS_LIGHTS, "m");
+            // 1 FLICKER (first variety)
+            GameBase.gi.configstring(Defines.CS_LIGHTS + 1,
+                    "mmnmmommommnonmmonqnmmo");
+            // 2 SLOW STRONG PULSE
+            GameBase.gi.configstring(Defines.CS_LIGHTS + 2,
+                    "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba");
+            // 3 CANDLE (first variety)
+            GameBase.gi.configstring(Defines.CS_LIGHTS + 3,
+                    "mmmmmaaaaammmmmaaaaaabcdefgabcdefg");
+            // 4 FAST STROBE
+            GameBase.gi.configstring(Defines.CS_LIGHTS + 4, "mamamamamama");
+            // 5 GENTLE PULSE 1
+            GameBase.gi.configstring(Defines.CS_LIGHTS + 5,
+                    "jklmnopqrstuvwxyzyxwvutsrqponmlkj");
+            // 6 FLICKER (second variety)
+            GameBase.gi
+                    .configstring(Defines.CS_LIGHTS + 6, "nmonqnmomnmomomno");
+            // 7 CANDLE (second variety)
+            GameBase.gi.configstring(Defines.CS_LIGHTS + 7,
+                    "mmmaaaabcdefgmmmmaaaammmaamm");
+            // 8 CANDLE (third variety)
+            GameBase.gi.configstring(Defines.CS_LIGHTS + 8,
+                    "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa");
+            // 9 SLOW STROBE (fourth variety)
+            GameBase.gi.configstring(Defines.CS_LIGHTS + 9, "aaaaaaaazzzzzzzz");
+            // 10 FLUORESCENT FLICKER
+            GameBase.gi.configstring(Defines.CS_LIGHTS + 10,
+                    "mmamammmmammamamaaamammma");
+            // 11 SLOW PULSE NOT FADE TO BLACK
+            GameBase.gi.configstring(Defines.CS_LIGHTS + 11,
+                    "abcdefghijklmnopqrrqponmlkjihgfedcba");
+            // styles 32-62 are assigned by the light program for switchable
+            // lights
+            // 63 testing
+            GameBase.gi.configstring(Defines.CS_LIGHTS + 63, "a");
+        }
+    };
+    static final spawn_t[] spawns = {
+            new spawn_t("info_player_start", SP_info_player_start),
+            new spawn_t("info_player_deathmatch", SP_info_player_deathmatch),
+            new spawn_t("info_player_coop", SP_info_player_coop),
+            new spawn_t("info_player_intermission", SP_info_player_intermission),
+            new spawn_t("func_plat", SP_func_plat),
+            new spawn_t("func_train", SP_func_train),
+            new spawn_t("func_conveyor", GameFunc.SP_func_conveyor),
+            new spawn_t("func_areaportal", GameMisc.SP_func_areaportal),
+            new spawn_t("func_clock", SP_func_clock),
+            new spawn_t("func_timer", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_func_timer";
+                }
+
+                public void think(EDict ent) {
+                    GameFunc.SP_func_timer(ent);
+                }
+            }),
+            new spawn_t("trigger_always", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_trigger_always";
+                }
+
+                public void think(EDict ent) {
+                    GameTrigger.SP_trigger_always(ent);
+                }
+            }),
+            new spawn_t("trigger_once", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_trigger_once";
+                }
+
+                public void think(EDict ent) {
+                    GameTrigger.SP_trigger_once(ent);
+                }
+            }),
+            new spawn_t("trigger_multiple", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_trigger_multiple";
+                }
+
+                public void think(EDict ent) {
+                    GameTrigger.SP_trigger_multiple(ent);
+                }
+            }),
+            new spawn_t("trigger_relay", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_trigger_relay";
+                }
+
+                public void think(EDict ent) {
+                    GameTrigger.SP_trigger_relay(ent);
+                }
+            }),
+            new spawn_t("trigger_push", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_trigger_push";
+                }
+
+                public void think(EDict ent) {
+                    GameTrigger.SP_trigger_push(ent);
+                }
+            }),
+            new spawn_t("trigger_key", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_trigger_key";
+                }
+
+                public void think(EDict ent) {
+                    GameTrigger.SP_trigger_key(ent);
+                }
+            }),
+            new spawn_t("trigger_counter", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_trigger_counter";
+                }
+
+                public void think(EDict ent) {
+                    GameTrigger.SP_trigger_counter(ent);
+                }
+            }),
+            new spawn_t("trigger_elevator", GameFunc.SP_trigger_elevator),
+            new spawn_t("trigger_gravity", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_trigger_gravity";
+                }
+
+                public void think(EDict ent) {
+                    GameTrigger.SP_trigger_gravity(ent);
+                }
+            }),
+            new spawn_t("trigger_monsterjump", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_trigger_monsterjump";
+                }
+
+                public void think(EDict ent) {
+                    GameTrigger.SP_trigger_monsterjump(ent);
+                }
+            }),
+            new spawn_t("target_temp_entity", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_target_temp_entity";
+                }
+
+                public void think(EDict ent) {
+                    GameTarget.SP_target_temp_entity(ent);
+                }
+            }),
+            new spawn_t("target_speaker", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_target_speaker";
+                }
+
+                public void think(EDict ent) {
+                    GameTarget.SP_target_speaker(ent);
+                }
+            }),
+            new spawn_t("target_explosion", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_target_explosion";
+                }
+
+                public void think(EDict ent) {
+                    GameTarget.SP_target_explosion(ent);
+                }
+            }),
+            new spawn_t("target_changelevel", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_target_changelevel";
+                }
+
+                public void think(EDict ent) {
+                    GameTarget.SP_target_changelevel(ent);
+                }
+            }),
+            new spawn_t("target_secret", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_target_secret";
+                }
+
+                public void think(EDict ent) {
+                    GameTarget.SP_target_secret(ent);
+                }
+            }),
+            new spawn_t("target_goal", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_target_goal";
+                }
+
+                public void think(EDict ent) {
+                    GameTarget.SP_target_goal(ent);
+                }
+            }),
+            new spawn_t("target_splash", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_target_splash";
+                }
+
+                public void think(EDict ent) {
+                    GameTarget.SP_target_splash(ent);
+                }
+            }),
+            new spawn_t("target_blaster", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_target_blaster";
+                }
+
+                public void think(EDict ent) {
+                    GameTarget.SP_target_blaster(ent);
+                }
+            }),
+            new spawn_t("target_crosslevel_trigger", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_target_crosslevel_trigger";
+                }
+
+                public void think(EDict ent) {
+                    GameTarget.SP_target_crosslevel_trigger(ent);
+                }
+            }),
+            new spawn_t("target_crosslevel_target", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_target_crosslevel_target";
+                }
+
+                public void think(EDict ent) {
+                    GameTarget.SP_target_crosslevel_target(ent);
+                }
+            }),
+            new spawn_t("target_laser", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_target_laser";
+                }
+
+                public void think(EDict ent) {
+                    GameTarget.SP_target_laser(ent);
+                }
+            }),
+            new spawn_t("target_help", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_target_help";
+                }
+
+                public void think(EDict ent) {
+                    GameTarget.SP_target_help(ent);
+                }
+            }),
+            new spawn_t("target_lightramp", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_target_lightramp";
+                }
+
+                public void think(EDict ent) {
+                    GameTarget.SP_target_lightramp(ent);
+                }
+            }),
+            new spawn_t("target_earthquake", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_target_earthquake";
+                }
+
+                public void think(EDict ent) {
+                    GameTarget.SP_target_earthquake(ent);
+                }
+            }),
+            new spawn_t("target_character", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_target_character";
+                }
+
+                public void think(EDict ent) {
+                    GameMisc.SP_target_character(ent);
+                }
+            }),
+            new spawn_t("target_string", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_target_string";
+                }
+
+                public void think(EDict ent) {
+                    GameMisc.SP_target_string(ent);
+                }
+            }),
+            new spawn_t("worldspawn", SP_worldspawn),
+            new spawn_t("viewthing", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_viewthing";
+                }
+
+                public void think(EDict ent) {
+                    GameMisc.SP_viewthing(ent);
+                }
+            }),
+            new spawn_t("light", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_light";
+                }
+
+                public void think(EDict ent) {
+                    GameMisc.SP_light(ent);
+                }
+            }),
+            new spawn_t("light_mine1", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_light_mine1";
+                }
+
+                public void think(EDict ent) {
+                    GameMisc.SP_light_mine1(ent);
+                }
+            }),
+            new spawn_t("light_mine2", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_light_mine2";
+                }
+
+                public void think(EDict ent) {
+                    GameMisc.SP_light_mine2(ent);
+                }
+            }),
+            new spawn_t("info_null", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_info_null";
+                }
+
+                public void think(EDict ent) {
+                    GameMisc.SP_info_null(ent);
+                }
+            }),
+            new spawn_t("func_group", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_info_null";
+                }
+
+                public void think(EDict ent) {
+                    GameMisc.SP_info_null(ent);
+                }
+            }),
+            new spawn_t("info_notnull", new EntThinkAdapter() {
+                public String getID() {
+                    return "info_notnull";
+                }
+
+                public void think(EDict ent) {
+                    GameMisc.SP_info_notnull(ent);
+                }
+            }),
+            new spawn_t("path_corner", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_path_corner";
+                }
+
+                public void think(EDict ent) {
+                    GameMisc.SP_path_corner(ent);
+                }
+            }),
+            new spawn_t("point_combat", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_point_combat";
+                }
+
+                public void think(EDict ent) {
+                    GameMisc.SP_point_combat(ent);
+                }
+            }),
+            new spawn_t("misc_banner", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_misc_banner";
+                }
+
+                public void think(EDict ent) {
+                    GameMisc.SP_misc_banner(ent);
+                }
+            }),
+            new spawn_t("misc_satellite_dish", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_misc_satellite_dish";
+                }
+
+                public void think(EDict ent) {
+                    GameMisc.SP_misc_satellite_dish(ent);
+                }
+            }),
+            new spawn_t("misc_viper", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_misc_viper";
+                }
+
+                public void think(EDict ent) {
+                    GameMisc.SP_misc_viper(ent);
+                }
+            }),
+            new spawn_t("misc_viper_bomb", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_misc_viper_bomb";
+                }
+
+                public void think(EDict ent) {
+                    GameMisc.SP_misc_viper_bomb(ent);
+                }
+            }),
+            new spawn_t("misc_bigviper", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_misc_bigviper";
+                }
+
+                public void think(EDict ent) {
+                    GameMisc.SP_misc_bigviper(ent);
+                }
+            }),
+            new spawn_t("misc_strogg_ship", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_misc_strogg_ship";
+                }
+
+                public void think(EDict ent) {
+                    GameMisc.SP_misc_strogg_ship(ent);
+                }
+            }),
+            new spawn_t("misc_eastertank", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_misc_eastertank";
+                }
+
+                public void think(EDict ent) {
+                    GameMisc.SP_misc_eastertank(ent);
+                }
+            }),
+            new spawn_t("misc_easterchick", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_misc_easterchick";
+                }
+
+                public void think(EDict ent) {
+                    GameMisc.SP_misc_easterchick(ent);
+                }
+            }),
+            new spawn_t("misc_easterchick2", new EntThinkAdapter() {
+                public String getID() {
+                    return "SP_misc_easterchick2";
+                }
+
+                public void think(EDict ent) {
+                    GameMisc.SP_misc_easterchick2(ent);
+                }
+            }), new spawn_t("turret_breach", new EntThinkAdapter() {
+        public String getID() {
+            return "SP_turret_breach";
+        }
+
+        public void think(EDict ent) {
+        }
+    }), new spawn_t("turret_base", new EntThinkAdapter() {
+        public String getID() {
+            return "SP_turret_base";
+        }
+
+        public void think(EDict ent) {
+        }
+    }), new spawn_t(null, null)};
+
+    /**
+     * ED_NewString.
+     */
+    static String ED_NewString(String string) {
+
+        int l = string.length();
+        StringBuilder newb = new StringBuilder(l);
+
+        for (int i = 0; i < l; i++) {
+            char c = string.charAt(i);
+            if (c == '\\' && i < l - 1) {
+                c = string.charAt(++i);
+                if (c == 'n')
+                    newb.append('\n');
+                else
+                    newb.append('\\');
+            } else
+                newb.append(c);
+        }
+
+        return newb.toString();
+    }
+
+    /**
+     * ED_ParseField
+     * <p>
+     * Takes a key/value pair and sets the binary values in an edict.
+     */
+    static void ED_ParseField(String key, String value, EDict ent) {
+
+        if (key.equals("nextmap"))
+            Com.Println("nextmap: " + value);
+        if (!GameBase.st.set(key, value))
+            if (!ent.setField(key, value))
+                GameBase.gi.dprintf("??? The key [" + key
+                        + "] is not a field\n");
+
+    }
+
+    /**
+     * ED_ParseEdict
+     * <p>
+     * Parses an edict out of the given string, returning the new position ed
+     * should be a properly initialized empty edict.
+     */
+
+    static void ED_ParseEdict(Com.ParseHelp ph, EDict ent) {
+
+        boolean init;
+        String keyname;
+        String com_token;
+        init = false;
+
+        GameBase.st = new spawn_temp_t();
+        while (true) {
+
+            // parse key
+            com_token = Com.Parse(ph);
+            if (com_token.equals("}"))
+                break;
+
+            if (ph.isEof())
+                GameBase.gi.error("ED_ParseEntity: EOF without closing brace");
+
+            keyname = com_token;
+
+            // parse value
+            com_token = Com.Parse(ph);
+
+            if (ph.isEof())
+                GameBase.gi.error("ED_ParseEntity: EOF without closing brace");
+
+            if (com_token.equals("}"))
+                GameBase.gi.error("ED_ParseEntity: closing brace without data");
+
+            init = true;
+            // keynames with a leading underscore are used for utility comments,
+            // and are immediately discarded by quake
+            if (keyname.charAt(0) == '_')
+                continue;
+
+            ED_ParseField(keyname.toLowerCase(), com_token, ent);
+
+        }
+
+        if (!init) {
+            GameUtil.G_ClearEdict(ent);
+        }
+
+    }
+
+    /**
+     * G_FindTeams
+     * <p>
+     * Chain together all entities with a matching team field.
+     * <p>
+     * All but the first will have the FL_TEAMSLAVE flag set. All but the last
+     * will have the teamchain field set to the next one.
+     */
+
+    static void G_FindTeams() {
+        EDict e, e2, chain;
+        int i, j;
+        for (i = 1; i < GameBase.num_edicts; i++) {
+            e = GameBase.g_edicts[i];
+
+            if (!e.inuse)
+                continue;
+            if (e.team == null)
+                continue;
+            if ((e.flags & Defines.FL_TEAMSLAVE) != 0)
+                continue;
+            chain = e;
+            e.teammaster = e;
+
+            for (j = i + 1; j < GameBase.num_edicts; j++) {
+                e2 = GameBase.g_edicts[j];
+                if (!e2.inuse)
+                    continue;
+                if (null == e2.team)
+                    continue;
+                if ((e2.flags & Defines.FL_TEAMSLAVE) != 0)
+                    continue;
+                if (0 == Lib.strcmp(e.team, e2.team)) {
+                    chain.teamchain = e2;
+                    e2.teammaster = e;
+                    chain = e2;
+                    e2.flags |= Defines.FL_TEAMSLAVE;
+
+                }
+            }
+        }
+    }
+
+    /**
+     * SpawnEntities
+     * <p>
+     * Creates a server's entity / program execution context by parsing textual
+     * entity definitions out of an ent file.
+     */
+
+    public static void SpawnEntities(String mapname, String entities,
+                                     String spawnpoint) {
+
+        Com.dprintln("SpawnEntities(), mapname=" + mapname);
+        EDict ent;
+        int inhibit;
+        String com_token;
+        int i;
+
+        PlayerClient.SaveClientData();
+
+        GameBase.level = new level_locals_t();
+        for (int n = 0; n < GameBase.game.maxentities; n++) {
+            GameBase.g_edicts[n] = new EDict(n);
+        }
+
+        GameBase.level.mapname = mapname;
+        GameBase.game.spawnpoint = spawnpoint;
+
+        // set client fields on player ents
+        for (i = 0; i < GameBase.game.maxclients; i++)
+            GameBase.g_edicts[i + 1].client = GameBase.game.clients[i];
+
+        ent = null;
+        inhibit = 0;
+
+        Com.ParseHelp ph = new Com.ParseHelp(entities);
+
+        while (true) { // parse the opening brace
+
+            com_token = Com.Parse(ph);
+            if (ph.isEof())
+                break;
+            if (!com_token.startsWith("{"))
+                GameBase.gi.error("ED_LoadFromFile: found " + com_token
+                        + " when expecting {");
+
+            if (ent == null)
+                ent = GameBase.g_edicts[0];
+            else
+                ent = GameUtil.G_Spawn();
+
+            ED_ParseEdict(ph, ent);
+            Com.DPrintf("spawning ent[" + ent.index + "], classname=" +
+                    ent.classname + ", flags= " + Integer.toHexString(ent.spawnflags));
+
+            // yet another map hack
+            if (0 == Lib.Q_stricmp(GameBase.level.mapname, "command")
+                    && 0 == Lib.Q_stricmp(ent.classname, "trigger_once")
+                    && 0 == Lib.Q_stricmp(ent.model, "*27"))
+                ent.spawnflags &= ~Defines.SPAWNFLAG_NOT_HARD;
+
+            // remove things (except the world) from different skill levels or
+            // deathmatch
+            if (ent != GameBase.g_edicts[0]) {
+                if (GameBase.deathmatch.value != 0) {
+                    if ((ent.spawnflags & Defines.SPAWNFLAG_NOT_DEATHMATCH) != 0) {
+
+                        Com.DPrintf("->inhibited.\n");
+                        GameUtil.G_FreeEdict(ent);
+                        inhibit++;
+                        continue;
+                    }
+                } else {
+                }
+
+                ent.spawnflags &= ~(Defines.SPAWNFLAG_NOT_EASY
+                        | Defines.SPAWNFLAG_NOT_MEDIUM
+                        | Defines.SPAWNFLAG_NOT_HARD
+                        | Defines.SPAWNFLAG_NOT_COOP | Defines.SPAWNFLAG_NOT_DEATHMATCH);
+            }
+            ED_CallSpawn(ent);
+            Com.DPrintf("\n");
+        }
+        Com.DPrintf(inhibit + " entities inhibited.\n");
+        i = 1;
+        G_FindTeams();
+        PlayerTrail.Init();
+    }
+
+    /**
+     * ED_CallSpawn
+     * <p>
+     * Finds the spawn function for the entity and calls it.
+     */
+    public static void ED_CallSpawn(EDict ent) {
+
+        spawn_t s;
+        int i;
+        if (null == ent.classname) {
+            GameBase.gi.dprintf("ED_CallSpawn: null classname\n");
+            return;
+        } // check item spawn functions
+
+        for (i = 0; (s = spawns[i]) != null && s.name != null; i++) {
+            if (s.name.equalsIgnoreCase(ent.classname)) { // found it
+
+                if (s.spawn == null)
+                    GameBase.gi.error("ED_CallSpawn: null-spawn on index=" + i);
+                s.spawn.think(ent);
+                return;
+            }
+        }
+        GameBase.gi.dprintf(ent.classname + " doesn't have a spawn function\n");
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/GameTarget.java b/src/main/java/lwjake2/game/GameTarget.java
new file mode 100644 (file)
index 0000000..788722c
--- /dev/null
@@ -0,0 +1,829 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.Defines;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+
+class GameTarget {
+
+    /**
+     * QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8) Fire an origin based
+     * temp entity event to the clients. "style" type byte
+     */
+    private static final EntUseAdapter Use_Target_Tent = new EntUseAdapter() {
+        public String getID() {
+            return "Use_Target_Tent";
+        }
+
+        public void use(EDict ent, EDict other, EDict activator) {
+            GameBase.gi.WriteByte(Defines.svc_temp_entity);
+            GameBase.gi.WriteByte(ent.style);
+            GameBase.gi.WritePosition(ent.s.origin);
+            GameBase.gi.multicast(ent.s.origin, Defines.MULTICAST_PVS);
+        }
+    };
+    /**
+     * QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off
+     * reliable "noise" wav file to play "attenuation" -1 = none, send to whole
+     * level 1 = normal fighting sounds 2 = idle sound level 3 = ambient sound
+     * level "volume" 0.0 to 1.0
+     * <p>
+     * Normal sounds play each time the target is used. The reliable flag can be
+     * set for crucial voiceovers.
+     * <p>
+     * Looped sounds are always atten 3 / vol 1, and the use function toggles it
+     * on/off. Multiple identical looping sounds will just increase volume
+     * without any speed cost.
+     */
+    private static final EntUseAdapter Use_Target_Speaker = new EntUseAdapter() {
+        public String getID() {
+            return "Use_Target_Speaker";
+        }
+
+        public void use(EDict ent, EDict other, EDict activator) {
+            int chan;
+
+            if ((ent.spawnflags & 3) != 0) { // looping sound toggles
+                if (ent.s.sound != 0)
+                    ent.s.sound = 0; // turn it off
+                else
+                    ent.s.sound = ent.noise_index; // start it
+            } else { // normal sound
+                if ((ent.spawnflags & 4) != 0)
+                    chan = Defines.CHAN_VOICE | Defines.CHAN_RELIABLE;
+                else
+                    chan = Defines.CHAN_VOICE;
+                // use a positioned_sound, because this entity won't normally be
+                // sent to any clients because it is invisible
+                GameBase.gi.positioned_sound(ent.s.origin, ent, chan,
+                        ent.noise_index, ent.volume, ent.attenuation, 0);
+            }
+
+        }
+    };
+    private static final EntUseAdapter Use_Target_Help = new EntUseAdapter() {
+        public String getID() {
+            return "Use_Target_Help";
+        }
+
+        public void use(EDict ent, EDict other, EDict activator) {
+
+            if ((ent.spawnflags & 1) != 0)
+                GameBase.game.helpmessage1 = ent.message;
+            else
+                GameBase.game.helpmessage2 = ent.message;
+
+            GameBase.game.helpchanged++;
+        }
+    };
+    /**
+     * QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS Fires
+     * a blaster bolt in the set direction when triggered.
+     * <p>
+     * dmg default is 15 speed default is 1000
+     */
+    private static final EntUseAdapter use_target_blaster = new EntUseAdapter() {
+        public String getID() {
+            return "use_target_blaster";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+
+               /* Wait what - flibit
+            int effect;
+
+            if ((self.spawnflags & 2) != 0)
+                effect = 0;
+            else if ((self.spawnflags & 1) != 0)
+                effect = Defines.EF_HYPERBLASTER;
+            else
+                effect = Defines.EF_BLASTER;
+                       */
+
+            GameBase.gi.sound(self, Defines.CHAN_VOICE, self.noise_index, 1,
+                    Defines.ATTN_NORM, 0);
+        }
+    };
+    /**
+     * QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1
+     * trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 Once this
+     * trigger is touched/used, any trigger_crosslevel_target with the same
+     * trigger number is automatically used when a level is started within the
+     * same unit. It is OK to check multiple triggers. Message, delay, target,
+     * and killtarget also work.
+     */
+    private static final EntUseAdapter trigger_crosslevel_trigger_use = new EntUseAdapter() {
+        public String getID() {
+            return "trigger_crosslevel_trigger_use";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            GameBase.game.serverflags |= self.spawnflags;
+            GameUtil.G_FreeEdict(self);
+        }
+    };
+    /**
+     * QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE
+     * YELLOW ORANGE FAT When triggered, fires a laser. You can either set a
+     * target or a direction.
+     */
+    private static final EntThinkAdapter target_laser_think = new EntThinkAdapter() {
+        public String getID() {
+            return "target_laser_think";
+        }
+
+        public void think(EDict self) {
+
+            EDict ignore;
+            float[] start = {0, 0, 0};
+            float[] end = {0, 0, 0};
+            trace_t tr;
+            float[] point = {0, 0, 0};
+            float[] last_movedir = {0, 0, 0};
+            int count;
+
+            if ((self.spawnflags & 0x80000000) != 0)
+                count = 8;
+            else
+                count = 4;
+
+            if (self.enemy != null) {
+                Math3D.vectorCopy(self.movedir, last_movedir);
+                Math3D.vectorMA(self.enemy.absmin, 0.5f, self.enemy.size, point);
+                Math3D.vectorSubtract(point, self.s.origin, self.movedir);
+                Math3D.vectorNormalize(self.movedir);
+                if (!Math3D.vectorEquals(self.movedir, last_movedir))
+                    self.spawnflags |= 0x80000000;
+            }
+
+            ignore = self;
+            Math3D.vectorCopy(self.s.origin, start);
+            Math3D.vectorMA(start, 2048, self.movedir, end);
+            while (true) {
+                tr = GameBase.gi.trace(start, null, null, end, ignore,
+                        Defines.CONTENTS_SOLID | Defines.CONTENTS_MONSTER
+                                | Defines.CONTENTS_DEADMONSTER);
+
+                if (tr.ent == null)
+                    break;
+
+
+                // if we hit something that's not a monster or player or is
+                // immune to lasers, we're done
+                if (0 == (tr.ent.svflags & Defines.SVF_MONSTER)
+                        && (null == tr.ent.client)) {
+                    if ((self.spawnflags & 0x80000000) != 0) {
+                        self.spawnflags &= ~0x80000000;
+                        GameBase.gi.WriteByte(Defines.svc_temp_entity);
+                        GameBase.gi.WriteByte(Defines.TE_LASER_SPARKS);
+                        GameBase.gi.WriteByte(count);
+                        GameBase.gi.WritePosition(tr.endpos);
+                        GameBase.gi.WriteDir(tr.plane.normal);
+                        GameBase.gi.WriteByte(self.s.skinnum);
+                        GameBase.gi.multicast(tr.endpos, Defines.MULTICAST_PVS);
+                    }
+                    break;
+                }
+
+                ignore = tr.ent;
+                Math3D.vectorCopy(tr.endpos, start);
+            }
+
+            Math3D.vectorCopy(tr.endpos, self.s.old_origin);
+
+            self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+        }
+    };
+    private static final EntUseAdapter target_laser_use = new EntUseAdapter() {
+        public String getID() {
+            return "target_laser_use";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            self.activator = activator;
+            if ((self.spawnflags & 1) != 0)
+                target_laser_off(self);
+            else
+                target_laser_on(self);
+        }
+    };
+    /**
+     * QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8) Counts a secret found.
+     * These are single use targets.
+     */
+    private static final EntUseAdapter use_target_secret = new EntUseAdapter() {
+        public String getID() {
+            return "use_target_secret";
+        }
+
+        public void use(EDict ent, EDict other, EDict activator) {
+            GameBase.gi.sound(ent, Defines.CHAN_VOICE, ent.noise_index, 1,
+                    Defines.ATTN_NORM, 0);
+
+            GameBase.level.found_secrets++;
+
+            GameUtil.G_UseTargets(ent, activator);
+            GameUtil.G_FreeEdict(ent);
+        }
+    };
+    /**
+     * QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8) Counts a goal completed.
+     * These are single use targets.
+     */
+    private static final EntUseAdapter use_target_goal = new EntUseAdapter() {
+        public String getID() {
+            return "use_target_goal";
+        }
+
+        public void use(EDict ent, EDict other, EDict activator) {
+            GameBase.gi.sound(ent, Defines.CHAN_VOICE, ent.noise_index, 1,
+                    Defines.ATTN_NORM, 0);
+
+            GameBase.level.found_goals++;
+
+            if (GameBase.level.found_goals == GameBase.level.total_goals)
+                GameBase.gi.configstring(Defines.CS_CDTRACK, "0");
+
+            GameUtil.G_UseTargets(ent, activator);
+            GameUtil.G_FreeEdict(ent);
+        }
+    };
+    /**
+     * QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8) Spawns an explosion
+     * temporary entity when used.
+     * <p>
+     * "delay" wait this long before going off "dmg" how much radius damage
+     * should be done, defaults to 0
+     */
+    private static final EntThinkAdapter target_explosion_explode = new EntThinkAdapter() {
+        public String getID() {
+            return "target_explosion_explode";
+        }
+
+        public void think(EDict self) {
+
+            float save;
+
+            GameBase.gi.WriteByte(Defines.svc_temp_entity);
+            GameBase.gi.WriteByte(Defines.TE_EXPLOSION1);
+            GameBase.gi.WritePosition(self.s.origin);
+            GameBase.gi.multicast(self.s.origin, Defines.MULTICAST_PHS);
+
+            save = self.delay;
+            self.delay = 0;
+            GameUtil.G_UseTargets(self, self.activator);
+            self.delay = save;
+        }
+    };
+    private static final EntUseAdapter use_target_explosion = new EntUseAdapter() {
+        public String getID() {
+            return "use_target_explosion";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            self.activator = activator;
+
+            if (0 == self.delay) {
+                target_explosion_explode.think(self);
+                return;
+            }
+
+            self.think = target_explosion_explode;
+            self.nextthink = GameBase.level.time + self.delay;
+        }
+    };
+    /**
+     * QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8) Changes level to
+     * "map" when fired
+     */
+    private static final EntUseAdapter use_target_changelevel = new EntUseAdapter() {
+        public String getID() {
+            return "use_target_changelevel";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            if (GameBase.level.intermissiontime != 0)
+                return; // already activated
+
+            if (0 == GameBase.deathmatch.value && 0 == GameBase.coop.value) {
+                if (GameBase.g_edicts[1].health <= 0)
+                    return;
+            }
+
+            // if noexit, do a ton of damage to other
+            if (GameBase.deathmatch.value != 0
+                    && 0 == ((int) GameBase.dmflags.value & Defines.DF_ALLOW_EXIT)
+                    && other != GameBase.g_edicts[0] /* world */
+                    ) {
+                return;
+            }
+
+            // if multiplayer, let everyone know who hit the exit
+            if (GameBase.deathmatch.value != 0) {
+                if (activator != null && activator.client != null)
+                    GameBase.gi.bprintf(Defines.PRINT_HIGH,
+                            activator.client.pers.netname
+                                    + " exited the level.\n");
+            }
+
+            // if going to a new unit, clear cross triggers
+            if (self.map.indexOf('*') > -1)
+                GameBase.game.serverflags &= ~(Defines.SFL_CROSS_TRIGGER_MASK);
+
+            PlayerHud.BeginIntermission(self);
+        }
+    };
+    /**
+     * QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8) Creates a particle splash
+     * effect when used.
+     * <p>
+     * Set "sounds" to one of the following: 1) sparks 2) blue water 3) brown
+     * water 4) slime 5) lava 6) blood
+     * <p>
+     * "count" how many pixels in the splash "dmg" if set, does a radius damage
+     * at this location when it splashes useful for lava/sparks
+     */
+    private static final EntUseAdapter use_target_splash = new EntUseAdapter() {
+        public String getID() {
+            return "use_target_splash";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            GameBase.gi.WriteByte(Defines.svc_temp_entity);
+            GameBase.gi.WriteByte(Defines.TE_SPLASH);
+            GameBase.gi.WriteByte(self.count);
+            GameBase.gi.WritePosition(self.s.origin);
+            GameBase.gi.WriteDir(self.movedir);
+            GameBase.gi.WriteByte(self.sounds);
+            GameBase.gi.multicast(self.s.origin, Defines.MULTICAST_PVS);
+
+        }
+    };
+    /**
+     * QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8) Set target to the type
+     * of entity you want spawned. Useful for spawning monsters and gibs in the
+     * factory levels.
+     * <p>
+     * For monsters: Set direction to the facing you want it to have.
+     * <p>
+     * For gibs: Set direction if you want it moving and speed how fast it
+     * should be moving otherwise it will just be dropped
+     */
+
+    /**
+     * QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1
+     * trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 Triggered
+     * by a trigger_crosslevel elsewhere within a unit. If multiple triggers are
+     * checked, all must be true. Delay, target and killtarget also work.
+     * <p>
+     * "delay" delay before using targets if the trigger has been activated
+     * (default 1)
+     */
+    private static final EntThinkAdapter target_crosslevel_target_think = new EntThinkAdapter() {
+        public String getID() {
+            return "target_crosslevel_target_think";
+        }
+
+        public void think(EDict self) {
+            if (self.spawnflags == (GameBase.game.serverflags
+                    & Defines.SFL_CROSS_TRIGGER_MASK & self.spawnflags)) {
+                GameUtil.G_UseTargets(self, self);
+                GameUtil.G_FreeEdict(self);
+            }
+        }
+    };
+    private static final EntThinkAdapter target_laser_start = new EntThinkAdapter() {
+        public String getID() {
+            return "target_laser_start";
+        }
+
+        public void think(EDict self) {
+
+            self.movetype = Defines.MOVETYPE_NONE;
+            self.solid = Defines.SOLID_NOT;
+            self.s.modelindex = 1; // must be non-zero
+
+            // set the beam diameter
+            if ((self.spawnflags & 64) != 0)
+                self.s.frame = 16;
+            else
+                self.s.frame = 4;
+
+            // set the color
+            if ((self.spawnflags & 2) != 0)
+                self.s.skinnum = 0xf2f2f0f0;
+            else if ((self.spawnflags & 4) != 0)
+                self.s.skinnum = 0xd0d1d2d3;
+            else if ((self.spawnflags & 8) != 0)
+                self.s.skinnum = 0xf3f3f1f1;
+            else if ((self.spawnflags & 16) != 0)
+                self.s.skinnum = 0xdcdddedf;
+            else if ((self.spawnflags & 32) != 0)
+                self.s.skinnum = 0xe0e1e2e3;
+
+            if (null == self.enemy) {
+                if (self.target != null) {
+                    EdictIterator edit = GameBase.G_Find(null, GameBase.findByTarget,
+                            self.target);
+                    if (edit == null)
+                        GameBase.gi.dprintf(self.classname + " at "
+                                + Lib.vtos(self.s.origin) + ": " + self.target
+                                + " is a bad target\n");
+                    self.enemy = edit.o;
+                } else {
+                    GameBase.G_SetMovedir(self.s.angles, self.movedir);
+                }
+            }
+            self.use = target_laser_use;
+            self.think = target_laser_think;
+
+            if (0 == self.dmg)
+                self.dmg = 1;
+
+            Math3D.vectorSet(self.mins, -8, -8, -8);
+            Math3D.vectorSet(self.maxs, 8, 8, 8);
+            GameBase.gi.linkentity(self);
+
+            if ((self.spawnflags & 1) != 0)
+                target_laser_on(self);
+            else
+                target_laser_off(self);
+        }
+    };
+    /**
+     * QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE speed How
+     * many seconds the ramping will take message two letters; starting
+     * lightlevel and ending lightlevel
+     */
+
+    private static final EntThinkAdapter target_lightramp_think = new EntThinkAdapter() {
+        public String getID() {
+            return "target_lightramp_think";
+        }
+
+        public void think(EDict self) {
+
+            char tmp[] = {(char) ('a' + (int) (self.movedir[0] + (GameBase.level.time - self.timestamp)
+                    / Defines.FRAMETIME * self.movedir[2]))};
+
+            GameBase.gi.configstring(Defines.CS_LIGHTS + self.enemy.style,
+                    new String(tmp));
+
+            if ((GameBase.level.time - self.timestamp) < self.speed) {
+                self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+            } else if ((self.spawnflags & 1) != 0) {
+                char temp;
+
+                temp = (char) self.movedir[0];
+                self.movedir[0] = self.movedir[1];
+                self.movedir[1] = temp;
+                self.movedir[2] *= -1;
+            }
+
+        }
+    };
+    private static final EntUseAdapter target_lightramp_use = new EntUseAdapter() {
+        public String getID() {
+            return "target_lightramp_use";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            if (self.enemy == null) {
+                EDict e;
+
+                // check all the targets
+                e = null;
+                EdictIterator es = null;
+
+                while (true) {
+                    es = GameBase
+                            .G_Find(es, GameBase.findByTarget, self.target);
+
+                    if (es == null)
+                        break;
+
+                    e = es.o;
+
+                    if (Lib.strcmp(e.classname, "light") != 0) {
+                        GameBase.gi.dprintf(self.classname + " at "
+                                + Lib.vtos(self.s.origin));
+                        GameBase.gi.dprintf("target " + self.target + " ("
+                                + e.classname + " at " + Lib.vtos(e.s.origin)
+                                + ") is not a light\n");
+                    } else {
+                        self.enemy = e;
+                    }
+                }
+
+                if (null == self.enemy) {
+                    GameBase.gi.dprintf(self.classname + " target "
+                            + self.target + " not found at "
+                            + Lib.vtos(self.s.origin) + "\n");
+                    GameUtil.G_FreeEdict(self);
+                    return;
+                }
+            }
+
+            self.timestamp = GameBase.level.time;
+            target_lightramp_think.think(self);
+        }
+    };
+    /**
+     * QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8) When triggered, this
+     * initiates a level-wide earthquake. All players and monsters are affected.
+     * "speed" severity of the quake (default:200) "count" duration of the quake
+     * (default:5)
+     */
+
+    private static final EntThinkAdapter target_earthquake_think = new EntThinkAdapter() {
+        public String getID() {
+            return "target_earthquake_think";
+        }
+
+        public void think(EDict self) {
+
+            int i;
+            EDict e;
+
+            if (self.last_move_time < GameBase.level.time) {
+                GameBase.gi.positioned_sound(self.s.origin, self,
+                        Defines.CHAN_AUTO, self.noise_index, 1.0f,
+                        Defines.ATTN_NONE, 0);
+                self.last_move_time = GameBase.level.time + 0.5f;
+            }
+
+            for (i = 1; i < GameBase.num_edicts; i++) {
+                e = GameBase.g_edicts[i];
+
+                if (!e.inuse)
+                    continue;
+                if (null == e.client)
+                    continue;
+                if (null == e.groundentity)
+                    continue;
+
+                e.groundentity = null;
+                e.velocity[0] += Lib.crandom() * 150;
+                e.velocity[1] += Lib.crandom() * 150;
+                e.velocity[2] = self.speed * (100.0f / e.mass);
+            }
+
+            if (GameBase.level.time < self.timestamp)
+                self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+
+        }
+    };
+    private static final EntUseAdapter target_earthquake_use = new EntUseAdapter() {
+        public String getID() {
+            return "target_earthquake_use";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            self.timestamp = GameBase.level.time + self.count;
+            self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+            self.activator = activator;
+            self.last_move_time = 0;
+        }
+    };
+
+    public static void SP_target_temp_entity(EDict ent) {
+        ent.use = Use_Target_Tent;
+    }
+
+    public static void SP_target_speaker(EDict ent) {
+        //char buffer[MAX_QPATH];
+        String buffer;
+
+        if (GameBase.st.noise == null) {
+            GameBase.gi.dprintf("target_speaker with no noise set at "
+                    + Lib.vtos(ent.s.origin) + "\n");
+            return;
+        }
+        if (!GameBase.st.noise.contains(".wav"))
+            buffer = "" + GameBase.st.noise + ".wav";
+        else
+            buffer = GameBase.st.noise;
+
+        ent.noise_index = GameBase.gi.soundindex(buffer);
+
+        if (ent.volume == 0)
+            ent.volume = 1.0f;
+
+        if (ent.attenuation == 0)
+            ent.attenuation = 1.0f;
+        else if (ent.attenuation == -1) // use -1 so 0 defaults to 1
+            ent.attenuation = 0;
+
+        // check for prestarted looping sound
+        if ((ent.spawnflags & 1) != 0)
+            ent.s.sound = ent.noise_index;
+
+        ent.use = Use_Target_Speaker;
+
+        // must link the entity so we get areas and clusters so
+        // the server can determine who to send updates to
+        GameBase.gi.linkentity(ent);
+    }
+
+    /**
+     * QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1 When fired, the
+     * "message" key becomes the current personal computer string, and the
+     * message light will be set on all clients status bars.
+     */
+    public static void SP_target_help(EDict ent) {
+        if (GameBase.deathmatch.value != 0) { // auto-remove for deathmatch
+            GameUtil.G_FreeEdict(ent);
+            return;
+        }
+
+        if (ent.message == null) {
+            GameBase.gi.dprintf(ent.classname + " with no message at "
+                    + Lib.vtos(ent.s.origin) + "\n");
+            GameUtil.G_FreeEdict(ent);
+            return;
+        }
+        ent.use = Use_Target_Help;
+    }
+
+    public static void SP_target_secret(EDict ent) {
+        if (GameBase.deathmatch.value != 0) { // auto-remove for deathmatch
+            GameUtil.G_FreeEdict(ent);
+            return;
+        }
+
+        ent.use = use_target_secret;
+        if (GameBase.st.noise == null)
+            GameBase.st.noise = "misc/secret.wav";
+        ent.noise_index = GameBase.gi.soundindex(GameBase.st.noise);
+        ent.svflags = Defines.SVF_NOCLIENT;
+        GameBase.level.total_secrets++;
+        // map bug hack
+        if (0 == Lib.Q_stricmp(GameBase.level.mapname, "mine3")
+                && ent.s.origin[0] == 280 && ent.s.origin[1] == -2048
+                && ent.s.origin[2] == -624)
+            ent.message = "You have found a secret area.";
+    }
+
+    public static void SP_target_goal(EDict ent) {
+        if (GameBase.deathmatch.value != 0) { // auto-remove for deathmatch
+            GameUtil.G_FreeEdict(ent);
+            return;
+        }
+
+        ent.use = use_target_goal;
+        if (GameBase.st.noise == null)
+            GameBase.st.noise = "misc/secret.wav";
+        ent.noise_index = GameBase.gi.soundindex(GameBase.st.noise);
+        ent.svflags = Defines.SVF_NOCLIENT;
+        GameBase.level.total_goals++;
+    }
+
+    public static void SP_target_explosion(EDict ent) {
+        ent.use = use_target_explosion;
+        ent.svflags = Defines.SVF_NOCLIENT;
+    }
+
+    public static void SP_target_changelevel(EDict ent) {
+        if (ent.map == null) {
+            GameBase.gi.dprintf("target_changelevel with no map at "
+                    + Lib.vtos(ent.s.origin) + "\n");
+            GameUtil.G_FreeEdict(ent);
+            return;
+        }
+
+        // ugly hack because *SOMEBODY* screwed up their map
+        if ((Lib.Q_stricmp(GameBase.level.mapname, "fact1") == 0)
+                && (Lib.Q_stricmp(ent.map, "fact3") == 0))
+            ent.map = "fact3$secret1";
+
+        ent.use = use_target_changelevel;
+        ent.svflags = Defines.SVF_NOCLIENT;
+    }
+
+    public static void SP_target_splash(EDict self) {
+        self.use = use_target_splash;
+        GameBase.G_SetMovedir(self.s.angles, self.movedir);
+
+        if (0 == self.count)
+            self.count = 32;
+
+        self.svflags = Defines.SVF_NOCLIENT;
+    }
+
+
+    public static void SP_target_blaster(EDict self) {
+        self.use = use_target_blaster;
+        GameBase.G_SetMovedir(self.s.angles, self.movedir);
+        self.noise_index = GameBase.gi.soundindex("weapons/laser2.wav");
+
+        if (0 == self.dmg)
+            self.dmg = 15;
+        if (0 == self.speed)
+            self.speed = 1000;
+
+        self.svflags = Defines.SVF_NOCLIENT;
+    }
+
+    public static void SP_target_crosslevel_trigger(EDict self) {
+        self.svflags = Defines.SVF_NOCLIENT;
+        self.use = trigger_crosslevel_trigger_use;
+    }
+
+    public static void SP_target_crosslevel_target(EDict self) {
+        if (0 == self.delay)
+            self.delay = 1;
+        self.svflags = Defines.SVF_NOCLIENT;
+
+        self.think = target_crosslevel_target_think;
+        self.nextthink = GameBase.level.time + self.delay;
+    }
+
+    private static void target_laser_on(EDict self) {
+        if (null == self.activator)
+            self.activator = self;
+        self.spawnflags |= 0x80000001;
+        self.svflags &= ~Defines.SVF_NOCLIENT;
+        target_laser_think.think(self);
+    }
+
+    private static void target_laser_off(EDict self) {
+        self.spawnflags &= ~1;
+        self.svflags |= Defines.SVF_NOCLIENT;
+        self.nextthink = 0;
+    }
+
+    public static void SP_target_laser(EDict self) {
+        // let everything else get spawned before we start firing
+        self.think = target_laser_start;
+        self.nextthink = GameBase.level.time + 1;
+    }
+
+    public static void SP_target_lightramp(EDict self) {
+        if (self.message == null || self.message.length() != 2
+                || self.message.charAt(0) < 'a' || self.message.charAt(0) > 'z'
+                || self.message.charAt(1) < 'a' || self.message.charAt(1) > 'z'
+                || self.message.charAt(0) == self.message.charAt(1)) {
+            GameBase.gi.dprintf("target_lightramp has bad ramp ("
+                    + self.message + ") at " + Lib.vtos(self.s.origin) + "\n");
+            GameUtil.G_FreeEdict(self);
+            return;
+        }
+
+        if (GameBase.deathmatch.value != 0) {
+            GameUtil.G_FreeEdict(self);
+            return;
+        }
+
+        if (self.target == null) {
+            GameBase.gi.dprintf(self.classname + " with no target at "
+                    + Lib.vtos(self.s.origin) + "\n");
+            GameUtil.G_FreeEdict(self);
+            return;
+        }
+
+        self.svflags |= Defines.SVF_NOCLIENT;
+        self.use = target_lightramp_use;
+        self.think = target_lightramp_think;
+
+        self.movedir[0] = self.message.charAt(0) - 'a';
+        self.movedir[1] = self.message.charAt(1) - 'a';
+        self.movedir[2] = (self.movedir[1] - self.movedir[0])
+                / (self.speed / Defines.FRAMETIME);
+    }
+
+    public static void SP_target_earthquake(EDict self) {
+        if (null == self.targetname)
+            GameBase.gi.dprintf("untargeted " + self.classname + " at "
+                    + Lib.vtos(self.s.origin) + "\n");
+
+        if (0 == self.count)
+            self.count = 5;
+
+        if (0 == self.speed)
+            self.speed = 200;
+
+        self.svflags |= Defines.SVF_NOCLIENT;
+        self.think = target_earthquake_think;
+        self.use = target_earthquake_use;
+
+        self.noise_index = GameBase.gi.soundindex("world/quake.wav");
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/GameTrigger.java b/src/main/java/lwjake2/game/GameTrigger.java
new file mode 100644 (file)
index 0000000..04acf6a
--- /dev/null
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+
+public class GameTrigger {
+
+    public static final int PUSH_ONCE = 1;
+    // the wait time has passed, so set back up for another activation
+    public static final EntThinkAdapter multi_wait = new EntThinkAdapter() {
+        public String getID() {
+            return "multi_wait";
+        }
+
+        public void think(EDict ent) {
+
+            ent.nextthink = 0;
+        }
+    };
+    /**
+     * QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8) This fixed size
+     * trigger cannot be touched, it can only be fired by other events.
+     */
+    public static final EntUseAdapter trigger_relay_use = new EntUseAdapter() {
+        public String getID() {
+            return "trigger_relay_use";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            GameUtil.G_UseTargets(self, activator);
+        }
+    };
+    static final EntUseAdapter Use_Multi = new EntUseAdapter() {
+        public String getID() {
+            return "Use_Multi";
+        }
+
+        public void use(EDict ent, EDict other, EDict activator) {
+            ent.activator = activator;
+            multi_trigger(ent);
+        }
+    };
+    static final EntTouchAdapter Touch_Multi = new EntTouchAdapter() {
+        public String getID() {
+            return "Touch_Multi";
+        }
+
+        public void touch(EDict self, EDict other, cplane_t plane,
+                          csurface_t surf) {
+            if (other.client != null) {
+                if ((self.spawnflags & 2) != 0)
+                    return;
+            } else if ((other.svflags & Defines.SVF_MONSTER) != 0) {
+                if (0 == (self.spawnflags & 1))
+                    return;
+            } else
+                return;
+
+            if (!Math3D.vectorEquals(self.movedir, Globals.vec3_origin)) {
+                float[] forward = {0, 0, 0};
+
+                Math3D.angleVectors(other.s.angles, forward, null, null);
+                if (Math3D.dotProduct(forward, self.movedir) < 0)
+                    return;
+            }
+
+            self.activator = other;
+            multi_trigger(self);
+        }
+    };
+    /**
+     * QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED
+     * Variable sized repeatable trigger. Must be targeted at one or more
+     * entities. If "delay" is set, the trigger waits some time after activating
+     * before firing. "wait" : Seconds between triggerings. (.2 default) sounds
+     * 1) secret 2) beep beep 3) large switch 4) set "message" to text string
+     */
+    static final EntUseAdapter trigger_enable = new EntUseAdapter() {
+        public String getID() {
+            return "trigger_enable";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            self.solid = Defines.SOLID_TRIGGER;
+            self.use = Use_Multi;
+            GameBase.gi.linkentity(self);
+        }
+    };
+    /**
+     * QUAKED trigger_counter (.5 .5 .5) ? nomessage Acts as an intermediary for
+     * an action that takes multiple inputs.
+     * <p>
+     * If nomessage is not set, t will print "1 more.. " etc when triggered and
+     * "sequence complete" when finished.
+     * <p>
+     * After the counter has been triggered "count" times (default 2), it will
+     * fire all of it's targets and remove itself.
+     */
+    static final EntUseAdapter trigger_counter_use = new EntUseAdapter() {
+        public String getID() {
+            return "trigger_counter_use";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            if (self.count == 0)
+                return;
+
+            self.count--;
+
+            if (self.count != 0) {
+                if (0 == (self.spawnflags & 1)) {
+                    GameBase.gi.centerprintf(activator, self.count
+                            + " more to go...");
+                    GameBase.gi.sound(activator, Defines.CHAN_AUTO, GameBase.gi
+                                    .soundindex("misc/talk1.wav"), 1,
+                            Defines.ATTN_NORM, 0);
+                }
+                return;
+            }
+
+            if (0 == (self.spawnflags & 1)) {
+                GameBase.gi.centerprintf(activator, "Sequence completed!");
+                GameBase.gi.sound(activator, Defines.CHAN_AUTO, GameBase.gi
+                        .soundindex("misc/talk1.wav"), 1, Defines.ATTN_NORM, 0);
+            }
+            self.activator = activator;
+            multi_trigger(self);
+        }
+    };
+
+    /*
+     * ==============================================================================
+     * 
+     * trigger_always
+     * 
+     * ==============================================================================
+     */
+    /**
+     * QUAKED trigger_gravity (.5 .5 .5) ? Changes the touching entites gravity
+     * to the value of "gravity". 1.0 is standard gravity for the level.
+     */
+
+    static final EntTouchAdapter trigger_gravity_touch = new EntTouchAdapter() {
+        public String getID() {
+            return "trigger_gravity_touch";
+        }
+
+        public void touch(EDict self, EDict other, cplane_t plane,
+                          csurface_t surf) {
+            other.gravity = self.gravity;
+        }
+    };
+    /**
+     * QUAKED trigger_monsterjump (.5 .5 .5) ? Walking monsters that touch this
+     * will jump in the direction of the trigger's angle "speed" default to 200,
+     * the speed thrown forward "height" default to 200, the speed thrown
+     * upwards
+     */
+
+    static final EntTouchAdapter trigger_monsterjump_touch = new EntTouchAdapter() {
+        public String getID() {
+            return "trigger_monsterjump_touch";
+        }
+
+        public void touch(EDict self, EDict other, cplane_t plane,
+                          csurface_t surf) {
+            if ((other.flags & (Defines.FL_FLY | Defines.FL_SWIM)) != 0)
+                return;
+            if ((other.svflags & Defines.SVF_DEADMONSTER) != 0)
+                return;
+            if (0 == (other.svflags & Defines.SVF_MONSTER))
+                return;
+
+            // set XY even if not on ground, so the jump will clear lips
+            other.velocity[0] = self.movedir[0] * self.speed;
+            other.velocity[1] = self.movedir[1] * self.speed;
+
+            if (other.groundentity != null)
+                return;
+
+            other.groundentity = null;
+            other.velocity[2] = self.movedir[2];
+        }
+    };
+    public static int windsound;
+    static final EntTouchAdapter trigger_push_touch = new EntTouchAdapter() {
+        public String getID() {
+            return "trigger_push_touch";
+        }
+
+        public void touch(EDict self, EDict other, cplane_t plane,
+                          csurface_t surf) {
+            if (Lib.strcmp(other.classname, "grenade") == 0) {
+                Math3D.vectorScale(self.movedir, self.speed * 10,
+                        other.velocity);
+            } else if (other.health > 0) {
+                Math3D.vectorScale(self.movedir, self.speed * 10,
+                        other.velocity);
+
+                if (other.client != null) {
+                    // don't take falling damage immediately from this
+                    Math3D.vectorCopy(other.velocity, other.client.oldvelocity);
+                    if (other.fly_sound_debounce_time < GameBase.level.time) {
+                        other.fly_sound_debounce_time = GameBase.level.time + 1.5f;
+                        GameBase.gi.sound(other, Defines.CHAN_AUTO, windsound,
+                                1, Defines.ATTN_NORM, 0);
+                    }
+                }
+            }
+            if ((self.spawnflags & PUSH_ONCE) != 0)
+                GameUtil.G_FreeEdict(self);
+        }
+    };
+    /**
+     * QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION
+     * SLOW Any entity that touches this will be hurt.
+     * <p>
+     * It does dmg points of damage each server frame
+     * <p>
+     * SILENT supresses playing the sound SLOW changes the damage rate to once
+     * per second NO_PROTECTION *nothing* stops the damage
+     * <p>
+     * "dmg" default 5 (whole numbers only)
+     */
+    static EntUseAdapter hurt_use = new EntUseAdapter() {
+        public String getID() {
+            return "hurt_use";
+        }
+
+        public void use(EDict self, EDict other, EDict activator) {
+            if (self.solid == Defines.SOLID_NOT)
+                self.solid = Defines.SOLID_TRIGGER;
+            else
+                self.solid = Defines.SOLID_NOT;
+            GameBase.gi.linkentity(self);
+
+            if (0 == (self.spawnflags & 2))
+                self.use = null;
+        }
+    };
+
+    public static void InitTrigger(EDict self) {
+        if (!Math3D.vectorEquals(self.s.angles, Globals.vec3_origin))
+            GameBase.G_SetMovedir(self.s.angles, self.movedir);
+
+        self.solid = Defines.SOLID_TRIGGER;
+        self.movetype = Defines.MOVETYPE_NONE;
+        GameBase.gi.setmodel(self, self.model);
+        self.svflags = Defines.SVF_NOCLIENT;
+    }
+
+    // the trigger was just activated
+    // ent.activator should be set to the activator so it can be held through a
+    // delay so wait for the delay time before firing
+    public static void multi_trigger(EDict ent) {
+        if (ent.nextthink != 0)
+            return; // already been triggered
+
+        GameUtil.G_UseTargets(ent, ent.activator);
+
+        if (ent.wait > 0) {
+            ent.think = multi_wait;
+            ent.nextthink = GameBase.level.time + ent.wait;
+        } else { // we can't just remove (self) here, because this is a touch
+            // function
+            // called while looping through area links...
+            ent.touch = null;
+            ent.nextthink = GameBase.level.time + Defines.FRAMETIME;
+            ent.think = GameUtil.G_FreeEdictA;
+        }
+    }
+
+    public static void SP_trigger_multiple(EDict ent) {
+        if (ent.sounds == 1)
+            ent.noise_index = GameBase.gi.soundindex("misc/secret.wav");
+        else if (ent.sounds == 2)
+            ent.noise_index = GameBase.gi.soundindex("misc/talk.wav");
+        else if (ent.sounds == 3)
+            ent.noise_index = GameBase.gi.soundindex("misc/trigger1.wav");
+
+        if (ent.wait == 0)
+            ent.wait = 0.2f;
+
+        ent.touch = Touch_Multi;
+        ent.movetype = Defines.MOVETYPE_NONE;
+        ent.svflags |= Defines.SVF_NOCLIENT;
+
+        if ((ent.spawnflags & 4) != 0) {
+            ent.solid = Defines.SOLID_NOT;
+            ent.use = trigger_enable;
+        } else {
+            ent.solid = Defines.SOLID_TRIGGER;
+            ent.use = Use_Multi;
+        }
+
+        if (!Math3D.vectorEquals(ent.s.angles, Globals.vec3_origin))
+            GameBase.G_SetMovedir(ent.s.angles, ent.movedir);
+
+        GameBase.gi.setmodel(ent, ent.model);
+        GameBase.gi.linkentity(ent);
+    }
+
+    /*
+     * ==============================================================================
+     * 
+     * trigger_key
+     * 
+     * ==============================================================================
+     */
+
+    /**
+     * QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED Triggers once, then
+     * removes itself. You must set the key "target" to the name of another
+     * object in the level that has a matching "targetname".
+     * <p>
+     * If TRIGGERED, this trigger must be triggered before it is live.
+     * <p>
+     * sounds 1) secret 2) beep beep 3) large switch 4)
+     * <p>
+     * "message" string to be displayed when triggered
+     */
+
+    public static void SP_trigger_once(EDict ent) {
+        // make old maps work because I messed up on flag assignments here
+        // triggered was on bit 1 when it should have been on bit 4
+        if ((ent.spawnflags & 1) != 0) {
+            float[] v = {0, 0, 0};
+
+            Math3D.vectorMA(ent.mins, 0.5f, ent.size, v);
+            ent.spawnflags &= ~1;
+            ent.spawnflags |= 4;
+            GameBase.gi.dprintf("fixed TRIGGERED flag on " + ent.classname
+                    + " at " + Lib.vtos(v) + "\n");
+        }
+
+        ent.wait = -1;
+        SP_trigger_multiple(ent);
+    }
+
+    public static void SP_trigger_relay(EDict self) {
+        self.use = trigger_relay_use;
+    }
+
+    /*
+     * ==============================================================================
+     * 
+     * trigger_push
+     * 
+     * ==============================================================================
+     */
+
+    public static void SP_trigger_key(EDict self) {
+        if (GameBase.st.item == null) {
+            GameBase.gi.dprintf("no key item for trigger_key at "
+                    + Lib.vtos(self.s.origin) + "\n");
+        }
+    }
+
+    public static void SP_trigger_counter(EDict self) {
+        self.wait = -1;
+        if (0 == self.count)
+            self.count = 2;
+
+        self.use = trigger_counter_use;
+    }
+
+    /*
+     * QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8) This trigger will
+     * always fire. It is activated by the world.
+     */
+    public static void SP_trigger_always(EDict ent) {
+        // we must have some delay to make sure our use targets are present
+        if (ent.delay < 0.2f)
+            ent.delay = 0.2f;
+        GameUtil.G_UseTargets(ent, ent);
+    }
+
+    /*
+     * QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE Pushes the player "speed"
+     * defaults to 1000
+     */
+    public static void SP_trigger_push(EDict self) {
+        InitTrigger(self);
+        windsound = GameBase.gi.soundindex("misc/windfly.wav");
+        self.touch = trigger_push_touch;
+        if (0 == self.speed)
+            self.speed = 1000;
+        GameBase.gi.linkentity(self);
+    }
+
+
+    /*
+     * ==============================================================================
+     * 
+     * trigger_gravity
+     * 
+     * ==============================================================================
+     */
+
+    public static void SP_trigger_gravity(EDict self) {
+        if (GameBase.st.gravity == null) {
+            GameBase.gi.dprintf("trigger_gravity without gravity set at "
+                    + Lib.vtos(self.s.origin) + "\n");
+            GameUtil.G_FreeEdict(self);
+            return;
+        }
+
+        InitTrigger(self);
+        self.gravity = Lib.atoi(GameBase.st.gravity);
+        self.touch = trigger_gravity_touch;
+    }
+
+    /*
+     * ==============================================================================
+     * 
+     * trigger_monsterjump
+     * 
+     * ==============================================================================
+     */
+
+    public static void SP_trigger_monsterjump(EDict self) {
+        if (0 == self.speed)
+            self.speed = 200;
+        if (0 == GameBase.st.height)
+            GameBase.st.height = 200;
+        if (self.s.angles[Defines.YAW] == 0)
+            self.s.angles[Defines.YAW] = 360;
+        InitTrigger(self);
+        self.touch = trigger_monsterjump_touch;
+        self.movedir[2] = GameBase.st.height;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/GameUtil.java b/src/main/java/lwjake2/game/GameUtil.java
new file mode 100644 (file)
index 0000000..0452684
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.qcommon.Com;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+
+class GameUtil {
+
+    public static final EntThinkAdapter G_FreeEdictA = new EntThinkAdapter() {
+        public String getID() {
+            return "G_FreeEdictA";
+        }
+
+        public void think(EDict ent) {
+            G_FreeEdict(ent);
+        }
+    };
+    private static final EntThinkAdapter Think_Delay = new EntThinkAdapter() {
+        public String getID() {
+            return "Think_Delay";
+        }
+
+        public void think(EDict ent) {
+            G_UseTargets(ent, ent.activator);
+            G_FreeEdict(ent);
+        }
+    };
+
+    private static void checkClassname(EDict ent) {
+
+        if (ent.classname == null) {
+            Com.Printf("edict with classname = null: " + ent.index);
+        }
+    }
+
+    /**
+     * Use the targets.
+     * <p>
+     * The global "activator" should be set to the entity that initiated the
+     * firing.
+     * <p>
+     * If self.delay is set, a DelayedUse entity will be created that will
+     * actually do the SUB_UseTargets after that many seconds have passed.
+     * <p>
+     * Centerprints any self.message to the activator.
+     * <p>
+     * Search for (string)targetname in all entities that match
+     * (string)self.target and call their .use function
+     */
+
+    public static void G_UseTargets(EDict ent, EDict activator) {
+        EDict t;
+
+        checkClassname(ent);
+
+        // check for a delay
+        if (ent.delay != 0) {
+            // create a temp object to fire at a later time
+            t = G_Spawn();
+            t.classname = "DelayedUse";
+            t.nextthink = GameBase.level.time + ent.delay;
+            t.think = Think_Delay;
+            t.activator = activator;
+            if (activator == null)
+                GameBase.gi.dprintf("Think_Delay with no activator\n");
+            t.message = ent.message;
+            t.target = ent.target;
+            return;
+        }
+
+
+        // print the message
+        if ((ent.message != null)
+                && (activator.svflags & Defines.SVF_MONSTER) == 0) {
+            GameBase.gi.centerprintf(activator, "" + ent.message);
+            if (ent.noise_index != 0)
+                GameBase.gi.sound(activator, Defines.CHAN_AUTO,
+                        ent.noise_index, 1, Defines.ATTN_NORM, 0);
+            else
+                GameBase.gi.sound(activator, Defines.CHAN_AUTO, GameBase.gi
+                        .soundindex("misc/talk1.wav"), 1, Defines.ATTN_NORM, 0);
+        }
+
+        // kill killtargets
+        EdictIterator edit = null;
+
+        // fire targets
+        if (ent.target != null) {
+            edit = null;
+            while ((edit = GameBase.G_Find(edit, GameBase.findByTarget,
+                    ent.target)) != null) {
+                t = edit.o;
+                // doors fire area portals in a specific way
+                if (Lib.Q_stricmp("func_areaportal", t.classname) == 0
+                        && (Lib.Q_stricmp("func_door", ent.classname) == 0 || Lib
+                        .Q_stricmp("func_door_rotating", ent.classname) == 0))
+                    continue;
+
+                if (t == ent) {
+                    GameBase.gi.dprintf("WARNING: Entity used itself.\n");
+                } else {
+                    if (t.use != null)
+                        t.use.use(t, ent, activator);
+                }
+                if (!ent.inuse) {
+                    GameBase.gi
+                            .dprintf("entity was removed while using targets\n");
+                    return;
+                }
+            }
+        }
+    }
+
+    public static void G_InitEdict(EDict e, int i) {
+        e.inuse = true;
+        e.classname = "noclass";
+        e.gravity = 1.0f;
+        //e.s.number= e - g_edicts;
+        e.s = new entity_state_t(e);
+        e.s.number = i;
+        e.index = i;
+    }
+
+    /**
+     * Either finds a free edict, or allocates a new one. Try to avoid reusing
+     * an entity that was recently freed, because it can cause the client to
+     * think the entity morphed into something else instead of being removed and
+     * recreated, which can cause interpolated angles and bad trails.
+     */
+    public static EDict G_Spawn() {
+        int i;
+        EDict e = null;
+
+        for (i = (int) GameBase.maxclients.value + 1; i < GameBase.num_edicts; i++) {
+            e = GameBase.g_edicts[i];
+            // the first couple seconds of server time can involve a lot of
+            // freeing and allocating, so relax the replacement policy
+            if (!e.inuse
+                    && (e.freetime < 2 || GameBase.level.time - e.freetime > 0.5)) {
+                e = GameBase.g_edicts[i] = new EDict(i);
+                G_InitEdict(e, i);
+                return e;
+            }
+        }
+
+        if (i == GameBase.game.maxentities)
+            GameBase.gi.error("ED_Alloc: no free edicts");
+
+        e = GameBase.g_edicts[i] = new EDict(i);
+        GameBase.num_edicts++;
+        G_InitEdict(e, i);
+        return e;
+    }
+
+    /**
+     * Marks the edict as free
+     */
+    public static void G_FreeEdict(EDict ed) {
+        GameBase.gi.unlinkentity(ed); // unlink from world
+
+        //if ((ed - g_edicts) <= (maxclients.value + BODY_QUEUE_SIZE))
+        if (ed.index <= (GameBase.maxclients.value + Defines.BODY_QUEUE_SIZE)) {
+            // gi.dprintf("tried to free special edict\n");
+            return;
+        }
+
+        GameBase.g_edicts[ed.index] = new EDict(ed.index);
+        ed.classname = "freed";
+        ed.freetime = GameBase.level.time;
+        ed.inuse = false;
+    }
+
+    /**
+     * Call after linking a new trigger in during gameplay to force all entities
+     * it covers to immediately touch it.
+     */
+
+    public static void G_ClearEdict(EDict ent) {
+        int i = ent.index;
+        GameBase.g_edicts[i] = new EDict(i);
+    }
+
+    /**
+     * Returns true, if two edicts are on the same team.
+     */
+    public static boolean OnSameTeam(EDict ent1, EDict ent2) {
+        return 0 != ((int) (GameBase.dmflags.value) & (Defines.DF_MODELTEAMS | Defines.DF_SKINTEAMS)) && ClientTeam(ent1).equals(ClientTeam(ent2));
+
+    }
+
+    /**
+     * Returns the team string of an entity
+     * with respect to rteam_by_model and team_by_skin.
+     */
+    private static String ClientTeam(EDict ent) {
+        String value;
+
+        if (ent.client == null)
+            return "";
+
+        value = Info.Info_ValueForKey(ent.client.pers.userinfo, "skin");
+
+        int p = value.indexOf("/");
+
+        if (p == -1)
+            return value;
+
+        if (((int) (GameBase.dmflags.value) & Defines.DF_MODELTEAMS) != 0) {
+            return value.substring(0, p);
+        }
+
+        return value.substring(p + 1, value.length());
+    }
+
+    /**
+     * Returns 1 if the entity is visible to self, even if not infront().
+     */
+    public static boolean visible(EDict self, EDict other) {
+        float[] spot1 = {0, 0, 0};
+        float[] spot2 = {0, 0, 0};
+        trace_t trace;
+
+        Math3D.vectorCopy(self.s.origin, spot1);
+        spot1[2] += self.viewheight;
+        Math3D.vectorCopy(other.s.origin, spot2);
+        spot2[2] += other.viewheight;
+        trace = GameBase.gi.trace(spot1, Globals.vec3_origin,
+                Globals.vec3_origin, spot2, self, Defines.MASK_OPAQUE);
+
+        return trace.fraction == 1.0;
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/Info.java b/src/main/java/lwjake2/game/Info.java
new file mode 100644 (file)
index 0000000..eaff1a6
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.Defines;
+import lwjake2.qcommon.Com;
+
+import java.util.StringTokenizer;
+
+public class Info {
+
+    /**
+     * Returns a value for a key from an info string.
+     */
+    public static String Info_ValueForKey(String s, String key) {
+
+        StringTokenizer tk = new StringTokenizer(s, "\\");
+
+        while (tk.hasMoreTokens()) {
+            String key1 = tk.nextToken();
+
+            if (!tk.hasMoreTokens()) {
+                Com.Printf("MISSING VALUE\n");
+                return s;
+            }
+            String value1 = tk.nextToken();
+
+            if (key.equals(key1))
+                return value1;
+        }
+
+        return "";
+    }
+
+    /**
+     * Sets a value for a key in the user info string.
+     */
+    public static String Info_SetValueForKey(String s, String key, String value) {
+
+        if (value == null || value.length() == 0)
+            return s;
+
+        if (key.indexOf('\\') != -1 || value.indexOf('\\') != -1) {
+            Com.Printf("Can't use keys or values with a \\\n");
+            return s;
+        }
+
+        if (key.indexOf(';') != -1) {
+            Com.Printf("Can't use keys or values with a semicolon\n");
+            return s;
+        }
+
+        if (key.indexOf('"') != -1 || value.indexOf('"') != -1) {
+            Com.Printf("Can't use keys or values with a \"\n");
+            return s;
+        }
+
+        if (key.length() > Defines.MAX_INFO_KEY - 1
+                || value.length() > Defines.MAX_INFO_KEY - 1) {
+            Com.Printf("Keys and values must be < 64 characters.\n");
+            return s;
+        }
+
+        StringBuilder sb = new StringBuilder(Info_RemoveKey(s, key));
+
+        if (sb.length() + 2 + key.length() + value.length() > Defines.MAX_INFO_STRING) {
+
+            Com.Printf("Info string length exceeded\n");
+            return s;
+        }
+
+        sb.append('\\').append(key).append('\\').append(value);
+
+        return sb.toString();
+    }
+
+    /**
+     * Removes a key and value from an info string.
+     */
+    private static String Info_RemoveKey(String s, String key) {
+
+        StringBuilder sb = new StringBuilder(512);
+
+        if (key.indexOf('\\') != -1) {
+            Com.Printf("Can't use a key with a \\\n");
+            return s;
+        }
+
+        StringTokenizer tk = new StringTokenizer(s, "\\");
+
+        while (tk.hasMoreTokens()) {
+            String key1 = tk.nextToken();
+
+            if (!tk.hasMoreTokens()) {
+                Com.Printf("MISSING VALUE\n");
+                return s;
+            }
+            String value1 = tk.nextToken();
+
+            if (!key.equals(key1))
+                sb.append('\\').append(key1).append('\\').append(value1);
+        }
+
+        return sb.toString();
+
+    }
+
+    /**
+     * Some characters are illegal in info strings because they can mess up the
+     * server's parsing.
+     */
+    public static boolean Info_Validate(String s) {
+        return !((s.indexOf('"') != -1) || (s.indexOf(';') != -1));
+    }
+
+    public static void Print(String s) {
+
+        StringBuilder sb = new StringBuilder(512);
+        StringTokenizer tk = new StringTokenizer(s, "\\");
+
+        while (tk.hasMoreTokens()) {
+
+            String key1 = tk.nextToken();
+
+            if (!tk.hasMoreTokens()) {
+                Com.Printf("MISSING VALUE\n");
+                return;
+            }
+
+            String value1 = tk.nextToken();
+
+            sb.append(key1);
+
+            int len = key1.length();
+
+            if (len < 20) {
+                String fillspaces = "                     ";
+                sb.append(fillspaces.substring(len));
+            }
+            sb.append('=').append(value1).append('\n');
+        }
+        Com.Printf(sb.toString());
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/ItemDropAdapter.java b/src/main/java/lwjake2/game/ItemDropAdapter.java
new file mode 100644 (file)
index 0000000..55f871b
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+abstract class ItemDropAdapter extends SuperAdapter {
+    public void drop(EDict ent, gitem_t item) {
+    }
+}
diff --git a/src/main/java/lwjake2/game/ItemUseAdapter.java b/src/main/java/lwjake2/game/ItemUseAdapter.java
new file mode 100644 (file)
index 0000000..7bebfd2
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+abstract class ItemUseAdapter extends SuperAdapter {
+    public void use(EDict ent, gitem_t item) {
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/M_Player.java b/src/main/java/lwjake2/game/M_Player.java
new file mode 100644 (file)
index 0000000..7a19d13
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+class M_Player {
+    // This file generated by qdata - Do NOT Modify
+
+    public final static int FRAME_stand01 = 0;
+
+    public final static int FRAME_stand40 = 39;
+
+    public final static int FRAME_run1 = 40;
+
+    public final static int FRAME_run6 = 45;
+
+    public final static int FRAME_pain101 = 54;
+
+    public final static int FRAME_pain104 = 57;
+
+    public final static int FRAME_pain201 = 58;
+
+    public final static int FRAME_pain204 = 61;
+
+    public final static int FRAME_pain301 = 62;
+
+    public final static int FRAME_pain304 = 65;
+
+    public final static int FRAME_jump1 = 66;
+
+    public final static int FRAME_jump2 = 67;
+
+    public final static int FRAME_jump3 = 68;
+
+    public final static int FRAME_jump6 = 71;
+
+    public final static int FRAME_flip01 = 72;
+
+    public final static int FRAME_flip12 = 83;
+
+    public final static int FRAME_salute01 = 84;
+
+    public final static int FRAME_salute11 = 94;
+
+    public final static int FRAME_taunt01 = 95;
+
+    public final static int FRAME_taunt17 = 111;
+
+    public final static int FRAME_wave01 = 112;
+
+    public final static int FRAME_wave11 = 122;
+
+    public final static int FRAME_point01 = 123;
+
+    public final static int FRAME_point12 = 134;
+
+    public final static int FRAME_crstnd01 = 135;
+
+    public final static int FRAME_crstnd19 = 153;
+
+    public final static int FRAME_crwalk1 = 154;
+
+    public final static int FRAME_crwalk6 = 159;
+
+    public final static int FRAME_crpain1 = 169;
+
+    public final static int FRAME_crpain4 = 172;
+
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/Move.java b/src/main/java/lwjake2/game/Move.java
new file mode 100644 (file)
index 0000000..26d15ca
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.Defines;
+import lwjake2.util.Math3D;
+
+import java.util.Arrays;
+
+public class Move {
+
+    // pmove->pm_flags
+    public final static int PMF_DUCKED = 1;
+    public final static int PMF_JUMP_HELD = 2;
+    public final static int PMF_ON_GROUND = 4;
+    public final static int PMF_TIME_WATERJUMP = 8; // pm_time is waterjump
+    public final static int PMF_TIME_LAND = 16; // pm_time is time before rejump
+    public final static int PMF_TIME_TELEPORT = 32; // pm_time is non-moving
+    public final static int PMF_NO_PREDICTION = 64; // temporarily disables
+    // state (in / out)
+    public final pmove_state_t s = new pmove_state_t();
+    // command (in)
+    public final usercmd_t cmd = new usercmd_t();
+    public final EDict[] touchents = new EDict[Defines.MAXTOUCH];
+    public final float[] viewangles = {0, 0, 0}; // clamped
+    public final float[] mins = {0, 0, 0};
+    public final float[] maxs = {0, 0, 0}; // bounding box size
+    public boolean snapinitial; // if s has been changed outside pmove
+    // results (out)
+    public int numtouch;
+    public float viewheight;
+    public EDict groundentity;
+    public int watertype;
+    public int waterlevel;
+    public TraceAdapter trace;
+    public PointContentsAdapter pointcontents;
+
+    public void clear() {
+        groundentity = null;
+        waterlevel = watertype = 0;
+        trace = null;
+        pointcontents = null;
+        Math3D.vectorClear(mins);
+        Math3D.vectorClear(maxs);
+        viewheight = 0;
+        Math3D.vectorClear(viewangles);
+        Arrays.fill(touchents, null);
+        numtouch = 0;
+        snapinitial = false;
+        cmd.clear();
+        s.clear();
+    }
+    // time
+
+    public static class TraceAdapter {
+        // callbacks to test the world
+        public trace_t trace(float[] start, float[] mins, float[] maxs,
+                             float[] end) {
+            return null;
+        }
+    }
+    // prediction (used for
+    // grappling hook)
+
+    public static class PointContentsAdapter {
+        // callbacks to test the world
+        public int pointcontents(float[] point) {
+            return 0;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/PlayerClient.java b/src/main/java/lwjake2/game/PlayerClient.java
new file mode 100644 (file)
index 0000000..4f4d736
--- /dev/null
@@ -0,0 +1,1182 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.Defines;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+
+public class PlayerClient {
+
+    static final EntThinkAdapter SP_FixCoopSpots = new EntThinkAdapter() {
+        public String getID() {
+            return "SP_FixCoopSpots";
+        }
+
+        public void think(EDict self) {
+
+            EDict spot;
+            float[] d = {0, 0, 0};
+
+            spot = null;
+            EdictIterator es = null;
+
+            while (true) {
+                es = GameBase.G_Find(es, GameBase.findByClass,
+                        "info_player_start");
+
+                if (es == null)
+                    return;
+
+                spot = es.o;
+
+                if (spot.targetname == null)
+                    continue;
+                Math3D.vectorSubtract(self.s.origin, spot.s.origin, d);
+                if (Math3D.vectorLength(d) < 384) {
+                    if ((self.targetname == null)
+                            || Lib.Q_stricmp(self.targetname, spot.targetname) != 0) {
+                        // gi.dprintf("FixCoopSpots changed %s at %s targetname
+                        // from %s to %s\n", self.classname,
+                        // vtos(self.s.origin), self.targetname,
+                        // spot.targetname);
+                        self.targetname = spot.targetname;
+                    }
+                    return;
+                }
+            }
+        }
+    };
+    static final EntThinkAdapter SP_CreateCoopSpots = new EntThinkAdapter() {
+        public String getID() {
+            return "SP_CreateCoopSpots";
+        }
+
+        public void think(EDict self) {
+
+            EDict spot;
+
+            if (Lib.Q_stricmp(GameBase.level.mapname, "security") == 0) {
+                spot = GameUtil.G_Spawn();
+                spot.classname = "info_player_coop";
+                spot.s.origin[0] = 188 - 64;
+                spot.s.origin[1] = -164;
+                spot.s.origin[2] = 80;
+                spot.targetname = "jail3";
+                spot.s.angles[1] = 90;
+
+                spot = GameUtil.G_Spawn();
+                spot.classname = "info_player_coop";
+                spot.s.origin[0] = 188 + 64;
+                spot.s.origin[1] = -164;
+                spot.s.origin[2] = 80;
+                spot.targetname = "jail3";
+                spot.s.angles[1] = 90;
+
+                spot = GameUtil.G_Spawn();
+                spot.classname = "info_player_coop";
+                spot.s.origin[0] = 188 + 128;
+                spot.s.origin[1] = -164;
+                spot.s.origin[2] = 80;
+                spot.targetname = "jail3";
+                spot.s.angles[1] = 90;
+            }
+        }
+    };
+    static EDict pm_passent;
+    // pmove doesn't need to know about passent and contentmask
+    public static final Move.TraceAdapter PM_trace = new Move.TraceAdapter() {
+
+        public trace_t trace(float[] start, float[] mins, float[] maxs,
+                             float[] end) {
+            if (pm_passent.health > 0)
+                return GameBase.gi.trace(start, mins, maxs, end, pm_passent,
+                        Defines.MASK_PLAYERSOLID);
+            else
+                return GameBase.gi.trace(start, mins, maxs, end, pm_passent,
+                        Defines.MASK_DEADSOLID);
+        }
+
+    };
+
+    /**
+     * QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) The normal
+     * starting point for a level.
+     */
+    public static void SP_info_player_start(EDict self) {
+        if (GameBase.coop.value == 0)
+            return;
+        if (Lib.Q_stricmp(GameBase.level.mapname, "security") == 0) {
+            // invoke one of our gross, ugly, disgusting hacks
+            self.think = PlayerClient.SP_CreateCoopSpots;
+            self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+        }
+    }
+
+    /**
+     * QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) potential
+     * spawning position for deathmatch games.
+     */
+    public static void SP_info_player_deathmatch(EDict self) {
+        if (0 == GameBase.deathmatch.value) {
+            GameUtil.G_FreeEdict(self);
+            return;
+        }
+        GameMisc.SP_misc_teleporter_dest.think(self);
+    }
+
+    /**
+     * QUAKED info_player_coop (1 0 1) (-16 -16 -24) (16 16 32) potential
+     * spawning position for coop games.
+     */
+
+    public static void SP_info_player_coop(EDict self) {
+        if (0 == GameBase.coop.value) {
+            GameUtil.G_FreeEdict(self);
+            return;
+        }
+
+        if ((Lib.Q_stricmp(GameBase.level.mapname, "jail2") == 0)
+                || (Lib.Q_stricmp(GameBase.level.mapname, "jail4") == 0)
+                || (Lib.Q_stricmp(GameBase.level.mapname, "mine1") == 0)
+                || (Lib.Q_stricmp(GameBase.level.mapname, "mine2") == 0)
+                || (Lib.Q_stricmp(GameBase.level.mapname, "mine3") == 0)
+                || (Lib.Q_stricmp(GameBase.level.mapname, "mine4") == 0)
+                || (Lib.Q_stricmp(GameBase.level.mapname, "lab") == 0)
+                || (Lib.Q_stricmp(GameBase.level.mapname, "boss1") == 0)
+                || (Lib.Q_stricmp(GameBase.level.mapname, "fact3") == 0)
+                || (Lib.Q_stricmp(GameBase.level.mapname, "biggun") == 0)
+                || (Lib.Q_stricmp(GameBase.level.mapname, "space") == 0)
+                || (Lib.Q_stricmp(GameBase.level.mapname, "command") == 0)
+                || (Lib.Q_stricmp(GameBase.level.mapname, "power2") == 0)
+                || (Lib.Q_stricmp(GameBase.level.mapname, "strike") == 0)) {
+            // invoke one of our gross, ugly, disgusting hacks
+            self.think = PlayerClient.SP_FixCoopSpots;
+            self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+        }
+    }
+
+    /**
+     * QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) The
+     * deathmatch intermission point will be at one of these Use 'angles'
+     * instead of 'angle', so you can set pitch or roll as well as yaw. 'pitch
+     * yaw roll'
+     */
+    public static void SP_info_player_intermission() {
+    }
+
+    /**
+     * This is only called when the game first initializes in single player, but
+     * is called after each death and level change in deathmatch.
+     */
+    public static void InitClientPersistant(Client client) {
+
+        client.pers = new client_persistant_t();
+
+        client.pers.inventory[client.pers.selected_item] = 1;
+
+        /*
+         * Give shotgun. item = FindItem("Shotgun"); client.pers.selected_item =
+         * ITEM_INDEX(item); client.pers.inventory[client.pers.selected_item] =
+         * 1;
+         */
+
+
+        client.pers.health = 100;
+        client.pers.max_health = 100;
+        client.pers.max_slugs = 50;
+
+        client.pers.connected = true;
+    }
+
+    public static void InitClientResp(Client client) {
+        //memset(& client.resp, 0, sizeof(client.resp));
+        client.resp.clear(); //  ok.
+        client.resp.enterframe = GameBase.level.framenum;
+        client.resp.coop_respawn.set(client.pers);
+    }
+
+    /**
+     * Some information that should be persistant, like health, is still stored
+     * in the edict structure, so it needs to be mirrored out to the client
+     * structure before all the edicts are wiped.
+     */
+    public static void SaveClientData() {
+        int i;
+        EDict ent;
+
+        for (i = 0; i < GameBase.game.maxclients; i++) {
+            ent = GameBase.g_edicts[1 + i];
+            if (!ent.inuse)
+                continue;
+
+            GameBase.game.clients[i].pers.health = ent.health;
+            GameBase.game.clients[i].pers.max_health = ent.max_health;
+            GameBase.game.clients[i].pers.savedFlags = (ent.flags & (Defines.FL_GODMODE
+                    | Defines.FL_NOTARGET | Defines.FL_POWER_ARMOR));
+
+        }
+    }
+
+    public static void FetchClientEntData(EDict ent) {
+        ent.health = ent.client.pers.health;
+        ent.max_health = ent.client.pers.max_health;
+        ent.flags |= ent.client.pers.savedFlags;
+    }
+
+    /**
+     * Returns the distance to the nearest player from the given spot.
+     */
+    static float PlayersRangeFromSpot(EDict spot) {
+        EDict player;
+        float bestplayerdistance;
+        float[] v = {0, 0, 0};
+        int n;
+        float playerdistance;
+
+        bestplayerdistance = 9999999;
+
+        for (n = 1; n <= GameBase.maxclients.value; n++) {
+            player = GameBase.g_edicts[n];
+
+            if (!player.inuse)
+                continue;
+
+            if (player.health <= 0)
+                continue;
+
+            Math3D.vectorSubtract(spot.s.origin, player.s.origin, v);
+            playerdistance = Math3D.vectorLength(v);
+
+            if (playerdistance < bestplayerdistance)
+                bestplayerdistance = playerdistance;
+        }
+
+        return bestplayerdistance;
+    }
+
+    /**
+     * Go to a random point, but NOT the two points closest to other players.
+     */
+    public static EDict SelectRandomDeathmatchSpawnPoint() {
+        EDict spot, spot1, spot2;
+        int count = 0;
+        int selection;
+        float range, range1, range2;
+
+        spot = null;
+        range1 = range2 = 99999;
+        spot1 = spot2 = null;
+
+        EdictIterator es = null;
+
+        while ((es = GameBase.G_Find(es, GameBase.findByClass,
+                "info_player_deathmatch")) != null) {
+            spot = es.o;
+            count++;
+            range = PlayersRangeFromSpot(spot);
+            if (range < range1) {
+                range1 = range;
+                spot1 = spot;
+            } else if (range < range2) {
+                range2 = range;
+                spot2 = spot;
+            }
+        }
+
+        if (count == 0)
+            return null;
+
+        if (count <= 2) {
+            spot1 = spot2 = null;
+        } else
+            count -= 2;
+
+        selection = Lib.rand() % count;
+
+        spot = null;
+        es = null;
+        do {
+            es = GameBase.G_Find(es, GameBase.findByClass,
+                    "info_player_deathmatch");
+
+            if (es == null)
+                break;
+
+            spot = es.o;
+            if (spot == spot1 || spot == spot2)
+                selection++;
+        } while (selection-- > 0);
+
+        return spot;
+    }
+
+    /**
+     * If turned on in the dmflags, select a spawn point far away from other players.
+     */
+    static EDict SelectFarthestDeathmatchSpawnPoint() {
+        EDict bestspot;
+        float bestdistance, bestplayerdistance;
+        EDict spot;
+
+        spot = null;
+        bestspot = null;
+        bestdistance = 0;
+
+        EdictIterator es = null;
+        while ((es = GameBase.G_Find(es, GameBase.findByClass,
+                "info_player_deathmatch")) != null) {
+            spot = es.o;
+            bestplayerdistance = PlayersRangeFromSpot(spot);
+
+            if (bestplayerdistance > bestdistance) {
+                bestspot = spot;
+                bestdistance = bestplayerdistance;
+            }
+        }
+
+        if (bestspot != null) {
+            return bestspot;
+        }
+
+        // if there is a player just spawned on each and every start spot
+        // we have no choice to turn one into a telefrag meltdown
+        EdictIterator edit = GameBase.G_Find(null, GameBase.findByClass,
+                "info_player_deathmatch");
+        if (edit == null)
+            return null;
+
+        return edit.o;
+    }
+
+
+    public static EDict SelectDeathmatchSpawnPoint() {
+        if (0 != ((int) (GameBase.dmflags.value) & Defines.DF_SPAWN_FARTHEST))
+            return SelectFarthestDeathmatchSpawnPoint();
+        else
+            return SelectRandomDeathmatchSpawnPoint();
+    }
+
+    public static EDict SelectCoopSpawnPoint(EDict ent) {
+        int index;
+        EDict spot = null;
+        String target;
+
+        //index = ent.client - game.clients;
+        index = ent.client.index;
+
+        // player 0 starts in normal player spawn point
+        if (index == 0)
+            return null;
+
+        spot = null;
+        EdictIterator es = null;
+
+        // assume there are four coop spots at each spawnpoint
+        while (true) {
+
+            es = GameBase.G_Find(es, GameBase.findByClass,
+                    "info_player_coop");
+
+            if (es == null)
+                return null;
+
+            spot = es.o;
+
+            if (spot == null)
+                return null; // we didn't have enough...
+
+            target = spot.targetname;
+            if (target == null)
+                target = "";
+            if (Lib.Q_stricmp(GameBase.game.spawnpoint, target) == 0) {
+                // this is a coop spawn point for one of the clients here
+                index--;
+                if (0 == index)
+                    return spot; // this is it
+            }
+        }
+
+    }
+
+    /**
+     * Chooses a player start, deathmatch start, coop start, etc.
+     */
+    public static void SelectSpawnPoint(EDict ent, float[] origin,
+                                        float[] angles) {
+        EDict spot = null;
+
+        if (GameBase.deathmatch.value != 0)
+            spot = SelectDeathmatchSpawnPoint();
+        else if (GameBase.coop.value != 0)
+            spot = SelectCoopSpawnPoint(ent);
+
+        EdictIterator es = null;
+        // find a single player start spot
+        if (null == spot) {
+            while ((es = GameBase.G_Find(es, GameBase.findByClass,
+                    "info_player_start")) != null) {
+                spot = es.o;
+
+                if (GameBase.game.spawnpoint.length() == 0
+                        && spot.targetname == null)
+                    break;
+
+                if (GameBase.game.spawnpoint.length() == 0
+                        || spot.targetname == null)
+                    continue;
+
+                if (Lib.Q_stricmp(GameBase.game.spawnpoint, spot.targetname) == 0)
+                    break;
+            }
+
+            if (null == spot) {
+                if (GameBase.game.spawnpoint.length() == 0) {
+                    // there wasn't a spawnpoint without a
+                    // target, so use any
+                    es = GameBase.G_Find(es, GameBase.findByClass,
+                            "info_player_start");
+
+                    if (es != null)
+                        spot = es.o;
+                }
+                if (null == spot) {
+                    GameBase.gi.error("Couldn't find spawn point "
+                            + GameBase.game.spawnpoint + "\n");
+                    return;
+                }
+            }
+        }
+
+        Math3D.vectorCopy(spot.s.origin, origin);
+        origin[2] += 9;
+        Math3D.vectorCopy(spot.s.angles, angles);
+    }
+
+
+    public static void InitBodyQue() {
+        int i;
+        EDict ent;
+
+        GameBase.level.body_que = 0;
+        for (i = 0; i < Defines.BODY_QUEUE_SIZE; i++) {
+            ent = GameUtil.G_Spawn();
+            ent.classname = "bodyque";
+        }
+    }
+
+    public static void CopyToBodyQue(EDict ent) {
+        EDict body;
+
+        // grab a body que and cycle to the next one
+        int i = (int) GameBase.maxclients.value + GameBase.level.body_que + 1;
+        body = GameBase.g_edicts[i];
+        GameBase.level.body_que = (GameBase.level.body_que + 1)
+                % Defines.BODY_QUEUE_SIZE;
+
+        // FIXME: send an effect on the removed body
+
+        GameBase.gi.unlinkentity(ent);
+
+        GameBase.gi.unlinkentity(body);
+        body.s = ent.s.getClone();
+
+        body.s.number = body.index;
+
+        body.svflags = ent.svflags;
+        Math3D.vectorCopy(ent.mins, body.mins);
+        Math3D.vectorCopy(ent.maxs, body.maxs);
+        Math3D.vectorCopy(ent.absmin, body.absmin);
+        Math3D.vectorCopy(ent.absmax, body.absmax);
+        Math3D.vectorCopy(ent.size, body.size);
+        body.solid = ent.solid;
+        body.clipmask = ent.clipmask;
+        body.owner = ent.owner;
+        body.movetype = ent.movetype;
+
+
+        GameBase.gi.linkentity(body);
+    }
+
+    public static void respawn(EDict self) {
+        if (GameBase.deathmatch.value != 0 || GameBase.coop.value != 0) {
+            // spectator's don't leave bodies
+            if (self.movetype != Defines.MOVETYPE_NOCLIP)
+                CopyToBodyQue(self);
+            self.svflags &= ~Defines.SVF_NOCLIENT;
+            PutClientInServer(self);
+
+            // add a teleportation effect
+            self.s.event = Defines.EV_PLAYER_TELEPORT;
+
+            // hold in place briefly
+            self.client.ps.pmove.pm_flags = Move.PMF_TIME_TELEPORT;
+            self.client.ps.pmove.pm_time = 14;
+
+            self.client.respawn_time = GameBase.level.time;
+
+            return;
+        }
+
+        // restart the entire server
+        GameBase.gi.AddCommandString("menu_loadgame\n");
+    }
+
+    private static boolean passwdOK(String i1, String i2) {
+        return !(i1.length() != 0 && !i1.equals("none") && !i1.equals(i2));
+    }
+
+    /**
+     * Only called when pers.spectator changes note that resp.spectator should
+     * be the opposite of pers.spectator here
+     */
+    public static void spectator_respawn(EDict ent) {
+        int i, numspec;
+
+        // if the user wants to become a spectator, make sure he doesn't
+        // exceed max_spectators
+
+        if (ent.client.pers.spectator) {
+            String value = Info.Info_ValueForKey(ent.client.pers.userinfo,
+                    "spectator");
+
+            if (!passwdOK(GameBase.spectator_password.string, value)) {
+                GameBase.gi.cprintf(ent, Defines.PRINT_HIGH,
+                        "Spectator password incorrect.\n");
+                ent.client.pers.spectator = false;
+                GameBase.gi.WriteByte(Defines.svc_stufftext);
+                GameBase.gi.WriteString("spectator 0\n");
+                GameBase.gi.unicast(ent, true);
+                return;
+            }
+
+            // count spectators
+            for (i = 1, numspec = 0; i <= GameBase.maxclients.value; i++)
+                if (GameBase.g_edicts[i].inuse
+                        && GameBase.g_edicts[i].client.pers.spectator)
+                    numspec++;
+
+            if (numspec >= GameBase.maxspectators.value) {
+                GameBase.gi.cprintf(ent, Defines.PRINT_HIGH,
+                        "Server spectator limit is full.");
+                ent.client.pers.spectator = false;
+                // reset his spectator var
+                GameBase.gi.WriteByte(Defines.svc_stufftext);
+                GameBase.gi.WriteString("spectator 0\n");
+                GameBase.gi.unicast(ent, true);
+                return;
+            }
+        } else {
+            // he was a spectator and wants to join the game
+            // he must have the right password
+            String value = Info.Info_ValueForKey(ent.client.pers.userinfo,
+                    "password");
+            if (!passwdOK(GameBase.spectator_password.string, value)) {
+                GameBase.gi.cprintf(ent, Defines.PRINT_HIGH,
+                        "Password incorrect.\n");
+                ent.client.pers.spectator = true;
+                GameBase.gi.WriteByte(Defines.svc_stufftext);
+                GameBase.gi.WriteString("spectator 1\n");
+                GameBase.gi.unicast(ent, true);
+                return;
+            }
+        }
+
+
+        ent.svflags &= ~Defines.SVF_NOCLIENT;
+        PutClientInServer(ent);
+
+        // add a teleportation effect
+        if (!ent.client.pers.spectator) {
+            //gi.WriteShort(ent - g_edicts);
+            GameBase.gi.WriteShort(ent.index);
+
+            GameBase.gi.WriteByte(Defines.MZ_LOGIN);
+            GameBase.gi.multicast(ent.s.origin, Defines.MULTICAST_PVS);
+
+            // hold in place briefly
+            ent.client.ps.pmove.pm_flags = Move.PMF_TIME_TELEPORT;
+            ent.client.ps.pmove.pm_time = 14;
+        }
+
+        ent.client.respawn_time = GameBase.level.time;
+
+        if (ent.client.pers.spectator)
+            GameBase.gi.bprintf(Defines.PRINT_HIGH, ent.client.pers.netname
+                    + " has moved to the sidelines\n");
+        else
+            GameBase.gi.bprintf(Defines.PRINT_HIGH, ent.client.pers.netname
+                    + " joined the game\n");
+    }
+
+    /**
+     * Called when a player connects to a server or respawns in a deathmatch.
+     */
+    public static void PutClientInServer(EDict ent) {
+        float[] mins = {-16, -16, -24};
+        float[] maxs = {16, 16, 32};
+        int index;
+        float[] spawn_origin = {0, 0, 0}, spawn_angles = {0, 0, 0};
+        Client client;
+        int i;
+        client_persistant_t saved = new client_persistant_t();
+        client_respawn_t resp = new client_respawn_t();
+
+        // find a spawn point
+        // do it before setting health back up, so farthest
+        // ranging doesn't count this client
+        SelectSpawnPoint(ent, spawn_origin, spawn_angles);
+
+        index = ent.index - 1;
+        client = ent.client;
+
+        // deathmatch wipes most client data every spawn
+        if (GameBase.deathmatch.value != 0) {
+
+            resp.set(client.resp);
+            String userinfo = client.pers.userinfo;
+            InitClientPersistant(client);
+
+            userinfo = ClientUserinfoChanged(ent, userinfo);
+
+        } else if (GameBase.coop.value != 0) {
+
+            resp.set(client.resp);
+
+            String userinfo = client.pers.userinfo;
+
+            resp.coop_respawn.game_helpchanged = client.pers.game_helpchanged;
+            resp.coop_respawn.helpchanged = client.pers.helpchanged;
+            client.pers.set(resp.coop_respawn);
+            ClientUserinfoChanged(ent, userinfo);
+        } else {
+            resp.clear();
+        }
+
+        // clear everything but the persistant data
+        saved.set(client.pers);
+        client.clear();
+        client.pers.set(saved);
+        if (client.pers.health <= 0)
+            InitClientPersistant(client);
+
+        client.resp.set(resp);
+
+        // copy some data from the client to the entity
+        FetchClientEntData(ent);
+
+        // clear entity values
+        ent.groundentity = null;
+        ent.client = GameBase.game.clients[index];
+        ent.movetype = Defines.MOVETYPE_WALK;
+        ent.viewheight = 22;
+        ent.inuse = true;
+        ent.classname = "player";
+        ent.mass = 200;
+        ent.solid = Defines.SOLID_BBOX;
+        ent.air_finished = GameBase.level.time + 12;
+        ent.clipmask = Defines.MASK_PLAYERSOLID;
+        ent.model = "players/male/tris.md2";
+        ent.waterlevel = 0;
+        ent.watertype = 0;
+        ent.flags &= ~Defines.FL_NO_KNOCKBACK;
+        ent.svflags &= ~Defines.SVF_DEADMONSTER;
+
+        Math3D.vectorCopy(mins, ent.mins);
+        Math3D.vectorCopy(maxs, ent.maxs);
+        Math3D.vectorClear(ent.velocity);
+
+        // clear playerstate values
+        ent.client.ps.clear();
+
+        client.ps.pmove.origin[0] = (short) (spawn_origin[0] * 8);
+        client.ps.pmove.origin[1] = (short) (spawn_origin[1] * 8);
+        client.ps.pmove.origin[2] = (short) (spawn_origin[2] * 8);
+
+        if (GameBase.deathmatch.value != 0
+                && 0 != ((int) GameBase.dmflags.value & Defines.DF_FIXED_FOV)) {
+            client.ps.fov = 90;
+        } else {
+            client.ps.fov = Lib.atoi(Info.Info_ValueForKey(
+                    client.pers.userinfo, "fov"));
+            if (client.ps.fov < 1)
+                client.ps.fov = 90;
+            else if (client.ps.fov > 160)
+                client.ps.fov = 160;
+        }
+
+        // clear entity state values
+        ent.s.effects = 0;
+        ent.s.modelindex = 255; // will use the skin specified model
+        ent.s.modelindex2 = 255; // custom gun model
+        // sknum is player num and weapon number
+        // weapon number will be added in changeweapon
+        ent.s.skinnum = ent.index - 1;
+
+        ent.s.frame = 0;
+        Math3D.vectorCopy(spawn_origin, ent.s.origin);
+        ent.s.origin[2] += 1; // make sure off ground
+        Math3D.vectorCopy(ent.s.origin, ent.s.old_origin);
+
+        // set the delta angle
+        for (i = 0; i < 3; i++) {
+            client.ps.pmove.delta_angles[i] = (short) Math3D
+                    .angle2Short(spawn_angles[i] - client.resp.cmd_angles[i]);
+        }
+
+        ent.s.angles[Defines.PITCH] = 0;
+        ent.s.angles[Defines.YAW] = spawn_angles[Defines.YAW];
+        ent.s.angles[Defines.ROLL] = 0;
+        Math3D.vectorCopy(ent.s.angles, client.ps.viewangles);
+        Math3D.vectorCopy(ent.s.angles, client.v_angle);
+
+        // spawn a spectator
+        if (client.pers.spectator) {
+            client.chase_target = null;
+
+            client.resp.spectator = true;
+
+            ent.movetype = Defines.MOVETYPE_NOCLIP;
+            ent.solid = Defines.SOLID_NOT;
+            ent.svflags |= Defines.SVF_NOCLIENT;
+            GameBase.gi.linkentity(ent);
+            return;
+        } else
+            client.resp.spectator = false;
+
+
+        GameBase.gi.linkentity(ent);
+
+    }
+
+    /**
+     * A client has just connected to the server in deathmatch mode, so clear
+     * everything out before starting them.
+     */
+    public static void ClientBeginDeathmatch(EDict ent) {
+        GameUtil.G_InitEdict(ent, ent.index);
+
+        InitClientResp(ent.client);
+
+        // locate ent at a spawn point
+        PutClientInServer(ent);
+
+        if (GameBase.level.intermissiontime != 0) {
+            PlayerHud.MoveClientToIntermission(ent);
+        } else {
+            //gi.WriteShort(ent - g_edicts);
+            GameBase.gi.WriteShort(ent.index);
+            GameBase.gi.WriteByte(Defines.MZ_LOGIN);
+            GameBase.gi.multicast(ent.s.origin, Defines.MULTICAST_PVS);
+        }
+
+        GameBase.gi.bprintf(Defines.PRINT_HIGH, ent.client.pers.netname
+                + " entered the game\n");
+
+        // make sure all view stuff is valid
+        PlayerView.ClientEndServerFrame(ent);
+    }
+
+    /**
+     * Called when a client has finished connecting, and is ready to be placed
+     * into the game. This will happen every level load.
+     */
+    public static void ClientBegin(EDict ent) {
+        int i;
+
+        //ent.client = game.clients + (ent - g_edicts - 1);
+        ent.client = GameBase.game.clients[ent.index - 1];
+
+        if (GameBase.deathmatch.value != 0) {
+            ClientBeginDeathmatch(ent);
+            return;
+        }
+
+        // if there is already a body waiting for us (a loadgame), just
+        // take it, otherwise spawn one from scratch
+        if (ent.inuse) {
+            // the client has cleared the client side viewangles upon
+            // connecting to the server, which is different than the
+            // state when the game is saved, so we need to compensate
+            // with deltaangles
+            for (i = 0; i < 3; i++)
+                ent.client.ps.pmove.delta_angles[i] = (short) Math3D
+                        .angle2Short(ent.client.ps.viewangles[i]);
+        } else {
+            // a spawn point will completely reinitialize the entity
+            // except for the persistant data that was initialized at
+            // ClientConnect() time
+            GameUtil.G_InitEdict(ent, ent.index);
+            ent.classname = "player";
+            InitClientResp(ent.client);
+            PutClientInServer(ent);
+        }
+
+        if (GameBase.level.intermissiontime != 0) {
+            PlayerHud.MoveClientToIntermission(ent);
+        }
+
+        // make sure all view stuff is valid
+        PlayerView.ClientEndServerFrame(ent);
+    }
+
+    /**
+     * Called whenever the player updates a userinfo variable.
+     * <p>
+     * The game can override any of the settings in place (forcing skins or
+     * names, etc) before copying it off.
+     */
+    public static String ClientUserinfoChanged(EDict ent, String userinfo) {
+        String s;
+        int playernum;
+
+        // check for malformed or illegal info strings
+        if (!Info.Info_Validate(userinfo)) {
+            return "\\name\\badinfo\\skin\\male/grunt";
+        }
+
+        // set name
+        s = Info.Info_ValueForKey(userinfo, "name");
+
+        ent.client.pers.netname = s;
+
+        // set spectator
+        s = Info.Info_ValueForKey(userinfo, "spectator");
+        // spectators are only supported in deathmatch
+        ent.client.pers.spectator = GameBase.deathmatch.value != 0 && !s.equals("0");
+
+        // set skin
+        s = Info.Info_ValueForKey(userinfo, "skin");
+
+        playernum = ent.index - 1;
+
+        // combine name and skin into a configstring
+        GameBase.gi.configstring(Defines.CS_PLAYERSKINS + playernum,
+                ent.client.pers.netname + "\\" + s);
+
+        // fov
+        if (GameBase.deathmatch.value != 0
+                && 0 != ((int) GameBase.dmflags.value & Defines.DF_FIXED_FOV)) {
+            ent.client.ps.fov = 90;
+        } else {
+            ent.client.ps.fov = Lib
+                    .atoi(Info.Info_ValueForKey(userinfo, "fov"));
+            if (ent.client.ps.fov < 1)
+                ent.client.ps.fov = 90;
+            else if (ent.client.ps.fov > 160)
+                ent.client.ps.fov = 160;
+        }
+
+        // handedness
+        s = Info.Info_ValueForKey(userinfo, "hand");
+        if (s.length() > 0) {
+            ent.client.pers.hand = Lib.atoi(s);
+        }
+
+        // save off the userinfo in case we want to check something later
+        ent.client.pers.userinfo = userinfo;
+
+        return userinfo;
+    }
+
+    /**
+     * Called when a player begins connecting to the server. The game can refuse
+     * entrance to a client by returning false. If the client is allowed, the
+     * connection process will continue and eventually get to ClientBegin()
+     * Changing levels will NOT cause this to be called again, but loadgames
+     * will.
+     */
+    public static boolean ClientConnect(EDict ent, String userinfo) {
+        String value;
+
+        // check to see if they are on the banned IP list
+        value = Info.Info_ValueForKey(userinfo, "ip");
+        if (GameSVCmds.SV_FilterPacket(value)) {
+            userinfo = Info.Info_SetValueForKey(userinfo, "rejmsg", "Banned.");
+            return false;
+        }
+
+        // check for a spectator
+        value = Info.Info_ValueForKey(userinfo, "spectator");
+        if (GameBase.deathmatch.value != 0 && value.length() != 0
+                && 0 != Lib.strcmp(value, "0")) {
+            int i, numspec;
+
+            if (!passwdOK(GameBase.spectator_password.string, value)) {
+                userinfo = Info.Info_SetValueForKey(userinfo, "rejmsg",
+                        "Spectator password required or incorrect.");
+                return false;
+            }
+
+            // count spectators
+            for (i = numspec = 0; i < GameBase.maxclients.value; i++)
+                if (GameBase.g_edicts[i + 1].inuse
+                        && GameBase.g_edicts[i + 1].client.pers.spectator)
+                    numspec++;
+
+            if (numspec >= GameBase.maxspectators.value) {
+                userinfo = Info.Info_SetValueForKey(userinfo, "rejmsg",
+                        "Server spectator limit is full.");
+                return false;
+            }
+        } else {
+            // check for a password
+            value = Info.Info_ValueForKey(userinfo, "password");
+            if (!passwdOK(GameBase.spectator_password.string, value)) {
+                userinfo = Info.Info_SetValueForKey(userinfo, "rejmsg",
+                        "Password required or incorrect.");
+                return false;
+            }
+        }
+
+        // they can connect
+        ent.client = GameBase.game.clients[ent.index - 1];
+
+        // if there is already a body waiting for us (a loadgame), just
+        // take it, otherwise spawn one from scratch
+        if (!ent.inuse) {
+            // clear the respawning variables
+            InitClientResp(ent.client);
+        }
+
+        ClientUserinfoChanged(ent, userinfo);
+
+        if (GameBase.game.maxclients > 1)
+            GameBase.gi.dprintf(ent.client.pers.netname + " connected\n");
+
+        ent.svflags = 0; // make sure we start with known default
+        ent.client.pers.connected = true;
+        return true;
+    }
+
+    /**
+     * Called when a player drops from the server. Will not be called between levels.
+     */
+    public static void ClientDisconnect(EDict ent) {
+        int playernum;
+
+        if (ent.client == null)
+            return;
+
+        GameBase.gi.bprintf(Defines.PRINT_HIGH, ent.client.pers.netname
+                + " disconnected\n");
+
+
+        GameBase.gi.unlinkentity(ent);
+        ent.s.modelindex = 0;
+        ent.solid = Defines.SOLID_NOT;
+        ent.inuse = false;
+        ent.classname = "disconnected";
+        ent.client.pers.connected = false;
+
+        playernum = ent.index - 1;
+        GameBase.gi.configstring(Defines.CS_PLAYERSKINS + playernum, "");
+    }
+
+    /*
+     * static int CheckBlock(int c) 
+     * { 
+     *                 int v, i; 
+     *                 v = 0; 
+     *                 for (i = 0; i < c; i++)
+     *                 v += ((byte *) b)[i]; 
+     *         return v; 
+     * }
+     * 
+     * public static void PrintPmove(pmove_t * pm) 
+     * { 
+     *         unsigned c1, c2;
+     * 
+     *                 c1 = CheckBlock(&pm.s, sizeof(pm.s));
+     *                 c2 = CheckBlock(&pm.cmd, sizeof(pm.cmd)); 
+     *      Com_Printf("sv %3i:%i %i\n", pm.cmd.impulse, c1, c2); 
+     * }
+     */
+
+    /**
+     * This will be called once for each client frame, which will usually be a
+     * couple times for each server frame.
+     */
+    public static void ClientThink(EDict ent, usercmd_t ucmd) {
+        Client client;
+        EDict other;
+        int i, j;
+        Move pm = null;
+
+        GameBase.level.current_entity = ent;
+        client = ent.client;
+
+        if (GameBase.level.intermissiontime != 0) {
+            client.ps.pmove.pm_type = Defines.PM_FREEZE;
+            // can exit intermission after five seconds
+            if (GameBase.level.time > GameBase.level.intermissiontime + 5.0f
+                    && 0 != (ucmd.buttons & Defines.BUTTON_ANY))
+                GameBase.level.exitintermission = true;
+            return;
+        }
+
+        PlayerClient.pm_passent = ent;
+
+        if (ent.client.chase_target != null) {
+
+            client.resp.cmd_angles[0] = Math3D.short2Angle(ucmd.angles[0]);
+            client.resp.cmd_angles[1] = Math3D.short2Angle(ucmd.angles[1]);
+            client.resp.cmd_angles[2] = Math3D.short2Angle(ucmd.angles[2]);
+
+        } else {
+
+            // set up for pmove
+            pm = new Move();
+
+            if (ent.movetype == Defines.MOVETYPE_NOCLIP)
+                client.ps.pmove.pm_type = Defines.PM_SPECTATOR;
+            else if (ent.s.modelindex != 255)
+                client.ps.pmove.pm_type = Defines.PM_GIB;
+            else
+                client.ps.pmove.pm_type = Defines.PM_NORMAL;
+
+            client.ps.pmove.gravity = (short) GameBase.sv_gravity.value;
+            pm.s.set(client.ps.pmove);
+
+            for (i = 0; i < 3; i++) {
+                pm.s.origin[i] = (short) (ent.s.origin[i] * 8);
+                pm.s.velocity[i] = (short) (ent.velocity[i] * 8);
+            }
+
+            if (client.old_pmove.equals(pm.s)) {
+                pm.snapinitial = true;
+                // gi.dprintf ("pmove changed!\n");
+            }
+
+            // this should be a copy
+            pm.cmd.set(ucmd);
+
+            pm.trace = PlayerClient.PM_trace; // adds default parms
+            pm.pointcontents = GameBase.gi.pointcontents;
+
+            // perform a pmove
+            GameBase.gi.Pmove(pm);
+
+            // save results of pmove
+            client.ps.pmove.set(pm.s);
+            client.old_pmove.set(pm.s);
+
+            for (i = 0; i < 3; i++) {
+                ent.s.origin[i] = pm.s.origin[i] * 0.125f;
+                ent.velocity[i] = pm.s.velocity[i] * 0.125f;
+            }
+
+            Math3D.vectorCopy(pm.mins, ent.mins);
+            Math3D.vectorCopy(pm.maxs, ent.maxs);
+
+            client.resp.cmd_angles[0] = Math3D.short2Angle(ucmd.angles[0]);
+            client.resp.cmd_angles[1] = Math3D.short2Angle(ucmd.angles[1]);
+            client.resp.cmd_angles[2] = Math3D.short2Angle(ucmd.angles[2]);
+
+            if (ent.groundentity != null && null == pm.groundentity
+                    && (pm.cmd.upmove >= 10) && (pm.waterlevel == 0)) {
+                GameBase.gi.sound(ent, Defines.CHAN_VOICE, GameBase.gi
+                        .soundindex("*jump1.wav"), 1, Defines.ATTN_NORM, 0);
+            }
+
+            ent.viewheight = (int) pm.viewheight;
+            ent.waterlevel = pm.waterlevel;
+            ent.watertype = pm.watertype;
+            ent.groundentity = pm.groundentity;
+            if (pm.groundentity != null)
+                ent.groundentity_linkcount = pm.groundentity.linkcount;
+
+            Math3D.vectorCopy(pm.viewangles, client.v_angle);
+            Math3D.vectorCopy(pm.viewangles, client.ps.viewangles);
+
+            GameBase.gi.linkentity(ent);
+
+            if (ent.movetype != Defines.MOVETYPE_NOCLIP)
+                GameBase.G_TouchTriggers(ent);
+
+            // touch other objects
+            for (i = 0; i < pm.numtouch; i++) {
+                other = pm.touchents[i];
+                for (j = 0; j < i; j++)
+                    if (pm.touchents[j] == other)
+                        break;
+                if (j != i)
+                    continue; // duplicated
+                if (other.touch == null)
+                    continue;
+                other.touch.touch(other, ent, GameBase.dummyplane, null);
+            }
+
+        }
+
+        client.oldbuttons = client.buttons;
+        client.buttons = ucmd.buttons;
+        client.latched_buttons |= client.buttons & ~client.oldbuttons;
+
+        // save light level the player is standing on for
+        // monster sighting AI
+        ent.light_level = ucmd.lightlevel;
+
+        // fire weapon from final position if needed
+        if ((client.latched_buttons & Defines.BUTTON_ATTACK) != 0) {
+            if (client.resp.spectator) {
+
+                client.latched_buttons = 0;
+
+                if (client.chase_target != null) {
+                    client.chase_target = null;
+                    client.ps.pmove.pm_flags &= ~Move.PMF_NO_PREDICTION;
+                }
+
+            }
+        }
+
+    }
+
+    /**
+     * This will be called once for each server frame, before running any other
+     * entities in the world.
+     */
+    public static void clientBeginServerFrame(EDict ent) {
+        Client client;
+        int buttonMask;
+
+        if (GameBase.level.intermissiontime != 0)
+            return;
+
+        client = ent.client;
+
+        if (GameBase.deathmatch.value != 0
+                && client.pers.spectator != client.resp.spectator
+                && (GameBase.level.time - client.respawn_time) >= 5) {
+            spectator_respawn(ent);
+            return;
+        }
+
+
+        // add player trail so monsters can follow
+        if (GameBase.deathmatch.value != 0)
+            if (!GameUtil.visible(ent, PlayerTrail.LastSpot()))
+                PlayerTrail.Add(ent.s.old_origin);
+
+        client.latched_buttons = 0;
+    }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/PlayerHud.java b/src/main/java/lwjake2/game/PlayerHud.java
new file mode 100644 (file)
index 0000000..e4f904c
--- /dev/null
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.Defines;
+import lwjake2.qcommon.Com;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+import lwjake2.util.Vargs;
+
+class PlayerHud {
+
+    /*
+     * ======================================================================
+     * 
+     * INTERMISSION
+     * 
+     * ======================================================================
+     */
+
+    public static void MoveClientToIntermission(EDict ent) {
+        if (GameBase.deathmatch.value != 0 || GameBase.coop.value != 0)
+            ent.client.showscores = true;
+        Math3D.vectorCopy(GameBase.level.intermission_origin, ent.s.origin);
+        ent.client.ps.pmove.origin[0] = (short) (GameBase.level.intermission_origin[0] * 8);
+        ent.client.ps.pmove.origin[1] = (short) (GameBase.level.intermission_origin[1] * 8);
+        ent.client.ps.pmove.origin[2] = (short) (GameBase.level.intermission_origin[2] * 8);
+        Math3D.vectorCopy(GameBase.level.intermission_angle,
+                ent.client.ps.viewangles);
+        ent.client.ps.pmove.pm_type = Defines.PM_FREEZE;
+        ent.client.ps.blend[3] = 0;
+        ent.client.ps.rdflags &= ~Defines.RDF_UNDERWATER;
+
+        // clean up powerup info
+        ent.client.quad_framenum = 0;
+        ent.client.invincible_framenum = 0;
+        ent.client.enviro_framenum = 0;
+
+        ent.viewheight = 0;
+        ent.s.modelindex = 0;
+        ent.s.modelindex2 = 0;
+        ent.s.modelindex3 = 0;
+        ent.s.modelindex = 0;
+        ent.s.effects = 0;
+        ent.s.sound = 0;
+        ent.solid = Defines.SOLID_NOT;
+
+        // add the layout
+
+        if (GameBase.deathmatch.value != 0 || GameBase.coop.value != 0) {
+            DeathmatchScoreboardMessage(ent, null);
+            GameBase.gi.unicast(ent, true);
+        }
+
+    }
+
+    public static void BeginIntermission(EDict targ) {
+        int i, n;
+        EDict ent, client;
+
+        if (GameBase.level.intermissiontime != 0)
+            return; // already activated
+
+        GameBase.game.autosaved = false;
+
+        // respawn any dead clients
+        for (i = 0; i < GameBase.maxclients.value; i++) {
+            client = GameBase.g_edicts[1 + i];
+            if (!client.inuse)
+                continue;
+            if (client.health <= 0)
+                PlayerClient.respawn(client);
+        }
+
+        GameBase.level.intermissiontime = GameBase.level.time;
+        GameBase.level.changemap = targ.map;
+
+        if (GameBase.level.changemap.indexOf('*') > -1) {
+            if (GameBase.coop.value != 0) {
+                for (i = 0; i < GameBase.maxclients.value; i++) {
+                    client = GameBase.g_edicts[1 + i];
+                    if (!client.inuse) {
+                    }
+                }
+            }
+        } else {
+            if (0 == GameBase.deathmatch.value) {
+                GameBase.level.exitintermission = true; // go immediately to the
+                // next level
+                return;
+            }
+        }
+
+        GameBase.level.exitintermission = false;
+
+        // find an intermission spot
+        ent = GameBase.G_FindEdict(null, GameBase.findByClass,
+                "info_player_intermission");
+        if (ent == null) { // the map creator forgot to put in an intermission
+            // point...
+            ent = GameBase.G_FindEdict(null, GameBase.findByClass,
+                    "info_player_start");
+            if (ent == null)
+                ent = GameBase.G_FindEdict(null, GameBase.findByClass,
+                        "info_player_deathmatch");
+        } else { // chose one of four spots
+            i = Lib.rand() & 3;
+            EdictIterator es = null;
+
+            while (i-- > 0) {
+                es = GameBase.G_Find(es, GameBase.findByClass,
+                        "info_player_intermission");
+
+                if (es == null) // wrap around the list
+                    continue;
+                ent = es.o;
+            }
+        }
+
+        Math3D.vectorCopy(ent.s.origin, GameBase.level.intermission_origin);
+        Math3D.vectorCopy(ent.s.angles, GameBase.level.intermission_angle);
+
+        // move all clients to the intermission point
+        for (i = 0; i < GameBase.maxclients.value; i++) {
+            client = GameBase.g_edicts[1 + i];
+            if (!client.inuse)
+                continue;
+            MoveClientToIntermission(client);
+        }
+    }
+
+    /*
+     * ================== 
+     * DeathmatchScoreboardMessage
+     * ==================
+     */
+    public static void DeathmatchScoreboardMessage(EDict ent, EDict killer) {
+        StringBuilder string = new StringBuilder(1400);
+
+        int i, j, k;
+        int sorted[] = new int[Defines.MAX_CLIENTS];
+        int sortedscores[] = new int[Defines.MAX_CLIENTS];
+        int score, total;
+        int x, y;
+        Client cl;
+        EDict cl_ent;
+        String tag;
+
+        // sort the clients by score
+        total = 0;
+        for (i = 0; i < GameBase.game.maxclients; i++) {
+            cl_ent = GameBase.g_edicts[1 + i];
+            if (!cl_ent.inuse || GameBase.game.clients[i].resp.spectator)
+                continue;
+            score = GameBase.game.clients[i].resp.score;
+            for (j = 0; j < total; j++) {
+                if (score > sortedscores[j])
+                    break;
+            }
+            for (k = total; k > j; k--) {
+                sorted[k] = sorted[k - 1];
+                sortedscores[k] = sortedscores[k - 1];
+            }
+            sorted[j] = i;
+            sortedscores[j] = score;
+            total++;
+        }
+
+        // print level name and exit rules
+
+        // add the clients in sorted order
+        if (total > 12)
+            total = 12;
+
+        for (i = 0; i < total; i++) {
+            cl = GameBase.game.clients[sorted[i]];
+            cl_ent = GameBase.g_edicts[1 + sorted[i]];
+
+            GameBase.gi.imageindex("i_fixme");
+            x = (i >= 6) ? 160 : 0;
+            y = 32 + 32 * (i % 6);
+
+            // add a dogtag
+            if (cl_ent == ent)
+                tag = "tag1";
+            else if (cl_ent == killer)
+                tag = "tag2";
+            else
+                tag = null;
+
+            if (tag != null) {
+                string.append("xv ").append(x + 32).append(" yv ").append(y)
+                        .append(" picn ").append(tag);
+            }
+
+            // send the layout
+            string
+                    .append(" client ")
+                    .append(x)
+                    .append(" ")
+                    .append(y)
+                    .append(" ")
+                    .append(sorted[i])
+                    .append(" ")
+                    .append(cl.resp.score)
+                    .append(" ")
+                    .append(cl.ping)
+                    .append(" ")
+                    .append(
+                            (GameBase.level.framenum - cl.resp.enterframe) / 600);
+        }
+
+        GameBase.gi.WriteByte(Defines.svc_layout);
+        GameBase.gi.WriteString(string.toString());
+    }
+
+    /*
+     * ================== 
+     * DeathmatchScoreboard
+     * 
+     * Draw instead of help message. Note that it isn't that hard to overflow
+     * the 1400 byte message limit! 
+     * ==================
+     */
+    public static void DeathmatchScoreboard(EDict ent) {
+        DeathmatchScoreboardMessage(ent, ent.enemy);
+        GameBase.gi.unicast(ent, true);
+    }
+
+    /*
+     * ================== 
+     * Cmd_Score_f
+     * 
+     * Display the scoreboard 
+     * ==================
+     */
+    public static void Cmd_Score_f(EDict ent) {
+        ent.client.showinventory = false;
+        ent.client.showhelp = false;
+
+        if (0 == GameBase.deathmatch.value && 0 == GameBase.coop.value)
+            return;
+
+        if (ent.client.showscores) {
+            ent.client.showscores = false;
+            return;
+        }
+
+        ent.client.showscores = true;
+        DeathmatchScoreboard(ent);
+    }
+
+    //=======================================================================
+
+    /*
+     * =============== 
+     * G_SetStats 
+     * ===============
+     */
+    public static void G_SetStats(EDict ent) {
+        int index, cells = 0;
+        int power_armor_type;
+
+        //
+        // health
+        //
+        ent.client.ps.stats[Defines.STAT_HEALTH_ICON] = (short) GameBase.level.pic_health;
+        ent.client.ps.stats[Defines.STAT_HEALTH] = (short) ent.health;
+
+
+        //
+        // pickup message
+        //
+        if (GameBase.level.time > ent.client.pickup_msg_time) {
+            ent.client.ps.stats[Defines.STAT_PICKUP_ICON] = 0;
+            ent.client.ps.stats[Defines.STAT_PICKUP_STRING] = 0;
+        }
+
+        //
+        // timers
+        //
+        if (ent.client.quad_framenum > GameBase.level.framenum) {
+            ent.client.ps.stats[Defines.STAT_TIMER_ICON] = (short) GameBase.gi
+                    .imageindex("p_quad");
+            ent.client.ps.stats[Defines.STAT_TIMER] = (short) ((ent.client.quad_framenum - GameBase.level.framenum) / 10);
+        } else if (ent.client.invincible_framenum > GameBase.level.framenum) {
+            ent.client.ps.stats[Defines.STAT_TIMER_ICON] = (short) GameBase.gi
+                    .imageindex("p_invulnerability");
+            ent.client.ps.stats[Defines.STAT_TIMER] = (short) ((ent.client.invincible_framenum - GameBase.level.framenum) / 10);
+        } else if (ent.client.enviro_framenum > GameBase.level.framenum) {
+            ent.client.ps.stats[Defines.STAT_TIMER_ICON] = (short) GameBase.gi
+                    .imageindex("p_envirosuit");
+            ent.client.ps.stats[Defines.STAT_TIMER] = (short) ((ent.client.enviro_framenum - GameBase.level.framenum) / 10);
+        } else {
+            ent.client.ps.stats[Defines.STAT_TIMER_ICON] = 0;
+            ent.client.ps.stats[Defines.STAT_TIMER] = 0;
+        }
+
+        //
+        // layouts
+        //
+        ent.client.ps.stats[Defines.STAT_LAYOUTS] = 0;
+
+        if (GameBase.deathmatch.value != 0) {
+            if (ent.client.pers.health <= 0
+                    || GameBase.level.intermissiontime != 0
+                    || ent.client.showscores)
+                ent.client.ps.stats[Defines.STAT_LAYOUTS] |= 1;
+            if (ent.client.showinventory && ent.client.pers.health > 0)
+                ent.client.ps.stats[Defines.STAT_LAYOUTS] |= 2;
+        } else {
+            if (ent.client.showscores || ent.client.showhelp)
+                ent.client.ps.stats[Defines.STAT_LAYOUTS] |= 1;
+            if (ent.client.showinventory && ent.client.pers.health > 0)
+                ent.client.ps.stats[Defines.STAT_LAYOUTS] |= 2;
+        }
+
+        //
+        // frags
+        //
+        ent.client.ps.stats[Defines.STAT_FRAGS] = (short) ent.client.resp.score;
+
+        //
+        // help icon / current weapon if not shown
+        //
+        if (ent.client.pers.helpchanged != 0
+                && (GameBase.level.framenum & 8) != 0)
+            ent.client.ps.stats[Defines.STAT_HELPICON] = (short) GameBase.gi
+                    .imageindex("i_help");
+        else
+            ent.client.ps.stats[Defines.STAT_HELPICON] = 0;
+
+        ent.client.ps.stats[Defines.STAT_SPECTATOR] = 0;
+    }
+
+    /*
+     * =============== 
+     * G_CheckChaseStats 
+     * ===============
+     */
+    public static void G_CheckChaseStats(EDict ent) {
+        int i;
+        Client cl;
+
+        for (i = 1; i <= GameBase.maxclients.value; i++) {
+            cl = GameBase.g_edicts[i].client;
+            if (!GameBase.g_edicts[i].inuse || cl.chase_target != ent)
+                continue;
+            //memcpy(cl.ps.stats, ent.client.ps.stats, sizeof(cl.ps.stats));
+            System.arraycopy(ent.client.ps.stats, 0, cl.ps.stats, 0,
+                    Defines.MAX_STATS);
+
+            G_SetSpectatorStats(GameBase.g_edicts[i]);
+        }
+    }
+
+    /*
+     * =============== 
+     * G_SetSpectatorStats 
+     * ===============
+     */
+    public static void G_SetSpectatorStats(EDict ent) {
+        Client cl = ent.client;
+
+        if (null == cl.chase_target)
+            G_SetStats(ent);
+
+        cl.ps.stats[Defines.STAT_SPECTATOR] = 1;
+
+        // layouts are independant in spectator
+        cl.ps.stats[Defines.STAT_LAYOUTS] = 0;
+        if (cl.pers.health <= 0 || GameBase.level.intermissiontime != 0
+                || cl.showscores)
+            cl.ps.stats[Defines.STAT_LAYOUTS] |= 1;
+        if (cl.showinventory && cl.pers.health > 0)
+            cl.ps.stats[Defines.STAT_LAYOUTS] |= 2;
+
+        if (cl.chase_target != null && cl.chase_target.inuse)
+            //cl.ps.stats[STAT_CHASE] = (short) (CS_PLAYERSKINS +
+            // (cl.chase_target - g_edicts) - 1);
+            cl.ps.stats[Defines.STAT_CHASE] = (short) (Defines.CS_PLAYERSKINS
+                    + cl.chase_target.index - 1);
+        else
+            cl.ps.stats[Defines.STAT_CHASE] = 0;
+    }
+
+    /**
+     * HelpComputer. Draws the help computer.
+     */
+    public static void HelpComputer(EDict ent) {
+        String sb = "xv 32 yv 8 picn help " + // background
+                "xv 0 yv 24 cstring2 \"" + GameBase.level.level_name +
+                "\" " + // level name
+                "xv 0 yv 54 cstring2 \"" + GameBase.game.helpmessage1 +
+                "\" " + // help 1
+                "xv 0 yv 110 cstring2 \"" + GameBase.game.helpmessage2 +
+                "\" " + // help 2
+                "xv 50 yv 164 string2 \" kills     goals    secrets\" " +
+                "xv 50 yv 172 string2 \"" +
+                Com.sprintf("%3i/%3i     %i/%i       %i/%i\" ", new Vargs(6)
+                        .add(
+                                GameBase.level.found_goals).add(
+                                GameBase.level.total_goals).add(
+                                GameBase.level.found_secrets).add(
+                                GameBase.level.total_secrets));
+
+        // send the layout
+
+        GameBase.gi.WriteByte(Defines.svc_layout);
+        GameBase.gi.WriteString(sb);
+        GameBase.gi.unicast(ent, true);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/PlayerTrail.java b/src/main/java/lwjake2/game/PlayerTrail.java
new file mode 100644 (file)
index 0000000..e8e455d
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.util.Math3D;
+
+class PlayerTrail {
+
+    /*
+     * ==============================================================================
+     * 
+     * PLAYER TRAIL
+     * 
+     * ==============================================================================
+     * 
+     * This is a circular list containing the a list of points of where the
+     * player has been recently. It is used by monsters for pursuit.
+     * 
+     * .origin the spot .owner forward link .aiment backward link
+     */
+
+    private static final int TRAIL_LENGTH = 8;
+
+    private static final EDict[] trail = new EDict[TRAIL_LENGTH];
+
+    private static int trail_head;
+
+    private static boolean trail_active = false;
+
+    static {
+        //TODO: potential error
+        for (int n = 0; n < TRAIL_LENGTH; n++)
+            trail[n] = new EDict(n);
+    }
+
+    private static int NEXT(int n) {
+        return (n + 1) % PlayerTrail.TRAIL_LENGTH;
+    }
+
+    private static int PREV(int n) {
+        return (n + PlayerTrail.TRAIL_LENGTH - 1) % PlayerTrail.TRAIL_LENGTH;
+    }
+
+    static void Init() {
+
+        // FIXME || coop
+        if (GameBase.deathmatch.value != 0)
+            return;
+
+        for (int n = 0; n < PlayerTrail.TRAIL_LENGTH; n++) {
+            PlayerTrail.trail[n] = GameUtil.G_Spawn();
+            PlayerTrail.trail[n].classname = "player_trail";
+        }
+
+        trail_head = 0;
+        trail_active = true;
+    }
+
+    static void Add(float[] spot) {
+        float[] temp = {0, 0, 0};
+
+        if (!trail_active)
+            return;
+
+        Math3D.vectorCopy(spot, PlayerTrail.trail[trail_head].s.origin);
+
+        PlayerTrail.trail[trail_head].timestamp = GameBase.level.time;
+
+        Math3D.vectorSubtract(spot,
+                PlayerTrail.trail[PREV(trail_head)].s.origin, temp);
+        PlayerTrail.trail[trail_head].s.angles[1] = Math3D.vectoyaw(temp);
+
+        trail_head = NEXT(trail_head);
+    }
+
+
+    static EDict LastSpot() {
+        return PlayerTrail.trail[PREV(trail_head)];
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/PlayerView.java b/src/main/java/lwjake2/game/PlayerView.java
new file mode 100644 (file)
index 0000000..2f4f079
--- /dev/null
@@ -0,0 +1,653 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.Defines;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+
+public class PlayerView {
+
+    public static final float[] forward = {0, 0, 0};
+    public static final float[] right = {0, 0, 0};
+    public static final float[] up = {0, 0, 0};
+    public static EDict current_player;
+    public static Client current_client;
+    public static float xyspeed;
+
+    /*
+     * =============== 
+     * P_DamageFeedback
+     * 
+     * Handles color blends and view kicks 
+     * ===============
+     */
+    public static float bobmove;
+    public static int bobcycle; // odd cycles are right foot going forward
+    public static float bobfracsin; // sin(bobfrac*M_PI)}
+    private static int xxxi = 0;
+
+    /**
+     * SV_CalcRoll.
+     */
+    public static float SV_CalcRoll(float[] angles, float[] velocity) {
+        float sign;
+        float side;
+        float value;
+
+        side = Math3D.dotProduct(velocity, right);
+        sign = side < 0 ? -1 : 1;
+        side = Math.abs(side);
+
+        value = GameBase.sv_rollangle.value;
+
+        if (side < GameBase.sv_rollspeed.value)
+            side = side * value / GameBase.sv_rollspeed.value;
+        else
+            side = value;
+
+        return side * sign;
+    }
+
+    public static void P_DamageFeedback(EDict player) {
+        Client client;
+        float side;
+        float realcount, kick;
+        float[] v = {0, 0, 0};
+        int r, l;
+        float[] power_color = {0.0f, 1.0f, 0.0f};
+        float[] acolor = {1.0f, 1.0f, 1.0f};
+        float[] bcolor = {1.0f, 0.0f, 0.0f};
+
+        client = player.client;
+
+        // flash the backgrounds behind the status numbers
+        client.ps.stats[Defines.STAT_FLASHES] = 0;
+        // total points of damage shot at the player this frame
+    }
+
+    /**
+     * fall from 128: 400 = 160000
+     * fall from 256: 580 = 336400
+     * fall from 384: 720 = 518400
+     * fall from 512: 800 = 640000
+     * fall from 640: 960 =
+     * damage = deltavelocity*deltavelocity * 0.0001
+     */
+    public static void SV_CalcViewOffset(EDict ent) {
+        float angles[] = {0, 0, 0};
+        float bob;
+        float ratio;
+        float delta;
+        float[] v = {0, 0, 0};
+
+        // base angles
+        angles = ent.client.ps.kick_angles;
+
+        // if dead, fix the angle and don't add any kick
+
+        Math3D.vectorClear(angles);
+
+        // add angles based on damage kick
+        ratio = (ent.client.v_dmg_time - GameBase.level.time)
+                / Defines.DAMAGE_TIME;
+        if (ratio < 0) {
+            ratio = 0;
+            ent.client.v_dmg_pitch = 0;
+            ent.client.v_dmg_roll = 0;
+        }
+        angles[Defines.PITCH] += ratio * ent.client.v_dmg_pitch;
+        angles[Defines.ROLL] += ratio * ent.client.v_dmg_roll;
+
+        // add pitch based on fall kick
+        ratio = (ent.client.fall_time - GameBase.level.time)
+                / Defines.FALL_TIME;
+        if (ratio < 0)
+            ratio = 0;
+        angles[Defines.PITCH] += ratio * ent.client.fall_value;
+
+        // add angles based on velocity
+        delta = Math3D.dotProduct(ent.velocity, forward);
+        angles[Defines.PITCH] += delta * GameBase.run_pitch.value;
+
+        delta = Math3D.dotProduct(ent.velocity, right);
+        angles[Defines.ROLL] += delta * GameBase.run_roll.value;
+
+        // add angles based on bob
+        delta = bobfracsin * GameBase.bob_pitch.value * xyspeed;
+        if ((ent.client.ps.pmove.pm_flags & Move.PMF_DUCKED) != 0)
+            delta *= 6; // crouching
+        angles[Defines.PITCH] += delta;
+        delta = bobfracsin * GameBase.bob_roll.value * xyspeed;
+        if ((ent.client.ps.pmove.pm_flags & Move.PMF_DUCKED) != 0)
+            delta *= 6; // crouching
+        if ((bobcycle & 1) != 0)
+            delta = -delta;
+        angles[Defines.ROLL] += delta;
+
+        // base origin
+        Math3D.vectorClear(v);
+
+        // add view height
+        v[2] += ent.viewheight;
+
+        // add fall height
+        ratio = (ent.client.fall_time - GameBase.level.time)
+                / Defines.FALL_TIME;
+        if (ratio < 0)
+            ratio = 0;
+        v[2] -= ratio * ent.client.fall_value * 0.4;
+
+        // add bob height
+        bob = bobfracsin * xyspeed * GameBase.bob_up.value;
+        if (bob > 6)
+            bob = 6;
+
+        //gi.DebugGraph (bob *2, 255);
+        v[2] += bob;
+
+        // add kick offset
+
+        // absolutely bound offsets
+        // so the view can never be outside the player box
+
+        if (v[0] < -14)
+            v[0] = -14;
+        else if (v[0] > 14)
+            v[0] = 14;
+        if (v[1] < -14)
+            v[1] = -14;
+        else if (v[1] > 14)
+            v[1] = 14;
+        if (v[2] < -22)
+            v[2] = -22;
+        else if (v[2] > 30)
+            v[2] = 30;
+
+        Math3D.vectorCopy(v, ent.client.ps.viewoffset);
+    }
+
+    /**
+     * Calculates where to draw the gun.
+     */
+    public static void SV_CalcGunOffset(EDict ent) {
+
+    }
+
+    /**
+     * Adds a blending effect to the clients view.
+     */
+    public static void SV_AddBlend(float r, float g, float b, float a,
+                                   float v_blend[]) {
+        float a2, a3;
+
+        if (a <= 0)
+            return;
+        a2 = v_blend[3] + (1 - v_blend[3]) * a; // new total alpha
+        a3 = v_blend[3] / a2; // fraction of color from old
+
+        v_blend[0] = v_blend[0] * a3 + r * (1 - a3);
+        v_blend[1] = v_blend[1] * a3 + g * (1 - a3);
+        v_blend[2] = v_blend[2] * a3 + b * (1 - a3);
+        v_blend[3] = a2;
+    }
+
+    /**
+     * Calculates the blending color according to the players environment.
+     */
+    public static void SV_CalcBlend(EDict ent) {
+        int contents;
+        float[] vieworg = {0, 0, 0};
+        int remaining;
+
+        ent.client.ps.blend[0] = ent.client.ps.blend[1] = ent.client.ps.blend[2] = ent.client.ps.blend[3] = 0;
+
+        // add for contents
+        Math3D.vectorAdd(ent.s.origin, ent.client.ps.viewoffset, vieworg);
+        contents = GameBase.gi.pointcontents.pointcontents(vieworg);
+        if ((contents & (Defines.CONTENTS_LAVA | Defines.CONTENTS_SLIME | Defines.CONTENTS_WATER)) != 0)
+            ent.client.ps.rdflags |= Defines.RDF_UNDERWATER;
+        else
+            ent.client.ps.rdflags &= ~Defines.RDF_UNDERWATER;
+
+        if ((contents & (Defines.CONTENTS_SOLID | Defines.CONTENTS_LAVA)) != 0)
+            SV_AddBlend(1.0f, 0.3f, 0.0f, 0.6f, ent.client.ps.blend);
+        else if ((contents & Defines.CONTENTS_SLIME) != 0)
+            SV_AddBlend(0.0f, 0.1f, 0.05f, 0.6f, ent.client.ps.blend);
+        else if ((contents & Defines.CONTENTS_WATER) != 0)
+            SV_AddBlend(0.5f, 0.3f, 0.2f, 0.4f, ent.client.ps.blend);
+
+        // add for powerups
+        if (ent.client.quad_framenum > GameBase.level.framenum) {
+            remaining = (int) (ent.client.quad_framenum - GameBase.level.framenum);
+            if (remaining == 30) // beginning to fade
+                GameBase.gi.sound(ent, Defines.CHAN_ITEM,
+                        GameBase.gi.soundindex("items/damage2.wav"), 1, Defines.ATTN_NORM, 0);
+            if (remaining > 30 || (remaining & 4) != 0)
+                SV_AddBlend(0, 0, 1, 0.08f, ent.client.ps.blend);
+        } else if (ent.client.invincible_framenum > GameBase.level.framenum) {
+            remaining = (int) ent.client.invincible_framenum - GameBase.level.framenum;
+            if (remaining == 30) // beginning to fade
+                GameBase.gi.sound(ent, Defines.CHAN_ITEM,
+                        GameBase.gi.soundindex("items/protect2.wav"), 1, Defines.ATTN_NORM, 0);
+            if (remaining > 30 || (remaining & 4) != 0)
+                SV_AddBlend(1, 1, 0, 0.08f, ent.client.ps.blend);
+        } else if (ent.client.enviro_framenum > GameBase.level.framenum) {
+            remaining = (int) ent.client.enviro_framenum
+                    - GameBase.level.framenum;
+            if (remaining == 30) // beginning to fade
+                GameBase.gi.sound(ent, Defines.CHAN_ITEM,
+                        GameBase.gi.soundindex("items/airout.wav"), 1, Defines.ATTN_NORM, 0);
+            if (remaining > 30 || (remaining & 4) != 0)
+                SV_AddBlend(0, 1, 0, 0.08f, ent.client.ps.blend);
+        }
+
+    }
+
+
+    /**
+     * General effect handling for a player.
+     */
+    public static void P_WorldEffects() {
+        boolean envirosuit;
+        int waterlevel, old_waterlevel;
+
+        if (current_player.movetype == Defines.MOVETYPE_NOCLIP) {
+            current_player.air_finished = GameBase.level.time + 12; // don't
+            // need air
+            return;
+        }
+
+        waterlevel = current_player.waterlevel;
+        old_waterlevel = current_client.old_waterlevel;
+        current_client.old_waterlevel = waterlevel;
+
+        envirosuit = current_client.enviro_framenum > GameBase.level.framenum;
+
+        //
+        // if just entered a water volume, play a sound
+        //
+        if (old_waterlevel == 0 && waterlevel != 0) {
+            if ((current_player.watertype & Defines.CONTENTS_LAVA) != 0)
+                GameBase.gi.sound(current_player, Defines.CHAN_BODY,
+                        GameBase.gi.soundindex("player/lava_in.wav"), 1,
+                        Defines.ATTN_NORM, 0);
+            else if ((current_player.watertype & Defines.CONTENTS_SLIME) != 0)
+                GameBase.gi.sound(current_player, Defines.CHAN_BODY,
+                        GameBase.gi.soundindex("player/watr_in.wav"), 1,
+                        Defines.ATTN_NORM, 0);
+            else if ((current_player.watertype & Defines.CONTENTS_WATER) != 0)
+                GameBase.gi.sound(current_player, Defines.CHAN_BODY,
+                        GameBase.gi.soundindex("player/watr_in.wav"), 1,
+                        Defines.ATTN_NORM, 0);
+            current_player.flags |= Defines.FL_INWATER;
+
+            // clear damage_debounce, so the pain sound will play immediately
+            current_player.damage_debounce_time = GameBase.level.time - 1;
+        }
+
+        //
+        // if just completely exited a water volume, play a sound
+        //
+        if (old_waterlevel != 0 && waterlevel == 0) {
+
+            GameBase.gi
+                    .sound(current_player, Defines.CHAN_BODY, GameBase.gi
+                                    .soundindex("player/watr_out.wav"), 1,
+                            Defines.ATTN_NORM, 0);
+            current_player.flags &= ~Defines.FL_INWATER;
+        }
+
+        //
+        // check for head just going under water
+        //
+        if (old_waterlevel != 3 && waterlevel == 3) {
+            GameBase.gi.sound(current_player, Defines.CHAN_BODY, GameBase.gi
+                    .soundindex("player/watr_un.wav"), 1, Defines.ATTN_NORM, 0);
+        }
+
+        //
+        // check for head just coming out of water
+        //
+        if (old_waterlevel == 3 && waterlevel != 3) {
+            if (current_player.air_finished < GameBase.level.time) { // gasp for
+                // air
+                GameBase.gi.sound(current_player, Defines.CHAN_VOICE,
+                        GameBase.gi.soundindex("player/gasp1.wav"), 1,
+                        Defines.ATTN_NORM, 0);
+            } else if (current_player.air_finished < GameBase.level.time + 11) { // just
+                // break
+                // surface
+                GameBase.gi.sound(current_player, Defines.CHAN_VOICE,
+                        GameBase.gi.soundindex("player/gasp2.wav"), 1,
+                        Defines.ATTN_NORM, 0);
+            }
+        }
+
+
+        //
+        // check for sizzle damage
+        //
+        if (waterlevel != 0
+                && 0 != (current_player.watertype & (Defines.CONTENTS_LAVA | Defines.CONTENTS_SLIME))) {
+            if ((current_player.watertype & Defines.CONTENTS_LAVA) != 0) {
+                if (current_player.health > 0
+                        && current_player.pain_debounce_time <= GameBase.level.time
+                        && current_client.invincible_framenum < GameBase.level.framenum) {
+                    if ((Lib.rand() & 1) != 0)
+                        GameBase.gi.sound(current_player, Defines.CHAN_VOICE,
+                                GameBase.gi.soundindex("player/burn1.wav"), 1,
+                                Defines.ATTN_NORM, 0);
+                    else
+                        GameBase.gi.sound(current_player, Defines.CHAN_VOICE,
+                                GameBase.gi.soundindex("player/burn2.wav"), 1,
+                                Defines.ATTN_NORM, 0);
+                    current_player.pain_debounce_time = GameBase.level.time + 1;
+                }
+
+            }
+
+        }
+    }
+
+    /*
+     * ===============
+     * G_SetClientEffects
+     * ===============
+     */
+    public static void G_SetClientEffects(EDict ent) {
+        int pa_type;
+        int remaining;
+
+        ent.s.effects = 0;
+        ent.s.renderfx = 0;
+
+        if (ent.health <= 0 || GameBase.level.intermissiontime != 0)
+            return;
+
+        if (ent.client.quad_framenum > GameBase.level.framenum) {
+            remaining = (int) ent.client.quad_framenum
+                    - GameBase.level.framenum;
+            if (remaining > 30 || 0 != (remaining & 4))
+                ent.s.effects |= Defines.EF_QUAD;
+        }
+
+        if (ent.client.invincible_framenum > GameBase.level.framenum) {
+            remaining = (int) ent.client.invincible_framenum
+                    - GameBase.level.framenum;
+            if (remaining > 30 || 0 != (remaining & 4))
+                ent.s.effects |= Defines.EF_PENT;
+        }
+
+        // show cheaters!!!
+        if ((ent.flags & Defines.FL_GODMODE) != 0) {
+            ent.s.effects |= Defines.EF_COLOR_SHELL;
+            ent.s.renderfx |= (Defines.RF_SHELL_RED | Defines.RF_SHELL_GREEN | Defines.RF_SHELL_BLUE);
+        }
+    }
+
+    /*
+     * ===============
+     * G_SetClientEvent
+     * ===============
+     */
+    public static void G_SetClientEvent(EDict ent) {
+        if (ent.s.event != 0)
+            return;
+
+        if (ent.groundentity != null && xyspeed > 225) {
+            if ((int) (current_client.bobtime + bobmove) != bobcycle)
+                ent.s.event = Defines.EV_FOOTSTEP;
+        }
+    }
+
+    /*
+     * ===============
+     * G_SetClientSound
+     * ===============
+     */
+    public static void G_SetClientSound(EDict ent) {
+        String weap;
+
+        if (ent.client.pers.game_helpchanged != GameBase.game.helpchanged) {
+            ent.client.pers.game_helpchanged = GameBase.game.helpchanged;
+            ent.client.pers.helpchanged = 1;
+        }
+
+        // help beep (no more than three times)
+        if (ent.client.pers.helpchanged != 0
+                && ent.client.pers.helpchanged <= 3
+                && 0 == (GameBase.level.framenum & 63)) {
+            ent.client.pers.helpchanged++;
+            GameBase.gi.sound(ent, Defines.CHAN_VOICE, GameBase.gi
+                    .soundindex("misc/pc_up.wav"), 1, Defines.ATTN_STATIC, 0);
+        }
+
+
+        ent.s.sound = 0;
+    }
+
+    /*
+     * ===============
+     * G_SetClientFrame
+     * ===============
+     */
+    public static void G_SetClientFrame(EDict ent) {
+        Client client;
+        boolean duck, run;
+
+        if (ent.s.modelindex != 255)
+            return; // not in the player model
+
+        client = ent.client;
+
+        duck = (client.ps.pmove.pm_flags & Move.PMF_DUCKED) != 0;
+        run = xyspeed != 0;
+
+        boolean skip = false;
+        // check for stand/duck and stop/go transitions
+        if (duck != client.anim_duck
+                && client.anim_priority < Defines.ANIM_DEATH)
+            skip = true;
+
+        if (run != client.anim_run
+                && client.anim_priority == Defines.ANIM_BASIC)
+            skip = true;
+
+        if (null == ent.groundentity
+                && client.anim_priority <= Defines.ANIM_WAVE)
+            skip = true;
+
+        if (!skip) {
+            if (client.anim_priority == Defines.ANIM_REVERSE) {
+                if (ent.s.frame > client.anim_end) {
+                    ent.s.frame--;
+                    return;
+                }
+            } else if (ent.s.frame < client.anim_end) { // continue an animation
+                ent.s.frame++;
+                return;
+            }
+
+            if (client.anim_priority == Defines.ANIM_DEATH)
+                return; // stay there
+            if (client.anim_priority == Defines.ANIM_JUMP) {
+                if (null == ent.groundentity)
+                    return; // stay there
+                ent.client.anim_priority = Defines.ANIM_WAVE;
+                ent.s.frame = M_Player.FRAME_jump3;
+                ent.client.anim_end = M_Player.FRAME_jump6;
+                return;
+            }
+        }
+
+        // return to either a running or standing frame
+        client.anim_priority = Defines.ANIM_BASIC;
+        client.anim_duck = duck;
+        client.anim_run = run;
+
+        if (null == ent.groundentity) {
+            client.anim_priority = Defines.ANIM_JUMP;
+            if (ent.s.frame != M_Player.FRAME_jump2)
+                ent.s.frame = M_Player.FRAME_jump1;
+            client.anim_end = M_Player.FRAME_jump2;
+        } else if (run) { // running
+            if (duck) {
+                ent.s.frame = M_Player.FRAME_crwalk1;
+                client.anim_end = M_Player.FRAME_crwalk6;
+            } else {
+                ent.s.frame = M_Player.FRAME_run1;
+                client.anim_end = M_Player.FRAME_run6;
+            }
+        } else { // standing
+            if (duck) {
+                ent.s.frame = M_Player.FRAME_crstnd01;
+                client.anim_end = M_Player.FRAME_crstnd19;
+            } else {
+                ent.s.frame = M_Player.FRAME_stand01;
+                client.anim_end = M_Player.FRAME_stand40;
+            }
+        }
+    }
+
+    /**
+     * Called for each player at the end of the server frame and right after
+     * spawning.
+     */
+    public static void ClientEndServerFrame(EDict ent) {
+        float bobtime;
+        int i;
+
+        current_player = ent;
+        current_client = ent.client;
+
+        //
+        // If the origin or velocity have changed since ClientThink(),
+        // update the pmove values. This will happen when the client
+        // is pushed by a bmodel or kicked by an explosion.
+        //
+        // If it wasn't updated here, the view position would lag a frame
+        // behind the body position when pushed -- "sinking into plats"
+        //
+        for (i = 0; i < 3; i++) {
+            current_client.ps.pmove.origin[i] = (short) (ent.s.origin[i] * 8.0);
+            current_client.ps.pmove.velocity[i] = (short) (ent.velocity[i] * 8.0);
+        }
+
+        //
+        // If the end of unit layout is displayed, don't give
+        // the player any normal movement attributes
+        //
+        if (GameBase.level.intermissiontime != 0) {
+            // FIXME: add view drifting here?
+            current_client.ps.blend[3] = 0;
+            current_client.ps.fov = 90;
+            PlayerHud.G_SetStats(ent);
+            return;
+        }
+
+        Math3D.angleVectors(ent.client.v_angle, forward, right, up);
+
+        // burn from lava, etc
+        P_WorldEffects();
+
+        //
+        // set model angles from view angles so other things in
+        // the world can tell which direction you are looking
+        //
+        if (ent.client.v_angle[Defines.PITCH] > 180)
+            ent.s.angles[Defines.PITCH] = (-360 + ent.client.v_angle[Defines.PITCH]) / 3;
+        else
+            ent.s.angles[Defines.PITCH] = ent.client.v_angle[Defines.PITCH] / 3;
+        ent.s.angles[Defines.YAW] = ent.client.v_angle[Defines.YAW];
+        ent.s.angles[Defines.ROLL] = 0;
+        ent.s.angles[Defines.ROLL] = SV_CalcRoll(ent.s.angles, ent.velocity) * 4;
+
+        //
+        // calculate speed and cycle to be used for
+        // all cyclic walking effects
+        //
+        xyspeed = (float) Math.sqrt(ent.velocity[0] * ent.velocity[0]
+                + ent.velocity[1] * ent.velocity[1]);
+
+        if (xyspeed < 5) {
+            bobmove = 0;
+            current_client.bobtime = 0; // start at beginning of cycle again
+        } else if (ent.groundentity != null) { // so bobbing only cycles when on
+            // ground
+            if (xyspeed > 210)
+                bobmove = 0.25f;
+            else if (xyspeed > 100)
+                bobmove = 0.125f;
+            else
+                bobmove = 0.0625f;
+        }
+
+        bobtime = (current_client.bobtime += bobmove);
+
+        if ((current_client.ps.pmove.pm_flags & Move.PMF_DUCKED) != 0)
+            bobtime *= 4;
+
+        bobcycle = (int) bobtime;
+        bobfracsin = (float) Math.abs(Math.sin(bobtime * Math.PI));
+
+
+        // apply all the damage taken this frame
+        P_DamageFeedback(ent);
+
+        // determine the view offsets
+        SV_CalcViewOffset(ent);
+
+        // determine the gun offsets
+        SV_CalcGunOffset(ent);
+
+        // determine the full screen color blend
+        // must be after viewoffset, so eye contents can be
+        // accurately determined
+        // FIXME: with client prediction, the contents
+        // should be determined by the client
+        SV_CalcBlend(ent);
+
+        // chase cam stuff
+        if (ent.client.resp.spectator)
+            PlayerHud.G_SetSpectatorStats(ent);
+        else
+            PlayerHud.G_SetStats(ent);
+        PlayerHud.G_CheckChaseStats(ent);
+
+        G_SetClientEvent(ent);
+
+        G_SetClientEffects(ent);
+
+        G_SetClientSound(ent);
+
+        G_SetClientFrame(ent);
+
+        Math3D.vectorCopy(ent.velocity, ent.client.oldvelocity);
+        Math3D.vectorCopy(ent.client.ps.viewangles, ent.client.oldviewangles);
+
+        // clear weapon kicks
+
+        // if the scoreboard is up, update it
+        if (ent.client.showscores && 0 == (GameBase.level.framenum & 31)) {
+            PlayerHud.DeathmatchScoreboardMessage(ent, ent.enemy);
+            GameBase.gi.unicast(ent, false);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/SuperAdapter.java b/src/main/java/lwjake2/game/SuperAdapter.java
new file mode 100644 (file)
index 0000000..a6d094d
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.qcommon.Com;
+
+import java.util.Hashtable;
+
+public abstract class SuperAdapter {
+
+    /**
+     * Adapter repository.
+     */
+    private static final Hashtable<String, SuperAdapter> adapters = new Hashtable<>();
+
+    /**
+     * Constructor, does the adapter registration.
+     */
+    SuperAdapter() {
+        register(this, getID());
+    }
+
+    /**
+     * Adapter registration.
+     */
+    private static void register(SuperAdapter sa, String id) {
+        adapters.put(id, sa);
+    }
+
+    /**
+     * Returns the adapter from the repository given by its ID.
+     */
+    public static SuperAdapter getFromID(String key) {
+        SuperAdapter sa = adapters.get(key);
+
+        // try to create the adapter
+        if (sa == null) {
+            Com.DPrintf("SuperAdapter.getFromID():adapter not found->" + key + "\n");
+        }
+
+        return sa;
+    }
+
+    /**
+     * Returns the Adapter-ID.
+     */
+    public abstract String getID();
+}
diff --git a/src/main/java/lwjake2/game/client_persistant_t.java b/src/main/java/lwjake2/game/client_persistant_t.java
new file mode 100644 (file)
index 0000000..68cca88
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.Defines;
+import lwjake2.util.QuakeFile;
+
+import java.io.IOException;
+
+public class client_persistant_t {
+
+    final int[] inventory = new int[Defines.MAX_ITEMS];
+    // just don't have a connection yet
+    public int max_slugs;
+    // client data that stays across multiple level loads
+    String userinfo = "";
+    String netname = "";
+    int hand;
+    boolean connected; // a loadgame will leave valid entities that
+    // values saved and restored from edicts when changing levels
+    int health;
+    int max_health;
+    int savedFlags;
+    int selected_item;
+    int game_helpchanged;
+    int helpchanged;
+    boolean spectator; // client is a spectator
+
+    public void set(client_persistant_t from) {
+
+        userinfo = from.userinfo;
+        netname = from.netname;
+        hand = from.hand;
+        connected = from.connected;
+        health = from.health;
+        max_health = from.max_health;
+        savedFlags = from.savedFlags;
+        selected_item = from.selected_item;
+        System.arraycopy(from.inventory, 0, inventory, 0, inventory.length);
+        max_slugs = from.max_slugs;
+        game_helpchanged = from.game_helpchanged;
+        helpchanged = from.helpchanged;
+        spectator = from.spectator;
+    }
+
+    /**
+     * Reads a client_persistant structure from a file.
+     */
+    public void read(QuakeFile f) throws IOException {
+
+        userinfo = f.readString();
+        netname = f.readString();
+
+        hand = f.readInt();
+
+        connected = f.readInt() != 0;
+        health = f.readInt();
+
+        max_health = f.readInt();
+        savedFlags = f.readInt();
+        selected_item = f.readInt();
+
+        for (int n = 0; n < Defines.MAX_ITEMS; n++)
+            inventory[n] = f.readInt();
+
+        max_slugs = f.readInt();
+
+        game_helpchanged = f.readInt();
+        helpchanged = f.readInt();
+        spectator = f.readInt() != 0;
+    }
+
+    /**
+     * Writes a client_persistant structure to a file.
+     */
+    public void write(QuakeFile f) throws IOException {
+        // client persistant_t
+        f.writeString(userinfo);
+        f.writeString(netname);
+
+        f.writeInt(hand);
+
+        f.writeInt(connected ? 1 : 0);
+        f.writeInt(health);
+
+        f.writeInt(max_health);
+        f.writeInt(savedFlags);
+        f.writeInt(selected_item);
+
+        for (int n = 0; n < Defines.MAX_ITEMS; n++)
+            f.writeInt(inventory[n]);
+
+        f.writeInt(max_slugs);
+
+        f.writeInt(game_helpchanged);
+        f.writeInt(helpchanged);
+        f.writeInt(spectator ? 1 : 0);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/client_respawn_t.java b/src/main/java/lwjake2/game/client_respawn_t.java
new file mode 100644 (file)
index 0000000..03aaf32
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.util.Math3D;
+import lwjake2.util.QuakeFile;
+
+import java.io.IOException;
+
+/**
+ * Client data that stays across deathmatch respawns.
+ */
+public class client_respawn_t {
+    /**
+     * angles sent over in the last command.
+     */
+    protected final float[] cmd_angles = {0, 0, 0};
+    /**
+     * What to set client->pers to on a respawn
+     */
+    protected client_persistant_t coop_respawn = new client_persistant_t();
+    /**
+     * Level.framenum the client entered the game.
+     */
+    protected int enterframe;
+    /**
+     * frags, etc.
+     */
+    protected int score;
+    /**
+     * client is a spectator.
+     */
+    protected boolean spectator;
+
+
+    /**
+     * Copies the client respawn data.
+     */
+    public void set(client_respawn_t from) {
+        coop_respawn.set(from.coop_respawn);
+        enterframe = from.enterframe;
+        score = from.score;
+        Math3D.vectorCopy(from.cmd_angles, cmd_angles);
+        spectator = from.spectator;
+    }
+
+    /**
+     * Clears the client reaspawn informations.
+     */
+    public void clear() {
+        coop_respawn = new client_persistant_t();
+        enterframe = 0;
+        score = 0;
+        Math3D.vectorClear(cmd_angles);
+        spectator = false;
+    }
+
+    /**
+     * Reads a client_respawn from a file.
+     */
+    public void read(QuakeFile f) throws IOException {
+        coop_respawn.read(f);
+        enterframe = f.readInt();
+        score = f.readInt();
+        cmd_angles[0] = f.readFloat();
+        cmd_angles[1] = f.readFloat();
+        cmd_angles[2] = f.readFloat();
+        spectator = f.readInt() != 0;
+    }
+
+    /**
+     * Writes a client_respawn to a file.
+     */
+    public void write(QuakeFile f) throws IOException {
+        coop_respawn.write(f);
+        f.writeInt(enterframe);
+        f.writeInt(score);
+        f.writeFloat(cmd_angles[0]);
+        f.writeFloat(cmd_angles[1]);
+        f.writeFloat(cmd_angles[2]);
+        f.writeInt(spectator ? 1 : 0);
+    }
+}
diff --git a/src/main/java/lwjake2/game/cmdalias_t.java b/src/main/java/lwjake2/game/cmdalias_t.java
new file mode 100644 (file)
index 0000000..71358bf
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+public final class cmdalias_t {
+    public cmdalias_t next;
+    public String name = "";
+    public String value;
+}
diff --git a/src/main/java/lwjake2/game/cmodel_t.java b/src/main/java/lwjake2/game/cmodel_t.java
new file mode 100644 (file)
index 0000000..3020998
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+public class cmodel_t {
+    public final float[] mins = {0, 0, 0};
+    public final float[] maxs = {0, 0, 0};
+    public final float[] origin = {0, 0, 0}; // for sounds or lights
+    public int headnode;
+}
diff --git a/src/main/java/lwjake2/game/cplane_t.java b/src/main/java/lwjake2/game/cplane_t.java
new file mode 100644 (file)
index 0000000..a431c8f
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.util.Math3D;
+
+public class cplane_t {
+    public final float[] normal = new float[3];
+    public final byte[] pad = {0, 0};
+    public float dist;
+    /**
+     * This is for fast side tests, 0=xplane, 1=yplane, 2=zplane and 3=arbitrary.
+     */
+    public byte type;
+    /**
+     * This represents signx + (signy<<1) + (signz << 1).
+     */
+    public byte signbits; // signx + (signy<<1) + (signz<<1)
+
+    public void set(cplane_t c) {
+        Math3D.set(normal, c.normal);
+        dist = c.dist;
+        type = c.type;
+        signbits = c.signbits;
+        pad[0] = c.pad[0];
+        pad[1] = c.pad[1];
+    }
+
+    public void clear() {
+        Math3D.vectorClear(normal);
+        dist = 0;
+        type = 0;
+        signbits = 0;
+        pad[0] = 0;
+        pad[1] = 0;
+    }
+}
diff --git a/src/main/java/lwjake2/game/csurface_t.java b/src/main/java/lwjake2/game/csurface_t.java
new file mode 100644 (file)
index 0000000..e27b882
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+public class csurface_t {
+    public String name = "";
+    public int flags;
+    public int value;
+}
diff --git a/src/main/java/lwjake2/game/entity_state_t.java b/src/main/java/lwjake2/game/entity_state_t.java
new file mode 100644 (file)
index 0000000..1d065bb
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.util.Math3D;
+import lwjake2.util.QuakeFile;
+
+import java.io.IOException;
+
+public class entity_state_t implements Cloneable {
+    /**
+     * edict index. TODO: this is critical. The index has to be proper managed.
+     */
+    public int number = 0;
+    // TODO: why was this introduced?
+    public EDict surrounding_ent = null;
+    public float[] origin = {0, 0, 0};
+    public float[] angles = {0, 0, 0};
+    /**
+     * for lerping.
+     */
+    public float[] old_origin = {0, 0, 0};
+    public int modelindex;
+    /**
+     * weapons, CTF flags, etc.
+     */
+    public int modelindex2, modelindex3, modelindex4;
+    public int frame;
+    public int skinnum;
+    /**
+     * PGM - we're filling it, so it needs to be unsigned.
+     */
+    public int effects;
+    public int renderfx;
+    public int solid;
+    // for client side prediction, 8*(bits 0-4) is x/y radius
+    // 8*(bits 5-9) is z down distance, 8(bits10-15) is z up
+    // gi.linkentity sets this properly
+    public int sound; // for looping sounds, to guarantee shutoff
+    public int event; // impulse events -- muzzle flashes, footsteps, etc
+
+    /**
+     * entity_state_t is the information conveyed from the server
+     * in an update message about entities that the client will
+     * need to render in some way.
+     */
+    public entity_state_t(EDict ent) {
+        this.surrounding_ent = ent;
+        if (ent != null)
+            number = ent.index;
+    }
+    // events only go out for a single frame, they
+    // are automatically cleared each frame
+
+    /**
+     * Writes the entity state to the file.
+     */
+    public void write(QuakeFile f) throws IOException {
+        f.writeEdictRef(surrounding_ent);
+        f.writeVector(origin);
+        f.writeVector(angles);
+        f.writeVector(old_origin);
+
+        f.writeInt(modelindex);
+
+        f.writeInt(modelindex2);
+        f.writeInt(modelindex3);
+        f.writeInt(modelindex4);
+
+        f.writeInt(frame);
+        f.writeInt(skinnum);
+
+        f.writeInt(effects);
+        f.writeInt(renderfx);
+        f.writeInt(solid);
+
+        f.writeInt(sound);
+        f.writeInt(event);
+
+    }
+
+    /**
+     * Reads the entity state from the file.
+     */
+    public void read(QuakeFile f) throws IOException {
+        surrounding_ent = f.readEdictRef();
+        origin = f.readVector();
+        angles = f.readVector();
+        old_origin = f.readVector();
+
+        modelindex = f.readInt();
+
+        modelindex2 = f.readInt();
+        modelindex3 = f.readInt();
+        modelindex4 = f.readInt();
+
+        frame = f.readInt();
+        skinnum = f.readInt();
+
+        effects = f.readInt();
+        renderfx = f.readInt();
+        solid = f.readInt();
+
+        sound = f.readInt();
+        event = f.readInt();
+
+
+    }
+
+
+    public entity_state_t getClone() {
+        entity_state_t out = new entity_state_t(this.surrounding_ent);
+        out.set(this);
+        return out;
+    }
+
+    public void set(entity_state_t from) {
+        number = from.number;
+        Math3D.vectorCopy(from.origin, origin);
+        Math3D.vectorCopy(from.angles, angles);
+        Math3D.vectorCopy(from.old_origin, old_origin);
+
+        modelindex = from.modelindex;
+        modelindex2 = from.modelindex2;
+        modelindex3 = from.modelindex3;
+        modelindex4 = from.modelindex4;
+
+        frame = from.frame;
+        skinnum = from.skinnum;
+        effects = from.effects;
+        renderfx = from.renderfx;
+        solid = from.solid;
+        sound = from.sound;
+        event = from.event;
+    }
+
+    public void clear() {
+        //TODO: this is critical. The index has to be proper managed.
+        number = 0;
+        surrounding_ent = null;
+        Math3D.vectorClear(origin);
+        Math3D.vectorClear(angles);
+        Math3D.vectorClear(old_origin);
+        modelindex = 0;
+        modelindex2 = modelindex3 = modelindex4 = 0;
+        frame = 0;
+        skinnum = 0;
+        effects = 0;
+        renderfx = 0;
+        solid = 0;
+        sound = 0;
+        event = 0;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/game_import_t.java b/src/main/java/lwjake2/game/game_import_t.java
new file mode 100644 (file)
index 0000000..4de0539
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.Defines;
+import lwjake2.qcommon.*;
+import lwjake2.server.SV_GAME;
+import lwjake2.server.SV_INIT;
+import lwjake2.server.SV_SEND;
+import lwjake2.server.SV_WORLD;
+
+//
+//     collection of functions provided by the main engine
+//
+public class game_import_t {
+    public Move.PointContentsAdapter pointcontents = new Move.PointContentsAdapter() {
+        public int pointcontents(float[] o) {
+            return 0;
+        }
+    };
+
+    // special messages
+    public void bprintf(int printlevel, String s) {
+        SV_SEND.SV_BroadcastPrintf(printlevel, s);
+    }
+
+    public void dprintf(String s) {
+        SV_GAME.PF_dprintf(s);
+    }
+
+    public void cprintf(EDict ent, int printlevel, String s) {
+        SV_GAME.PF_cprintf(ent, printlevel, s);
+    }
+
+    public void centerprintf(EDict ent, String s) {
+        SV_GAME.PF_centerprintf(ent, s);
+    }
+
+    public void sound(EDict ent, int channel, int soundindex, float volume,
+                      float attenuation, float timeofs) {
+        SV_GAME.PF_StartSound(ent, channel, soundindex, volume, attenuation,
+                timeofs);
+    }
+
+    public void positioned_sound(float[] origin, EDict ent, int channel,
+                                 int soundinedex, float volume, float attenuation, float timeofs) {
+
+        SV_SEND.SV_StartSound(origin, ent, channel, soundinedex, volume,
+                attenuation, timeofs);
+    }
+
+    // config strings hold all the index strings, the lightstyles,
+    // and misc data like the sky definition and cdtrack.
+    // All of the current configstrings are sent to clients when
+    // they connect, and changes are sent to all connected clients.
+    public void configstring(int num, String string) {
+        SV_GAME.PF_Configstring(num, string);
+    }
+
+    public void error(String err) {
+        Com.Error(Defines.ERR_FATAL, err);
+    }
+
+    // the *index functions create configstrings and some internal server state
+    public int modelindex(String name) {
+        return SV_INIT.SV_ModelIndex(name);
+    }
+
+    public int soundindex(String name) {
+        return SV_INIT.SV_SoundIndex(name);
+    }
+
+    public int imageindex(String name) {
+        return SV_INIT.SV_ImageIndex(name);
+    }
+
+    public void setmodel(EDict ent, String name) {
+        SV_GAME.PF_setmodel(ent, name);
+    }
+
+    // collision detection
+    public trace_t trace(float[] start, float[] mins, float[] maxs,
+                         float[] end, EDict passent, int contentmask) {
+        return SV_WORLD.SV_Trace(start, mins, maxs, end, passent, contentmask);
+    }
+
+    public void SetAreaPortalState(int portalnum, boolean open) {
+        CM.CM_SetAreaPortalState(portalnum, open);
+    }
+
+    // an entity will never be sent to a client or used for collision
+    // if it is not passed to linkentity. If the size, position, or
+    // solidity changes, it must be relinked.
+    public void linkentity(EDict ent) {
+        SV_WORLD.SV_LinkEdict(ent);
+    }
+
+    public void unlinkentity(EDict ent) {
+        SV_WORLD.SV_UnlinkEdict(ent);
+    }
+
+    // call before removing an interactive edict
+    public int BoxEdicts(float[] mins, float[] maxs, EDict list[],
+                         int maxcount, int areatype) {
+        return SV_WORLD.SV_AreaEdicts(mins, maxs, list, maxcount, areatype);
+    }
+
+    public void Pmove(Move move) {
+        PMove.Pmove(move);
+    }
+
+    // player movement code common with client prediction
+    // network messaging
+    public void multicast(float[] origin, int to) {
+        SV_SEND.SV_Multicast(origin, to);
+    }
+
+    public void unicast(EDict ent, boolean reliable) {
+        SV_GAME.PF_Unicast(ent, reliable);
+    }
+
+
+    public void WriteByte(int c) {
+        SV_GAME.PF_WriteByte(c);
+    }
+
+    public void WriteShort(int c) {
+        SV_GAME.PF_WriteShort(c);
+    }
+
+    public void WriteString(String s) {
+        SV_GAME.PF_WriteString(s);
+    }
+
+    public void WritePosition(float[] pos) {
+        SV_GAME.PF_WritePos(pos);
+    }
+
+    // some fractional bits
+    public void WriteDir(float[] pos) {
+        SV_GAME.PF_WriteDir(pos);
+    }
+
+    // console variable interaction
+    public CvarT cvar(String var_name, String value, int flags) {
+        return Cvar.get(var_name, value, flags);
+    }
+
+    // console variable interaction
+    public void cvar_set(String var_name, String value) {
+        Cvar.set(var_name, value);
+    }
+
+    // console variable interaction
+    public CvarT cvar_forceset(String var_name, String value) {
+        return Cvar.forceSet(var_name, value);
+    }
+
+    // ClientCommand and ServerCommand parameter access
+    public int argc() {
+        return Cmd.Argc();
+    }
+
+
+    public String argv(int n) {
+        return Cmd.Argv(n);
+    }
+
+    // add commands to the server console as if they were typed in
+    // for map changing, etc
+    public void AddCommandString(String text) {
+        CommandBuffer.AddText(text);
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/game_locals_t.java b/src/main/java/lwjake2/game/game_locals_t.java
new file mode 100644 (file)
index 0000000..1298a73
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.Defines;
+import lwjake2.qcommon.Com;
+import lwjake2.util.QuakeFile;
+
+import java.io.IOException;
+import java.util.Date;
+
+public class game_locals_t {
+    //
+    // this structure is left intact through an entire game
+    // it should be initialized at dll load time, and read/written to
+    // the server.ssv file for savegames
+    //
+
+    public String helpmessage1 = "";
+
+    public String helpmessage2 = "";
+
+    public int helpchanged; // flash F1 icon if non 0, play sound
+
+    // and increment only if 1, 2, or 3
+
+    public Client clients[] = new Client[Defines.MAX_CLIENTS];
+
+    // can't store spawnpoint in level, because
+    // it would get overwritten by the savegame restore
+    public String spawnpoint = ""; // needed for coop respawns
+
+    // store latched cvars here that we want to get at often
+    public int maxclients;
+
+    public int maxentities;
+
+    // cross level triggers
+    public int serverflags;
+    public boolean autosaved;
+    // items
+    private int num_items;
+
+    /**
+     * Reads the game locals from a file.
+     */
+    public void load(QuakeFile f) throws IOException {
+        f.readString(); // Reads date?
+
+        helpmessage1 = f.readString();
+        helpmessage2 = f.readString();
+
+        helpchanged = f.readInt();
+        // gclient_t*
+
+        spawnpoint = f.readString();
+        maxclients = f.readInt();
+        maxentities = f.readInt();
+        serverflags = f.readInt();
+        num_items = f.readInt();
+        autosaved = f.readInt() != 0;
+
+        // rst's checker :-)
+        if (f.readInt() != 1928)
+            Com.DPrintf("error in loading game_locals, 1928\n");
+
+    }
+
+    /**
+     * Writes the game locals to a file.
+     */
+    public void write(QuakeFile f) throws IOException {
+        f.writeString(new Date().toString());
+
+        f.writeString(helpmessage1);
+        f.writeString(helpmessage2);
+
+        f.writeInt(helpchanged);
+        // gclient_t*
+
+        f.writeString(spawnpoint);
+        f.writeInt(maxclients);
+        f.writeInt(maxentities);
+        f.writeInt(serverflags);
+        f.writeInt(num_items);
+        f.writeInt(autosaved ? 1 : 0);
+        // rst's checker :-)
+        f.writeInt(1928);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/gitem_armor_t.java b/src/main/java/lwjake2/game/gitem_armor_t.java
new file mode 100644 (file)
index 0000000..5256a27
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+public class gitem_armor_t {
+
+}
diff --git a/src/main/java/lwjake2/game/gitem_t.java b/src/main/java/lwjake2/game/gitem_t.java
new file mode 100644 (file)
index 0000000..d21b751
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+public class gitem_t {
+    public int index;
+    String classname; // spawning name
+    ItemUseAdapter use;
+    int flags; // IT_* flags
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/level_locals_t.java b/src/main/java/lwjake2/game/level_locals_t.java
new file mode 100644 (file)
index 0000000..3048625
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.util.QuakeFile;
+
+import java.io.IOException;
+
+public class level_locals_t {
+
+    // this structure is cleared as each map is entered
+    // it is read/written to the level.sav file for savegames
+    //
+    public int framenum;
+    public float time;
+
+    public String level_name = ""; // the descriptive name (Outer Base, etc)
+    public String mapname = ""; // the server name (base1, etc)
+    public String nextmap = ""; // go here when fraglimit is hit
+
+    // intermission state
+    public float intermissiontime; // time the intermission was started
+    public String changemap;
+    public boolean exitintermission;
+    public float[] intermission_origin = {0, 0, 0};
+    public float[] intermission_angle = {0, 0, 0};
+
+    public EDict sight_client; // changed once each frame for coop games
+    public int pic_health;
+    public int total_secrets;
+    public int found_secrets;
+    public int total_goals;
+    public int found_goals;
+    public EDict current_entity; // entity running from G_RunFrame
+    public int body_que; // dead bodies
+    private EDict sight_entity;
+    private int sight_entity_framenum;
+    private EDict sound_entity;
+    private int sound_entity_framenum;
+    private EDict sound2_entity;
+    private int sound2_entity_framenum;
+    private int power_cubes; // ugly necessity for coop
+
+    /**
+     * Writes the levellocales to the file.
+     */
+    public void write(QuakeFile f) throws IOException {
+        f.writeInt(framenum);
+        f.writeFloat(time);
+        f.writeString(level_name);
+        f.writeString(mapname);
+        f.writeString(nextmap);
+        f.writeFloat(intermissiontime);
+        f.writeString(changemap);
+        f.writeBoolean(exitintermission);
+        f.writeVector(intermission_origin);
+        f.writeVector(intermission_angle);
+        f.writeEdictRef(sight_client);
+
+        f.writeEdictRef(sight_entity);
+        f.writeInt(sight_entity_framenum);
+
+        f.writeEdictRef(sound_entity);
+        f.writeInt(sound_entity_framenum);
+        f.writeEdictRef(sound2_entity);
+        f.writeInt(sound2_entity_framenum);
+
+        f.writeInt(pic_health);
+
+        f.writeInt(total_secrets);
+        f.writeInt(found_secrets);
+
+        f.writeInt(total_goals);
+        f.writeInt(found_goals);
+
+        f.writeEdictRef(current_entity);
+        f.writeInt(body_que); // dead bodies
+        f.writeInt(power_cubes); // ugly necessity for coop
+
+        // rst's checker :-)
+        f.writeInt(4711);
+    }
+
+    /**
+     * Reads the level locals from the file.
+     */
+    public void read(QuakeFile f) throws IOException {
+        framenum = f.readInt();
+        time = f.readFloat();
+        level_name = f.readString();
+        mapname = f.readString();
+        nextmap = f.readString();
+        intermissiontime = f.readFloat();
+        changemap = f.readString();
+        exitintermission = f.readBoolean();
+        intermission_origin = f.readVector();
+        intermission_angle = f.readVector();
+        sight_client = f.readEdictRef();
+
+        sight_entity = f.readEdictRef();
+        sight_entity_framenum = f.readInt();
+
+        sound_entity = f.readEdictRef();
+        sound_entity_framenum = f.readInt();
+        sound2_entity = f.readEdictRef();
+        sound2_entity_framenum = f.readInt();
+
+        pic_health = f.readInt();
+
+        total_secrets = f.readInt();
+        found_secrets = f.readInt();
+
+        total_goals = f.readInt();
+        found_goals = f.readInt();
+
+        current_entity = f.readEdictRef();
+        body_que = f.readInt(); // dead bodies
+        power_cubes = f.readInt(); // ugly necessity for coop
+
+        // rst's checker :-)
+        if (f.readInt() != 4711)
+            System.out.println("error in reading level_locals.");
+    }
+}
diff --git a/src/main/java/lwjake2/game/link_t.java b/src/main/java/lwjake2/game/link_t.java
new file mode 100644 (file)
index 0000000..4e5667a
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+public class link_t {
+    public final Object o;
+    public link_t prev, next;
+
+    public link_t(Object o) {
+        this.o = o;
+    }
+}
diff --git a/src/main/java/lwjake2/game/mapsurface_t.java b/src/main/java/lwjake2/game/mapsurface_t.java
new file mode 100644 (file)
index 0000000..8ea29eb
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+public class mapsurface_t {
+    public final csurface_t c = new csurface_t();
+    public String rname;
+}
diff --git a/src/main/java/lwjake2/game/mframe_t.java b/src/main/java/lwjake2/game/mframe_t.java
new file mode 100644 (file)
index 0000000..b6cabd8
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.util.QuakeFile;
+
+import java.io.IOException;
+
+public class mframe_t {
+    public float dist;
+    public EntThinkAdapter think;
+
+    public mframe_t(float dist, EntThinkAdapter think) {
+        this.dist = dist;
+        this.think = think;
+    }
+
+    /**
+     * Empty constructor.
+     */
+    public mframe_t() {
+    }
+
+    public void write(QuakeFile f) throws IOException {
+        f.writeFloat(dist);
+        f.writeAdapter(think);
+    }
+
+    public void read(QuakeFile f) throws IOException {
+        dist = f.readFloat();
+        think = (EntThinkAdapter) f.readAdapter();
+    }
+}
diff --git a/src/main/java/lwjake2/game/mmove_t.java b/src/main/java/lwjake2/game/mmove_t.java
new file mode 100644 (file)
index 0000000..3af3cd0
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.util.QuakeFile;
+
+import java.io.IOException;
+
+public class mmove_t {
+    private int firstframe;
+    private int lastframe;
+    private mframe_t[] frame; //ptr
+    private EntThinkAdapter endfunc;
+
+    public mmove_t() {
+    }
+
+    /**
+     * Writes the structure to a random acccess file.
+     */
+    public void write(QuakeFile f) throws IOException {
+        f.writeInt(firstframe);
+        f.writeInt(lastframe);
+        if (frame == null)
+            f.writeInt(-1);
+        else {
+            f.writeInt(frame.length);
+            for (mframe_t aFrame : frame) aFrame.write(f);
+        }
+        f.writeAdapter(endfunc);
+    }
+
+    /**
+     * Read the mmove_t from the RandomAccessFile.
+     */
+    public void read(QuakeFile f) throws IOException {
+        firstframe = f.readInt();
+        lastframe = f.readInt();
+
+        int len = f.readInt();
+
+        frame = new mframe_t[len];
+        for (int n = 0; n < len; n++) {
+            frame[n] = new mframe_t();
+            frame[n].read(f);
+        }
+        endfunc = (EntThinkAdapter) f.readAdapter();
+    }
+}
diff --git a/src/main/java/lwjake2/game/monsterinfo_t.java b/src/main/java/lwjake2/game/monsterinfo_t.java
new file mode 100644 (file)
index 0000000..84883dd
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.util.QuakeFile;
+
+import java.io.IOException;
+
+public class monsterinfo_t {
+
+    private mmove_t currentmove;
+    private int aiflags;
+    private int nextframe;
+    private float scale;
+
+    private EntThinkAdapter stand;
+    private EntThinkAdapter idle;
+    private EntThinkAdapter search;
+    private EntThinkAdapter walk;
+    private EntThinkAdapter run;
+
+    private EntDodgeAdapter dodge;
+
+    private EntThinkAdapter attack;
+    private EntThinkAdapter melee;
+
+    private EntInteractAdapter sight;
+
+    private EntThinkAdapter checkattack;
+
+    private float pausetime;
+    private float attack_finished;
+
+    private float[] saved_goal = {0, 0, 0};
+    private float search_time;
+    private float trail_time;
+    private float[] last_sighting = {0, 0, 0};
+    private int attack_state;
+    private int lefty;
+    private float idle_time;
+    private int linkcount;
+
+    private int power_armor_type;
+    private int power_armor_power;
+
+    /**
+     * Writes the monsterinfo to the file.
+     */
+    public void write(QuakeFile f) throws IOException {
+        f.writeBoolean(currentmove != null);
+        if (currentmove != null)
+            currentmove.write(f);
+        f.writeInt(aiflags);
+        f.writeInt(nextframe);
+        f.writeFloat(scale);
+        f.writeAdapter(stand);
+        f.writeAdapter(idle);
+        f.writeAdapter(search);
+        f.writeAdapter(walk);
+        f.writeAdapter(run);
+
+        f.writeAdapter(dodge);
+
+        f.writeAdapter(attack);
+        f.writeAdapter(melee);
+
+        f.writeAdapter(sight);
+
+        f.writeAdapter(checkattack);
+
+        f.writeFloat(pausetime);
+        f.writeFloat(attack_finished);
+
+        f.writeVector(saved_goal);
+
+        f.writeFloat(search_time);
+        f.writeFloat(trail_time);
+
+        f.writeVector(last_sighting);
+
+        f.writeInt(attack_state);
+        f.writeInt(lefty);
+
+        f.writeFloat(idle_time);
+        f.writeInt(linkcount);
+
+        f.writeInt(power_armor_power);
+        f.writeInt(power_armor_type);
+    }
+
+    /**
+     * Writes the monsterinfo to the file.
+     */
+    public void read(QuakeFile f) throws IOException {
+        if (f.readBoolean()) {
+            currentmove = new mmove_t();
+            currentmove.read(f);
+        } else
+            currentmove = null;
+        aiflags = f.readInt();
+        nextframe = f.readInt();
+        scale = f.readFloat();
+        stand = (EntThinkAdapter) f.readAdapter();
+        idle = (EntThinkAdapter) f.readAdapter();
+        search = (EntThinkAdapter) f.readAdapter();
+        walk = (EntThinkAdapter) f.readAdapter();
+        run = (EntThinkAdapter) f.readAdapter();
+
+        dodge = (EntDodgeAdapter) f.readAdapter();
+
+        attack = (EntThinkAdapter) f.readAdapter();
+        melee = (EntThinkAdapter) f.readAdapter();
+
+        sight = (EntInteractAdapter) f.readAdapter();
+
+        checkattack = (EntThinkAdapter) f.readAdapter();
+
+        pausetime = f.readFloat();
+        attack_finished = f.readFloat();
+
+        saved_goal = f.readVector();
+
+        search_time = f.readFloat();
+        trail_time = f.readFloat();
+
+        last_sighting = f.readVector();
+
+        attack_state = f.readInt();
+        lefty = f.readInt();
+
+        idle_time = f.readFloat();
+        linkcount = f.readInt();
+
+        power_armor_power = f.readInt();
+        power_armor_type = f.readInt();
+
+    }
+
+
+}
diff --git a/src/main/java/lwjake2/game/moveinfo_t.java b/src/main/java/lwjake2/game/moveinfo_t.java
new file mode 100644 (file)
index 0000000..400ef2b
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.util.QuakeFile;
+
+import java.io.IOException;
+
+public class moveinfo_t {
+    // fixed data
+    float[] start_origin = {0, 0, 0};
+    float[] start_angles = {0, 0, 0};
+    float[] end_origin = {0, 0, 0};
+    float[] end_angles = {0, 0, 0};
+
+    int sound_start;
+    int sound_middle;
+    int sound_end;
+
+    float accel;
+    float speed;
+    float decel;
+    float distance;
+
+    float wait;
+
+    // state data
+    int state;
+    float[] dir = {0, 0, 0};
+
+    float current_speed;
+    float move_speed;
+    float next_speed;
+    float remaining_distance;
+    float decel_distance;
+    EntThinkAdapter endfunc;
+
+    /**
+     * saves the moveinfo to the file.
+     */
+    public void write(QuakeFile f) throws IOException {
+        f.writeVector(start_origin);
+        f.writeVector(start_angles);
+        f.writeVector(end_origin);
+        f.writeVector(end_angles);
+
+        f.writeInt(sound_start);
+        f.writeInt(sound_middle);
+        f.writeInt(sound_end);
+
+        f.writeFloat(accel);
+        f.writeFloat(speed);
+        f.writeFloat(decel);
+        f.writeFloat(distance);
+
+        f.writeFloat(wait);
+
+        f.writeInt(state);
+        f.writeVector(dir);
+
+        f.writeFloat(current_speed);
+        f.writeFloat(move_speed);
+        f.writeFloat(next_speed);
+        f.writeFloat(remaining_distance);
+        f.writeFloat(decel_distance);
+        f.writeAdapter(endfunc);
+    }
+
+    /**
+     * Reads the moveinfo from a file.
+     */
+    public void read(QuakeFile f) throws IOException {
+        start_origin = f.readVector();
+        start_angles = f.readVector();
+        end_origin = f.readVector();
+        end_angles = f.readVector();
+
+        sound_start = f.readInt();
+        sound_middle = f.readInt();
+        sound_end = f.readInt();
+
+        accel = f.readFloat();
+        speed = f.readFloat();
+        decel = f.readFloat();
+        distance = f.readFloat();
+
+        wait = f.readFloat();
+
+        state = f.readInt();
+        dir = f.readVector();
+
+        current_speed = f.readFloat();
+        move_speed = f.readFloat();
+        next_speed = f.readFloat();
+        remaining_distance = f.readFloat();
+        decel_distance = f.readFloat();
+        endfunc = (EntThinkAdapter) f.readAdapter();
+    }
+}
diff --git a/src/main/java/lwjake2/game/player_state_t.java b/src/main/java/lwjake2/game/player_state_t.java
new file mode 100644 (file)
index 0000000..3da55f9
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.Defines;
+import lwjake2.util.Math3D;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * Player_state_t is the information needed in addition to pmove_state_t
+ * to rendered a view.  There will only be 10 player_state_t sent each second,
+ * but the number of pmove_state_t changes will be relative to client
+ * frame rates.
+ */
+
+public class player_state_t {
+
+    /**
+     * Lets cleverly reset the structure.
+     */
+    private static final player_state_t prototype = new player_state_t();
+    public final pmove_state_t pmove = new pmove_state_t(); // for prediction
+    // these fields do not need to be communicated bit-precise
+    public final float[] viewangles = {0, 0, 0}; // for fixed views
+    public final float[] viewoffset = {0, 0, 0}; // add to pmovestate->origin
+    public final float[] kick_angles = {0, 0, 0}; // add to view direction to get render angles
+    // set by weapon kicks, pain effects, etc
+    public final float[] blend = new float[4]; // rgba full screen effect
+    public final short[] stats = new short[Defines.MAX_STATS];
+    public float fov; // horizontal field of view
+    public int rdflags; // refdef flags
+
+    /**
+     * Clears the player_state.
+     */
+    public void clear() {
+        this.set(prototype);
+    }
+
+    /**
+     * Clones the object.
+     */
+    public player_state_t getClone() {
+        return new player_state_t().set(this);
+    }
+
+    /**
+     * Copies the player state data.
+     */
+    public player_state_t set(player_state_t from) {
+        pmove.set(from.pmove);
+        Math3D.vectorCopy(from.viewangles, viewangles);
+        Math3D.vectorCopy(from.viewoffset, viewoffset);
+        Math3D.vectorCopy(from.kick_angles, kick_angles);
+
+        blend[0] = from.blend[0];
+        blend[1] = from.blend[1];
+        blend[2] = from.blend[2];
+        blend[3] = from.blend[3];
+
+        fov = from.fov;
+        rdflags = from.rdflags;
+
+        System.arraycopy(from.stats, 0, stats, 0, Defines.MAX_STATS);
+
+        return this;
+    }
+
+    /**
+     * Reads a player_state from a file.
+     */
+    public void load(RandomAccessFile f) throws IOException {
+        pmove.load(f);
+
+        viewangles[0] = f.readFloat();
+        viewangles[1] = f.readFloat();
+        viewangles[2] = f.readFloat();
+
+        viewoffset[0] = f.readFloat();
+        viewoffset[1] = f.readFloat();
+        viewoffset[2] = f.readFloat();
+
+        kick_angles[0] = f.readFloat();
+        kick_angles[1] = f.readFloat();
+        kick_angles[2] = f.readFloat();
+
+        blend[0] = f.readFloat();
+        blend[1] = f.readFloat();
+        blend[2] = f.readFloat();
+        blend[3] = f.readFloat();
+
+        fov = f.readFloat();
+
+        rdflags = f.readInt();
+
+        for (int n = 0; n < Defines.MAX_STATS; n++)
+            stats[n] = f.readShort();
+    }
+
+    /**
+     * Writes a player_state to a file.
+     */
+    public void write(RandomAccessFile f) throws IOException {
+        pmove.write(f);
+
+        f.writeFloat(viewangles[0]);
+        f.writeFloat(viewangles[1]);
+        f.writeFloat(viewangles[2]);
+
+        f.writeFloat(viewoffset[0]);
+        f.writeFloat(viewoffset[1]);
+        f.writeFloat(viewoffset[2]);
+
+        f.writeFloat(kick_angles[0]);
+        f.writeFloat(kick_angles[1]);
+        f.writeFloat(kick_angles[2]);
+
+        f.writeFloat(blend[0]);
+        f.writeFloat(blend[1]);
+        f.writeFloat(blend[2]);
+        f.writeFloat(blend[3]);
+
+        f.writeFloat(fov);
+
+        f.writeInt(rdflags);
+
+        for (int n = 0; n < Defines.MAX_STATS; n++)
+            f.writeShort(stats[n]);
+    }
+}
diff --git a/src/main/java/lwjake2/game/pmove_state_t.java b/src/main/java/lwjake2/game/pmove_state_t.java
new file mode 100644 (file)
index 0000000..bda385a
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.qcommon.Com;
+import lwjake2.util.Math3D;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+public class pmove_state_t {
+    // this structure needs to be communicated bit-accurate
+    // from the server to the client to guarantee that
+    // prediction stays in sync, so no floats are used.
+    // if any part of the game code modifies this struct, it
+    // will result in a prediction error of some degree.
+
+    /**
+     * changed by spawns, rotating objects, and teleporters.
+     */
+
+    private static final pmove_state_t prototype = new pmove_state_t();
+    public final short[] origin = {0, 0, 0}; // 12.3
+    public final short[] velocity = {0, 0, 0}; // 12.3
+    /**
+     * add to command angles to get view direction.
+     */
+    public final short[] delta_angles = {0, 0, 0};
+    public int pm_type;
+    /**
+     * ducked, jump_held, etc.
+     */
+    public byte pm_flags;
+    /**
+     * each unit = 8 ms.
+     */
+    public byte pm_time;
+    public short gravity;
+
+    public void clear() {
+        this.set(prototype);
+    }
+
+    public void set(pmove_state_t from) {
+        pm_type = from.pm_type;
+        Math3D.vectorCopy(from.origin, origin);
+        Math3D.vectorCopy(from.velocity, velocity);
+        pm_flags = from.pm_flags;
+        pm_time = from.pm_time;
+        gravity = from.gravity;
+        Math3D.vectorCopy(from.delta_angles, delta_angles);
+    }
+
+    public boolean equals(pmove_state_t p2) {
+        return pm_type == p2.pm_type
+                && origin[0] == p2.origin[0]
+                && origin[1] == p2.origin[1]
+                && origin[2] == p2.origin[2]
+                && velocity[0] == p2.velocity[0]
+                && velocity[1] == p2.velocity[1]
+                && velocity[2] == p2.origin[2]
+                && pm_flags == p2.pm_flags
+                && pm_time == p2.pm_time
+                && delta_angles[0] == p2.delta_angles[0]
+                && delta_angles[1] == p2.delta_angles[1]
+                && delta_angles[2] == p2.origin[2];
+
+    }
+
+    /**
+     * Reads the playermove from the file.
+     */
+    public void load(RandomAccessFile f) throws IOException {
+
+        pm_type = f.readInt();
+
+        origin[0] = f.readShort();
+        origin[1] = f.readShort();
+        origin[2] = f.readShort();
+
+        velocity[0] = f.readShort();
+        velocity[1] = f.readShort();
+        velocity[2] = f.readShort();
+
+        pm_flags = f.readByte();
+        pm_time = f.readByte();
+        gravity = f.readShort();
+
+        f.readShort();
+
+        delta_angles[0] = f.readShort();
+        delta_angles[1] = f.readShort();
+        delta_angles[2] = f.readShort();
+
+    }
+
+    /**
+     * Writes the playermove to the file.
+     */
+    public void write(RandomAccessFile f) throws IOException {
+
+        f.writeInt(pm_type);
+
+        f.writeShort(origin[0]);
+        f.writeShort(origin[1]);
+        f.writeShort(origin[2]);
+
+        f.writeShort(velocity[0]);
+        f.writeShort(velocity[1]);
+        f.writeShort(velocity[2]);
+
+        f.writeByte(pm_flags);
+        f.writeByte(pm_time);
+        f.writeShort(gravity);
+
+        f.writeShort(0);
+
+        f.writeShort(delta_angles[0]);
+        f.writeShort(delta_angles[1]);
+        f.writeShort(delta_angles[2]);
+    }
+
+    public void dump() {
+        Com.Println("pm_type: " + pm_type);
+
+        Com.Println("origin[0]: " + origin[0]);
+        Com.Println("origin[1]: " + origin[0]);
+        Com.Println("origin[2]: " + origin[0]);
+
+        Com.Println("velocity[0]: " + velocity[0]);
+        Com.Println("velocity[1]: " + velocity[1]);
+        Com.Println("velocity[2]: " + velocity[2]);
+
+        Com.Println("pmflags: " + pm_flags);
+        Com.Println("pmtime: " + pm_time);
+        Com.Println("gravity: " + gravity);
+
+        Com.Println("delta-angle[0]: " + delta_angles[0]);
+        Com.Println("delta-angle[1]: " + delta_angles[0]);
+        Com.Println("delta-angle[2]: " + delta_angles[0]);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/game/pushed_t.java b/src/main/java/lwjake2/game/pushed_t.java
new file mode 100644 (file)
index 0000000..ca42283
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+public class pushed_t {
+    public final float[] origin = {0.0f, 0.0f, 0.0f};
+    public final float[] angles = {0.0f, 0.0f, 0.0f};
+    public EDict ent;
+    public float deltayaw;
+}
diff --git a/src/main/java/lwjake2/game/spawn_t.java b/src/main/java/lwjake2/game/spawn_t.java
new file mode 100644 (file)
index 0000000..7dc3ddc
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+public class spawn_t {
+    final String name;
+    final EntThinkAdapter spawn;
+
+    public spawn_t(String name, EntThinkAdapter spawn) {
+        this.name = name;
+        this.spawn = spawn;
+    }
+}
diff --git a/src/main/java/lwjake2/game/spawn_temp_t.java b/src/main/java/lwjake2/game/spawn_temp_t.java
new file mode 100644 (file)
index 0000000..12006c2
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.util.Lib;
+
+public class spawn_temp_t {
+    // world vars
+    public String sky = "";
+    public float skyrotate;
+    public float[] skyaxis = {0, 0, 0};
+
+    public String nextmap = "";
+
+    public int lip;
+    public int distance;
+    public int height;
+
+    public String noise = "";
+    public float pausetime;
+
+    public String item = "";
+    public String gravity = "";
+
+    public float minyaw;
+    public float maxyaw;
+    public float minpitch;
+    public float maxpitch;
+
+    public boolean set(String key, String value) {
+        if (key.equals("lip")) {
+            lip = Lib.atoi(value);
+            return true;
+        } // F_INT, FFL_SPAWNTEMP),
+
+        if (key.equals("distance")) {
+            distance = Lib.atoi(value);
+            return true;
+        } // F_INT, FFL_SPAWNTEMP),
+
+        if (key.equals("height")) {
+            height = Lib.atoi(value);
+            return true;
+        } // F_INT, FFL_SPAWNTEMP),
+
+        if (key.equals("noise")) {
+            noise = GameSpawn.ED_NewString(value);
+            return true;
+        } // F_LSTRING, FFL_SPAWNTEMP),
+
+        if (key.equals("pausetime")) {
+            pausetime = Lib.atof(value);
+            return true;
+        } // F_FLOAT, FFL_SPAWNTEMP),
+
+        if (key.equals("item")) {
+            item = GameSpawn.ED_NewString(value);
+            return true;
+        } // F_LSTRING, FFL_SPAWNTEMP),
+
+        if (key.equals("gravity")) {
+            gravity = GameSpawn.ED_NewString(value);
+            return true;
+        } // F_LSTRING, FFL_SPAWNTEMP),
+
+        if (key.equals("sky")) {
+            sky = GameSpawn.ED_NewString(value);
+            return true;
+        } // F_LSTRING, FFL_SPAWNTEMP),
+
+        if (key.equals("skyrotate")) {
+            skyrotate = Lib.atof(value);
+            return true;
+        } // F_FLOAT, FFL_SPAWNTEMP),
+
+        if (key.equals("skyaxis")) {
+            skyaxis = Lib.atov(value);
+            return true;
+        } // F_VECTOR, FFL_SPAWNTEMP),
+
+        if (key.equals("minyaw")) {
+            minyaw = Lib.atof(value);
+            return true;
+        } // F_FLOAT, FFL_SPAWNTEMP),
+
+        if (key.equals("maxyaw")) {
+            maxyaw = Lib.atof(value);
+            return true;
+        } // F_FLOAT, FFL_SPAWNTEMP),
+
+        if (key.equals("minpitch")) {
+            minpitch = Lib.atof(value);
+            return true;
+        } // F_FLOAT, FFL_SPAWNTEMP),
+
+        if (key.equals("maxpitch")) {
+            maxpitch = Lib.atof(value);
+            return true;
+        } // F_FLOAT, FFL_SPAWNTEMP),
+
+        if (key.equals("nextmap")) {
+            nextmap = GameSpawn.ED_NewString(value);
+            return true;
+        } // F_LSTRING, FFL_SPAWNTEMP),
+
+        return false;
+    }
+}
diff --git a/src/main/java/lwjake2/game/trace_t.java b/src/main/java/lwjake2/game/trace_t.java
new file mode 100644 (file)
index 0000000..9938e6d
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+import lwjake2.util.Math3D;
+
+//a trace is returned when a box is swept through the world
+public class trace_t {
+    public final float[] endpos = {0, 0, 0}; // final position
+    // memory
+    public final cplane_t plane = new cplane_t(); // surface normal at impact
+    public boolean allsolid; // if true, plane is not valid
+    public boolean startsolid; // if true, the initial point was in a solid area
+    public float fraction; // time completed, 1.0 = didn't hit anything
+    // pointer
+    public csurface_t surface; // surface hit
+    public int contents; // contents on other side of surface hit
+    // pointer
+    public EDict ent; // not set by CM_*() functions
+
+    public void set(trace_t from) {
+        allsolid = from.allsolid;
+        startsolid = from.allsolid;
+        fraction = from.fraction;
+        Math3D.vectorCopy(from.endpos, endpos);
+        plane.set(from.plane);
+        surface = from.surface;
+        contents = from.contents;
+        ent = from.ent;
+    }
+
+    public void clear() {
+        allsolid = false;
+        startsolid = false;
+        fraction = 0;
+        Math3D.vectorClear(endpos);
+        plane.clear();
+        surface = null;
+        contents = 0;
+        ent = null;
+    }
+}
diff --git a/src/main/java/lwjake2/game/usercmd_t.java b/src/main/java/lwjake2/game/usercmd_t.java
new file mode 100644 (file)
index 0000000..4858655
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.game;
+
+public class usercmd_t implements Cloneable {
+    public final short[] angles = new short[3];
+    public byte msec;
+    public byte buttons;
+    public short forwardmove, sidemove, upmove;
+    public byte impulse; // remove?
+    public byte lightlevel; // light level the player is standing on
+
+    public usercmd_t() {
+    }
+
+    public usercmd_t(usercmd_t from) {
+        msec = from.msec;
+        buttons = from.buttons;
+        angles[0] = from.angles[0];
+        angles[1] = from.angles[1];
+        angles[2] = from.angles[2];
+        forwardmove = from.forwardmove;
+        sidemove = from.sidemove;
+        upmove = from.upmove;
+        impulse = from.impulse;
+        lightlevel = from.lightlevel;
+    }
+
+    public void clear() {
+        forwardmove = sidemove = upmove = msec = buttons = impulse = lightlevel = 0;
+        angles[0] = angles[1] = angles[2] = 0;
+    }
+
+    public void set(usercmd_t from) {
+        msec = from.msec;
+        buttons = from.buttons;
+        angles[0] = from.angles[0];
+        angles[1] = from.angles[1];
+        angles[2] = from.angles[2];
+        forwardmove = from.forwardmove;
+        sidemove = from.sidemove;
+        upmove = from.upmove;
+        impulse = from.impulse;
+        lightlevel = from.lightlevel;
+
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/qcommon/CM.java b/src/main/java/lwjake2/qcommon/CM.java
new file mode 100644 (file)
index 0000000..6fc47d0
--- /dev/null
@@ -0,0 +1,1779 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.qcommon;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.*;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+import lwjake2.util.Vargs;
+import lwjake2.util.Vec3Cache;
+
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.IntBuffer;
+import java.util.Arrays;
+
+public class CM {
+
+    public static final mapsurface_t[] map_surfaces = new mapsurface_t[Defines.MAX_MAP_TEXINFO];
+    public static final int[] map_leafbrushes = new int[Defines.MAX_MAP_LEAFBRUSHES];
+    public static final cmodel_t[] map_cmodels = new cmodel_t[Defines.MAX_MAP_MODELS];
+    public static final cbrush_t[] map_brushes = new cbrush_t[Defines.MAX_MAP_BRUSHES];
+    public static final byte[] map_visibility = new byte[Defines.MAX_MAP_VISIBILITY];
+    public static final carea_t[] map_areas = new carea_t[Defines.MAX_MAP_AREAS];
+    public static final qfiles.dareaportal_t[] map_areaportals = new qfiles.dareaportal_t[Defines.MAX_MAP_AREAPORTALS];
+    public static final mapsurface_t nullsurface = new mapsurface_t();
+    public static final boolean[] portalopen = new boolean[Defines.MAX_MAP_AREAPORTALS];
+    public static final byte[] pvsrow = new byte[Defines.MAX_MAP_LEAFS / 8];
+    public static final byte[] phsrow = new byte[Defines.MAX_MAP_LEAFS / 8];
+    static final cbrushside_t[] map_brushsides = new cbrushside_t[Defines.MAX_MAP_BRUSHSIDES];
+    /**
+     * Extra for box hull ( +6)
+     */
+    static final cplane_t[] map_planes = new cplane_t[Defines.MAX_MAP_PLANES + 6];
+    /**
+     * Extra for box hull ( +6)
+     */
+    static final cnode_t[] map_nodes = new cnode_t[Defines.MAX_MAP_NODES + 6];
+    static final cleaf_t[] map_leafs = new cleaf_t[Defines.MAX_MAP_LEAFS];
+    static final boolean debugloadmap = false;
+    // 1/32 epsilon to keep floating point happy
+    private static final float DIST_EPSILON = 0.03125f;
+    private static final float[] trace_start = {0, 0, 0};
+    private static final float[] trace_end = {0, 0, 0};
+    private static final float[] trace_mins = {0, 0, 0};
+    private static final float[] trace_maxs = {0, 0, 0};
+    private static final float[] trace_extents = {0, 0, 0};
+    public static int numtexinfo;
+    public static int numcmodels;
+    public static int numbrushes;
+    public static int numvisibility;
+    /**
+     * Main visibility data.
+     */
+    public static qfiles.dvis_t map_vis = new qfiles.dvis_t(ByteBuffer
+            .wrap(map_visibility));
+    public static String map_entitystring;
+    public static int numareas = 1;
+    public static int numareaportals;
+    public static int numclusters = 1;
+    public static int floodvalid;
+    public static CvarT map_noareas;
+    public static byte cmod_base[];
+    public static int last_checksum;
+    static int checkcount;
+    static String map_name = "";
+    static int numbrushsides;
+    static int numplanes;
+    static int numnodes;
+    static int numleafs = 1; // allow leaf funcs to be called without a map
+    static int emptyleaf;
+    static int numleafbrushes;
+    static cplane_t box_planes[];
+    static int box_headnode;
+    static cbrush_t box_brush;
+    static cleaf_t box_leaf;
+    private static int leaf_count, leaf_maxcount;
+    private static int leaf_list[];
+    private static float leaf_mins[], leaf_maxs[];
+    private static int leaf_topnode;
+    private static trace_t trace_trace = new trace_t();
+    private static int trace_contents;
+    private static boolean trace_ispoint; // optimized case
+
+    static {
+        for (int n = 0; n < Defines.MAX_MAP_BRUSHSIDES; n++)
+            map_brushsides[n] = new cbrushside_t();
+    }
+
+    static {
+        for (int n = 0; n < Defines.MAX_MAP_TEXINFO; n++)
+            map_surfaces[n] = new mapsurface_t();
+    }
+
+    static {
+        for (int n = 0; n < Defines.MAX_MAP_PLANES + 6; n++)
+            map_planes[n] = new cplane_t();
+    }
+
+    static {
+        for (int n = 0; n < Defines.MAX_MAP_NODES + 6; n++)
+            map_nodes[n] = new cnode_t();
+    }
+
+    static {
+        for (int n = 0; n < Defines.MAX_MAP_LEAFS; n++)
+            map_leafs[n] = new cleaf_t();
+    }
+
+    static {
+        for (int n = 0; n < Defines.MAX_MAP_MODELS; n++)
+            map_cmodels[n] = new cmodel_t();
+
+    }
+
+    static {
+        for (int n = 0; n < Defines.MAX_MAP_BRUSHES; n++)
+            map_brushes[n] = new cbrush_t();
+
+    }
+
+    static {
+        for (int n = 0; n < Defines.MAX_MAP_AREAS; n++)
+            map_areas[n] = new carea_t();
+
+    }
+
+    static {
+        for (int n = 0; n < Defines.MAX_MAP_AREAPORTALS; n++)
+            map_areaportals[n] = new qfiles.dareaportal_t();
+
+    }
+
+    /**
+     * Loads in the map and all submodels.
+     */
+    public static cmodel_t CM_LoadMap(String name, boolean clientload,
+                                      int checksum[]) {
+        Com.DPrintf("CM_LoadMap(" + name + ")...\n");
+        byte buf[];
+        qfiles.dheader_t header;
+        int length;
+
+        map_noareas = Cvar.get("map_noareas", "0", 0);
+
+        if (map_name.equals(name)
+                && (clientload || 0 == Cvar.variableValue("flushmap"))) {
+
+            checksum[0] = last_checksum;
+
+            if (!clientload) {
+                Arrays.fill(portalopen, false);
+                FloodAreaConnections();
+            }
+            return map_cmodels[0]; // still have the right version
+        }
+
+        // free old stuff
+        numnodes = 0;
+        numleafs = 0;
+        numcmodels = 0;
+        numvisibility = 0;
+        map_entitystring = "";
+        map_name = "";
+
+        if (name == null || name.length() == 0) {
+            numleafs = 1;
+            numclusters = 1;
+            numareas = 1;
+            checksum[0] = 0;
+            return map_cmodels[0];
+            // cinematic servers won't have anything at all
+        }
+
+        //
+        // load the file
+        //
+        buf = FS.LoadFile(name);
+
+        if (buf == null)
+            Com.Error(Defines.ERR_DROP, "Couldn't load " + name);
+
+        length = buf.length;
+
+        ByteBuffer bbuf = ByteBuffer.wrap(buf);
+
+        last_checksum = MD4.Com_BlockChecksum(buf, length);
+        checksum[0] = last_checksum;
+
+        header = new qfiles.dheader_t(bbuf.slice());
+
+        if (header.version != Defines.BSPVERSION)
+            Com.Error(Defines.ERR_DROP, "CMod_LoadBrushModel: " + name
+                    + " has wrong version number (" + header.version
+                    + " should be " + Defines.BSPVERSION + ")");
+
+        cmod_base = buf;
+
+        // load into heap
+        CMod_LoadSurfaces(header.lumps[Defines.LUMP_TEXINFO]); // ok
+        CMod_LoadLeafs(header.lumps[Defines.LUMP_LEAFS]);
+        CMod_LoadLeafBrushes(header.lumps[Defines.LUMP_LEAFBRUSHES]);
+        CMod_LoadPlanes(header.lumps[Defines.LUMP_PLANES]);
+        CMod_LoadBrushes(header.lumps[Defines.LUMP_BRUSHES]);
+        CMod_LoadBrushSides(header.lumps[Defines.LUMP_BRUSHSIDES]);
+        CMod_LoadSubmodels(header.lumps[Defines.LUMP_MODELS]);
+
+        CMod_LoadNodes(header.lumps[Defines.LUMP_NODES]);
+        CMod_LoadAreas(header.lumps[Defines.LUMP_AREAS]);
+        CMod_LoadAreaPortals(header.lumps[Defines.LUMP_AREAPORTALS]);
+        CMod_LoadVisibility(header.lumps[Defines.LUMP_VISIBILITY]);
+        CMod_LoadEntityString(header.lumps[Defines.LUMP_ENTITIES]);
+
+        FS.FreeFile();
+
+        CM_InitBoxHull();
+
+        Arrays.fill(portalopen, false);
+
+        FloodAreaConnections();
+
+        map_name = name;
+
+        return map_cmodels[0];
+    }
+
+    /**
+     * Loads Submodels.
+     */
+    public static void CMod_LoadSubmodels(lump_t l) {
+        Com.DPrintf("CMod_LoadSubmodels()\n");
+        qfiles.dmodel_t in;
+        cmodel_t out;
+        int i, j, count;
+
+        if ((l.filelen % qfiles.dmodel_t.SIZE) != 0)
+            Com.Error(Defines.ERR_DROP, "CMod_LoadBmodel: funny lump size");
+
+        count = l.filelen / qfiles.dmodel_t.SIZE;
+
+        if (count < 1)
+            Com.Error(Defines.ERR_DROP, "Map with no models");
+        if (count > Defines.MAX_MAP_MODELS)
+            Com.Error(Defines.ERR_DROP, "Map has too many models");
+
+        Com.DPrintf(" numcmodels=" + count + "\n");
+        numcmodels = count;
+
+        if (debugloadmap) {
+            Com.DPrintf("submodles(headnode, <origin>, <mins>, <maxs>)\n");
+        }
+        for (i = 0; i < count; i++) {
+            in = new qfiles.dmodel_t(ByteBuffer.wrap(cmod_base, i
+                    * qfiles.dmodel_t.SIZE + l.fileofs, qfiles.dmodel_t.SIZE));
+            out = map_cmodels[i];
+
+            for (j = 0; j < 3; j++) { // spread the mins / maxs by a pixel
+                out.mins[j] = in.mins[j] - 1;
+                out.maxs[j] = in.maxs[j] + 1;
+                out.origin[j] = in.origin[j];
+            }
+            out.headnode = in.headnode;
+            if (debugloadmap) {
+                Com
+                        .DPrintf(
+                                "|%6i|%8.2f|%8.2f|%8.2f|  %8.2f|%8.2f|%8.2f|   %8.2f|%8.2f|%8.2f|\n",
+                                new Vargs().add(out.headnode)
+                                        .add(out.origin[0]).add(out.origin[1])
+                                        .add(out.origin[2]).add(out.mins[0])
+                                        .add(out.mins[1]).add(out.mins[2]).add(
+                                        out.maxs[0]).add(out.maxs[1])
+                                        .add(out.maxs[2]));
+            }
+        }
+    }
+
+    /**
+     * Loads surfaces.
+     */
+    public static void CMod_LoadSurfaces(lump_t l) {
+        Com.DPrintf("CMod_LoadSurfaces()\n");
+        texinfo_t in;
+        mapsurface_t out;
+        int i, count;
+
+        if ((l.filelen % texinfo_t.SIZE) != 0)
+            Com.Error(Defines.ERR_DROP, "MOD_LoadBmodel: funny lump size");
+
+        count = l.filelen / texinfo_t.SIZE;
+        if (count < 1)
+            Com.Error(Defines.ERR_DROP, "Map with no surfaces");
+        if (count > Defines.MAX_MAP_TEXINFO)
+            Com.Error(Defines.ERR_DROP, "Map has too many surfaces");
+
+        numtexinfo = count;
+        Com.DPrintf(" numtexinfo=" + count + "\n");
+        if (debugloadmap)
+            Com.DPrintf("surfaces:\n");
+
+        for (i = 0; i < count; i++) {
+            out = map_surfaces[i] = new mapsurface_t();
+            in = new texinfo_t(cmod_base, l.fileofs + i * texinfo_t.SIZE,
+                    texinfo_t.SIZE);
+
+            out.c.name = in.texture;
+            out.rname = in.texture;
+            out.c.flags = in.flags;
+            out.c.value = in.value;
+
+            if (debugloadmap) {
+                Com.DPrintf("|%20s|%20s|%6i|%6i|\n", new Vargs()
+                        .add(out.c.name).add(out.rname).add(out.c.value).add(
+                                out.c.flags));
+            }
+
+        }
+    }
+
+    /**
+     * Loads nodes.
+     */
+    public static void CMod_LoadNodes(lump_t l) {
+        Com.DPrintf("CMod_LoadNodes()\n");
+        qfiles.dnode_t in;
+        int child;
+        cnode_t out;
+        int i, j, count;
+
+        if ((l.filelen % qfiles.dnode_t.SIZE) != 0)
+            Com.Error(Defines.ERR_DROP, "MOD_LoadBmodel: funny lump size:"
+                    + l.fileofs + "," + qfiles.dnode_t.SIZE);
+        count = l.filelen / qfiles.dnode_t.SIZE;
+
+        if (count < 1)
+            Com.Error(Defines.ERR_DROP, "Map has no nodes");
+        if (count > Defines.MAX_MAP_NODES)
+            Com.Error(Defines.ERR_DROP, "Map has too many nodes");
+
+        numnodes = count;
+        Com.DPrintf(" numnodes=" + count + "\n");
+
+        if (debugloadmap) {
+            Com.DPrintf("nodes(planenum, child[0], child[1])\n");
+        }
+
+        for (i = 0; i < count; i++) {
+            in = new qfiles.dnode_t(ByteBuffer.wrap(cmod_base,
+                    qfiles.dnode_t.SIZE * i + l.fileofs, qfiles.dnode_t.SIZE));
+            out = map_nodes[i];
+
+            out.plane = map_planes[in.planenum];
+            for (j = 0; j < 2; j++) {
+                child = in.children[j];
+                out.children[j] = child;
+            }
+            if (debugloadmap) {
+                Com.DPrintf("|%6i| %6i| %6i|\n", new Vargs().add(in.planenum)
+                        .add(out.children[0]).add(out.children[1]));
+            }
+        }
+    }
+
+    /**
+     * Loads brushes.
+     */
+    public static void CMod_LoadBrushes(lump_t l) {
+        Com.DPrintf("CMod_LoadBrushes()\n");
+        qfiles.dbrush_t in;
+        cbrush_t out;
+        int i, count;
+
+        if ((l.filelen % qfiles.dbrush_t.SIZE) != 0)
+            Com.Error(Defines.ERR_DROP, "MOD_LoadBmodel: funny lump size");
+
+        count = l.filelen / qfiles.dbrush_t.SIZE;
+
+        if (count > Defines.MAX_MAP_BRUSHES)
+            Com.Error(Defines.ERR_DROP, "Map has too many brushes");
+
+        numbrushes = count;
+        Com.DPrintf(" numbrushes=" + count + "\n");
+        if (debugloadmap) {
+            Com.DPrintf("brushes:(firstbrushside, numsides, contents)\n");
+        }
+        for (i = 0; i < count; i++) {
+            in = new qfiles.dbrush_t(ByteBuffer.wrap(cmod_base, i
+                    * qfiles.dbrush_t.SIZE + l.fileofs, qfiles.dbrush_t.SIZE));
+            out = map_brushes[i];
+            out.firstbrushside = in.firstside;
+            out.numsides = in.numsides;
+            out.contents = in.contents;
+
+            if (debugloadmap) {
+                Com
+                        .DPrintf("| %6i| %6i| %8X|\n", new Vargs().add(
+                                out.firstbrushside).add(out.numsides).add(
+                                out.contents));
+            }
+        }
+    }
+
+    /**
+     * Loads leafs.
+     */
+    public static void CMod_LoadLeafs(lump_t l) {
+        Com.DPrintf("CMod_LoadLeafs()\n");
+        int i;
+        cleaf_t out;
+        qfiles.dleaf_t in;
+        int count;
+
+        if ((l.filelen % qfiles.dleaf_t.SIZE) != 0)
+            Com.Error(Defines.ERR_DROP, "MOD_LoadBmodel: funny lump size");
+
+        count = l.filelen / qfiles.dleaf_t.SIZE;
+
+        if (count < 1)
+            Com.Error(Defines.ERR_DROP, "Map with no leafs");
+
+        // need to save space for box planes
+        if (count > Defines.MAX_MAP_PLANES)
+            Com.Error(Defines.ERR_DROP, "Map has too many planes");
+
+        Com.DPrintf(" numleafes=" + count + "\n");
+
+        numleafs = count;
+        numclusters = 0;
+        if (debugloadmap)
+            Com.DPrintf("cleaf-list:(contents, cluster, area, firstleafbrush, numleafbrushes)\n");
+        for (i = 0; i < count; i++) {
+            in = new qfiles.dleaf_t(cmod_base, i * qfiles.dleaf_t.SIZE
+                    + l.fileofs, qfiles.dleaf_t.SIZE);
+
+            out = map_leafs[i];
+
+            out.contents = in.contents;
+            out.cluster = in.cluster;
+            out.area = in.area;
+            out.firstleafbrush = (short) in.firstleafbrush;
+            out.numleafbrushes = (short) in.numleafbrushes;
+
+            if (out.cluster >= numclusters)
+                numclusters = out.cluster + 1;
+
+            if (debugloadmap) {
+                Com.DPrintf("|%8x|%6i|%6i|%6i|\n", new Vargs()
+                        .add(out.contents).add(out.cluster).add(out.area).add(
+                                out.firstleafbrush).add(out.numleafbrushes));
+            }
+
+        }
+
+        Com.DPrintf(" numclusters=" + numclusters + "\n");
+
+        if (map_leafs[0].contents != Defines.CONTENTS_SOLID)
+            Com.Error(Defines.ERR_DROP, "Map leaf 0 is not CONTENTS_SOLID");
+
+        emptyleaf = -1;
+
+        for (i = 1; i < numleafs; i++) {
+            if (map_leafs[i].contents == 0) {
+                emptyleaf = i;
+                break;
+            }
+        }
+
+        if (emptyleaf == -1)
+            Com.Error(Defines.ERR_DROP, "Map does not have an empty leaf");
+    }
+
+    /**
+     * Loads planes.
+     */
+    public static void CMod_LoadPlanes(lump_t l) {
+        Com.DPrintf("CMod_LoadPlanes()\n");
+        int i, j;
+        cplane_t out;
+        qfiles.dplane_t in;
+        int count;
+        int bits;
+
+        if ((l.filelen % qfiles.dplane_t.SIZE) != 0)
+            Com.Error(Defines.ERR_DROP, "MOD_LoadBmodel: funny lump size");
+
+        count = l.filelen / qfiles.dplane_t.SIZE;
+
+        if (count < 1)
+            Com.Error(Defines.ERR_DROP, "Map with no planes");
+
+        // need to save space for box planes
+        if (count > Defines.MAX_MAP_PLANES)
+            Com.Error(Defines.ERR_DROP, "Map has too many planes");
+
+        Com.DPrintf(" numplanes=" + count + "\n");
+
+        numplanes = count;
+        if (debugloadmap) {
+            Com
+                    .DPrintf("cplanes(normal[0],normal[1],normal[2], dist, type, signbits)\n");
+        }
+
+        for (i = 0; i < count; i++) {
+            in = new qfiles.dplane_t(ByteBuffer.wrap(cmod_base, i
+                    * qfiles.dplane_t.SIZE + l.fileofs, qfiles.dplane_t.SIZE));
+
+            out = map_planes[i];
+
+            bits = 0;
+            for (j = 0; j < 3; j++) {
+                out.normal[j] = in.normal[j];
+
+                if (out.normal[j] < 0)
+                    bits |= 1 << j;
+            }
+
+            out.dist = in.dist;
+            out.type = (byte) in.type;
+            out.signbits = (byte) bits;
+
+            if (debugloadmap) {
+                Com.DPrintf("|%6.2f|%6.2f|%6.2f| %10.2f|%3i| %1i|\n",
+                        new Vargs().add(out.normal[0]).add(out.normal[1]).add(
+                                out.normal[2]).add(out.dist).add(out.type).add(
+                                out.signbits));
+            }
+        }
+    }
+
+    /**
+     * Loads leaf brushes.
+     */
+    public static void CMod_LoadLeafBrushes(lump_t l) {
+        Com.DPrintf("CMod_LoadLeafBrushes()\n");
+        int i;
+        int out[];
+        int count;
+
+        if ((l.filelen % 2) != 0)
+            Com.Error(Defines.ERR_DROP, "MOD_LoadBmodel: funny lump size");
+
+        count = l.filelen / 2;
+
+        Com.DPrintf(" numbrushes=" + count + "\n");
+
+        if (count < 1)
+            Com.Error(Defines.ERR_DROP, "Map with no planes");
+
+        // need to save space for box planes
+        if (count > Defines.MAX_MAP_LEAFBRUSHES)
+            Com.Error(Defines.ERR_DROP, "Map has too many leafbrushes");
+
+        out = map_leafbrushes;
+        numleafbrushes = count;
+
+        ByteBuffer bb = ByteBuffer.wrap(cmod_base, l.fileofs, count * 2).order(
+                ByteOrder.LITTLE_ENDIAN);
+
+        if (debugloadmap) {
+            Com.DPrintf("map_brushes:\n");
+        }
+
+        for (i = 0; i < count; i++) {
+            out[i] = bb.getShort();
+            if (debugloadmap) {
+                Com.DPrintf("|%6i|%6i|\n", new Vargs().add(i).add(out[i]));
+            }
+        }
+    }
+
+    /**
+     * Loads brush sides.
+     */
+    public static void CMod_LoadBrushSides(lump_t l) {
+        Com.DPrintf("CMod_LoadBrushSides()\n");
+        int i, j;
+        cbrushside_t out;
+        qfiles.dbrushside_t in;
+        int count;
+        int num;
+
+        if ((l.filelen % qfiles.dbrushside_t.SIZE) != 0)
+            Com.Error(Defines.ERR_DROP, "MOD_LoadBmodel: funny lump size");
+        count = l.filelen / qfiles.dbrushside_t.SIZE;
+
+        // need to save space for box planes
+        if (count > Defines.MAX_MAP_BRUSHSIDES)
+            Com.Error(Defines.ERR_DROP, "Map has too many planes");
+
+        numbrushsides = count;
+
+        Com.DPrintf(" numbrushsides=" + count + "\n");
+
+        if (debugloadmap) {
+            Com.DPrintf("brushside(planenum, surfacenum):\n");
+        }
+        for (i = 0; i < count; i++) {
+
+            in = new qfiles.dbrushside_t(ByteBuffer.wrap(cmod_base, i
+                            * qfiles.dbrushside_t.SIZE + l.fileofs,
+                    qfiles.dbrushside_t.SIZE));
+
+            out = map_brushsides[i];
+
+            num = in.planenum;
+
+            out.plane = map_planes[num]; // pointer
+
+            j = in.texinfo;
+
+            if (j >= numtexinfo)
+                Com.Error(Defines.ERR_DROP, "Bad brushside texinfo");
+
+            // java specific handling of -1
+            if (j == -1)
+                out.surface = new mapsurface_t(); // just for safety
+            else
+                out.surface = map_surfaces[j];
+
+            if (debugloadmap) {
+                Com.DPrintf("| %6i| %6i|\n", new Vargs().add(num).add(j));
+            }
+        }
+    }
+
+    /**
+     * Loads areas.
+     */
+    public static void CMod_LoadAreas(lump_t l) {
+        Com.DPrintf("CMod_LoadAreas()\n");
+        int i;
+        carea_t out;
+        qfiles.darea_t in;
+        int count;
+
+        if ((l.filelen % qfiles.darea_t.SIZE) != 0)
+            Com.Error(Defines.ERR_DROP, "MOD_LoadBmodel: funny lump size");
+
+        count = l.filelen / qfiles.darea_t.SIZE;
+
+        if (count > Defines.MAX_MAP_AREAS)
+            Com.Error(Defines.ERR_DROP, "Map has too many areas");
+
+        Com.DPrintf(" numareas=" + count + "\n");
+        numareas = count;
+
+        if (debugloadmap) {
+            Com.DPrintf("areas(numportals, firstportal)\n");
+        }
+
+        for (i = 0; i < count; i++) {
+
+            in = new qfiles.darea_t(ByteBuffer.wrap(cmod_base, i
+                    * qfiles.darea_t.SIZE + l.fileofs, qfiles.darea_t.SIZE));
+            out = map_areas[i];
+
+            out.numareaportals = in.numareaportals;
+            out.firstareaportal = in.firstareaportal;
+            out.floodvalid = 0;
+            out.floodnum = 0;
+            if (debugloadmap) {
+                Com.DPrintf("| %6i| %6i|\n", new Vargs()
+                        .add(out.numareaportals).add(out.firstareaportal));
+            }
+        }
+    }
+
+    /**
+     * Loads area portals.
+     */
+    public static void CMod_LoadAreaPortals(lump_t l) {
+        Com.DPrintf("CMod_LoadAreaPortals()\n");
+        int i;
+        qfiles.dareaportal_t out;
+        qfiles.dareaportal_t in;
+        int count;
+
+        if ((l.filelen % qfiles.dareaportal_t.SIZE) != 0)
+            Com.Error(Defines.ERR_DROP, "MOD_LoadBmodel: funny lump size");
+        count = l.filelen / qfiles.dareaportal_t.SIZE;
+
+        if (count > Defines.MAX_MAP_AREAS)
+            Com.Error(Defines.ERR_DROP, "Map has too many areas");
+
+        numareaportals = count;
+        Com.DPrintf(" numareaportals=" + count + "\n");
+        if (debugloadmap) {
+            Com.DPrintf("areaportals(portalnum, otherarea)\n");
+        }
+        for (i = 0; i < count; i++) {
+            in = new qfiles.dareaportal_t(ByteBuffer.wrap(cmod_base, i
+                            * qfiles.dareaportal_t.SIZE + l.fileofs,
+                    qfiles.dareaportal_t.SIZE));
+
+            out = map_areaportals[i];
+
+            out.portalnum = in.portalnum;
+            out.otherarea = in.otherarea;
+
+            if (debugloadmap) {
+                Com.DPrintf("|%6i|%6i|\n", new Vargs().add(out.portalnum).add(
+                        out.otherarea));
+            }
+        }
+    }
+
+    /**
+     * Loads visibility data.
+     */
+    public static void CMod_LoadVisibility(lump_t l) {
+        Com.DPrintf("CMod_LoadVisibility()\n");
+
+        numvisibility = l.filelen;
+
+        Com.DPrintf(" numvisibility=" + numvisibility + "\n");
+
+        if (l.filelen > Defines.MAX_MAP_VISIBILITY)
+            Com.Error(Defines.ERR_DROP, "Map has too large visibility lump");
+
+        System.arraycopy(cmod_base, l.fileofs, map_visibility, 0, l.filelen);
+
+        ByteBuffer bb = ByteBuffer.wrap(map_visibility, 0, l.filelen);
+        bb.order(ByteOrder.LITTLE_ENDIAN);
+
+        map_vis = new qfiles.dvis_t(bb);
+
+    }
+
+    /**
+     * Loads entity strings.
+     */
+    public static void CMod_LoadEntityString(lump_t l) {
+        Com.DPrintf("CMod_LoadEntityString()\n");
+
+        if (l.filelen > Defines.MAX_MAP_ENTSTRING)
+            Com.Error(Defines.ERR_DROP, "Map has too large entity lump");
+
+        int x = 0;
+        for (; x < l.filelen && cmod_base[x + l.fileofs] != 0; x++) ;
+
+        map_entitystring = new String(cmod_base, l.fileofs, x).trim();
+        Com.dprintln("entitystring=" + map_entitystring.length() +
+                " bytes, [" + map_entitystring.substring(0, Math.min(
+                map_entitystring.length(), 15)) + "...]");
+    }
+
+    /**
+     * Returns the model with a given id "*" + <number>
+     */
+    public static cmodel_t InlineModel(String name) {
+        int num;
+
+        if (name == null || name.charAt(0) != '*')
+            Com.Error(Defines.ERR_DROP, "CM_InlineModel: bad name");
+
+        num = Lib.atoi(name.substring(1));
+
+        if (num < 1 || num >= numcmodels)
+            Com.Error(Defines.ERR_DROP, "CM_InlineModel: bad number");
+
+        return map_cmodels[num];
+    }
+
+    public static int CM_NumClusters() {
+        return numclusters;
+    }
+
+    public static int CM_NumInlineModels() {
+        return numcmodels;
+    }
+
+    public static String CM_EntityString() {
+        return map_entitystring;
+    }
+
+    public static int CM_LeafCluster(int leafnum) {
+        if (leafnum < 0 || leafnum >= numleafs)
+            Com.Error(Defines.ERR_DROP, "CM_LeafCluster: bad number");
+        return map_leafs[leafnum].cluster;
+    }
+
+    public static int CM_LeafArea(int leafnum) {
+        if (leafnum < 0 || leafnum >= numleafs)
+            Com.Error(Defines.ERR_DROP, "CM_LeafArea: bad number");
+        return map_leafs[leafnum].area;
+    }
+
+    /**
+     * Set up the planes and nodes so that the six floats of a bounding box can
+     * just be stored out and get a proper clipping hull structure.
+     */
+    public static void CM_InitBoxHull() {
+        int i;
+        int side;
+        cnode_t c;
+        cplane_t p;
+        cbrushside_t s;
+
+        box_headnode = numnodes; //rst: still room for 6 brushes left?
+
+        box_planes = new cplane_t[]{map_planes[numplanes],
+                map_planes[numplanes + 1], map_planes[numplanes + 2],
+                map_planes[numplanes + 3], map_planes[numplanes + 4],
+                map_planes[numplanes + 5], map_planes[numplanes + 6],
+                map_planes[numplanes + 7], map_planes[numplanes + 8],
+                map_planes[numplanes + 9], map_planes[numplanes + 10],
+                map_planes[numplanes + 11], map_planes[numplanes + 12]};
+
+        if (numnodes + 6 > Defines.MAX_MAP_NODES
+                || numbrushes + 1 > Defines.MAX_MAP_BRUSHES
+                || numleafbrushes + 1 > Defines.MAX_MAP_LEAFBRUSHES
+                || numbrushsides + 6 > Defines.MAX_MAP_BRUSHSIDES
+                || numplanes + 12 > Defines.MAX_MAP_PLANES)
+            Com.Error(Defines.ERR_DROP, "Not enough room for box tree");
+
+        box_brush = map_brushes[numbrushes];
+        box_brush.numsides = 6;
+        box_brush.firstbrushside = numbrushsides;
+        box_brush.contents = Defines.CONTENTS_MONSTER;
+
+        box_leaf = map_leafs[numleafs];
+        box_leaf.contents = Defines.CONTENTS_MONSTER;
+        box_leaf.firstleafbrush = (short) numleafbrushes;
+        box_leaf.numleafbrushes = 1;
+
+        map_leafbrushes[numleafbrushes] = numbrushes;
+
+        for (i = 0; i < 6; i++) {
+            side = i & 1;
+
+            // brush sides
+            s = map_brushsides[numbrushsides + i];
+            s.plane = map_planes[(numplanes + i * 2 + side)];
+            s.surface = nullsurface;
+
+            // nodes
+            c = map_nodes[box_headnode + i];
+            c.plane = map_planes[(numplanes + i * 2)];
+            c.children[side] = -1 - emptyleaf;
+            if (i != 5)
+                c.children[side ^ 1] = box_headnode + i + 1;
+            else
+                c.children[side ^ 1] = -1 - numleafs;
+
+            // planes
+            p = box_planes[i * 2];
+            p.type = (byte) (i >> 1);
+            p.signbits = 0;
+            Math3D.vectorClear(p.normal);
+            p.normal[i >> 1] = 1;
+
+            p = box_planes[i * 2 + 1];
+            p.type = (byte) (3 + (i >> 1));
+            p.signbits = 0;
+            Math3D.vectorClear(p.normal);
+            p.normal[i >> 1] = -1;
+        }
+    }
+
+    /**
+     * To keep everything totally uniform, bounding boxes are turned into small
+     * BSP trees instead of being compared directly.
+     */
+    public static int HeadnodeForBox(float[] mins, float[] maxs) {
+        box_planes[0].dist = maxs[0];
+        box_planes[1].dist = -maxs[0];
+        box_planes[2].dist = mins[0];
+        box_planes[3].dist = -mins[0];
+        box_planes[4].dist = maxs[1];
+        box_planes[5].dist = -maxs[1];
+        box_planes[6].dist = mins[1];
+        box_planes[7].dist = -mins[1];
+        box_planes[8].dist = maxs[2];
+        box_planes[9].dist = -maxs[2];
+        box_planes[10].dist = mins[2];
+        box_planes[11].dist = -mins[2];
+
+        return box_headnode;
+    }
+
+    /**
+     * Recursively searches the leaf number that contains the 3d point.
+     */
+    private static int CM_PointLeafnum_r(float[] p, int num) {
+        float d;
+        cnode_t node;
+        cplane_t plane;
+
+        while (num >= 0) {
+            node = map_nodes[num];
+            plane = node.plane;
+
+            if (plane.type < 3)
+                d = p[plane.type] - plane.dist;
+            else
+                d = Math3D.dotProduct(plane.normal, p) - plane.dist;
+            if (d < 0)
+                num = node.children[1];
+            else
+                num = node.children[0];
+        }
+
+        Globals.c_pointcontents++; // optimize counter
+
+        return -1 - num;
+    }
+
+    /**
+     * Searches the leaf number that contains the 3d point.
+     */
+    public static int CM_PointLeafnum(float[] p) {
+        // sound may call this without map loaded
+        if (numplanes == 0)
+            return 0;
+        return CM_PointLeafnum_r(p, 0);
+    }
+
+    /**
+     * Recursively fills in a list of all the leafs touched.
+     */
+    private static void CM_BoxLeafnums_r(int nodenum) {
+        cplane_t plane;
+        cnode_t node;
+        int s;
+
+        while (true) {
+            if (nodenum < 0) {
+                if (leaf_count >= leaf_maxcount) {
+                    Com.DPrintf("CM_BoxLeafnums_r: overflow\n");
+                    return;
+                }
+                leaf_list[leaf_count++] = -1 - nodenum;
+                return;
+            }
+
+            node = map_nodes[nodenum];
+            plane = node.plane;
+
+            s = Math3D.boxOnPlaneSide(leaf_mins, leaf_maxs, plane);
+
+            if (s == 1)
+                nodenum = node.children[0];
+            else if (s == 2)
+                nodenum = node.children[1];
+            else {
+                // go down both
+                if (leaf_topnode == -1)
+                    leaf_topnode = nodenum;
+                CM_BoxLeafnums_r(node.children[0]);
+                nodenum = node.children[1];
+            }
+        }
+    }
+
+    /*
+     * ===============================================================================
+     * 
+     * BOX TRACING
+     * 
+     * ===============================================================================
+     */
+
+    /**
+     * Fills in a list of all the leafs touched and starts with the head node.
+     */
+    private static int CM_BoxLeafnums_headnode(float[] mins, float[] maxs,
+                                               int list[], int listsize, int headnode, int topnode[]) {
+        leaf_list = list;
+        leaf_count = 0;
+        leaf_maxcount = listsize;
+        leaf_mins = mins;
+        leaf_maxs = maxs;
+
+        leaf_topnode = -1;
+
+        CM_BoxLeafnums_r(headnode);
+
+        if (topnode != null)
+            topnode[0] = leaf_topnode;
+
+        return leaf_count;
+    }
+
+    /**
+     * Fills in a list of all the leafs touched.
+     */
+    public static int CM_BoxLeafnums(float[] mins, float[] maxs, int list[],
+                                     int listsize, int topnode[]) {
+        return CM_BoxLeafnums_headnode(mins, maxs, list, listsize,
+                map_cmodels[0].headnode, topnode);
+    }
+
+    /**
+     * Returns a tag that describes the content of the point.
+     */
+    public static int PointContents(float[] p, int headnode) {
+        int l;
+
+        if (numnodes == 0) // map not loaded
+            return 0;
+
+        l = CM_PointLeafnum_r(p, headnode);
+
+        return map_leafs[l].contents;
+    }
+
+    /*
+     * ================== CM_TransformedPointContents
+     *
+     * Handles offseting and rotation of the end points for moving and rotating
+     * entities ==================
+     */
+    public static int TransformedPointContents(float[] p, int headnode,
+                                               float[] origin, float[] angles) {
+        float[] p_l = {0, 0, 0};
+        float[] temp = {0, 0, 0};
+        float[] forward = {0, 0, 0}, right = {0, 0, 0}, up = {0, 0, 0};
+        int l;
+
+        // subtract origin offset
+        Math3D.vectorSubtract(p, origin, p_l);
+
+        // rotate start and end into the models frame of reference
+        if (headnode != box_headnode
+                && (angles[0] != 0 || angles[1] != 0 || angles[2] != 0)) {
+            Math3D.angleVectors(angles, forward, right, up);
+
+            Math3D.vectorCopy(p_l, temp);
+            p_l[0] = Math3D.dotProduct(temp, forward);
+            p_l[1] = -Math3D.dotProduct(temp, right);
+            p_l[2] = Math3D.dotProduct(temp, up);
+        }
+
+        l = CM_PointLeafnum_r(p_l, headnode);
+
+        return map_leafs[l].contents;
+    }
+
+    /*
+     * ================ CM_ClipBoxToBrush ================
+     */
+    public static void CM_ClipBoxToBrush(float[] mins, float[] maxs,
+                                         float[] p1, float[] p2, trace_t trace, cbrush_t brush) {
+        int i, j;
+        cplane_t plane, clipplane;
+        float dist;
+        float enterfrac, leavefrac;
+        float[] ofs = {0, 0, 0};
+        float d1, d2;
+        boolean getout, startout;
+        float f;
+        cbrushside_t side, leadside;
+
+        enterfrac = -1;
+        leavefrac = 1;
+        clipplane = null;
+
+        if (brush.numsides == 0)
+            return;
+
+        Globals.c_brush_traces++;
+
+        getout = false;
+        startout = false;
+        leadside = null;
+
+        for (i = 0; i < brush.numsides; i++) {
+            side = map_brushsides[brush.firstbrushside + i];
+            plane = side.plane;
+
+            // FIXME: special case for axial
+
+            if (!trace_ispoint) { // general box case
+
+                // push the plane out apropriately for mins/maxs
+
+                // FIXME: use signbits into 8 way lookup for each mins/maxs
+                for (j = 0; j < 3; j++) {
+                    if (plane.normal[j] < 0)
+                        ofs[j] = maxs[j];
+                    else
+                        ofs[j] = mins[j];
+                }
+                dist = Math3D.dotProduct(ofs, plane.normal);
+                dist = plane.dist - dist;
+            } else { // special point case
+                dist = plane.dist;
+            }
+
+            d1 = Math3D.dotProduct(p1, plane.normal) - dist;
+            d2 = Math3D.dotProduct(p2, plane.normal) - dist;
+
+            if (d2 > 0)
+                getout = true; // endpoint is not in solid
+            if (d1 > 0)
+                startout = true;
+
+            // if completely in front of face, no intersection
+            if (d1 > 0 && d2 >= d1)
+                return;
+
+            if (d1 <= 0 && d2 <= 0)
+                continue;
+
+            // crosses face
+            if (d1 > d2) { // enter
+                f = (d1 - DIST_EPSILON) / (d1 - d2);
+                if (f > enterfrac) {
+                    enterfrac = f;
+                    clipplane = plane;
+                    leadside = side;
+                }
+            } else { // leave
+                f = (d1 + DIST_EPSILON) / (d1 - d2);
+                if (f < leavefrac)
+                    leavefrac = f;
+            }
+        }
+
+        if (!startout) { // original point was inside brush
+            trace.startsolid = true;
+            if (!getout)
+                trace.allsolid = true;
+            return;
+        }
+        if (enterfrac < leavefrac) {
+            if (enterfrac > -1 && enterfrac < trace.fraction) {
+                if (enterfrac < 0)
+                    enterfrac = 0;
+                trace.fraction = enterfrac;
+                // copy
+                trace.plane.set(clipplane);
+                trace.surface = leadside.surface.c;
+                trace.contents = brush.contents;
+            }
+        }
+    }
+
+    /*
+     * ================ CM_TestBoxInBrush ================
+     */
+    public static void CM_TestBoxInBrush(float[] mins, float[] maxs,
+                                         float[] p1, trace_t trace, cbrush_t brush) {
+        int i, j;
+        cplane_t plane;
+        float dist;
+        float[] ofs = {0, 0, 0};
+        float d1;
+        cbrushside_t side;
+
+        if (brush.numsides == 0)
+            return;
+
+        for (i = 0; i < brush.numsides; i++) {
+            side = map_brushsides[brush.firstbrushside + i];
+            plane = side.plane;
+
+            // FIXME: special case for axial
+            // general box case
+            // push the plane out apropriately for mins/maxs
+            // FIXME: use signbits into 8 way lookup for each mins/maxs
+
+            for (j = 0; j < 3; j++) {
+                if (plane.normal[j] < 0)
+                    ofs[j] = maxs[j];
+                else
+                    ofs[j] = mins[j];
+            }
+            dist = Math3D.dotProduct(ofs, plane.normal);
+            dist = plane.dist - dist;
+
+            d1 = Math3D.dotProduct(p1, plane.normal) - dist;
+
+            // if completely in front of face, no intersection
+            if (d1 > 0)
+                return;
+
+        }
+
+        // inside this brush
+        trace.startsolid = trace.allsolid = true;
+        trace.fraction = 0;
+        trace.contents = brush.contents;
+    }
+
+    /*
+     * ================ CM_TraceToLeaf ================
+     */
+    public static void CM_TraceToLeaf(int leafnum) {
+        int k;
+        int brushnum;
+        cleaf_t leaf;
+        cbrush_t b;
+
+        leaf = map_leafs[leafnum];
+        if (0 == (leaf.contents & trace_contents))
+            return;
+
+        // trace line against all brushes in the leaf
+        for (k = 0; k < leaf.numleafbrushes; k++) {
+
+            brushnum = map_leafbrushes[leaf.firstleafbrush + k];
+            b = map_brushes[brushnum];
+            if (b.checkcount == checkcount)
+                continue; // already checked this brush in another leaf
+            b.checkcount = checkcount;
+
+            if (0 == (b.contents & trace_contents))
+                continue;
+            CM_ClipBoxToBrush(trace_mins, trace_maxs, trace_start, trace_end,
+                    trace_trace, b);
+            if (0 == trace_trace.fraction)
+                return;
+        }
+
+    }
+
+    /*
+     * ================ CM_TestInLeaf ================
+     */
+    public static void CM_TestInLeaf(int leafnum) {
+        int k;
+        int brushnum;
+        cleaf_t leaf;
+        cbrush_t b;
+
+        leaf = map_leafs[leafnum];
+        if (0 == (leaf.contents & trace_contents))
+            return;
+        // trace line against all brushes in the leaf
+        for (k = 0; k < leaf.numleafbrushes; k++) {
+            brushnum = map_leafbrushes[leaf.firstleafbrush + k];
+            b = map_brushes[brushnum];
+            if (b.checkcount == checkcount)
+                continue; // already checked this brush in another leaf
+            b.checkcount = checkcount;
+
+            if (0 == (b.contents & trace_contents))
+                continue;
+            CM_TestBoxInBrush(trace_mins, trace_maxs, trace_start, trace_trace,
+                    b);
+            if (0 == trace_trace.fraction)
+                return;
+        }
+
+    }
+
+    /*
+     * ================== CM_RecursiveHullCheck ==================
+     */
+    public static void CM_RecursiveHullCheck(int num, float p1f, float p2f,
+                                             float[] p1, float[] p2) {
+        cnode_t node;
+        cplane_t plane;
+        float t1, t2, offset;
+        float frac, frac2;
+        float idist;
+        int i;
+        int side;
+        float midf;
+
+        if (trace_trace.fraction <= p1f)
+            return; // already hit something nearer
+
+        // if < 0, we are in a leaf node
+        if (num < 0) {
+            CM_TraceToLeaf(-1 - num);
+            return;
+        }
+
+        //
+        // find the point distances to the seperating plane
+        // and the offset for the size of the box
+        //
+        node = map_nodes[num];
+        plane = node.plane;
+
+        if (plane.type < 3) {
+            t1 = p1[plane.type] - plane.dist;
+            t2 = p2[plane.type] - plane.dist;
+            offset = trace_extents[plane.type];
+        } else {
+            t1 = Math3D.dotProduct(plane.normal, p1) - plane.dist;
+            t2 = Math3D.dotProduct(plane.normal, p2) - plane.dist;
+            if (trace_ispoint)
+                offset = 0;
+            else
+                offset = Math.abs(trace_extents[0] * plane.normal[0])
+                        + Math.abs(trace_extents[1] * plane.normal[1])
+                        + Math.abs(trace_extents[2] * plane.normal[2]);
+        }
+
+        // see which sides we need to consider
+        if (t1 >= offset && t2 >= offset) {
+            CM_RecursiveHullCheck(node.children[0], p1f, p2f, p1, p2);
+            return;
+        }
+        if (t1 < -offset && t2 < -offset) {
+            CM_RecursiveHullCheck(node.children[1], p1f, p2f, p1, p2);
+            return;
+        }
+
+        // put the crosspoint DIST_EPSILON pixels on the near side
+        if (t1 < t2) {
+            idist = 1.0f / (t1 - t2);
+            side = 1;
+            frac2 = (t1 + offset + DIST_EPSILON) * idist;
+            frac = (t1 - offset + DIST_EPSILON) * idist;
+        } else if (t1 > t2) {
+            idist = 1.0f / (t1 - t2);
+            side = 0;
+            frac2 = (t1 - offset - DIST_EPSILON) * idist;
+            frac = (t1 + offset + DIST_EPSILON) * idist;
+        } else {
+            side = 0;
+            frac = 1;
+            frac2 = 0;
+        }
+
+        // move up to the node
+        if (frac < 0)
+            frac = 0;
+        if (frac > 1)
+            frac = 1;
+
+        midf = p1f + (p2f - p1f) * frac;
+        float[] mid = Vec3Cache.get();
+
+        for (i = 0; i < 3; i++)
+            mid[i] = p1[i] + frac * (p2[i] - p1[i]);
+
+        CM_RecursiveHullCheck(node.children[side], p1f, midf, p1, mid);
+
+        // go past the node
+        if (frac2 < 0)
+            frac2 = 0;
+        if (frac2 > 1)
+            frac2 = 1;
+
+        midf = p1f + (p2f - p1f) * frac2;
+        for (i = 0; i < 3; i++)
+            mid[i] = p1[i] + frac2 * (p2[i] - p1[i]);
+
+        CM_RecursiveHullCheck(node.children[side ^ 1], midf, p2f, mid, p2);
+        Vec3Cache.release();
+    }
+
+    /*
+     * ================== CM_BoxTrace ==================
+     */
+    public static trace_t boxTrace(float[] start, float[] end, float[] mins,
+                                   float[] maxs, int headnode, int brushmask) {
+
+        // for multi-check avoidance
+        checkcount++;
+
+        // for statistics, may be zeroed
+        Globals.c_traces++;
+
+        // fill in a default trace
+        //was: memset(& trace_trace, 0, sizeof(trace_trace));
+        trace_trace = new trace_t();
+
+        trace_trace.fraction = 1;
+        trace_trace.surface = nullsurface.c;
+
+        if (numnodes == 0) {
+            // map not loaded
+            return trace_trace;
+        }
+
+        trace_contents = brushmask;
+        Math3D.vectorCopy(start, trace_start);
+        Math3D.vectorCopy(end, trace_end);
+        Math3D.vectorCopy(mins, trace_mins);
+        Math3D.vectorCopy(maxs, trace_maxs);
+
+        //
+        // check for position test special case
+        //
+        if (start[0] == end[0] && start[1] == end[1] && start[2] == end[2]) {
+
+            int leafs[] = new int[1024];
+            int i, numleafs;
+            float[] c1 = {0, 0, 0}, c2 = {0, 0, 0};
+            int topnode = 0;
+
+            Math3D.vectorAdd(start, mins, c1);
+            Math3D.vectorAdd(start, maxs, c2);
+
+            for (i = 0; i < 3; i++) {
+                c1[i] -= 1;
+                c2[i] += 1;
+            }
+
+            int tn[] = {topnode};
+
+            numleafs = CM_BoxLeafnums_headnode(c1, c2, leafs, 1024, headnode,
+                    tn);
+            topnode = tn[0];
+            for (i = 0; i < numleafs; i++) {
+                CM_TestInLeaf(leafs[i]);
+                if (trace_trace.allsolid)
+                    break;
+            }
+            Math3D.vectorCopy(start, trace_trace.endpos);
+            return trace_trace;
+        }
+
+        //
+        // check for point special case
+        //
+        if (mins[0] == 0 && mins[1] == 0 && mins[2] == 0 && maxs[0] == 0
+                && maxs[1] == 0 && maxs[2] == 0) {
+            trace_ispoint = true;
+            Math3D.vectorClear(trace_extents);
+        } else {
+            trace_ispoint = false;
+            trace_extents[0] = -mins[0] > maxs[0] ? -mins[0] : maxs[0];
+            trace_extents[1] = -mins[1] > maxs[1] ? -mins[1] : maxs[1];
+            trace_extents[2] = -mins[2] > maxs[2] ? -mins[2] : maxs[2];
+        }
+
+        //
+        // general sweeping through world
+        //
+        CM_RecursiveHullCheck(headnode, 0, 1, start, end);
+
+        if (trace_trace.fraction == 1) {
+            Math3D.vectorCopy(end, trace_trace.endpos);
+        } else {
+            for (int i = 0; i < 3; i++)
+                trace_trace.endpos[i] = start[i] + trace_trace.fraction
+                        * (end[i] - start[i]);
+        }
+        return trace_trace;
+    }
+
+    /*
+     * ================== CM_TransformedBoxTrace
+     *
+     * Handles offseting and rotation of the end points for moving and rotating
+     * entities ==================
+     */
+    public static trace_t TransformedBoxTrace(float[] start, float[] end,
+                                              float[] mins, float[] maxs, int headnode, int brushmask,
+                                              float[] origin, float[] angles) {
+        trace_t trace;
+        float[] start_l = {0, 0, 0}, end_l = {0, 0, 0};
+        float[] a = {0, 0, 0};
+        float[] forward = {0, 0, 0}, right = {0, 0, 0}, up = {0, 0, 0};
+        float[] temp = {0, 0, 0};
+        boolean rotated;
+
+        // subtract origin offset
+        Math3D.vectorSubtract(start, origin, start_l);
+        Math3D.vectorSubtract(end, origin, end_l);
+
+        // rotate start and end into the models frame of reference
+        rotated = headnode != box_headnode
+                && (angles[0] != 0 || angles[1] != 0 || angles[2] != 0);
+
+        if (rotated) {
+            Math3D.angleVectors(angles, forward, right, up);
+
+            Math3D.vectorCopy(start_l, temp);
+            start_l[0] = Math3D.dotProduct(temp, forward);
+            start_l[1] = -Math3D.dotProduct(temp, right);
+            start_l[2] = Math3D.dotProduct(temp, up);
+
+            Math3D.vectorCopy(end_l, temp);
+            end_l[0] = Math3D.dotProduct(temp, forward);
+            end_l[1] = -Math3D.dotProduct(temp, right);
+            end_l[2] = Math3D.dotProduct(temp, up);
+        }
+
+        // sweep the box through the model
+        trace = boxTrace(start_l, end_l, mins, maxs, headnode, brushmask);
+
+        if (rotated && trace.fraction != 1.0) {
+            // FIXME: figure out how to do this with existing angles
+            Math3D.vectorNegate(angles, a);
+            Math3D.angleVectors(a, forward, right, up);
+
+            Math3D.vectorCopy(trace.plane.normal, temp);
+            trace.plane.normal[0] = Math3D.dotProduct(temp, forward);
+            trace.plane.normal[1] = -Math3D.dotProduct(temp, right);
+            trace.plane.normal[2] = Math3D.dotProduct(temp, up);
+        }
+
+        trace.endpos[0] = start[0] + trace.fraction * (end[0] - start[0]);
+        trace.endpos[1] = start[1] + trace.fraction * (end[1] - start[1]);
+        trace.endpos[2] = start[2] + trace.fraction * (end[2] - start[2]);
+
+        return trace;
+    }
+
+    /*
+     * =================== CM_DecompressVis ===================
+     */
+    public static void CM_DecompressVis(byte in[], int offset, byte out[]) {
+        int c;
+
+        int row;
+
+        row = (numclusters + 7) >> 3;
+        int outp = 0;
+        int inp = offset;
+
+        if (in == null || numvisibility == 0) { // no vis info, so make all
+            // visible
+            while (row != 0) {
+                out[outp++] = (byte) 0xFF;
+                row--;
+            }
+            return;
+        }
+
+        do {
+            if (in[inp] != 0) {
+                out[outp++] = in[inp++];
+                continue;
+            }
+
+            c = in[inp + 1] & 0xFF;
+            inp += 2;
+            if (outp + c > row) {
+                c = row - (outp);
+                Com.DPrintf("warning: Vis decompression overrun\n");
+            }
+            while (c != 0) {
+                out[outp++] = 0;
+                c--;
+            }
+        } while (outp < row);
+    }
+
+    //======================================================================
+
+    public static byte[] CM_ClusterPVS(int cluster) {
+        if (cluster == -1)
+            Arrays.fill(pvsrow, 0, (numclusters + 7) >> 3, (byte) 0);
+        else
+            CM_DecompressVis(map_visibility,
+                    map_vis.bitofs[cluster][Defines.DVIS_PVS], pvsrow);
+        return pvsrow;
+    }
+
+    public static byte[] CM_ClusterPHS(int cluster) {
+        if (cluster == -1)
+            Arrays.fill(phsrow, 0, (numclusters + 7) >> 3, (byte) 0);
+        else
+            CM_DecompressVis(map_visibility,
+                    map_vis.bitofs[cluster][Defines.DVIS_PHS], phsrow);
+        return phsrow;
+    }
+
+    /*
+     * ===============================================================================
+     * PVS / PHS
+     * ===============================================================================
+     */
+
+    public static void FloodArea_r(carea_t area, int floodnum) {
+        //Com.Printf("FloodArea_r(" + floodnum + ")...\n");
+        int i;
+        qfiles.dareaportal_t p;
+
+        if (area.floodvalid == floodvalid) {
+            if (area.floodnum == floodnum)
+                return;
+            Com.Error(Defines.ERR_DROP, "FloodArea_r: reflooded");
+        }
+
+        area.floodnum = floodnum;
+        area.floodvalid = floodvalid;
+
+        for (i = 0; i < area.numareaportals; i++) {
+            p = map_areaportals[area.firstareaportal + i];
+            if (portalopen[p.portalnum])
+                FloodArea_r(map_areas[p.otherarea], floodnum);
+        }
+    }
+
+    /*
+     * ==================== FloodAreaConnections ====================
+     */
+    public static void FloodAreaConnections() {
+        Com.DPrintf("FloodAreaConnections...\n");
+
+        int i;
+        carea_t area;
+        int floodnum;
+
+        // all current floods are now invalid
+        floodvalid++;
+        floodnum = 0;
+
+        // area 0 is not used
+        for (i = 1; i < numareas; i++) {
+
+            area = map_areas[i];
+
+            if (area.floodvalid == floodvalid)
+                continue; // already flooded into
+            floodnum++;
+            FloodArea_r(area, floodnum);
+        }
+    }
+
+    /*
+     * ================= CM_SetAreaPortalState =================
+     */
+    public static void CM_SetAreaPortalState(int portalnum, boolean open) {
+        if (portalnum > numareaportals)
+            Com.Error(Defines.ERR_DROP, "areaportal > numareaportals");
+
+        portalopen[portalnum] = open;
+        FloodAreaConnections();
+    }
+
+    public static boolean CM_AreasConnected(int area1, int area2) {
+        if (map_noareas.value != 0)
+            return true;
+
+        if (area1 > numareas || area2 > numareas)
+            Com.Error(Defines.ERR_DROP, "area > numareas");
+
+        return map_areas[area1].floodnum == map_areas[area2].floodnum;
+
+    }
+
+    /*
+     * ================= CM_WriteAreaBits
+     *
+     * Writes a length byte followed by a bit vector of all the areas that area
+     * in the same flood as the area parameter
+     *
+     * This is used by the client refreshes to cull visibility =================
+     */
+    public static int CM_WriteAreaBits(byte buffer[], int area) {
+        int i;
+        int floodnum;
+        int bytes;
+
+        bytes = (numareas + 7) >> 3;
+
+        if (map_noareas.value != 0) { // for debugging, send everything
+            Arrays.fill(buffer, 0, bytes, (byte) 255);
+        } else {
+            Arrays.fill(buffer, 0, bytes, (byte) 0);
+            floodnum = map_areas[area].floodnum;
+            for (i = 0; i < numareas; i++) {
+                if (map_areas[i].floodnum == floodnum || area == 0)
+                    buffer[i >> 3] |= 1 << (i & 7);
+            }
+        }
+
+        return bytes;
+    }
+
+    /*
+     * ===============================================================================
+     * AREAPORTALS
+     * ===============================================================================
+     */
+
+    public static void CM_WritePortalState(RandomAccessFile os) {
+
+        //was: fwrite(portalopen, sizeof(portalopen), 1, f);
+        try {
+
+            for (boolean aPortalopen : portalopen)
+                if (aPortalopen)
+                    os.writeInt(1);
+                else
+                    os.writeInt(0);
+        } catch (Exception e) {
+            Com.Printf("ERROR:" + e);
+            e.printStackTrace();
+        }
+    }
+
+    /*
+     * =================== CM_ReadPortalState
+     *
+     * Reads the portal state from a savegame file and recalculates the area
+     * connections ===================
+     */
+    public static void CM_ReadPortalState(RandomAccessFile f) {
+
+        //was: FS_Read(portalopen, sizeof(portalopen), f);
+        int len = portalopen.length * 4;
+
+        byte buf[] = new byte[len];
+
+        FS.Read(buf, len, f);
+
+        ByteBuffer bb = ByteBuffer.wrap(buf);
+        IntBuffer ib = bb.asIntBuffer();
+
+        for (int n = 0; n < portalopen.length; n++)
+            portalopen[n] = ib.get() != 0;
+
+        FloodAreaConnections();
+    }
+
+    /*
+     * ================= CM_AreasConnected =================
+     */
+
+    public static class cnode_t {
+        final int[] children = {0, 0}; // negative numbers are leafs
+        cplane_t plane; // ptr
+    }
+
+    public static class cbrushside_t {
+        cplane_t plane; // ptr
+
+        mapsurface_t surface; // ptr
+    }
+
+    /*
+     * =================== CM_WritePortalState
+     * 
+     * Writes the portal state to a savegame file ===================
+     */
+
+    public static class cleaf_t {
+        int contents;
+
+        int cluster;
+
+        int area;
+
+        // was unsigned short, but is ok (rst)
+        short firstleafbrush;
+
+        // was unsigned short, but is ok (rst)
+        short numleafbrushes;
+    }
+
+    public static class cbrush_t {
+        int contents;
+
+        int numsides;
+
+        int firstbrushside;
+
+        int checkcount; // to avoid repeated testings
+    }
+
+    public static class carea_t {
+        int numareaportals;
+
+        int firstareaportal;
+
+        int floodnum; // if two areas have equal floodnums, they are connected
+
+        int floodvalid;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/qcommon/CRC.java b/src/main/java/lwjake2/qcommon/CRC.java
new file mode 100644 (file)
index 0000000..a25b1d1
--- /dev/null
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.qcommon;
+
+public class CRC {
+
+    public final static short CRC_INIT_VALUE = (short) 0xffff;
+    public final static short CRC_XOR_VALUE = (short) 0x0000;
+
+    private static final int[] crctable = {0x0000, 0x1021, 0x2042, 0x3063, 0x4084,
+            0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c,
+            0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5,
+            0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd,
+            0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6,
+            0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee,
+            0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7,
+            0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df,
+            0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840,
+            0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948,
+            0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71,
+            0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79,
+            0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22,
+            0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a,
+            0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13,
+            0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b,
+            0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c,
+            0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004,
+            0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d,
+            0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235,
+            0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e,
+            0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466,
+            0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f,
+            0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657,
+            0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8,
+            0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0,
+            0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9,
+            0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1,
+            0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa,
+            0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2,
+            0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b,
+            0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93,
+            0x3eb2, 0x0ed1, 0x1ef0};
+
+    static int CRC_Block(byte start[], int count) {
+        short crc = CRC_INIT_VALUE;
+
+        int ndx = 0;
+
+        while (count-- > 0)
+            crc = (short) ((crc << 8) ^ crctable[0xff & ((crc >> 8) ^ start[ndx++])]);
+
+        // unsigned short
+        return crc & 0xFFFF;
+    }
+
+    public static void main(String[] args) {
+        byte data[] =
+                {
+                        (byte) 0x71,
+                        (byte) 0xa9,
+                        (byte) 0x05,
+                        (byte) 0xce,
+                        (byte) 0x8d,
+                        (byte) 0x75,
+                        (byte) 0x28,
+                        (byte) 0xc8,
+                        (byte) 0xba,
+                        (byte) 0x97,
+
+                        (byte) 0x45,
+                        (byte) 0xe9,
+                        (byte) 0x8a,
+                        (byte) 0xe0,
+                        (byte) 0x37,
+                        (byte) 0xbd,
+                        (byte) 0x6c,
+                        (byte) 0x6d,
+                        (byte) 0x67,
+                        (byte) 0x4a,
+                        (byte) 0x21};
+        System.out.println("crc:" + (CRC_Block(data, 21) & 0xffff));
+        System.out.println("----");
+        for (int n = 0; n < 5; n++)
+            System.out.println("seq:" + (Com.BlockSequenceCRCByte(data, 0, 21, n * 10) & 0xff));
+
+    }
+
+/* c test:
+ * 
+ * D:\Rene\gamesrc\quake2-3.21\qcommon>crc
+ * crc=-12353
+ * ----
+ * seq:215
+ * seq:252
+ * seq:164
+ * seq:202
+ * seq:201
+ * 
+int main()
+{
+       byte data[21] = 
+       {       
+                                 0x71,
+                                 0xa9,
+                                 0x05,
+                                 0xce,
+                                 0x8d,
+                                 0x75,
+                                 0x28,
+                                 0xc8,
+                                 0xba,
+                                 0x97,
+                               
+                                 0x45,
+                                 0xe9,
+                                 0x8a,
+                                 0xe0,
+                                 0x37,
+                                 0xbd,
+                                 0x6c,
+                                 0x6d,
+                                 0x67,
+                                 0x4a, 0x21 };
+       int n=0;
+
+       printf("crc=%d\n", (short) CRC_Block(&data, 21));
+
+       printf("----\n");
+       for (n=0; n < 5; n++)
+               printf("seq:%d\n", COM_BlockSequenceCRCByte( &data,21, n*10) );
+} 
+ */
+
+}
diff --git a/src/main/java/lwjake2/qcommon/Com.java b/src/main/java/lwjake2/qcommon/Com.java
new file mode 100644 (file)
index 0000000..bc16d95
--- /dev/null
@@ -0,0 +1,598 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.qcommon;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.client.Client;
+import lwjake2.client.Console;
+import lwjake2.game.Cmd;
+import lwjake2.server.Server;
+import lwjake2.sys.Sys;
+import lwjake2.util.PrintfFormat;
+import lwjake2.util.Vargs;
+
+import java.io.IOException;
+
+/**
+ * Com
+ */
+public final class Com {
+
+    public static final char[] com_token = new char[Defines.MAX_TOKEN_CHARS];
+    static final String[] com_argv = new String[Defines.MAX_NUM_ARGVS];
+    /**
+     * CRC table.
+     */
+    static final byte[] chktbl = {(byte) 0x84, (byte) 0x47, (byte) 0x51, (byte) 0xc1,
+            (byte) 0x93, (byte) 0x22, (byte) 0x21, (byte) 0x24, (byte) 0x2f,
+            (byte) 0x66, (byte) 0x60, (byte) 0x4d, (byte) 0xb0, (byte) 0x7c,
+            (byte) 0xda, (byte) 0x88, (byte) 0x54, (byte) 0x15, (byte) 0x2b,
+            (byte) 0xc6, (byte) 0x6c, (byte) 0x89, (byte) 0xc5, (byte) 0x9d,
+            (byte) 0x48, (byte) 0xee, (byte) 0xe6, (byte) 0x8a, (byte) 0xb5,
+            (byte) 0xf4, (byte) 0xcb, (byte) 0xfb, (byte) 0xf1, (byte) 0x0c,
+            (byte) 0x2e, (byte) 0xa0, (byte) 0xd7, (byte) 0xc9, (byte) 0x1f,
+            (byte) 0xd6, (byte) 0x06, (byte) 0x9a, (byte) 0x09, (byte) 0x41,
+            (byte) 0x54, (byte) 0x67, (byte) 0x46, (byte) 0xc7, (byte) 0x74,
+            (byte) 0xe3, (byte) 0xc8, (byte) 0xb6, (byte) 0x5d, (byte) 0xa6,
+            (byte) 0x36, (byte) 0xc4, (byte) 0xab, (byte) 0x2c, (byte) 0x7e,
+            (byte) 0x85, (byte) 0xa8, (byte) 0xa4, (byte) 0xa6, (byte) 0x4d,
+            (byte) 0x96, (byte) 0x19, (byte) 0x19, (byte) 0x9a, (byte) 0xcc,
+            (byte) 0xd8, (byte) 0xac, (byte) 0x39, (byte) 0x5e, (byte) 0x3c,
+            (byte) 0xf2, (byte) 0xf5, (byte) 0x5a, (byte) 0x72, (byte) 0xe5,
+            (byte) 0xa9, (byte) 0xd1, (byte) 0xb3, (byte) 0x23, (byte) 0x82,
+            (byte) 0x6f, (byte) 0x29, (byte) 0xcb, (byte) 0xd1, (byte) 0xcc,
+            (byte) 0x71, (byte) 0xfb, (byte) 0xea, (byte) 0x92, (byte) 0xeb,
+            (byte) 0x1c, (byte) 0xca, (byte) 0x4c, (byte) 0x70, (byte) 0xfe,
+            (byte) 0x4d, (byte) 0xc9, (byte) 0x67, (byte) 0x43, (byte) 0x47,
+            (byte) 0x94, (byte) 0xb9, (byte) 0x47, (byte) 0xbc, (byte) 0x3f,
+            (byte) 0x01, (byte) 0xab, (byte) 0x7b, (byte) 0xa6, (byte) 0xe2,
+            (byte) 0x76, (byte) 0xef, (byte) 0x5a, (byte) 0x7a, (byte) 0x29,
+            (byte) 0x0b, (byte) 0x51, (byte) 0x54, (byte) 0x67, (byte) 0xd8,
+            (byte) 0x1c, (byte) 0x14, (byte) 0x3e, (byte) 0x29, (byte) 0xec,
+            (byte) 0xe9, (byte) 0x2d, (byte) 0x48, (byte) 0x67, (byte) 0xff,
+            (byte) 0xed, (byte) 0x54, (byte) 0x4f, (byte) 0x48, (byte) 0xc0,
+            (byte) 0xaa, (byte) 0x61, (byte) 0xf7, (byte) 0x78, (byte) 0x12,
+            (byte) 0x03, (byte) 0x7a, (byte) 0x9e, (byte) 0x8b, (byte) 0xcf,
+            (byte) 0x83, (byte) 0x7b, (byte) 0xae, (byte) 0xca, (byte) 0x7b,
+            (byte) 0xd9, (byte) 0xe9, (byte) 0x53, (byte) 0x2a, (byte) 0xeb,
+            (byte) 0xd2, (byte) 0xd8, (byte) 0xcd, (byte) 0xa3, (byte) 0x10,
+            (byte) 0x25, (byte) 0x78, (byte) 0x5a, (byte) 0xb5, (byte) 0x23,
+            (byte) 0x06, (byte) 0x93, (byte) 0xb7, (byte) 0x84, (byte) 0xd2,
+            (byte) 0xbd, (byte) 0x96, (byte) 0x75, (byte) 0xa5, (byte) 0x5e,
+            (byte) 0xcf, (byte) 0x4e, (byte) 0xe9, (byte) 0x50, (byte) 0xa1,
+            (byte) 0xe6, (byte) 0x9d, (byte) 0xb1, (byte) 0xe3, (byte) 0x85,
+            (byte) 0x66, (byte) 0x28, (byte) 0x4e, (byte) 0x43, (byte) 0xdc,
+            (byte) 0x6e, (byte) 0xbb, (byte) 0x33, (byte) 0x9e, (byte) 0xf3,
+            (byte) 0x0d, (byte) 0x00, (byte) 0xc1, (byte) 0xcf, (byte) 0x67,
+            (byte) 0x34, (byte) 0x06, (byte) 0x7c, (byte) 0x71, (byte) 0xe3,
+            (byte) 0x63, (byte) 0xb7, (byte) 0xb7, (byte) 0xdf, (byte) 0x92,
+            (byte) 0xc4, (byte) 0xc2, (byte) 0x25, (byte) 0x5c, (byte) 0xff,
+            (byte) 0xc3, (byte) 0x6e, (byte) 0xfc, (byte) 0xaa, (byte) 0x1e,
+            (byte) 0x2a, (byte) 0x48, (byte) 0x11, (byte) 0x1c, (byte) 0x36,
+            (byte) 0x68, (byte) 0x78, (byte) 0x86, (byte) 0x79, (byte) 0x30,
+            (byte) 0xc3, (byte) 0xd6, (byte) 0xde, (byte) 0xbc, (byte) 0x3a,
+            (byte) 0x2a, (byte) 0x6d, (byte) 0x1e, (byte) 0x46, (byte) 0xdd,
+            (byte) 0xe0, (byte) 0x80, (byte) 0x1e, (byte) 0x44, (byte) 0x3b,
+            (byte) 0x6f, (byte) 0xaf, (byte) 0x31, (byte) 0xda, (byte) 0xa2,
+            (byte) 0xbd, (byte) 0x77, (byte) 0x06, (byte) 0x56, (byte) 0xc0,
+            (byte) 0xb7, (byte) 0x92, (byte) 0x4b, (byte) 0x37, (byte) 0xc0,
+            (byte) 0xfc, (byte) 0xc2, (byte) 0xd5, (byte) 0xfb, (byte) 0xa8,
+            (byte) 0xda, (byte) 0xf5, (byte) 0x57, (byte) 0xa8, (byte) 0x18,
+            (byte) 0xc0, (byte) 0xdf, (byte) 0xe7, (byte) 0xaa, (byte) 0x2a,
+            (byte) 0xe0, (byte) 0x7c, (byte) 0x6f, (byte) 0x77, (byte) 0xb1,
+            (byte) 0x26, (byte) 0xba, (byte) 0xf9, (byte) 0x2e, (byte) 0x1d,
+            (byte) 0x16, (byte) 0xcb, (byte) 0xb8, (byte) 0xa2, (byte) 0x44,
+            (byte) 0xd5, (byte) 0x2f, (byte) 0x1a, (byte) 0x79, (byte) 0x74,
+            (byte) 0x87, (byte) 0x4b, (byte) 0x00, (byte) 0xc9, (byte) 0x4a,
+            (byte) 0x3a, (byte) 0x65, (byte) 0x8f, (byte) 0xe6, (byte) 0x5d,
+            (byte) 0xe5, (byte) 0x0a, (byte) 0x77, (byte) 0xd8, (byte) 0x1a,
+            (byte) 0x14, (byte) 0x41, (byte) 0x75, (byte) 0xb1, (byte) 0xe2,
+            (byte) 0x50, (byte) 0x2c, (byte) 0x93, (byte) 0x38, (byte) 0x2b,
+            (byte) 0x6d, (byte) 0xf3, (byte) 0xf6, (byte) 0xdb, (byte) 0x1f,
+            (byte) 0xcd, (byte) 0xff, (byte) 0x14, (byte) 0x70, (byte) 0xe7,
+            (byte) 0x16, (byte) 0xe8, (byte) 0x3d, (byte) 0xf0, (byte) 0xe3,
+            (byte) 0xbc, (byte) 0x5e, (byte) 0xb6, (byte) 0x3f, (byte) 0xcc,
+            (byte) 0x81, (byte) 0x24, (byte) 0x67, (byte) 0xf3, (byte) 0x97,
+            (byte) 0x3b, (byte) 0xfe, (byte) 0x3a, (byte) 0x96, (byte) 0x85,
+            (byte) 0xdf, (byte) 0xe4, (byte) 0x6e, (byte) 0x3c, (byte) 0x85,
+            (byte) 0x05, (byte) 0x0e, (byte) 0xa3, (byte) 0x2b, (byte) 0x07,
+            (byte) 0xc8, (byte) 0xbf, (byte) 0xe5, (byte) 0x13, (byte) 0x82,
+            (byte) 0x62, (byte) 0x08, (byte) 0x61, (byte) 0x69, (byte) 0x4b,
+            (byte) 0x47, (byte) 0x62, (byte) 0x73, (byte) 0x44, (byte) 0x64,
+            (byte) 0x8e, (byte) 0xe2, (byte) 0x91, (byte) 0xa6, (byte) 0x9a,
+            (byte) 0xb7, (byte) 0xe9, (byte) 0x04, (byte) 0xb6, (byte) 0x54,
+            (byte) 0x0c, (byte) 0xc5, (byte) 0xa9, (byte) 0x47, (byte) 0xa6,
+            (byte) 0xc9, (byte) 0x08, (byte) 0xfe, (byte) 0x4e, (byte) 0xa6,
+            (byte) 0xcc, (byte) 0x8a, (byte) 0x5b, (byte) 0x90, (byte) 0x6f,
+            (byte) 0x2b, (byte) 0x3f, (byte) 0xb6, (byte) 0x0a, (byte) 0x96,
+            (byte) 0xc0, (byte) 0x78, (byte) 0x58, (byte) 0x3c, (byte) 0x76,
+            (byte) 0x6d, (byte) 0x94, (byte) 0x1a, (byte) 0xe4, (byte) 0x4e,
+            (byte) 0xb8, (byte) 0x38, (byte) 0xbb, (byte) 0xf5, (byte) 0xeb,
+            (byte) 0x29, (byte) 0xd8, (byte) 0xb0, (byte) 0xf3, (byte) 0x15,
+            (byte) 0x1e, (byte) 0x99, (byte) 0x96, (byte) 0x3c, (byte) 0x5d,
+            (byte) 0x63, (byte) 0xd5, (byte) 0xb1, (byte) 0xad, (byte) 0x52,
+            (byte) 0xb8, (byte) 0x55, (byte) 0x70, (byte) 0x75, (byte) 0x3e,
+            (byte) 0x1a, (byte) 0xd5, (byte) 0xda, (byte) 0xf6, (byte) 0x7a,
+            (byte) 0x48, (byte) 0x7d, (byte) 0x44, (byte) 0x41, (byte) 0xf9,
+            (byte) 0x11, (byte) 0xce, (byte) 0xd7, (byte) 0xca, (byte) 0xa5,
+            (byte) 0x3d, (byte) 0x7a, (byte) 0x79, (byte) 0x7e, (byte) 0x7d,
+            (byte) 0x25, (byte) 0x1b, (byte) 0x77, (byte) 0xbc, (byte) 0xf7,
+            (byte) 0xc7, (byte) 0x0f, (byte) 0x84, (byte) 0x95, (byte) 0x10,
+            (byte) 0x92, (byte) 0x67, (byte) 0x15, (byte) 0x11, (byte) 0x5a,
+            (byte) 0x5e, (byte) 0x41, (byte) 0x66, (byte) 0x0f, (byte) 0x38,
+            (byte) 0x03, (byte) 0xb2, (byte) 0xf1, (byte) 0x5d, (byte) 0xf8,
+            (byte) 0xab, (byte) 0xc0, (byte) 0x02, (byte) 0x76, (byte) 0x84,
+            (byte) 0x28, (byte) 0xf4, (byte) 0x9d, (byte) 0x56, (byte) 0x46,
+            (byte) 0x60, (byte) 0x20, (byte) 0xdb, (byte) 0x68, (byte) 0xa7,
+            (byte) 0xbb, (byte) 0xee, (byte) 0xac, (byte) 0x15, (byte) 0x01,
+            (byte) 0x2f, (byte) 0x20, (byte) 0x09, (byte) 0xdb, (byte) 0xc0,
+            (byte) 0x16, (byte) 0xa1, (byte) 0x89, (byte) 0xf9, (byte) 0x94,
+            (byte) 0x59, (byte) 0x00, (byte) 0xc1, (byte) 0x76, (byte) 0xbf,
+            (byte) 0xc1, (byte) 0x4d, (byte) 0x5d, (byte) 0x2d, (byte) 0xa9,
+            (byte) 0x85, (byte) 0x2c, (byte) 0xd6, (byte) 0xd3, (byte) 0x14,
+            (byte) 0xcc, (byte) 0x02, (byte) 0xc3, (byte) 0xc2, (byte) 0xfa,
+            (byte) 0x6b, (byte) 0xb7, (byte) 0xa6, (byte) 0xef, (byte) 0xdd,
+            (byte) 0x12, (byte) 0x26, (byte) 0xa4, (byte) 0x63, (byte) 0xe3,
+            (byte) 0x62, (byte) 0xbd, (byte) 0x56, (byte) 0x8a, (byte) 0x52,
+            (byte) 0x2b, (byte) 0xb9, (byte) 0xdf, (byte) 0x09, (byte) 0xbc,
+            (byte) 0x0e, (byte) 0x97, (byte) 0xa9, (byte) 0xb0, (byte) 0x82,
+            (byte) 0x46, (byte) 0x08, (byte) 0xd5, (byte) 0x1a, (byte) 0x8e,
+            (byte) 0x1b, (byte) 0xa7, (byte) 0x90, (byte) 0x98, (byte) 0xb9,
+            (byte) 0xbb, (byte) 0x3c, (byte) 0x17, (byte) 0x9a, (byte) 0xf2,
+            (byte) 0x82, (byte) 0xba, (byte) 0x64, (byte) 0x0a, (byte) 0x7f,
+            (byte) 0xca, (byte) 0x5a, (byte) 0x8c, (byte) 0x7c, (byte) 0xd3,
+            (byte) 0x79, (byte) 0x09, (byte) 0x5b, (byte) 0x26, (byte) 0xbb,
+            (byte) 0xbd, (byte) 0x25, (byte) 0xdf, (byte) 0x3d, (byte) 0x6f,
+            (byte) 0x9a, (byte) 0x8f, (byte) 0xee, (byte) 0x21, (byte) 0x66,
+            (byte) 0xb0, (byte) 0x8d, (byte) 0x84, (byte) 0x4c, (byte) 0x91,
+            (byte) 0x45, (byte) 0xd4, (byte) 0x77, (byte) 0x4f, (byte) 0xb3,
+            (byte) 0x8c, (byte) 0xbc, (byte) 0xa8, (byte) 0x99, (byte) 0xaa,
+            (byte) 0x19, (byte) 0x53, (byte) 0x7c, (byte) 0x02, (byte) 0x87,
+            (byte) 0xbb, (byte) 0x0b, (byte) 0x7c, (byte) 0x1a, (byte) 0x2d,
+            (byte) 0xdf, (byte) 0x48, (byte) 0x44, (byte) 0x06, (byte) 0xd6,
+            (byte) 0x7d, (byte) 0x0c, (byte) 0x2d, (byte) 0x35, (byte) 0x76,
+            (byte) 0xae, (byte) 0xc4, (byte) 0x5f, (byte) 0x71, (byte) 0x85,
+            (byte) 0x97, (byte) 0xc4, (byte) 0x3d, (byte) 0xef, (byte) 0x52,
+            (byte) 0xbe, (byte) 0x00, (byte) 0xe4, (byte) 0xcd, (byte) 0x49,
+            (byte) 0xd1, (byte) 0xd1, (byte) 0x1c, (byte) 0x3c, (byte) 0xd0,
+            (byte) 0x1c, (byte) 0x42, (byte) 0xaf, (byte) 0xd4, (byte) 0xbd,
+            (byte) 0x58, (byte) 0x34, (byte) 0x07, (byte) 0x32, (byte) 0xee,
+            (byte) 0xb9, (byte) 0xb5, (byte) 0xea, (byte) 0xff, (byte) 0xd7,
+            (byte) 0x8c, (byte) 0x0d, (byte) 0x2e, (byte) 0x2f, (byte) 0xaf,
+            (byte) 0x87, (byte) 0xbb, (byte) 0xe6, (byte) 0x52, (byte) 0x71,
+            (byte) 0x22, (byte) 0xf5, (byte) 0x25, (byte) 0x17, (byte) 0xa1,
+            (byte) 0x82, (byte) 0x04, (byte) 0xc2, (byte) 0x4a, (byte) 0xbd,
+            (byte) 0x57, (byte) 0xc6, (byte) 0xab, (byte) 0xc8, (byte) 0x35,
+            (byte) 0x0c, (byte) 0x3c, (byte) 0xd9, (byte) 0xc2, (byte) 0x43,
+            (byte) 0xdb, (byte) 0x27, (byte) 0x92, (byte) 0xcf, (byte) 0xb8,
+            (byte) 0x25, (byte) 0x60, (byte) 0xfa, (byte) 0x21, (byte) 0x3b,
+            (byte) 0x04, (byte) 0x52, (byte) 0xc8, (byte) 0x96, (byte) 0xba,
+            (byte) 0x74, (byte) 0xe3, (byte) 0x67, (byte) 0x3e, (byte) 0x8e,
+            (byte) 0x8d, (byte) 0x61, (byte) 0x90, (byte) 0x92, (byte) 0x59,
+            (byte) 0xb6, (byte) 0x1a, (byte) 0x1c, (byte) 0x5e, (byte) 0x21,
+            (byte) 0xc1, (byte) 0x65, (byte) 0xe5, (byte) 0xa6, (byte) 0x34,
+            (byte) 0x05, (byte) 0x6f, (byte) 0xc5, (byte) 0x60, (byte) 0xb1,
+            (byte) 0x83, (byte) 0xc1, (byte) 0xd5, (byte) 0xd5, (byte) 0xed,
+            (byte) 0xd9, (byte) 0xc7, (byte) 0x11, (byte) 0x7b, (byte) 0x49,
+            (byte) 0x7a, (byte) 0xf9, (byte) 0xf9, (byte) 0x84, (byte) 0x47,
+            (byte) 0x9b, (byte) 0xe2, (byte) 0xa5, (byte) 0x82, (byte) 0xe0,
+            (byte) 0xc2, (byte) 0x88, (byte) 0xd0, (byte) 0xb2, (byte) 0x58,
+            (byte) 0x88, (byte) 0x7f, (byte) 0x45, (byte) 0x09, (byte) 0x67,
+            (byte) 0x74, (byte) 0x61, (byte) 0xbf, (byte) 0xe6, (byte) 0x40,
+            (byte) 0xe2, (byte) 0x9d, (byte) 0xc2, (byte) 0x47, (byte) 0x05,
+            (byte) 0x89, (byte) 0xed, (byte) 0xcb, (byte) 0xbb, (byte) 0xb7,
+            (byte) 0x27, (byte) 0xe7, (byte) 0xdc, (byte) 0x7a, (byte) 0xfd,
+            (byte) 0xbf, (byte) 0xa8, (byte) 0xd0, (byte) 0xaa, (byte) 0x10,
+            (byte) 0x39, (byte) 0x3c, (byte) 0x20, (byte) 0xf0, (byte) 0xd3,
+            (byte) 0x6e, (byte) 0xb1, (byte) 0x72, (byte) 0xf8, (byte) 0xe6,
+            (byte) 0x0f, (byte) 0xef, (byte) 0x37, (byte) 0xe5, (byte) 0x09,
+            (byte) 0x33, (byte) 0x5a, (byte) 0x83, (byte) 0x43, (byte) 0x80,
+            (byte) 0x4f, (byte) 0x65, (byte) 0x2f, (byte) 0x7c, (byte) 0x8c,
+            (byte) 0x6a, (byte) 0xa0, (byte) 0x82, (byte) 0x0c, (byte) 0xd4,
+            (byte) 0xd4, (byte) 0xfa, (byte) 0x81, (byte) 0x60, (byte) 0x3d,
+            (byte) 0xdf, (byte) 0x06, (byte) 0xf1, (byte) 0x5f, (byte) 0x08,
+            (byte) 0x0d, (byte) 0x6d, (byte) 0x43, (byte) 0xf2, (byte) 0xe3,
+            (byte) 0x11, (byte) 0x7d, (byte) 0x80, (byte) 0x32, (byte) 0xc5,
+            (byte) 0xfb, (byte) 0xc5, (byte) 0xd9, (byte) 0x27, (byte) 0xec,
+            (byte) 0xc6, (byte) 0x4e, (byte) 0x65, (byte) 0x27, (byte) 0x76,
+            (byte) 0x87, (byte) 0xa6, (byte) 0xee, (byte) 0xee, (byte) 0xd7,
+            (byte) 0x8b, (byte) 0xd1, (byte) 0xa0, (byte) 0x5c, (byte) 0xb0,
+            (byte) 0x42, (byte) 0x13, (byte) 0x0e, (byte) 0x95, (byte) 0x4a,
+            (byte) 0xf2, (byte) 0x06, (byte) 0xc6, (byte) 0x43, (byte) 0x33,
+            (byte) 0xf4, (byte) 0xc7, (byte) 0xf8, (byte) 0xe7, (byte) 0x1f,
+            (byte) 0xdd, (byte) 0xe4, (byte) 0x46, (byte) 0x4a, (byte) 0x70,
+            (byte) 0x39, (byte) 0x6c, (byte) 0xd0, (byte) 0xed, (byte) 0xca,
+            (byte) 0xbe, (byte) 0x60, (byte) 0x3b, (byte) 0xd1, (byte) 0x7b,
+            (byte) 0x57, (byte) 0x48, (byte) 0xe5, (byte) 0x3a, (byte) 0x79,
+            (byte) 0xc1, (byte) 0x69, (byte) 0x33, (byte) 0x53, (byte) 0x1b,
+            (byte) 0x80, (byte) 0xb8, (byte) 0x91, (byte) 0x7d, (byte) 0xb4,
+            (byte) 0xf6, (byte) 0x17, (byte) 0x1a, (byte) 0x1d, (byte) 0x5a,
+            (byte) 0x32, (byte) 0xd6, (byte) 0xcc, (byte) 0x71, (byte) 0x29,
+            (byte) 0x3f, (byte) 0x28, (byte) 0xbb, (byte) 0xf3, (byte) 0x5e,
+            (byte) 0x71, (byte) 0xb8, (byte) 0x43, (byte) 0xaf, (byte) 0xf8,
+            (byte) 0xb9, (byte) 0x64, (byte) 0xef, (byte) 0xc4, (byte) 0xa5,
+            (byte) 0x6c, (byte) 0x08, (byte) 0x53, (byte) 0xc7, (byte) 0x00,
+            (byte) 0x10, (byte) 0x39, (byte) 0x4f, (byte) 0xdd, (byte) 0xe4,
+            (byte) 0xb6, (byte) 0x19, (byte) 0x27, (byte) 0xfb, (byte) 0xb8,
+            (byte) 0xf5, (byte) 0x32, (byte) 0x73, (byte) 0xe5, (byte) 0xcb,
+            (byte) 0x32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0};
+    static final byte[] chkb = new byte[60 + 4];
+    static String debugContext = "";
+    static String _debugContext = "";
+    static int com_argc;
+    static int rd_target;
+    static StringBuffer rd_buffer;
+    static int rd_buffersize;
+    static RD_Flusher rd_flusher;
+    static boolean recursive = false;
+    static String msg = "";
+    public static final xcommand_t Error_f = new xcommand_t() {
+        public void execute() throws longjmpException {
+            Error(Defines.ERR_FATAL, Cmd.Argv(1));
+        }
+    };
+
+    public static void BeginRedirect(int target, StringBuffer buffer, int buffersize, RD_Flusher flush) {
+        if (0 == target || null == buffer || 0 == buffersize || null == flush)
+            return;
+
+        rd_target = target;
+        rd_buffer = buffer;
+        rd_buffersize = buffersize;
+        rd_flusher = flush;
+
+        rd_buffer.setLength(0);
+    }
+
+    public static void EndRedirect() {
+        rd_flusher.rd_flush(rd_target, rd_buffer);
+
+        rd_target = 0;
+        rd_buffer = null;
+        rd_buffersize = 0;
+        rd_flusher = null;
+    }
+
+    // See GameSpanw.ED_ParseEdict() to see how to use it now.
+    public static String Parse(ParseHelp hlp) {
+        int c;
+        int len = 0;
+
+        if (hlp.data == null) {
+            return "";
+        }
+
+        while (true) {
+            //    skip whitespace
+            hlp.skipwhites();
+            if (hlp.isEof()) {
+                hlp.data = null;
+                return "";
+            }
+
+            //    skip // comments
+            if (hlp.getchar() == '/') {
+                if (hlp.nextchar() == '/') {
+                    hlp.skiptoeol();
+                    // goto skip whitespace
+                } else {
+                    hlp.prevchar();
+                    break;
+                }
+            } else
+                break;
+        }
+
+        //        handle quoted strings specially
+        if (hlp.getchar() == '\"') {
+            hlp.nextchar();
+            while (true) {
+                c = hlp.getchar();
+                hlp.nextchar();
+                if (c == '\"' || c == 0) {
+                    return new String(com_token, 0, len);
+                }
+                if (len < Defines.MAX_TOKEN_CHARS) {
+                    com_token[len] = (char) c;
+                    len++;
+                }
+            }
+        }
+
+        //        parse a regular word
+        c = hlp.getchar();
+        do {
+            if (len < Defines.MAX_TOKEN_CHARS) {
+                com_token[len] = (char) c;
+                len++;
+            }
+            c = hlp.nextchar();
+        } while (c > 32);
+
+        if (len == Defines.MAX_TOKEN_CHARS) {
+            Com.Printf("Token exceeded " + Defines.MAX_TOKEN_CHARS + " chars, discarded.\n");
+            len = 0;
+        }
+
+        return new String(com_token, 0, len);
+    }
+
+    public static void Error(int code, String fmt) throws longjmpException {
+        Error(code, fmt, null);
+    }
+
+    public static void Error(int code, String fmt, Vargs vargs) throws longjmpException {
+        // va_list argptr;
+        // static char msg[MAXPRINTMSG];
+
+        if (recursive) {
+            Sys.Error("recursive error after: " + msg);
+        }
+        recursive = true;
+
+        msg = sprintf(fmt, vargs);
+
+        if (code == Defines.ERR_DISCONNECT) {
+            Client.Drop();
+            recursive = false;
+            throw new longjmpException();
+        } else if (code == Defines.ERR_DROP) {
+            Com.Printf("********************\nERROR: " + msg + "\n********************\n");
+            Server.SV_Shutdown("Server crashed: " + msg + "\n", false);
+            Client.Drop();
+            recursive = false;
+            throw new longjmpException();
+        } else {
+            Server.SV_Shutdown("Server fatal crashed: %s" + msg + "\n", false);
+            Client.Shutdown();
+        }
+
+        Sys.Error(msg);
+    }
+
+    /**
+     * Com_InitArgv checks the number of command line arguments
+     * and copies all arguments with valid length into com_argv.
+     */
+    static void InitArgv(String[] args) throws longjmpException {
+
+        if (args.length > Defines.MAX_NUM_ARGVS) {
+            Com.Error(Defines.ERR_FATAL, "argc > MAX_NUM_ARGVS");
+        }
+
+        Com.com_argc = args.length;
+        for (int i = 0; i < Com.com_argc; i++) {
+            if (args[i].length() >= Defines.MAX_TOKEN_CHARS)
+                Com.com_argv[i] = "";
+            else
+                Com.com_argv[i] = args[i];
+        }
+    }
+
+    public static void DPrintf(String fmt) {
+        _debugContext = debugContext;
+        DPrintf(fmt, null);
+        _debugContext = "";
+    }
+
+    public static void dprintln(String fmt) {
+        DPrintf(_debugContext + fmt + "\n", null);
+    }
+
+    public static void Printf(String fmt) {
+        Printf(_debugContext + fmt, null);
+    }
+
+    public static void DPrintf(String fmt, Vargs vargs) {
+        if (Globals.developer == null || Globals.developer.value == 0)
+            return; // don't confuse non-developers with techie stuff...
+        _debugContext = debugContext;
+        Printf(fmt, vargs);
+        _debugContext = "";
+    }
+
+    /**
+     * Prints out messages, which can also be redirected to a remote client.
+     */
+    public static void Printf(String fmt, Vargs vargs) {
+        String msg = sprintf(_debugContext + fmt, vargs);
+        if (rd_target != 0) {
+            if ((msg.length() + rd_buffer.length()) > (rd_buffersize - 1)) {
+                rd_flusher.rd_flush(rd_target, rd_buffer);
+                rd_buffer.setLength(0);
+            }
+            rd_buffer.append(msg);
+            return;
+        }
+
+        Console.Print(msg);
+
+        // also echo to debugging console
+        Sys.ConsoleOutput(msg);
+
+    }
+
+    public static void Println(String fmt) {
+        Printf(_debugContext + fmt + "\n");
+    }
+
+    public static String sprintf(String fmt, Vargs vargs) {
+        String msg = "";
+        if (vargs == null || vargs.size() == 0) {
+            msg = fmt;
+        } else {
+            msg = new PrintfFormat(fmt).sprintf(vargs.toArray());
+        }
+        return msg;
+    }
+
+    public static int Argc() {
+        return Com.com_argc;
+    }
+
+    public static String Argv(int arg) {
+        if (arg < 0 || arg >= Com.com_argc || Com.com_argv[arg].length() < 1)
+            return "";
+        return Com.com_argv[arg];
+    }
+
+    public static void ClearArgv(int arg) {
+        if (arg < 0 || arg >= Com.com_argc || Com.com_argv[arg].length() < 1)
+            return;
+        Com.com_argv[arg] = "";
+    }
+
+    public static void Quit() {
+        Server.SV_Shutdown("Server quit\n", false);
+        Client.Shutdown();
+
+        if (Globals.logfile != null) {
+            try {
+                Globals.logfile.close();
+            } catch (IOException ignored) {
+            }
+            Globals.logfile = null;
+        }
+
+        Sys.Quit();
+    }
+
+    public static String StripExtension(String string) {
+        int i = string.lastIndexOf('.');
+        if (i < 0)
+            return string;
+        return string.substring(0, i);
+    }
+
+    /**
+     * Calculates a crc checksum-sequence over an array.
+     */
+    public static byte BlockSequenceCRCByte(byte base[], int offset, int length, int sequence) {
+        if (sequence < 0)
+            Sys.Error("sequence < 0, this shouldn't happen\n");
+
+        //p_ndx = (sequence % (sizeof(chktbl) - 4));
+        int p_ndx = (sequence % (1024 - 4));
+
+        //memcpy(chkb, base, length);
+        length = Math.min(60, length);
+        System.arraycopy(base, offset, chkb, 0, length);
+
+        chkb[length] = chktbl[p_ndx];
+        chkb[length + 1] = chktbl[p_ndx + 1];
+        chkb[length + 2] = chktbl[p_ndx + 2];
+        chkb[length + 3] = chktbl[p_ndx + 3];
+
+        length += 4;
+
+        // unsigned short
+        int crc = CRC.CRC_Block(chkb, length);
+
+        int x = 0;
+        for (int n = 0; n < length; n++)
+            x += chkb[n] & 0xFF;
+
+        crc ^= x;
+
+        return (byte) (crc & 0xFF);
+    }
+
+    public abstract static class RD_Flusher {
+        public abstract void rd_flush(int target, StringBuffer buffer);
+    }
+
+    // helper class to replace the pointer-pointer
+    public static class ParseHelp {
+        private final int length;
+        public int index;
+        public char data[];
+
+        public ParseHelp(String in) {
+            if (in == null) {
+                data = null;
+                length = 0;
+            } else {
+                data = in.toCharArray();
+                length = data.length;
+            }
+            index = 0;
+        }
+
+        public ParseHelp(char in[]) {
+            this(in, 0);
+        }
+
+        public ParseHelp(char in[], int offset) {
+            data = in;
+            index = offset;
+            if (data != null) length = data.length;
+            else length = 0;
+        }
+
+        public char getchar() {
+            if (index < length) {
+                return data[index];
+            }
+            return 0;
+        }
+
+        public char nextchar() {
+            // faster than if
+            index++;
+            if (index < length) {
+                return data[index];
+            }
+            return 0;
+        }
+
+        public void prevchar() {
+            if (index > 0) {
+                index--;
+            }
+        }
+
+        public boolean isEof() {
+            return index >= length;
+        }
+
+        public void skipwhites() {
+            char c = 0;
+            while (index < length && ((c = data[index]) <= ' ') && c != 0)
+                index++;
+        }
+
+        public char skipwhitestoeol() {
+            char c = 0;
+            while (index < length && ((c = data[index]) <= ' ') && c != '\n' && c != 0)
+                index++;
+            return c;
+        }
+
+        public void skiptoeol() {
+            char c = 0;
+            while (index < length && (c = data[index]) != '\n' && c != 0)
+                index++;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/qcommon/CommandBuffer.java b/src/main/java/lwjake2/qcommon/CommandBuffer.java
new file mode 100644 (file)
index 0000000..745b2e6
--- /dev/null
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.qcommon;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.Cmd;
+import lwjake2.util.Lib;
+
+public final class CommandBuffer {
+
+    private static final byte[] line = new byte[1024];
+    private static final byte[] tmp = new byte[8192];
+
+
+    public static void Init() {
+        SZ.Init(Globals.cmd_text, Globals.cmd_text_buf,
+                Globals.cmd_text_buf.length);
+    }
+
+    public static void InsertText(String text) {
+
+        int templen = 0;
+
+        // copy off any commands still remaining in the exec buffer
+        templen = Globals.cmd_text.cursize;
+        if (templen != 0) {
+            System.arraycopy(Globals.cmd_text.data, 0, tmp, 0, templen);
+            SZ.Clear(Globals.cmd_text);
+        }
+
+        // add the entire text of the file
+        CommandBuffer.AddText(text);
+
+        // add the copied off data
+        if (templen != 0) {
+            SZ.Write(Globals.cmd_text, tmp, templen);
+        }
+    }
+
+    /**
+     * @param clear
+     */
+    static void addEarlyCommands(boolean clear) {
+
+        for (int i = 0; i < Com.Argc(); i++) {
+            String s = Com.Argv(i);
+            if (!s.equals("+set"))
+                continue;
+            CommandBuffer.AddText("set " + Com.Argv(i + 1) + " " + Com.Argv(i + 2)
+                    + "\n");
+            if (clear) {
+                Com.ClearArgv(i);
+                Com.ClearArgv(i + 1);
+                Com.ClearArgv(i + 2);
+            }
+            i += 2;
+        }
+    }
+
+    /**
+     * @return
+     */
+    static boolean AddLateCommands() {
+        int i;
+        int j;
+        boolean ret = false;
+
+        // build the combined string to parse from
+        int s = 0;
+        int argc = Com.Argc();
+        for (i = 1; i < argc; i++) {
+            s += Com.Argv(i).length();
+        }
+        if (s == 0)
+            return false;
+
+        StringBuilder text = new StringBuilder();
+        for (i = 1; i < argc; i++) {
+            text.append(Com.Argv(i));
+            if (i != argc - 1)
+                text.append(" ");
+        }
+
+        // pull out the commands
+        StringBuilder build = new StringBuilder();
+        for (i = 0; i < text.length(); i++) {
+            if (text.charAt(i) == '+') {
+                i++;
+
+                for (j = i; j < text.length() && (text.charAt(j) != '+') && (text.charAt(j) != '-'); j++) ;
+
+                build.append(text.substring(i, j));
+                build.append("\n");
+
+                i = j - 1;
+            }
+        }
+
+        ret = (build.length() != 0);
+        if (ret)
+            CommandBuffer.AddText(build.toString());
+
+        text = null;
+        build = null;
+
+        return ret;
+    }
+
+    /**
+     * @param text
+     */
+    public static void AddText(String text) {
+        int l = text.length();
+
+        if (Globals.cmd_text.cursize + l >= Globals.cmd_text.maxsize) {
+            Com.Printf("Cbuf_AddText: overflow\n");
+            return;
+        }
+        SZ.Write(Globals.cmd_text, Lib.stringToBytes(text), l);
+    }
+
+    public static void execute() {
+
+        byte[] text = null;
+
+        Globals.alias_count = 0; // don't allow infinite alias loops
+
+        while (Globals.cmd_text.cursize != 0) {
+            // find a \n or ; line break
+            text = Globals.cmd_text.data;
+
+            int quotes = 0;
+            int i;
+
+            for (i = 0; i < Globals.cmd_text.cursize; i++) {
+                if (text[i] == '"')
+                    quotes++;
+                if (quotes % 2 == 0 && text[i] == ';')
+                    break; // don't break if inside a quoted string
+                if (text[i] == '\n')
+                    break;
+            }
+
+            System.arraycopy(text, 0, line, 0, i);
+            line[i] = 0;
+
+            // delete the text from the command buffer and move remaining
+            // commands down
+            // this is necessary because commands (exec, alias) can insert data
+            // at the
+            // beginning of the text buffer
+
+            if (i == Globals.cmd_text.cursize)
+                Globals.cmd_text.cursize = 0;
+            else {
+                i++;
+                Globals.cmd_text.cursize -= i;
+                //byte[] tmp = new byte[Globals.cmd_text.cursize];
+
+                System.arraycopy(text, i, tmp, 0, Globals.cmd_text.cursize);
+                System.arraycopy(tmp, 0, text, 0, Globals.cmd_text.cursize);
+                text[Globals.cmd_text.cursize] = '\0';
+
+            }
+
+            // execute the command line
+            int len = Lib.strlen(line);
+
+            String cmd = new String(line, 0, len);
+            Cmd.ExecuteString(cmd);
+
+            if (Globals.cmd_wait) {
+                // skip out while text still remains in buffer, leaving it
+                // for next frame
+                Globals.cmd_wait = false;
+                break;
+            }
+        }
+    }
+
+    public static void ExecuteText(int exec_when, String text) {
+        switch (exec_when) {
+            case Defines.EXEC_NOW:
+                Cmd.ExecuteString(text);
+                break;
+            case Defines.EXEC_INSERT:
+                CommandBuffer.InsertText(text);
+                break;
+            case Defines.EXEC_APPEND:
+                CommandBuffer.AddText(text);
+                break;
+            default:
+                Com.Error(Defines.ERR_FATAL, "Cbuf_ExecuteText: bad exec_when");
+        }
+    }
+
+    /*
+     * ============ Cbuf_CopyToDefer ============
+     */
+    public static void CopyToDefer() {
+        System.arraycopy(Globals.cmd_text_buf, 0, Globals.defer_text_buf, 0,
+                Globals.cmd_text.cursize);
+        Globals.defer_text_buf[Globals.cmd_text.cursize] = 0;
+        Globals.cmd_text.cursize = 0;
+    }
+
+    /*
+     * ============ Cbuf_InsertFromDefer ============
+     */
+    public static void InsertFromDefer() {
+        InsertText(new String(Globals.defer_text_buf).trim());
+        Globals.defer_text_buf[0] = 0;
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/qcommon/Cvar.java b/src/main/java/lwjake2/qcommon/Cvar.java
new file mode 100644 (file)
index 0000000..010e4e6
--- /dev/null
@@ -0,0 +1,450 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.qcommon;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.Cmd;
+import lwjake2.game.CvarT;
+import lwjake2.game.Info;
+import lwjake2.util.Lib;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.Vector;
+
+/**
+ * Cvar implements console variables. The original code is located in cvar.c
+ */
+public class Cvar extends Globals {
+
+
+    /**
+     * Set command, sets variables.
+     */
+
+    static final xcommand_t Set_f = new xcommand_t() {
+        public void execute() {
+            int c;
+            int flags;
+
+            c = Cmd.Argc();
+            if (c != 3 && c != 4) {
+                Com.Printf("usage: set <variable> <value> [u / s]\n");
+                return;
+            }
+
+            if (c == 4) {
+                switch (Cmd.Argv(3)) {
+                    case "u":
+                        flags = CVAR_USERINFO;
+                        break;
+                    case "s":
+                        flags = CVAR_SERVERINFO;
+                        break;
+                    default:
+                        Com.Printf("flags can only be 'u' or 's'\n");
+                        return;
+                }
+                Cvar.fullSet(Cmd.Argv(1), Cmd.Argv(2), flags);
+            } else
+                Cvar.set(Cmd.Argv(1), Cmd.Argv(2));
+
+        }
+
+    };
+    /**
+     * List command, lists all available commands.
+     */
+    static final xcommand_t List_f = new xcommand_t() {
+        public void execute() {
+            CvarT var;
+            int i;
+
+            i = 0;
+            for (var = Globals.cvar_vars; var != null; var = var.next, i++) {
+                if ((var.flags & CVAR_ARCHIVE) != 0)
+                    Com.Printf("*");
+                else
+                    Com.Printf(" ");
+                if ((var.flags & CVAR_USERINFO) != 0)
+                    Com.Printf("U");
+                else
+                    Com.Printf(" ");
+                if ((var.flags & CVAR_SERVERINFO) != 0)
+                    Com.Printf("S");
+                else
+                    Com.Printf(" ");
+                if ((var.flags & CVAR_NOSET) != 0)
+                    Com.Printf("-");
+                else if ((var.flags & CVAR_LATCH) != 0)
+                    Com.Printf("L");
+                else
+                    Com.Printf(" ");
+                Com.Printf(" " + var.name + " \"" + var.string + "\"\n");
+            }
+            Com.Printf(i + " cvars\n");
+        }
+    };
+
+    public static CvarT get(String var_name, String var_value, int flags) {
+        CvarT var;
+
+        if ((flags & (CVAR_USERINFO | CVAR_SERVERINFO)) != 0) {
+            if (!infoValidate(var_name)) {
+                Com.Printf("invalid info cvar name\n");
+                return null;
+            }
+        }
+
+        var = Cvar.FindVar(var_name);
+        if (var != null) {
+            var.flags |= flags;
+            return var;
+        }
+
+        if (var_value == null)
+            return null;
+
+        if ((flags & (CVAR_USERINFO | CVAR_SERVERINFO)) != 0) {
+            if (!infoValidate(var_value)) {
+                Com.Printf("invalid info cvar value\n");
+                return null;
+            }
+        }
+        var = new CvarT();
+        var.name = var_name;
+        var.string = var_value;
+        var.modified = true;
+        // handles atof(var.string)
+        try {
+            var.value = Float.parseFloat(var.string);
+        } catch (NumberFormatException e) {
+            var.value = 0.0f;
+        }
+        // link the variable in
+        var.next = Globals.cvar_vars;
+        Globals.cvar_vars = var;
+
+        var.flags = flags;
+
+        return var;
+    }
+
+    static void Init() {
+        Cmd.AddCommand("set", Set_f);
+        Cmd.AddCommand("cvarlist", List_f);
+    }
+
+    public static String variableString(String var_name) {
+        CvarT var;
+        var = FindVar(var_name);
+        return (var == null) ? "" : var.string;
+    }
+
+    static CvarT FindVar(String var_name) {
+        CvarT var;
+
+        for (var = Globals.cvar_vars; var != null; var = var.next) {
+            if (var_name.equals(var.name))
+                return var;
+        }
+
+        return null;
+    }
+
+    /**
+     * Creates a variable if not found and sets their value, the parsed float value and their flags.
+     */
+    public static void fullSet(String var_name, String value, int flags) {
+        CvarT var;
+
+        var = Cvar.FindVar(var_name);
+        if (null == var) { // create it
+            Cvar.get(var_name, value, flags);
+            return;
+        }
+
+        var.modified = true;
+
+        if ((var.flags & CVAR_USERINFO) != 0)
+            Globals.userinfo_modified = true; // transmit at next oportunity
+
+        var.string = value;
+        try {
+            var.value = Float.parseFloat(var.string);
+        } catch (Exception e) {
+            var.value = 0.0f;
+        }
+
+        var.flags = flags;
+
+    }
+
+    /**
+     * Sets the value of the variable without forcing.
+     */
+    public static void set(String var_name, String value) {
+        set2(var_name, value, false);
+    }
+
+    /**
+     * Sets the value of the variable with forcing.
+     */
+    public static CvarT forceSet(String var_name, String value) {
+        return Cvar.set2(var_name, value, true);
+    }
+
+    /**
+     * Gereric set function, sets the value of the variable, with forcing its even possible to
+     * override the variables write protection.
+     */
+    static CvarT set2(String var_name, String value, boolean force) {
+
+        CvarT var = Cvar.FindVar(var_name);
+        if (var == null) {
+            // create it
+            return Cvar.get(var_name, value, 0);
+        }
+
+        if ((var.flags & (CVAR_USERINFO | CVAR_SERVERINFO)) != 0) {
+            if (!infoValidate(value)) {
+                Com.Printf("invalid info cvar value\n");
+                return var;
+            }
+        }
+
+        if (!force) {
+            if ((var.flags & CVAR_NOSET) != 0) {
+                Com.Printf(var_name + " is write protected.\n");
+                return var;
+            }
+
+            if ((var.flags & CVAR_LATCH) != 0) {
+                if (var.latched_string != null) {
+                    if (value.equals(var.latched_string))
+                        return var;
+                    var.latched_string = null;
+                } else {
+                    if (value.equals(var.string))
+                        return var;
+                }
+
+                if (Globals.server_state != 0) {
+                    Com.Printf(var_name + " will be changed for next game.\n");
+                    var.latched_string = value;
+                } else {
+                    var.string = value;
+                    try {
+                        var.value = Float.parseFloat(var.string);
+                    } catch (Exception e) {
+                        var.value = 0.0f;
+                    }
+                    if (var.name.equals("game")) {
+                        FS.SetGamedir(var.string);
+                        FS.ExecAutoexec();
+                    }
+                }
+                return var;
+            }
+        } else {
+            if (var.latched_string != null) {
+                var.latched_string = null;
+            }
+        }
+
+        if (value.equals(var.string))
+            return var; // not changed
+
+        var.modified = true;
+
+        if ((var.flags & CVAR_USERINFO) != 0)
+            Globals.userinfo_modified = true; // transmit at next oportunity
+
+        var.string = value;
+        try {
+            var.value = Float.parseFloat(var.string);
+        } catch (Exception e) {
+            var.value = 0.0f;
+        }
+
+        return var;
+    }
+
+    /**
+     * Sets a float value of a variable.
+     * <p>
+     * The overloading is very important, there was a problem with
+     * networt "rate" string --> 10000 became "10000.0" and that wasn't right.
+     */
+    public static void setValue(String var_name, int value) {
+        Cvar.set(var_name, "" + value);
+    }
+
+    public static void setValue(String var_name, float value) {
+        if (value == (int) value) {
+            Cvar.set(var_name, "" + (int) value);
+        } else {
+            Cvar.set(var_name, "" + value);
+        }
+    }
+
+    /**
+     * Returns the float value of a variable.
+     */
+    public static float variableValue(String var_name) {
+        CvarT var = Cvar.FindVar(var_name);
+        if (var == null)
+            return 0;
+        float val = 0.0f;
+        try {
+            val = Float.parseFloat(var.string);
+        } catch (Exception ignored) {
+        }
+        return val;
+    }
+
+    /**
+     * Handles variable inspection and changing from the console.
+     */
+    public static boolean command() {
+        CvarT v;
+
+        // check variables
+        v = Cvar.FindVar(Cmd.Argv(0));
+        if (v == null)
+            return false;
+
+        // perform a variable print or set
+        if (Cmd.Argc() == 1) {
+            Com.Printf("\"" + v.name + "\" is \"" + v.string + "\"\n");
+            return true;
+        }
+
+        Cvar.set(v.name, Cmd.Argv(1));
+        return true;
+    }
+
+    public static String bitInfo(int bit) {
+        String info;
+        CvarT var;
+
+        info = "";
+
+        for (var = Globals.cvar_vars; var != null; var = var.next) {
+            if ((var.flags & bit) != 0)
+                info = Info.Info_SetValueForKey(info, var.name, var.string);
+        }
+        return info;
+    }
+
+    /**
+     * Returns an info string containing all the CVAR_SERVERINFO cvars.
+     */
+    public static String serverinfo() {
+        return bitInfo(Defines.CVAR_SERVERINFO);
+    }
+
+
+    /**
+     * Any variables with latched values will be updated.
+     */
+    public static void getLatchedVars() {
+        CvarT var;
+
+        for (var = Globals.cvar_vars; var != null; var = var.next) {
+            if (var.latched_string == null || var.latched_string.length() == 0)
+                continue;
+            var.string = var.latched_string;
+            var.latched_string = null;
+            try {
+                var.value = Float.parseFloat(var.string);
+            } catch (NumberFormatException e) {
+                var.value = 0.0f;
+            }
+            if (var.name.equals("game")) {
+                FS.SetGamedir(var.string);
+                FS.ExecAutoexec();
+            }
+        }
+    }
+
+    /**
+     * Returns an info string containing all the CVAR_USERINFO cvars.
+     */
+    public static String userinfo() {
+        return bitInfo(CVAR_USERINFO);
+    }
+
+    /**
+     * Appends lines containing \"set vaqriable value\" for all variables
+     * with the archive flag set true.
+     */
+
+    public static void writeVariables(String path) {
+        CvarT var;
+        RandomAccessFile f;
+        String buffer;
+
+        f = Lib.fopen(path, "rw");
+        if (f == null)
+            return;
+
+        try {
+            f.seek(f.length());
+        } catch (IOException e1) {
+            Lib.fclose(f);
+            return;
+        }
+        for (var = cvar_vars; var != null; var = var.next) {
+            if ((var.flags & CVAR_ARCHIVE) != 0) {
+                buffer = "set " + var.name + " \"" + var.string + "\"\n";
+                try {
+                    f.writeBytes(buffer);
+                } catch (IOException ignored) {
+                }
+            }
+        }
+        Lib.fclose(f);
+    }
+
+    /**
+     * Variable typing auto completition.
+     */
+    public static Vector<String> completeVariable(String partial) {
+
+        Vector<String> vars = new Vector<>();
+
+        // check match
+        for (CvarT cvar = Globals.cvar_vars; cvar != null; cvar = cvar.next)
+            if (cvar.name.startsWith(partial))
+                vars.add(cvar.name);
+
+        return vars;
+    }
+
+    /**
+     * Some characters are invalid for info strings.
+     */
+    static boolean infoValidate(String s) {
+        if (s.contains("\\"))
+            return false;
+        return !s.contains("\"") && !s.contains(";");
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/qcommon/FS.java b/src/main/java/lwjake2/qcommon/FS.java
new file mode 100644 (file)
index 0000000..9a655a2
--- /dev/null
@@ -0,0 +1,948 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.qcommon;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.Cmd;
+import lwjake2.game.CvarT;
+import lwjake2.sys.Sys;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * FS
+ *
+ * @author cwei
+ */
+public final class FS extends Globals {
+
+    /*
+     * ==================================================
+     * 
+     * QUAKE FILESYSTEM
+     * 
+     * ==================================================
+     */
+
+    // read in blocks of 64k
+    public static final int MAX_READ = 0x10000;
+    // with filelink_t entries
+    public static final List<filelink_t> fs_links = new LinkedList<>();
+    static final int IDPAKHEADER = (('K' << 24) + ('C' << 16) + ('A' << 8) + 'P');
+    static final int MAX_FILES_IN_PACK = 4096;
+    // buffer for C-Strings char[56]
+    static final byte[] tmpText = new byte[packfile_t.NAME_SIZE];
+    public static String fs_gamedir;
+    public static CvarT fs_basedir;
+    public static CvarT fs_cddir;
+    public static CvarT fs_gamedirvar;
+    public static searchpath_t fs_searchpaths;
+    // without gamedirs
+    public static searchpath_t fs_base_searchpaths;
+    public static int file_from_pak = 0;
+    /*
+     * All of Quake's data access is through a hierchal file system, but the
+     * contents of the file system can be transparently merged from several
+     * sources.
+     * 
+     * The "base directory" is the path to the directory holding the quake.exe
+     * and all game directories. The sys_* files pass this to host_init in
+     * quakeparms_t->basedir. This can be overridden with the "-basedir" command
+     * line parm to allow code debugging in a different directory. The base
+     * directory is only used during filesystem initialization.
+     * 
+     * The "game directory" is the first tree on the search path and directory
+     * that all generated files (savegames, screenshots, demos, config files)
+     * will be saved to. This can be overridden with the "-game" command line
+     * parameter. The game directory can never be changed while quake is
+     * executing. This is a precacution against having a malicious server
+     * instruct clients to write files over areas they shouldn't.
+     *  
+     */
+    private static String fs_userdir;
+
+    /*
+     * CreatePath
+     *
+     * Creates any directories needed to store the given filename.
+     */
+    public static void CreatePath(String path) {
+        int index = path.lastIndexOf('/');
+        // -1 if not found and 0 means write to root
+        if (index > 0) {
+            File f = new File(path.substring(0, index));
+            if (!f.mkdirs() && !f.isDirectory()) {
+                Com.Printf("can't create path \"" + path + '"' + "\n");
+            }
+        }
+    }
+
+    public static int FileLength(String filename) {
+        searchpath_t search;
+        String netpath;
+        pack_t pak;
+        filelink_t link;
+
+        file_from_pak = 0;
+
+        // check for links first
+        for (filelink_t fs_link : fs_links) {
+            link = fs_link;
+
+            if (filename.regionMatches(0, link.from, 0, link.fromlength)) {
+                netpath = link.to + filename.substring(link.fromlength);
+                File file = new File(netpath);
+                if (file.canRead()) {
+                    Com.DPrintf("link file: " + netpath + '\n');
+                    return (int) file.length();
+                }
+                return -1;
+            }
+        }
+
+        // search through the path, one element at a time
+
+        for (search = fs_searchpaths; search != null; search = search.next) {
+            // is the element a pak file?
+            if (search.pack != null) {
+                // look through all the pak file elements
+                pak = search.pack;
+                filename = filename.toLowerCase();
+                packfile_t entry = pak.files.get(filename);
+
+                if (entry != null) {
+                    // found it!
+                    file_from_pak = 1;
+                    Com.DPrintf("PackFile: " + pak.filename + " : " + filename
+                            + '\n');
+                    // open a new file on the pakfile
+                    File file = new File(pak.filename);
+                    if (!file.canRead()) {
+                        Com.Error(Defines.ERR_FATAL, "Couldn't reopen "
+                                + pak.filename);
+                    }
+                    return entry.filelen;
+                }
+            } else {
+                // check a file in the directory tree
+                netpath = search.filename + '/' + filename;
+
+                File file = new File(netpath);
+                if (!file.canRead())
+                    continue;
+
+                Com.DPrintf("FindFile: " + netpath + '\n');
+
+                return (int) file.length();
+            }
+        }
+        Com.DPrintf("FindFile: can't find " + filename + '\n');
+        return -1;
+    }
+
+    /*
+     * FOpenFile
+     * 
+     * Finds the file in the search path. returns a RadomAccesFile. Used for
+     * streaming data out of either a pak file or a seperate file.
+     */
+    public static RandomAccessFile FOpenFile(String filename)
+            throws IOException {
+        searchpath_t search;
+        String netpath;
+        pack_t pak;
+        filelink_t link;
+        File file = null;
+
+        file_from_pak = 0;
+
+        // check for links first
+        for (filelink_t fs_link : fs_links) {
+            link = fs_link;
+
+            //                 if (!strncmp (filename, link->from, link->fromlength))
+            if (filename.regionMatches(0, link.from, 0, link.fromlength)) {
+                netpath = link.to + filename.substring(link.fromlength);
+                file = new File(netpath);
+                if (file.canRead()) {
+                    //Com.DPrintf ("link file: " + netpath +'\n');
+                    return new RandomAccessFile(file, "r");
+                }
+                return null;
+            }
+        }
+
+        //
+        // search through the path, one element at a time
+        //
+        for (search = fs_searchpaths; search != null; search = search.next) {
+            // is the element a pak file?
+            if (search.pack != null) {
+                // look through all the pak file elements
+                pak = search.pack;
+                filename = filename.toLowerCase();
+                packfile_t entry = pak.files.get(filename);
+
+                if (entry != null) {
+                    // found it!
+                    file_from_pak = 1;
+                    //Com.DPrintf ("PackFile: " + pak.filename + " : " +
+                    // filename + '\n');
+                    file = new File(pak.filename);
+                    if (!file.canRead())
+                        Com.Error(Defines.ERR_FATAL, "Couldn't reopen "
+                                + pak.filename);
+                    if (pak.handle == null || !pak.handle.getFD().valid()) {
+                        // hold the pakfile handle open
+                        pak.handle = new RandomAccessFile(pak.filename, "r");
+                    }
+                    // open a new file on the pakfile
+
+                    RandomAccessFile raf = new RandomAccessFile(file, "r");
+                    raf.seek(entry.filepos);
+
+                    return raf;
+                }
+            } else {
+                // check a file in the directory tree
+                netpath = search.filename + '/' + filename;
+
+                file = new File(netpath);
+                if (!file.canRead())
+                    continue;
+
+                //Com.DPrintf("FindFile: " + netpath +'\n');
+
+                return new RandomAccessFile(file, "r");
+            }
+        }
+        //Com.DPrintf ("FindFile: can't find " + filename + '\n');
+        return null;
+    }
+
+    /**
+     * Read
+     * <p>
+     * Properly handles partial reads
+     */
+    public static void Read(byte[] buffer, int len, RandomAccessFile f) {
+
+        int block, remaining;
+        int offset = 0;
+        int read = 0;
+
+        // read in chunks for progress bar
+        remaining = len;
+
+        while (remaining != 0) {
+            block = Math.min(remaining, MAX_READ);
+            try {
+                read = f.read(buffer, offset, block);
+            } catch (IOException e) {
+                Com.Error(Defines.ERR_FATAL, e.toString());
+            }
+
+            if (read == 0) {
+                Com.Error(Defines.ERR_FATAL, "FS_Read: 0 bytes read");
+            } else if (read == -1) {
+                Com.Error(Defines.ERR_FATAL, "FS_Read: -1 bytes read");
+            }
+            //
+            // do some progress bar thing here...
+            //
+            remaining -= read;
+            offset += read;
+        }
+    }
+
+    /*
+     * LoadFile
+     *
+     * Filename are reletive to the quake search path a null buffer will just
+     * return the file content as byte[]
+     */
+    public static byte[] LoadFile(String path) {
+        RandomAccessFile file;
+
+        byte[] buf = null;
+        int len = 0;
+
+        // TODO hack for bad strings (fuck \0)
+        int index = path.indexOf('\0');
+        if (index != -1)
+            path = path.substring(0, index);
+
+        // look for it in the filesystem or pack files
+        len = FileLength(path);
+
+        if (len < 1)
+            return null;
+
+        try {
+            file = FOpenFile(path);
+            //Read(buf = new byte[len], len, h);
+            buf = new byte[len];
+            file.readFully(buf);
+            file.close();
+        } catch (IOException e) {
+            Com.Error(Defines.ERR_FATAL, e.toString());
+        }
+        return buf;
+    }
+
+    /*
+     * LoadMappedFile
+     *
+     * Filename are reletive to the quake search path a null buffer will just
+     * return the file content as ByteBuffer (memory mapped)
+     */
+    public static ByteBuffer LoadMappedFile(String filename) {
+        searchpath_t search;
+        String netpath;
+        pack_t pak;
+        filelink_t link;
+        File file = null;
+
+        int fileLength = 0;
+        FileChannel channel = null;
+        FileInputStream input = null;
+        ByteBuffer buffer = null;
+
+        file_from_pak = 0;
+
+        try {
+            // check for links first
+            for (filelink_t fs_link : fs_links) {
+                link = fs_link;
+
+                if (filename.regionMatches(0, link.from, 0, link.fromlength)) {
+                    netpath = link.to + filename.substring(link.fromlength);
+                    file = new File(netpath);
+                    if (file.canRead()) {
+                        input = new FileInputStream(file);
+                        channel = input.getChannel();
+                        fileLength = (int) channel.size();
+                        buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0,
+                                fileLength);
+                        input.close();
+                        return buffer;
+                    }
+                    return null;
+                }
+            }
+
+            //
+            // search through the path, one element at a time
+            //
+            for (search = fs_searchpaths; search != null; search = search.next) {
+                // is the element a pak file?
+                if (search.pack != null) {
+                    // look through all the pak file elements
+                    pak = search.pack;
+                    filename = filename.toLowerCase();
+                    packfile_t entry = pak.files.get(filename);
+
+                    if (entry != null) {
+                        // found it!
+                        file_from_pak = 1;
+                        //Com.DPrintf ("PackFile: " + pak.filename + " : " +
+                        // filename + '\n');
+                        file = new File(pak.filename);
+                        if (!file.canRead())
+                            Com.Error(Defines.ERR_FATAL, "Couldn't reopen "
+                                    + pak.filename);
+                        if (pak.handle == null || !pak.handle.getFD().valid()) {
+                            // hold the pakfile handle open
+                            pak.handle = new RandomAccessFile(pak.filename, "r");
+                        }
+                        // open a new file on the pakfile
+                        if (pak.backbuffer == null) {
+                            channel = pak.handle.getChannel();
+                            pak.backbuffer = channel.map(
+                                    FileChannel.MapMode.READ_ONLY, 0,
+                                    pak.handle.length());
+                            channel.close();
+                        }
+                        pak.backbuffer.position(entry.filepos);
+                        buffer = pak.backbuffer.slice();
+                        buffer.limit(entry.filelen);
+                        return buffer;
+                    }
+                } else {
+                    // check a file in the directory tree
+                    netpath = search.filename + '/' + filename;
+
+                    file = new File(netpath);
+                    if (!file.canRead())
+                        continue;
+
+                    //Com.DPrintf("FindFile: " + netpath +'\n');
+                    input = new FileInputStream(file);
+                    channel = input.getChannel();
+                    fileLength = (int) channel.size();
+                    buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0,
+                            fileLength);
+                    input.close();
+                    return buffer;
+                }
+            }
+        } catch (Exception ignored) {
+        }
+        try {
+            if (input != null)
+                input.close();
+            else if (channel != null && channel.isOpen())
+                channel.close();
+        } catch (IOException ignored) {
+        }
+        return null;
+    }
+
+    /*
+     * FreeFile
+     */
+    public static void FreeFile() {
+        byte[] buffer = null;
+    }
+
+    /*
+     * LoadPackFile
+     *
+     * Takes an explicit (not game tree related) path to a pak file.
+     *
+     * Loads the header and directory, adding the files at the beginning of the
+     * list so they override previous pack files.
+     */
+    static pack_t LoadPackFile(String packfile) {
+
+        dpackheader_t header;
+        Hashtable<String, packfile_t> newfiles;
+        RandomAccessFile file;
+        int numpackfiles = 0;
+        pack_t pack = null;
+        //             unsigned checksum;
+        //
+        try {
+            file = new RandomAccessFile(packfile, "r");
+            FileChannel fc = file.getChannel();
+            ByteBuffer packhandle = fc.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
+            packhandle.order(ByteOrder.LITTLE_ENDIAN);
+
+            fc.close();
+
+            if (packhandle == null || packhandle.limit() < 1)
+                return null;
+            //
+            header = new dpackheader_t();
+            header.ident = packhandle.getInt();
+            header.dirofs = packhandle.getInt();
+            header.dirlen = packhandle.getInt();
+
+            if (header.ident != IDPAKHEADER)
+                Com.Error(Defines.ERR_FATAL, packfile + " is not a packfile");
+
+            numpackfiles = header.dirlen / packfile_t.SIZE;
+
+            if (numpackfiles > MAX_FILES_IN_PACK)
+                Com.Error(Defines.ERR_FATAL, packfile + " has " + numpackfiles
+                        + " files");
+
+            newfiles = new Hashtable<>(numpackfiles);
+
+            packhandle.position(header.dirofs);
+
+            // parse the directory
+            packfile_t entry = null;
+
+            for (int i = 0; i < numpackfiles; i++) {
+                packhandle.get(tmpText);
+
+                entry = new packfile_t();
+                entry.name = new String(tmpText).trim();
+                entry.filepos = packhandle.getInt();
+                entry.filelen = packhandle.getInt();
+
+                newfiles.put(entry.name.toLowerCase(), entry);
+            }
+
+        } catch (IOException e) {
+            Com.DPrintf(e.getMessage() + '\n');
+            return null;
+        }
+
+        pack = new pack_t();
+        pack.filename = packfile;
+        pack.handle = file;
+        pack.numfiles = numpackfiles;
+        pack.files = newfiles;
+
+        Com.Printf("Added packfile " + packfile + " (" + numpackfiles
+                + " files)\n");
+
+        return pack;
+    }
+
+    /*
+     * AddGameDirectory
+     *
+     * Sets fs_gamedir, adds the directory to the head of the path, then loads
+     * and adds pak1.pak pak2.pak ...
+     */
+    static void AddGameDirectory(String dir) {
+        int i;
+        searchpath_t search;
+        pack_t pak;
+        String pakfile;
+
+        fs_gamedir = dir;
+
+        //
+        // add the directory to the search path
+        // ensure fs_userdir is first in searchpath
+        search = new searchpath_t();
+        search.filename = dir;
+        if (fs_searchpaths != null) {
+            search.next = fs_searchpaths.next;
+            fs_searchpaths.next = search;
+        } else {
+            fs_searchpaths = search;
+        }
+
+        //
+        // add any pak files in the format pak0.pak pak1.pak, ...
+        //
+        for (i = 0; i < 10; i++) {
+            pakfile = dir + "/pak" + i + ".pak";
+            if (!(new File(pakfile).canRead()))
+                continue;
+
+            pak = LoadPackFile(pakfile);
+            if (pak == null)
+                continue;
+
+            search = new searchpath_t();
+            search.pack = pak;
+            search.filename = "";
+            search.next = fs_searchpaths;
+            fs_searchpaths = search;
+        }
+    }
+
+    /*
+     * Gamedir
+     *
+     * Called to find where to write a file (demos, savegames, etc)
+     * this is modified to <user.home>/.lwjake2
+     */
+    public static String Gamedir() {
+        return (fs_userdir != null) ? fs_userdir : Globals.BASEDIRNAME;
+    }
+
+    /*
+     * BaseGamedir
+     *
+     * Called to find where to write a downloaded file
+     */
+    public static String BaseGamedir() {
+        return (fs_gamedir != null) ? fs_gamedir : Globals.BASEDIRNAME;
+    }
+
+    /*
+     * ExecAutoexec
+     */
+    public static void ExecAutoexec() {
+        String dir = fs_userdir;
+
+        String name;
+        if (dir != null && dir.length() > 0) {
+            name = dir + "/autoexec.cfg";
+        } else {
+            name = fs_basedir.string + '/' + Globals.BASEDIRNAME
+                    + "/autoexec.cfg";
+        }
+
+        int canthave = Defines.SFF_SUBDIR | Defines.SFF_HIDDEN
+                | Defines.SFF_SYSTEM;
+
+        if (Sys.FindAll(name, 0, canthave) != null) {
+            CommandBuffer.AddText("exec autoexec.cfg\n");
+        }
+    }
+
+    /*
+     * SetGamedir
+     *
+     * Sets the gamedir and path to a different directory.
+     */
+    public static void SetGamedir(String dir) {
+        searchpath_t next;
+
+        if (dir.contains("..") || dir.contains("/")
+                || dir.contains("\\") || dir.contains(":")) {
+            Com.Printf("Gamedir should be a single filename, not a path\n");
+            return;
+        }
+
+        //
+        // free up any current game dir info
+        //
+        while (fs_searchpaths != fs_base_searchpaths) {
+            if (fs_searchpaths.pack != null) {
+                try {
+                    fs_searchpaths.pack.handle.close();
+                } catch (IOException e) {
+                    Com.DPrintf(e.getMessage() + '\n');
+                }
+                // clear the hashtable
+                fs_searchpaths.pack.files.clear();
+                fs_searchpaths.pack.files = null;
+                fs_searchpaths.pack = null;
+            }
+            next = fs_searchpaths.next;
+            fs_searchpaths = null;
+            fs_searchpaths = next;
+        }
+
+        //
+        // flush all data, so it will be forced to reload
+        //
+        if ((Globals.dedicated != null) && (Globals.dedicated.value == 0.0f))
+            CommandBuffer.AddText("vid_restart\nsnd_restart\n");
+
+        fs_gamedir = fs_basedir.string + '/' + dir;
+
+        if (dir.equals(Globals.BASEDIRNAME) || (dir.length() == 0)) {
+            Cvar.fullSet("gamedir", "", CVAR_SERVERINFO | CVAR_NOSET);
+            Cvar.fullSet("game", "", CVAR_LATCH | CVAR_SERVERINFO);
+        } else {
+            Cvar.fullSet("gamedir", dir, CVAR_SERVERINFO | CVAR_NOSET);
+            if (fs_cddir.string != null && fs_cddir.string.length() > 0)
+                AddGameDirectory(fs_cddir.string + '/' + dir);
+
+            AddGameDirectory(fs_basedir.string + '/' + dir);
+        }
+    }
+
+    /*
+     * Link_f
+     *
+     * Creates a filelink_t
+     */
+    public static void Link_f() {
+        filelink_t entry = null;
+
+        if (Cmd.Argc() != 3) {
+            Com.Printf("USAGE: link <from> <to>\n");
+            return;
+        }
+
+        // see if the link already exists
+        for (Iterator<filelink_t> it = fs_links.iterator(); it.hasNext(); ) {
+            entry = it.next();
+
+            if (entry.from.equals(Cmd.Argv(1))) {
+                if (Cmd.Argv(2).length() < 1) {
+                    // delete it
+                    it.remove();
+                    return;
+                }
+                entry.to = Cmd.Argv(2);
+                return;
+            }
+        }
+
+        // create a new link if the <to> is not empty
+        if (Cmd.Argv(2).length() > 0) {
+            entry = new filelink_t();
+            entry.from = Cmd.Argv(1);
+            entry.fromlength = entry.from.length();
+            entry.to = Cmd.Argv(2);
+            fs_links.add(entry);
+        }
+    }
+
+    /*
+     * ListFiles
+     */
+    public static String[] ListFiles(String findname, int musthave, int canthave) {
+        String[] list = new String[0];
+
+        File[] files = Sys.FindAll(findname, musthave, canthave);
+
+        if (files != null) {
+            list = new String[files.length];
+            for (int i = 0; i < files.length; i++) {
+                list[i] = files[i].getPath();
+            }
+        }
+
+        return list;
+    }
+
+    /*
+     * Dir_f
+     */
+    public static void Dir_f() {
+        String path = null;
+        String findname = null;
+        String wildcard = "*.*";
+        String[] dirnames;
+
+        if (Cmd.Argc() != 1) {
+            wildcard = Cmd.Argv(1);
+        }
+
+        while ((path = NextPath(path)) != null) {
+            String tmp = findname;
+
+            findname = path + '/' + wildcard;
+
+            if (tmp != null)
+                tmp.replaceAll("\\\\", "/");
+
+            Com.Printf("Directory of " + findname + '\n');
+            Com.Printf("----\n");
+
+            dirnames = ListFiles(findname, 0, 0);
+
+            if (dirnames.length != 0) {
+                int index = 0;
+                for (String dirname : dirnames) {
+                    if ((index = dirname.lastIndexOf('/')) > 0) {
+                        Com.Printf(dirname.substring(index + 1, dirname
+                                .length()) + '\n');
+                    } else {
+                        Com.Printf(dirname + '\n');
+                    }
+                }
+            }
+
+            Com.Printf("\n");
+        }
+    }
+
+    /*
+     * Path_f
+     */
+    public static void Path_f() {
+
+        searchpath_t s;
+        filelink_t link;
+
+        Com.Printf("Current search path:\n");
+        for (s = fs_searchpaths; s != null; s = s.next) {
+            if (s == fs_base_searchpaths)
+                Com.Printf("----------\n");
+            if (s.pack != null)
+                Com.Printf(s.pack.filename + " (" + s.pack.numfiles
+                        + " files)\n");
+            else
+                Com.Printf(s.filename + '\n');
+        }
+
+        Com.Printf("\nLinks:\n");
+        for (filelink_t fs_link : fs_links) {
+            link = fs_link;
+            Com.Printf(link.from + " : " + link.to + '\n');
+        }
+    }
+
+    /*
+     * NextPath
+     *
+     * Allows enumerating all of the directories in the search path
+     */
+    public static String NextPath(String prevpath) {
+        searchpath_t s;
+        String prev;
+
+        if (prevpath == null || prevpath.length() == 0)
+            return fs_gamedir;
+
+        prev = fs_gamedir;
+        for (s = fs_searchpaths; s != null; s = s.next) {
+            if (s.pack != null)
+                continue;
+
+            if (prevpath == prev)
+                return s.filename;
+
+            prev = s.filename;
+        }
+
+        return null;
+    }
+
+    /*
+     * initFilesystem
+     */
+    public static void initFilesystem() {
+        Cmd.AddCommand("path", new xcommand_t() {
+            public void execute() {
+                Path_f();
+            }
+        });
+        Cmd.AddCommand("link", new xcommand_t() {
+            public void execute() {
+                Link_f();
+            }
+        });
+        Cmd.AddCommand("dir", new xcommand_t() {
+            public void execute() {
+                Dir_f();
+            }
+        });
+
+        fs_userdir = System.getProperty("user.home") + "/.lwjake2";
+        FS.CreatePath(fs_userdir + "/");
+        FS.AddGameDirectory(fs_userdir);
+
+        //
+        // basedir <path>
+        // allows the game to run from outside the data tree
+        //
+        fs_basedir = Cvar.get("basedir", ".", CVAR_NOSET);
+
+        //
+        // cddir <path>
+        // Logically concatenates the cddir after the basedir for
+        // allows the game to run from outside the data tree
+        //
+
+        setCDDir();
+
+        //
+        // start up with baseq2 by default
+        //
+        AddGameDirectory(fs_basedir.string + '/' + Globals.BASEDIRNAME);
+
+        // any set gamedirs will be freed up to here
+        markBaseSearchPaths();
+
+        // check for game override
+        checkOverride();
+    }
+
+    /**
+     * set baseq2 directory
+     */
+    static void setCDDir() {
+        fs_cddir = Cvar.get("cddir", "", CVAR_ARCHIVE);
+        if (fs_cddir.string.length() > 0)
+            AddGameDirectory(fs_cddir.string + '/' + Globals.BASEDIRNAME);
+    }
+
+    /**
+     * Check for "+set game" override - Used to properly set gamedir
+     */
+    static void checkOverride() {
+        fs_gamedirvar = Cvar.get("game", "", CVAR_LATCH | CVAR_SERVERINFO);
+
+        if (fs_gamedirvar.string.length() > 0)
+            SetGamedir(fs_gamedirvar.string);
+    }
+
+    static void markBaseSearchPaths() {
+        // any set gamedirs will be freed up to here
+        fs_base_searchpaths = fs_searchpaths;
+    }
+
+    // RAFAEL
+    /*
+     * Developer_searchpath
+     */
+    public static int Developer_searchpath(int who) {
+
+        // PMM - warning removal
+        //      char *start;
+        searchpath_t s;
+
+
+        for (s = fs_searchpaths; s != null; s = s.next) {
+            if (s.filename.contains("xatrix"))
+                return 1;
+
+            if (s.filename.contains("rogue"))
+                return 2;
+        }
+
+        return 0;
+    }
+
+    public static class packfile_t {
+        static final int SIZE = 64;
+
+        static final int NAME_SIZE = 56;
+
+        String name; // char name[56]
+
+        int filepos, filelen;
+
+        public String toString() {
+            return name + " [ length: " + filelen + " pos: " + filepos + " ]";
+        }
+    }
+
+    public static class pack_t {
+        String filename;
+
+        RandomAccessFile handle;
+
+        ByteBuffer backbuffer;
+
+        int numfiles;
+
+        Hashtable<String, packfile_t> files; // with packfile_t entries
+    }
+
+    public static class filelink_t {
+        String from;
+
+        int fromlength;
+
+        String to;
+    }
+
+    public static class searchpath_t {
+        String filename;
+
+        pack_t pack; // only one of filename or pack will be used
+
+        searchpath_t next;
+    }
+
+    static class dpackheader_t {
+        int ident; // IDPAKHEADER
+
+        int dirofs;
+
+        int dirlen;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/qcommon/MD4.java b/src/main/java/lwjake2/qcommon/MD4.java
new file mode 100644 (file)
index 0000000..76e7aeb
--- /dev/null
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.qcommon;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.MessageDigest;
+
+
+public class MD4 extends MessageDigest implements Cloneable {
+    // MD4 specific object variables
+    //...........................................................................
+
+    /**
+     * The size in bytes of the input block to the tranformation algorithm.
+     */
+    private static final int BLOCK_LENGTH = 64; //    = 512 / 8;
+    /**
+     * 512 bits work buffer = 16 x 32-bit words
+     */
+    private final int[] X = new int[16];
+    /**
+     * 4 32-bit words (interim result)
+     */
+    private int[] context = new int[4];
+    /**
+     * Number of bytes processed so far mod. 2 power of 64.
+     */
+    private long count;
+    /**
+     * 512 bits input buffer = 16 x 32-bit words holds until reaches 512 bits.
+     */
+    private byte[] buffer = new byte[BLOCK_LENGTH];
+
+    // Constructors
+    //...........................................................................
+
+    public MD4() {
+        super("MD4");
+        engineReset();
+    }
+
+    /**
+     * This constructor is here to implement cloneability of this class.
+     */
+    private MD4(MD4 md) {
+        this();
+        context = md.context.clone();
+        buffer = md.buffer.clone();
+        count = md.count;
+    }
+
+    // Cloneable method implementation
+    //...........................................................................
+
+    /**
+     * Bugfixed, now works prima (RST).
+     */
+    public static int Com_BlockChecksum(byte[] buffer, int length) {
+
+        int val;
+        MD4 md4 = new MD4();
+
+        md4.engineUpdate(buffer, 0, length);
+        byte data[] = md4.engineDigest();
+        ByteBuffer bb = ByteBuffer.wrap(data);
+        bb.order(ByteOrder.LITTLE_ENDIAN);
+        val = bb.getInt() ^ bb.getInt() ^ bb.getInt() ^ bb.getInt();
+        return val;
+    }
+
+    // JCE methods
+    //...........................................................................
+
+    /**
+     * Returns a copy of this MD object.
+     */
+    public Object clone() {
+        return new MD4(this);
+    }
+
+    /**
+     * Resets this object disregarding any temporary data present at the
+     * time of the invocation of this call.
+     */
+    public void engineReset() {
+        // initial values of MD4 i.e. A, B, C, D
+        // as per rfc-1320; they are low-order byte first
+        context[0] = 0x67452301;
+        context[1] = 0xEFCDAB89;
+        context[2] = 0x98BADCFE;
+        context[3] = 0x10325476;
+        count = 0L;
+        for (int i = 0; i < BLOCK_LENGTH; i++)
+            buffer[i] = 0;
+    }
+
+    /**
+     * Continues an MD4 message digest using the input byte.
+     */
+    public void engineUpdate(byte b) {
+        // compute number of bytes still unhashed; ie. present in buffer
+        int i = (int) (count % BLOCK_LENGTH);
+        count++; // update number of bytes
+        buffer[i] = b;
+        if (i == BLOCK_LENGTH - 1)
+            transform(buffer, 0);
+    }
+
+    /**
+     * MD4 block update operation.
+     * <p>
+     * Continues an MD4 message digest operation, by filling the buffer,
+     * transform(ing) data in 512-bit message block(s), updating the variables
+     * context and count, and leaving (buffering) the remaining bytes in buffer
+     * for the next update or finish.
+     *
+     * @param input  input block
+     * @param offset start of meaningful bytes in input
+     * @param len    count of bytes in input block to consider
+     */
+    public void engineUpdate(byte[] input, int offset, int len) {
+        // make sure we don't exceed input's allocated size/length
+        if (offset < 0 || len < 0 || (long) offset + len > input.length)
+            throw new ArrayIndexOutOfBoundsException();
+
+        // compute number of bytes still unhashed; ie. present in buffer
+        int bufferNdx = (int) (count % BLOCK_LENGTH);
+        count += len; // update number of bytes
+        int partLen = BLOCK_LENGTH - bufferNdx;
+        int i = 0;
+        if (len >= partLen) {
+            System.arraycopy(input, offset, buffer, bufferNdx, partLen);
+
+            transform(buffer, 0);
+
+            for (i = partLen; i + BLOCK_LENGTH - 1 < len; i += BLOCK_LENGTH)
+                transform(input, offset + i);
+            bufferNdx = 0;
+        }
+        // buffer remaining input
+        if (i < len)
+            System.arraycopy(input, offset + i, buffer, bufferNdx, len - i);
+    }
+
+    // own methods
+    //...........................................................................
+
+    /**
+     * Completes the hash computation by performing final operations such
+     * as padding. At the return of this engineDigest, the MD engine is
+     * reset.
+     *
+     * @return the array of bytes for the resulting hash value.
+     */
+    public byte[] engineDigest() {
+        // pad output to 56 mod 64; as RFC1320 puts it: congruent to 448 mod 512
+        int bufferNdx = (int) (count % BLOCK_LENGTH);
+        int padLen = (bufferNdx < 56) ? (56 - bufferNdx) : (120 - bufferNdx);
+
+        // padding is alwas binary 1 followed by binary 0s
+        byte[] tail = new byte[padLen + 8];
+        tail[0] = (byte) 0x80;
+
+        // append length before final transform:
+        // save number of bits, casting the long to an array of 8 bytes
+        // save low-order byte first.
+        for (int i = 0; i < 8; i++)
+            tail[padLen + i] = (byte) ((count * 8) >>> (8 * i));
+
+        engineUpdate(tail, 0, tail.length);
+
+        byte[] result = new byte[16];
+        // cast this MD4's context (array of 4 ints) into an array of 16 bytes.
+        for (int i = 0; i < 4; i++)
+            for (int j = 0; j < 4; j++)
+                result[i * 4 + j] = (byte) (context[i] >>> (8 * j));
+
+        // reset the engine
+        engineReset();
+        return result;
+    }
+
+    // The basic MD4 atomic functions.
+
+    /**
+     * MD4 basic transformation.
+     * <p>
+     * Transforms context based on 512 bits from input block starting
+     * from the offset'th byte.
+     *
+     * @param block  input sub-array.
+     * @param offset starting position of sub-array.
+     */
+    private void transform(byte[] block, int offset) {
+
+        // encodes 64 bytes from input block into an array of 16 32-bit
+        // entities. Use A as a temp var.
+        for (int i = 0; i < 16; i++)
+            X[i] =
+                    (block[offset++] & 0xFF) | (block[offset++] & 0xFF)
+                            << 8 | (block[offset++] & 0xFF)
+                            << 16 | (block[offset++] & 0xFF)
+                            << 24;
+
+        int A = context[0];
+        int B = context[1];
+        int C = context[2];
+        int D = context[3];
+
+        A = FF(A, B, C, D, X[0], 3);
+        D = FF(D, A, B, C, X[1], 7);
+        C = FF(C, D, A, B, X[2], 11);
+        B = FF(B, C, D, A, X[3], 19);
+        A = FF(A, B, C, D, X[4], 3);
+        D = FF(D, A, B, C, X[5], 7);
+        C = FF(C, D, A, B, X[6], 11);
+        B = FF(B, C, D, A, X[7], 19);
+        A = FF(A, B, C, D, X[8], 3);
+        D = FF(D, A, B, C, X[9], 7);
+        C = FF(C, D, A, B, X[10], 11);
+        B = FF(B, C, D, A, X[11], 19);
+        A = FF(A, B, C, D, X[12], 3);
+        D = FF(D, A, B, C, X[13], 7);
+        C = FF(C, D, A, B, X[14], 11);
+        B = FF(B, C, D, A, X[15], 19);
+
+        A = GG(A, B, C, D, X[0], 3);
+        D = GG(D, A, B, C, X[4], 5);
+        C = GG(C, D, A, B, X[8], 9);
+        B = GG(B, C, D, A, X[12], 13);
+        A = GG(A, B, C, D, X[1], 3);
+        D = GG(D, A, B, C, X[5], 5);
+        C = GG(C, D, A, B, X[9], 9);
+        B = GG(B, C, D, A, X[13], 13);
+        A = GG(A, B, C, D, X[2], 3);
+        D = GG(D, A, B, C, X[6], 5);
+        C = GG(C, D, A, B, X[10], 9);
+        B = GG(B, C, D, A, X[14], 13);
+        A = GG(A, B, C, D, X[3], 3);
+        D = GG(D, A, B, C, X[7], 5);
+        C = GG(C, D, A, B, X[11], 9);
+        B = GG(B, C, D, A, X[15], 13);
+
+        A = HH(A, B, C, D, X[0], 3);
+        D = HH(D, A, B, C, X[8], 9);
+        C = HH(C, D, A, B, X[4], 11);
+        B = HH(B, C, D, A, X[12], 15);
+        A = HH(A, B, C, D, X[2], 3);
+        D = HH(D, A, B, C, X[10], 9);
+        C = HH(C, D, A, B, X[6], 11);
+        B = HH(B, C, D, A, X[14], 15);
+        A = HH(A, B, C, D, X[1], 3);
+        D = HH(D, A, B, C, X[9], 9);
+        C = HH(C, D, A, B, X[5], 11);
+        B = HH(B, C, D, A, X[13], 15);
+        A = HH(A, B, C, D, X[3], 3);
+        D = HH(D, A, B, C, X[11], 9);
+        C = HH(C, D, A, B, X[7], 11);
+        B = HH(B, C, D, A, X[15], 15);
+
+        context[0] += A;
+        context[1] += B;
+        context[2] += C;
+        context[3] += D;
+    }
+
+    private int FF(int a, int b, int c, int d, int x, int s) {
+        int t = a + ((b & c) | (~b & d)) + x;
+        return t << s | t >>> (32 - s);
+    }
+
+    private int GG(int a, int b, int c, int d, int x, int s) {
+        int t = a + ((b & (c | d)) | (c & d)) + x + 0x5A827999;
+        return t << s | t >>> (32 - s);
+    }
+
+    private int HH(int a, int b, int c, int d, int x, int s) {
+        int t = a + (b ^ c ^ d) + x + 0x6ED9EBA1;
+        return t << s | t >>> (32 - s);
+    }
+}
diff --git a/src/main/java/lwjake2/qcommon/MSG.java b/src/main/java/lwjake2/qcommon/MSG.java
new file mode 100644 (file)
index 0000000..5b80768
--- /dev/null
@@ -0,0 +1,548 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.qcommon;
+
+import lwjake2.Globals;
+import lwjake2.game.entity_state_t;
+import lwjake2.game.usercmd_t;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+
+public class MSG extends Globals {
+
+    //
+    // writing functions
+    //
+
+    // 2k read buffer.
+    public static final byte[] readbuf = new byte[2048];
+
+    //ok.
+    public static void WriteChar(sizebuf_t sb, int c) {
+        sb.data[SZ.GetSpace(sb, 1)] = (byte) (c & 0xFF);
+    }
+
+    //ok.
+    public static void WriteChar(sizebuf_t sb, float c) {
+
+        WriteChar(sb, (int) c);
+    }
+
+    //ok.
+    public static void WriteByte(sizebuf_t sb, int c) {
+        sb.data[SZ.GetSpace(sb, 1)] = (byte) (c & 0xFF);
+    }
+
+    //ok.
+    public static void WriteByte(sizebuf_t sb, float c) {
+        WriteByte(sb, (int) c);
+    }
+
+    public static void WriteShort(sizebuf_t sb, int c) {
+        int i = SZ.GetSpace(sb, 2);
+        sb.data[i++] = (byte) (c & 0xff);
+        sb.data[i] = (byte) ((c >>> 8) & 0xFF);
+    }
+
+    //ok.
+    public static void WriteInt(sizebuf_t sb, int c) {
+        int i = SZ.GetSpace(sb, 4);
+        sb.data[i++] = (byte) ((c & 0xff));
+        sb.data[i++] = (byte) ((c >>> 8) & 0xff);
+        sb.data[i++] = (byte) ((c >>> 16) & 0xff);
+        sb.data[i++] = (byte) ((c >>> 24) & 0xff);
+    }
+
+    //ok.
+    public static void WriteLong(sizebuf_t sb, int c) {
+        WriteInt(sb, c);
+    }
+
+    // had a bug, now its ok.
+    public static void WriteString(sizebuf_t sb, String s) {
+        String x = s;
+
+        if (s == null)
+            x = "";
+
+        SZ.Write(sb, Lib.stringToBytes(x));
+        WriteByte(sb, 0);
+        //Com.dprintln("MSG.WriteString:" + s.replace('\0', '@'));
+    }
+
+    //ok.
+    public static void WriteString(sizebuf_t sb, byte s[]) {
+        WriteString(sb, new String(s).trim());
+    }
+
+    public static void WriteCoord(sizebuf_t sb, float f) {
+        WriteShort(sb, (int) (f * 8));
+    }
+
+    public static void WritePos(sizebuf_t sb, float[] pos) {
+        assert (pos.length == 3) : "vec3_t bug";
+        WriteShort(sb, (int) (pos[0] * 8));
+        WriteShort(sb, (int) (pos[1] * 8));
+        WriteShort(sb, (int) (pos[2] * 8));
+    }
+
+    public static void WriteAngle(sizebuf_t sb, float f) {
+        WriteByte(sb, (int) (f * 256 / 360) & 255);
+    }
+
+    public static void WriteAngle16(sizebuf_t sb, float f) {
+        WriteShort(sb, Math3D.angle2Short(f));
+    }
+
+    public static void WriteDeltaUsercmd(sizebuf_t buf, usercmd_t from,
+                                         usercmd_t cmd) {
+        int bits;
+
+        //
+        // send the movement message
+        //
+        bits = 0;
+        if (cmd.angles[0] != from.angles[0])
+            bits |= CM_ANGLE1;
+        if (cmd.angles[1] != from.angles[1])
+            bits |= CM_ANGLE2;
+        if (cmd.angles[2] != from.angles[2])
+            bits |= CM_ANGLE3;
+        if (cmd.forwardmove != from.forwardmove)
+            bits |= CM_FORWARD;
+        if (cmd.sidemove != from.sidemove)
+            bits |= CM_SIDE;
+        if (cmd.upmove != from.upmove)
+            bits |= CM_UP;
+        if (cmd.buttons != from.buttons)
+            bits |= CM_BUTTONS;
+        if (cmd.impulse != from.impulse)
+            bits |= CM_IMPULSE;
+
+        WriteByte(buf, bits);
+
+        if ((bits & CM_ANGLE1) != 0)
+            WriteShort(buf, cmd.angles[0]);
+        if ((bits & CM_ANGLE2) != 0)
+            WriteShort(buf, cmd.angles[1]);
+        if ((bits & CM_ANGLE3) != 0)
+            WriteShort(buf, cmd.angles[2]);
+
+        if ((bits & CM_FORWARD) != 0)
+            WriteShort(buf, cmd.forwardmove);
+        if ((bits & CM_SIDE) != 0)
+            WriteShort(buf, cmd.sidemove);
+        if ((bits & CM_UP) != 0)
+            WriteShort(buf, cmd.upmove);
+
+        if ((bits & CM_BUTTONS) != 0)
+            WriteByte(buf, cmd.buttons);
+        if ((bits & CM_IMPULSE) != 0)
+            WriteByte(buf, cmd.impulse);
+
+        WriteByte(buf, cmd.msec);
+        WriteByte(buf, cmd.lightlevel);
+    }
+
+    //should be ok.
+    public static void WriteDir(sizebuf_t sb, float[] dir) {
+        int i, best;
+        float d, bestd;
+
+        if (dir == null) {
+            WriteByte(sb, 0);
+            return;
+        }
+
+        bestd = 0;
+        best = 0;
+        for (i = 0; i < NUMVERTEXNORMALS; i++) {
+            d = Math3D.dotProduct(dir, bytedirs[i]);
+            if (d > bestd) {
+                bestd = d;
+                best = i;
+            }
+        }
+        WriteByte(sb, best);
+    }
+
+    //should be ok.
+    public static void ReadDir(sizebuf_t sb, float[] dir) {
+        int b;
+
+        b = ReadByte(sb);
+        if (b >= NUMVERTEXNORMALS)
+            Com.Error(ERR_DROP, "MSF_ReadDir: out of range");
+        Math3D.vectorCopy(bytedirs[b], dir);
+    }
+
+    //============================================================
+
+    //
+    // reading functions
+    //
+
+    /*
+     * ================== WriteDeltaEntity
+     *
+     * Writes part of a packetentities message. Can delta from either a baseline
+     * or a previous packet_entity ==================
+     */
+    public static void WriteDeltaEntity(entity_state_t from, entity_state_t to,
+                                        sizebuf_t msg, boolean force, boolean newentity) {
+        int bits;
+
+        if (0 == to.number)
+            Com.Error(ERR_FATAL, "Unset entity number");
+        if (to.number >= MAX_EDICTS)
+            Com.Error(ERR_FATAL, "Entity number >= MAX_EDICTS");
+
+        // send an update
+        bits = 0;
+
+        if (to.number >= 256)
+            bits |= U_NUMBER16; // number8 is implicit otherwise
+
+        if (to.origin[0] != from.origin[0])
+            bits |= U_ORIGIN1;
+        if (to.origin[1] != from.origin[1])
+            bits |= U_ORIGIN2;
+        if (to.origin[2] != from.origin[2])
+            bits |= U_ORIGIN3;
+
+        if (to.angles[0] != from.angles[0])
+            bits |= U_ANGLE1;
+        if (to.angles[1] != from.angles[1])
+            bits |= U_ANGLE2;
+        if (to.angles[2] != from.angles[2])
+            bits |= U_ANGLE3;
+
+        if (to.skinnum != from.skinnum) {
+            if (to.skinnum < 256)
+                bits |= U_SKIN8;
+            else if (to.skinnum < 0x10000)
+                bits |= U_SKIN16;
+            else
+                bits |= (U_SKIN8 | U_SKIN16);
+        }
+
+        if (to.frame != from.frame) {
+            if (to.frame < 256)
+                bits |= U_FRAME8;
+            else
+                bits |= U_FRAME16;
+        }
+
+        if (to.effects != from.effects) {
+            if (to.effects < 256)
+                bits |= U_EFFECTS8;
+            else if (to.effects < 0x8000)
+                bits |= U_EFFECTS16;
+            else
+                bits |= U_EFFECTS8 | U_EFFECTS16;
+        }
+
+        if (to.renderfx != from.renderfx) {
+            if (to.renderfx < 256)
+                bits |= U_RENDERFX8;
+            else if (to.renderfx < 0x8000)
+                bits |= U_RENDERFX16;
+            else
+                bits |= U_RENDERFX8 | U_RENDERFX16;
+        }
+
+        if (to.solid != from.solid)
+            bits |= U_SOLID;
+
+        // event is not delta compressed, just 0 compressed
+        if (to.event != 0)
+            bits |= U_EVENT;
+
+        if (to.modelindex != from.modelindex)
+            bits |= U_MODEL;
+        if (to.modelindex2 != from.modelindex2)
+            bits |= U_MODEL2;
+        if (to.modelindex3 != from.modelindex3)
+            bits |= U_MODEL3;
+        if (to.modelindex4 != from.modelindex4)
+            bits |= U_MODEL4;
+
+        if (to.sound != from.sound)
+            bits |= U_SOUND;
+
+        //
+        // write the message
+        //
+        if (bits == 0 && !force)
+            return; // nothing to send!
+
+        //----------
+
+        if ((bits & 0xff000000) != 0)
+            bits |= U_MOREBITS3 | U_MOREBITS2 | U_MOREBITS1;
+        else if ((bits & 0x00ff0000) != 0)
+            bits |= U_MOREBITS2 | U_MOREBITS1;
+        else if ((bits & 0x0000ff00) != 0)
+            bits |= U_MOREBITS1;
+
+        WriteByte(msg, bits & 255);
+
+        if ((bits & 0xff000000) != 0) {
+            WriteByte(msg, (bits >>> 8) & 255);
+            WriteByte(msg, (bits >>> 16) & 255);
+            WriteByte(msg, (bits >>> 24) & 255);
+        } else if ((bits & 0x00ff0000) != 0) {
+            WriteByte(msg, (bits >>> 8) & 255);
+            WriteByte(msg, (bits >>> 16) & 255);
+        } else if ((bits & 0x0000ff00) != 0) {
+            WriteByte(msg, (bits >>> 8) & 255);
+        }
+
+        //----------
+
+        if ((bits & U_NUMBER16) != 0)
+            WriteShort(msg, to.number);
+        else
+            WriteByte(msg, to.number);
+
+        if ((bits & U_MODEL) != 0)
+            WriteByte(msg, to.modelindex);
+        if ((bits & U_MODEL2) != 0)
+            WriteByte(msg, to.modelindex2);
+        if ((bits & U_MODEL3) != 0)
+            WriteByte(msg, to.modelindex3);
+        if ((bits & U_MODEL4) != 0)
+            WriteByte(msg, to.modelindex4);
+
+        if ((bits & U_FRAME8) != 0)
+            WriteByte(msg, to.frame);
+        if ((bits & U_FRAME16) != 0)
+            WriteShort(msg, to.frame);
+
+        if ((bits & U_SKIN8) != 0 && (bits & U_SKIN16) != 0) //used for laser
+            // colors
+            WriteInt(msg, to.skinnum);
+        else if ((bits & U_SKIN8) != 0)
+            WriteByte(msg, to.skinnum);
+        else if ((bits & U_SKIN16) != 0)
+            WriteShort(msg, to.skinnum);
+
+        if ((bits & (U_EFFECTS8 | U_EFFECTS16)) == (U_EFFECTS8 | U_EFFECTS16))
+            WriteInt(msg, to.effects);
+        else if ((bits & U_EFFECTS8) != 0)
+            WriteByte(msg, to.effects);
+        else if ((bits & U_EFFECTS16) != 0)
+            WriteShort(msg, to.effects);
+
+        if ((bits & (U_RENDERFX8 | U_RENDERFX16)) == (U_RENDERFX8 | U_RENDERFX16))
+            WriteInt(msg, to.renderfx);
+        else if ((bits & U_RENDERFX8) != 0)
+            WriteByte(msg, to.renderfx);
+        else if ((bits & U_RENDERFX16) != 0)
+            WriteShort(msg, to.renderfx);
+
+        if ((bits & U_ORIGIN1) != 0)
+            WriteCoord(msg, to.origin[0]);
+        if ((bits & U_ORIGIN2) != 0)
+            WriteCoord(msg, to.origin[1]);
+        if ((bits & U_ORIGIN3) != 0)
+            WriteCoord(msg, to.origin[2]);
+
+        if ((bits & U_ANGLE1) != 0)
+            WriteAngle(msg, to.angles[0]);
+        if ((bits & U_ANGLE2) != 0)
+            WriteAngle(msg, to.angles[1]);
+        if ((bits & U_ANGLE3) != 0)
+            WriteAngle(msg, to.angles[2]);
+
+        if ((bits & U_OLDORIGIN) != 0) {
+            WriteCoord(msg, to.old_origin[0]);
+            WriteCoord(msg, to.old_origin[1]);
+            WriteCoord(msg, to.old_origin[2]);
+        }
+
+        if ((bits & U_SOUND) != 0)
+            WriteByte(msg, to.sound);
+        if ((bits & U_EVENT) != 0)
+            WriteByte(msg, to.event);
+        if ((bits & U_SOLID) != 0)
+            WriteShort(msg, to.solid);
+    }
+
+    public static void BeginReading(sizebuf_t msg) {
+        msg.readcount = 0;
+    }
+
+    // returns -1 if no more characters are available, but also [-128 , 127]
+    public static int ReadChar(sizebuf_t msg_read) {
+        int c;
+
+        if (msg_read.readcount + 1 > msg_read.cursize)
+            c = -1;
+        else
+            c = msg_read.data[msg_read.readcount];
+        msg_read.readcount++;
+        // kickangles bugfix (rst)
+        return c;
+    }
+
+    public static int ReadByte(sizebuf_t msg_read) {
+        int c;
+
+        if (msg_read.readcount + 1 > msg_read.cursize)
+            c = -1;
+        else
+            c = msg_read.data[msg_read.readcount] & 0xff;
+
+        msg_read.readcount++;
+
+        return c;
+    }
+
+    public static short ReadShort(sizebuf_t msg_read) {
+        int c;
+
+        if (msg_read.readcount + 2 > msg_read.cursize)
+            c = -1;
+        else
+            c = (short) ((msg_read.data[msg_read.readcount] & 0xff) + (msg_read.data[msg_read.readcount + 1] << 8));
+
+        msg_read.readcount += 2;
+
+        return (short) c;
+    }
+
+    public static int ReadLong(sizebuf_t msg_read) {
+        int c;
+
+        if (msg_read.readcount + 4 > msg_read.cursize) {
+            Com.Printf("buffer underrun in ReadLong!");
+            c = -1;
+        } else
+            c = (msg_read.data[msg_read.readcount] & 0xff)
+                    | ((msg_read.data[msg_read.readcount + 1] & 0xff) << 8)
+                    | ((msg_read.data[msg_read.readcount + 2] & 0xff) << 16)
+                    | ((msg_read.data[msg_read.readcount + 3] & 0xff) << 24);
+
+        msg_read.readcount += 4;
+
+        return c;
+    }
+
+    public static String ReadString(sizebuf_t msg_read) {
+
+        byte c;
+        int l = 0;
+        do {
+            c = (byte) ReadByte(msg_read);
+            if (c == -1 || c == 0)
+                break;
+
+            readbuf[l] = c;
+            l++;
+        } while (l < 2047);
+
+        // Com.dprintln("MSG.ReadString:[" + ret + "]");
+        return new String(readbuf, 0, l);
+    }
+
+    public static String ReadStringLine(sizebuf_t msg_read) {
+
+        int l;
+        byte c;
+
+        l = 0;
+        do {
+            c = (byte) ReadChar(msg_read);
+            if (c == -1 || c == 0 || c == 0x0a)
+                break;
+            readbuf[l] = c;
+            l++;
+        } while (l < 2047);
+
+        String ret = new String(readbuf, 0, l).trim();
+        Com.dprintln("MSG.ReadStringLine:[" + ret.replace('\0', '@') + "]");
+        return ret;
+    }
+
+    public static float ReadCoord(sizebuf_t msg_read) {
+        return ReadShort(msg_read) * (1.0f / 8);
+    }
+
+    public static void ReadPos(sizebuf_t msg_read, float pos[]) {
+        assert (pos.length == 3) : "vec3_t bug";
+        pos[0] = ReadShort(msg_read) * (1.0f / 8);
+        pos[1] = ReadShort(msg_read) * (1.0f / 8);
+        pos[2] = ReadShort(msg_read) * (1.0f / 8);
+    }
+
+    public static float ReadAngle(sizebuf_t msg_read) {
+        return ReadChar(msg_read) * (360.0f / 256);
+    }
+
+    public static float ReadAngle16(sizebuf_t msg_read) {
+        return Math3D.short2Angle(ReadShort(msg_read));
+    }
+
+    public static void ReadDeltaUsercmd(sizebuf_t msg_read, usercmd_t from,
+                                        usercmd_t move) {
+        int bits;
+
+        //memcpy(move, from, sizeof(* move));
+        // IMPORTANT!! copy without new
+        move.set(from);
+        bits = ReadByte(msg_read);
+
+        // read current angles
+        if ((bits & CM_ANGLE1) != 0)
+            move.angles[0] = ReadShort(msg_read);
+        if ((bits & CM_ANGLE2) != 0)
+            move.angles[1] = ReadShort(msg_read);
+        if ((bits & CM_ANGLE3) != 0)
+            move.angles[2] = ReadShort(msg_read);
+
+        // read movement
+        if ((bits & CM_FORWARD) != 0)
+            move.forwardmove = ReadShort(msg_read);
+        if ((bits & CM_SIDE) != 0)
+            move.sidemove = ReadShort(msg_read);
+        if ((bits & CM_UP) != 0)
+            move.upmove = ReadShort(msg_read);
+
+        // read buttons
+        if ((bits & CM_BUTTONS) != 0)
+            move.buttons = (byte) ReadByte(msg_read);
+
+        if ((bits & CM_IMPULSE) != 0)
+            move.impulse = (byte) ReadByte(msg_read);
+
+        // read time to run command
+        move.msec = (byte) ReadByte(msg_read);
+
+        // read the light level
+        move.lightlevel = (byte) ReadByte(msg_read);
+
+    }
+
+    public static void ReadData(sizebuf_t msg_read, byte data[], int len) {
+        for (int i = 0; i < len; i++)
+            data[i] = (byte) ReadByte(msg_read);
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/qcommon/NetadrT.java b/src/main/java/lwjake2/qcommon/NetadrT.java
new file mode 100644 (file)
index 0000000..32a1e06
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.qcommon;
+
+import lwjake2.Defines;
+import lwjake2.sys.NET;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+public class NetadrT {
+
+    public int type;
+
+    public int port;
+
+    public byte ip[];
+
+    public NetadrT() {
+        this.type = Defines.NA_LOOPBACK;
+        this.port = 0; // any
+        try {
+            // localhost / 127.0.0.1
+            this.ip = InetAddress.getByName(null).getAddress();
+        } catch (UnknownHostException ignored) {
+        }
+    }
+
+    public InetAddress getInetAddress() throws UnknownHostException {
+        switch (type) {
+            case Defines.NA_BROADCAST:
+                return InetAddress.getByName("255.255.255.255");
+            case Defines.NA_LOOPBACK:
+                // localhost / 127.0.0.1
+                return InetAddress.getByName(null);
+            case Defines.NA_IP:
+                return InetAddress.getByAddress(ip);
+            default:
+                return null;
+        }
+    }
+
+    public void set(NetadrT from) {
+        type = from.type;
+        port = from.port;
+        ip[0] = from.ip[0];
+        ip[1] = from.ip[1];
+        ip[2] = from.ip[2];
+        ip[3] = from.ip[3];
+    }
+
+    public String toString() {
+        return (type == Defines.NA_LOOPBACK) ? "loopback" : NET
+                .AdrToString(this);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/qcommon/Netchan.java b/src/main/java/lwjake2/qcommon/Netchan.java
new file mode 100644 (file)
index 0000000..6dbc055
--- /dev/null
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.qcommon;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.CvarT;
+import lwjake2.server.Server;
+import lwjake2.sys.NET;
+import lwjake2.sys.Timer;
+import lwjake2.util.Lib;
+
+/**
+ * Netchan
+ */
+public final class Netchan extends Server {
+
+    /*
+     * 
+     * packet header ------------- 31 sequence 1 does this message contains a
+     * reliable payload 31 acknowledge sequence 1 acknowledge receipt of
+     * even/odd message 16 qport
+     * 
+     * The remote connection never knows if it missed a reliable message, the
+     * local side detects that it has been dropped by seeing a sequence
+     * acknowledge higher thatn the last reliable sequence, but without the
+     * correct evon/odd bit for the reliable set.
+     * 
+     * If the sender notices that a reliable message has been dropped, it will
+     * be retransmitted. It will not be retransmitted again until a message
+     * after the retransmit has been acknowledged and the reliable still failed
+     * to get there.
+     * 
+     * if the sequence number is -1, the packet should be handled without a
+     * netcon
+     * 
+     * The reliable message can be added to at any time by doing MSG_Write*
+     * (&netchan.message, <data>).
+     * 
+     * If the message buffer is overflowed, either by a single message, or by
+     * multiple frames worth piling up while the last reliable transmit goes
+     * unacknowledged, the netchan signals a fatal error.
+     * 
+     * Reliable messages are always placed first in a packet, then the
+     * unreliable message is included if there is sufficient room.
+     * 
+     * To the receiver, there is no distinction between the reliable and
+     * unreliable parts of the message, they are just processed out as a single
+     * larger message.
+     * 
+     * Illogical packet sequence numbers cause the packet to be dropped, but do
+     * not kill the connection. This, combined with the tight window of valid
+     * reliable acknowledgement numbers provides protection against malicious
+     * address spoofing.
+     * 
+     * 
+     * The qport field is a workaround for bad address translating routers that
+     * sometimes remap the client's source port on a packet during gameplay.
+     * 
+     * If the base part of the net address matches and the qport matches, then
+     * the channel matches even if the IP port differs. The IP port should be
+     * updated to the new value before sending out any replies.
+     * 
+     * 
+     * If there is no information that needs to be transfered on a given frame,
+     * such as during the connection stage while waiting for the client to load,
+     * then a packet only needs to be delivered if there is something in the
+     * unacknowledged reliable
+     */
+
+    private static final byte send_buf[] = new byte[Defines.MAX_MSGLEN];
+    private static final sizebuf_t send = new sizebuf_t();
+    public static CvarT showpackets;
+    public static CvarT showdrop;
+    public static CvarT qport;
+    //public static NetadrT net_from = new NetadrT();
+    public static sizebuf_t net_message = new sizebuf_t();
+    public static byte net_message_buffer[] = new byte[Defines.MAX_MSGLEN];
+
+    /*
+     * =============== Netchan_Init
+     *
+     * ===============
+     */
+    //ok.
+    public static void Netchan_Init() {
+        long port;
+
+        // pick a port value that should be nice and random
+        port = Timer.getCurrentTimeMillis() & 0xffff;
+
+        showpackets = Cvar.get("showpackets", "0", 0);
+        showdrop = Cvar.get("showdrop", "0", 0);
+        qport = Cvar.get("qport", "" + port, Defines.CVAR_NOSET);
+    }
+
+    /*
+     * =============== Netchan_OutOfBand
+     * 
+     * Sends an out-of-band datagram ================
+     */
+    //ok.
+    public static void Netchan_OutOfBand(int net_socket, NetadrT adr,
+                                         int length, byte data[]) {
+
+        // write the packet header
+        SZ.Init(send, send_buf, Defines.MAX_MSGLEN);
+
+        MSG.WriteInt(send, -1); // -1 sequence means out of band
+        SZ.Write(send, data, length);
+
+        // send the datagram
+        NET.SendPacket(net_socket, send.cursize, send.data, adr);
+    }
+
+    public static void OutOfBandPrint(int net_socket, NetadrT adr, String s) {
+        Netchan_OutOfBand(net_socket, adr, s.length(), Lib.stringToBytes(s));
+    }
+
+    /*
+     * ============== Netchan_Setup
+     * 
+     * called to open a channel to a remote system ==============
+     */
+    public static void Setup(int sock, netchan_t chan, NetadrT adr, int qport) {
+        //memset (chan, 0, sizeof(*chan));
+
+        chan.clear();
+        chan.sock = sock;
+        chan.remote_address.set(adr);
+        chan.qport = qport;
+        chan.last_received = Globals.curtime;
+        chan.incoming_sequence = 0;
+        chan.outgoing_sequence = 1;
+
+        SZ.Init(chan.message, chan.message_buf, chan.message_buf.length);
+        chan.message.allowoverflow = true;
+    }
+
+    /*
+     * =============== Netchan_CanReliable
+     * 
+     * Returns true if the last reliable message has acked ================
+     */
+    public static boolean Netchan_CanReliable(netchan_t chan) {
+        return chan.reliable_length == 0;
+    }
+
+    // das ist richtig !!!
+    public static boolean Netchan_NeedReliable(netchan_t chan) {
+        boolean send_reliable;
+
+        // if the remote side dropped the last reliable message, resend it
+
+        send_reliable = chan.incoming_acknowledged > chan.last_reliable_sequence
+                && chan.incoming_reliable_acknowledged != chan.reliable_sequence;
+
+        // if the reliable transmit buffer is empty, copy the current message
+        // out
+        if (0 == chan.reliable_length && chan.message.cursize != 0) {
+            send_reliable = true;
+        }
+
+        return send_reliable;
+    }
+
+    // private static final byte send_buf[] = new byte[Defines.MAX_MSGLEN];
+    // private static final sizebuf_t send = new sizebuf_t();
+    /*
+     * =============== Netchan_Transmit
+     * 
+     * tries to send an unreliable message to a connection, and handles the
+     * transmition / retransmition of the reliable messages.
+     * 
+     * A 0 length will still generate a packet and deal with the reliable
+     * messages. ================
+     */
+    public static void Transmit(netchan_t chan, int length, byte data[]) {
+        int send_reliable;
+        int w1, w2;
+
+        // check for message overflow
+        if (chan.message.overflowed) {
+            chan.fatal_error = true;
+            Com.Printf(NET.AdrToString(chan.remote_address)
+                    + ":Outgoing message overflow\n");
+            return;
+        }
+
+        send_reliable = Netchan_NeedReliable(chan) ? 1 : 0;
+
+        if (chan.reliable_length == 0 && chan.message.cursize != 0) {
+            System.arraycopy(chan.message_buf, 0, chan.reliable_buf, 0,
+                    chan.message.cursize);
+            chan.reliable_length = chan.message.cursize;
+            chan.message.cursize = 0;
+            chan.reliable_sequence ^= 1;
+        }
+
+        // write the packet header
+        SZ.Init(send, send_buf, send_buf.length);
+
+        w1 = (chan.outgoing_sequence & ~(1 << 31)) | (send_reliable << 31);
+        w2 = (chan.incoming_sequence & ~(1 << 31))
+                | (chan.incoming_reliable_sequence << 31);
+
+        chan.outgoing_sequence++;
+        chan.last_sent = Globals.curtime;
+
+        MSG.WriteInt(send, w1);
+        MSG.WriteInt(send, w2);
+
+        // send the qport if we are a client
+        if (chan.sock == Defines.NS_CLIENT)
+            MSG.WriteShort(send, (int) qport.value);
+
+        // copy the reliable message to the packet first
+        if (send_reliable != 0) {
+            SZ.Write(send, chan.reliable_buf, chan.reliable_length);
+            chan.last_reliable_sequence = chan.outgoing_sequence;
+        }
+
+        // add the unreliable part if space is available
+        if (send.maxsize - send.cursize >= length)
+            SZ.Write(send, data, length);
+        else
+            Com.Printf("Netchan_Transmit: dumped unreliable\n");
+
+        // send the datagram
+        NET.SendPacket(chan.sock, send.cursize, send.data, chan.remote_address);
+
+        if (showpackets.value != 0) {
+            if (send_reliable != 0)
+                Com.Printf(
+                        //"send %4i : s=%i reliable=%i ack=%i rack=%i\n"
+                        "send " + send.cursize + " : s="
+                                + (chan.outgoing_sequence - 1) + " reliable="
+                                + chan.reliable_sequence + " ack="
+                                + chan.incoming_sequence + " rack="
+                                + chan.incoming_reliable_sequence + "\n");
+            else
+                Com.Printf(
+                        //"send %4i : s=%i ack=%i rack=%i\n"
+                        "send " + send.cursize + " : s="
+                                + (chan.outgoing_sequence - 1) + " ack="
+                                + chan.incoming_sequence + " rack="
+                                + chan.incoming_reliable_sequence + "\n");
+        }
+    }
+
+    /*
+     * ================= Netchan_Process
+     * 
+     * called when the current net_message is from remote_address modifies
+     * net_message so that it points to the packet payload =================
+     */
+    public static boolean Process(netchan_t chan, sizebuf_t msg) {
+        int sequence, sequence_ack;
+        int reliable_ack, reliable_message;
+
+        // get sequence numbers
+        MSG.BeginReading(msg);
+        sequence = MSG.ReadLong(msg);
+        sequence_ack = MSG.ReadLong(msg);
+
+        // read the qport if we are a server
+        if (chan.sock == Defines.NS_SERVER)
+            MSG.ReadShort(msg);
+
+        // achtung unsigned int
+        reliable_message = sequence >>> 31;
+        reliable_ack = sequence_ack >>> 31;
+
+        sequence &= ~(1 << 31);
+        sequence_ack &= ~(1 << 31);
+
+        if (showpackets.value != 0) {
+            if (reliable_message != 0)
+                Com.Printf(
+                        //"recv %4i : s=%i reliable=%i ack=%i rack=%i\n"
+                        "recv " + msg.cursize + " : s=" + sequence
+                                + " reliable="
+                                + (chan.incoming_reliable_sequence ^ 1)
+                                + " ack=" + sequence_ack + " rack="
+                                + reliable_ack + "\n");
+            else
+                Com
+                        .Printf(
+                                //"recv %4i : s=%i ack=%i rack=%i\n"
+                                "recv " + msg.cursize + " : s=" + sequence + " ack="
+                                        + sequence_ack + " rack=" + reliable_ack + "\n");
+        }
+
+        //
+        // discard stale or duplicated packets
+        //
+        if (sequence <= chan.incoming_sequence) {
+            if (showdrop.value != 0)
+                Com.Printf(NET.AdrToString(chan.remote_address)
+                        + ":Out of order packet " + sequence + " at "
+                        + chan.incoming_sequence + "\n");
+            return false;
+        }
+
+        //
+        // dropped packets don't keep the message from being used
+        //
+        chan.dropped = sequence - (chan.incoming_sequence + 1);
+        if (chan.dropped > 0) {
+            if (showdrop.value != 0)
+                Com.Printf(NET.AdrToString(chan.remote_address) + ":Dropped "
+                        + chan.dropped + " packets at " + sequence + "\n");
+        }
+
+        //
+        // if the current outgoing reliable message has been acknowledged
+        // clear the buffer to make way for the next
+        //
+        if (reliable_ack == chan.reliable_sequence)
+            chan.reliable_length = 0; // it has been received
+
+        //
+        // if this message contains a reliable message, bump
+        // incoming_reliable_sequence
+        //
+        chan.incoming_sequence = sequence;
+        chan.incoming_acknowledged = sequence_ack;
+        chan.incoming_reliable_acknowledged = reliable_ack;
+        if (reliable_message != 0) {
+            chan.incoming_reliable_sequence ^= 1;
+        }
+
+        //
+        // the message can now be read from the current message pointer
+        //
+        chan.last_received = Globals.curtime;
+
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/qcommon/PMove.java b/src/main/java/lwjake2/qcommon/PMove.java
new file mode 100644 (file)
index 0000000..15c9f8a
--- /dev/null
@@ -0,0 +1,1093 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.qcommon;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.Move;
+import lwjake2.game.csurface_t;
+import lwjake2.game.trace_t;
+import lwjake2.server.SV;
+import lwjake2.util.Math3D;
+
+public class PMove {
+
+    // all of the locals will be zeroed before each
+    // pmove, just to make damn sure we don't have
+    // any differences when running on client or server
+
+    public static final pml_t pml = new pml_t();
+    // movement parameters
+    public static final float pm_stopspeed = 100;
+    public static final float pm_maxspeed = 300;
+    public static final float pm_duckspeed = 100;
+    public static final float pm_accelerate = 10;
+    public static final float pm_wateraccelerate = 10;
+    public static final float pm_friction = 6;
+    public static final float pm_waterfriction = 1;
+    public static final float pm_waterspeed = 400;
+    // try all single bits first
+    public static final int[] jitterbits = {0, 4, 1, 2, 3, 5, 6, 7};
+    public static final int[] offset = {0, -1, 1};
+    static final float[][] planes = new float[SV.MAX_CLIP_PLANES][3];
+    public static Move pm;
+    public static float pm_airaccelerate = 0;
+
+    /**
+     * Slide off of the impacting object returns the blocked flags (1 = floor, 2 = step / wall)
+     */
+    public static void PM_ClipVelocity(float[] in, float[] normal, float[] out, float overbounce) {
+        float backoff;
+        float change;
+        int i;
+
+        backoff = Math3D.dotProduct(in, normal) * overbounce;
+
+        for (i = 0; i < 3; i++) {
+            change = normal[i] * backoff;
+            out[i] = in[i] - change;
+            if (out[i] > -Defines.MOVE_STOP_EPSILON
+                    && out[i] < Defines.MOVE_STOP_EPSILON)
+                out[i] = 0;
+        }
+    }
+
+    public static void PM_StepSlideMove_() {
+        int bumpcount, numbumps;
+        float[] dir = {0, 0, 0};
+        float d;
+        int numplanes;
+
+        float[] primal_velocity = {0, 0, 0};
+        int i, j;
+        trace_t trace;
+        float[] end = {0, 0, 0};
+        float time_left;
+
+        numbumps = 4;
+
+        Math3D.vectorCopy(pml.velocity, primal_velocity);
+        numplanes = 0;
+
+        time_left = pml.frametime;
+
+        for (bumpcount = 0; bumpcount < numbumps; bumpcount++) {
+            for (i = 0; i < 3; i++)
+                end[i] = pml.origin[i] + time_left
+                        * pml.velocity[i];
+
+            trace = pm.trace.trace(pml.origin, pm.mins,
+                    pm.maxs, end);
+
+            if (trace.allsolid) { // entity is trapped in another solid
+                pml.velocity[2] = 0; // don't build up falling damage
+                return;
+            }
+
+            if (trace.fraction > 0) { // actually covered some distance
+                Math3D.vectorCopy(trace.endpos, pml.origin);
+                numplanes = 0;
+            }
+
+            if (trace.fraction == 1)
+                break; // moved the entire distance
+
+            // save entity for contact
+            if (pm.numtouch < Defines.MAXTOUCH && trace.ent != null) {
+                pm.touchents[pm.numtouch] = trace.ent;
+                pm.numtouch++;
+            }
+
+            time_left -= time_left * trace.fraction;
+
+            // slide along this plane
+            if (numplanes >= SV.MAX_CLIP_PLANES) {
+                // this shouldn't really happen
+                Math3D.vectorCopy(Globals.vec3_origin, pml.velocity);
+                break;
+            }
+
+            Math3D.vectorCopy(trace.plane.normal, planes[numplanes]);
+            numplanes++;
+
+            // modify original_velocity so it parallels all of the clip planes
+            for (i = 0; i < numplanes; i++) {
+                PM_ClipVelocity(pml.velocity, planes[i],
+                        pml.velocity, 1.01f);
+                for (j = 0; j < numplanes; j++)
+                    if (j != i) {
+                        if (Math3D.dotProduct(pml.velocity, planes[j]) < 0)
+                            break; // not ok
+                    }
+                if (j == numplanes)
+                    break;
+            }
+
+            if (i != numplanes) {
+                // go along this plane
+            } else {
+                // go along the crease
+                if (numplanes != 2) {
+                    // Com.printf("clip velocity, numplanes == " + numplanes + "\n");
+                    Math3D.vectorCopy(Globals.vec3_origin, pml.velocity);
+                    break;
+                }
+                Math3D.crossProduct(planes[0], planes[1], dir);
+                d = Math3D.dotProduct(dir, pml.velocity);
+                Math3D.vectorScale(dir, d, pml.velocity);
+            }
+
+
+            // if velocity is against the original velocity, stop dead
+            // to avoid tiny occilations in sloping corners
+            if (Math3D.dotProduct(pml.velocity, primal_velocity) <= 0) {
+                Math3D.vectorCopy(Globals.vec3_origin, pml.velocity);
+                break;
+            }
+        }
+
+        if (pm.s.pm_time != 0) {
+            Math3D.vectorCopy(primal_velocity, pml.velocity);
+        }
+    }
+
+    /**
+     * Each intersection will try to step over the obstruction instead of
+     * sliding along it.
+     * <p>
+     * Returns a new origin, velocity, and contact entity.
+     * Does not modify any world state?
+     */
+    public static void PM_StepSlideMove() {
+        float[] start_o = {0, 0, 0}, start_v = {0, 0, 0};
+        float[] down_o = {0, 0, 0}, down_v = {0, 0, 0};
+        trace_t trace;
+        float down_dist, up_dist;
+        //     float [] delta;
+        float[] up = {0, 0, 0}, down = {0, 0, 0};
+
+        Math3D.vectorCopy(pml.origin, start_o);
+        Math3D.vectorCopy(pml.velocity, start_v);
+
+        PM_StepSlideMove_();
+
+        Math3D.vectorCopy(pml.origin, down_o);
+        Math3D.vectorCopy(pml.velocity, down_v);
+
+        Math3D.vectorCopy(start_o, up);
+        up[2] += Defines.STEPSIZE;
+
+        trace = pm.trace.trace(up, pm.mins, pm.maxs, up);
+        if (trace.allsolid)
+            return; // can't step up
+
+        // try sliding above
+        Math3D.vectorCopy(up, pml.origin);
+        Math3D.vectorCopy(start_v, pml.velocity);
+
+        PM_StepSlideMove_();
+
+        // push down the final amount
+        Math3D.vectorCopy(pml.origin, down);
+        down[2] -= Defines.STEPSIZE;
+        trace = pm.trace.trace(pml.origin, pm.mins,
+                pm.maxs, down);
+        if (!trace.allsolid) {
+            Math3D.vectorCopy(trace.endpos, pml.origin);
+        }
+
+        Math3D.vectorCopy(pml.origin, up);
+
+        // decide which one went farther
+        down_dist = (down_o[0] - start_o[0]) * (down_o[0] - start_o[0])
+                + (down_o[1] - start_o[1]) * (down_o[1] - start_o[1]);
+        up_dist = (up[0] - start_o[0]) * (up[0] - start_o[0])
+                + (up[1] - start_o[1]) * (up[1] - start_o[1]);
+
+        if (down_dist > up_dist || trace.plane.normal[2] < Defines.MIN_STEP_NORMAL) {
+            Math3D.vectorCopy(down_o, pml.origin);
+            Math3D.vectorCopy(down_v, pml.velocity);
+            return;
+        }
+        //!! Special case
+        // if we were walking along a plane, then we need to copy the Z over
+        pml.velocity[2] = down_v[2];
+    }
+
+    /**
+     * Handles both ground friction and water friction.
+     */
+    public static void PM_Friction() {
+        float vel[];
+        float speed, newspeed, control;
+        float friction;
+        float drop;
+
+        vel = pml.velocity;
+
+        speed = (float) (Math.sqrt(vel[0] * vel[0] + vel[1] * vel[1] + vel[2] * vel[2]));
+        if (speed < 1) {
+            vel[0] = 0;
+            vel[1] = 0;
+            return;
+        }
+
+        drop = 0;
+
+        // apply ground friction
+        if ((pm.groundentity != null && pml.groundsurface != null &&
+                0 == (pml.groundsurface.flags & Defines.SURF_SLICK))
+                || (pml.ladder)) {
+            friction = pm_friction;
+            control = speed < pm_stopspeed ? pm_stopspeed : speed;
+            drop += control * friction * pml.frametime;
+        }
+
+        // apply water friction
+        if (pm.waterlevel != 0 && !pml.ladder)
+            drop += speed * pm_waterfriction * pm.waterlevel
+                    * pml.frametime;
+
+        // scale the velocity
+        newspeed = speed - drop;
+        if (newspeed < 0) {
+            newspeed = 0;
+        }
+        newspeed /= speed;
+
+        vel[0] = vel[0] * newspeed;
+        vel[1] = vel[1] * newspeed;
+        vel[2] = vel[2] * newspeed;
+    }
+
+    /**
+     * Handles user intended acceleration.
+     */
+    public static void PM_Accelerate(float[] wishdir, float wishspeed,
+                                     float accel) {
+        int i;
+        float addspeed, accelspeed, currentspeed;
+
+        currentspeed = Math3D.dotProduct(pml.velocity, wishdir);
+        addspeed = wishspeed - currentspeed;
+        if (addspeed <= 0)
+            return;
+        accelspeed = accel * pml.frametime * wishspeed;
+        if (accelspeed > addspeed)
+            accelspeed = addspeed;
+
+        for (i = 0; i < 3; i++)
+            pml.velocity[i] += accelspeed * wishdir[i];
+    }
+
+    /**
+     * PM_AirAccelerate.
+     */
+
+    public static void PM_AirAccelerate(float[] wishdir, float wishspeed,
+                                        float accel) {
+        int i;
+        float addspeed, accelspeed, currentspeed, wishspd = wishspeed;
+
+        if (wishspd > 30)
+            wishspd = 30;
+        currentspeed = Math3D.dotProduct(pml.velocity, wishdir);
+        addspeed = wishspd - currentspeed;
+        if (addspeed <= 0)
+            return;
+        accelspeed = accel * wishspeed * pml.frametime;
+        if (accelspeed > addspeed)
+            accelspeed = addspeed;
+
+        for (i = 0; i < 3; i++)
+            pml.velocity[i] += accelspeed * wishdir[i];
+    }
+
+    /**
+     * PM_AddCurrents.
+     */
+    public static void PM_AddCurrents(float[] wishvel) {
+        float[] v = {0, 0, 0};
+        float s;
+
+        // account for ladders
+        if (pml.ladder && Math.abs(pml.velocity[2]) <= 200) {
+            if ((pm.viewangles[Defines.PITCH] <= -15)
+                    && (pm.cmd.forwardmove > 0))
+                wishvel[2] = 200;
+            else if ((pm.viewangles[Defines.PITCH] >= 15)
+                    && (pm.cmd.forwardmove > 0))
+                wishvel[2] = -200;
+            else if (pm.cmd.upmove > 0)
+                wishvel[2] = 200;
+            else if (pm.cmd.upmove < 0)
+                wishvel[2] = -200;
+            else
+                wishvel[2] = 0;
+
+            // limit horizontal speed when on a ladder
+            if (wishvel[0] < -25)
+                wishvel[0] = -25;
+            else if (wishvel[0] > 25)
+                wishvel[0] = 25;
+
+            if (wishvel[1] < -25)
+                wishvel[1] = -25;
+            else if (wishvel[1] > 25)
+                wishvel[1] = 25;
+        }
+
+        // add water currents
+        if ((pm.watertype & Defines.MASK_CURRENT) != 0) {
+            Math3D.vectorClear(v);
+
+            if ((pm.watertype & Defines.CONTENTS_CURRENT_0) != 0)
+                v[0] += 1;
+            if ((pm.watertype & Defines.CONTENTS_CURRENT_90) != 0)
+                v[1] += 1;
+            if ((pm.watertype & Defines.CONTENTS_CURRENT_180) != 0)
+                v[0] -= 1;
+            if ((pm.watertype & Defines.CONTENTS_CURRENT_270) != 0)
+                v[1] -= 1;
+            if ((pm.watertype & Defines.CONTENTS_CURRENT_UP) != 0)
+                v[2] += 1;
+            if ((pm.watertype & Defines.CONTENTS_CURRENT_DOWN) != 0)
+                v[2] -= 1;
+
+            s = pm_waterspeed;
+            if ((pm.waterlevel == 1) && (pm.groundentity != null))
+                s /= 2;
+
+            Math3D.vectorMA(wishvel, s, v, wishvel);
+        }
+
+        // add conveyor belt velocities
+        if (pm.groundentity != null) {
+            Math3D.vectorClear(v);
+
+            if ((pml.groundcontents & Defines.CONTENTS_CURRENT_0) != 0)
+                v[0] += 1;
+            if ((pml.groundcontents & Defines.CONTENTS_CURRENT_90) != 0)
+                v[1] += 1;
+            if ((pml.groundcontents & Defines.CONTENTS_CURRENT_180) != 0)
+                v[0] -= 1;
+            if ((pml.groundcontents & Defines.CONTENTS_CURRENT_270) != 0)
+                v[1] -= 1;
+            if ((pml.groundcontents & Defines.CONTENTS_CURRENT_UP) != 0)
+                v[2] += 1;
+            if ((pml.groundcontents & Defines.CONTENTS_CURRENT_DOWN) != 0)
+                v[2] -= 1;
+
+            Math3D.vectorMA(wishvel, 100 /* pm.groundentity.speed */, v, wishvel);
+        }
+    }
+
+    /**
+     * PM_WaterMove.
+     */
+    public static void PM_WaterMove() {
+        int i;
+        float[] wishvel = {0, 0, 0};
+        float wishspeed;
+        float[] wishdir = {0, 0, 0};
+
+
+        // user intentions
+        for (i = 0; i < 3; i++)
+            wishvel[i] = pml.forward[i] * pm.cmd.forwardmove
+                    + pml.right[i] * pm.cmd.sidemove;
+
+        if (0 == pm.cmd.forwardmove && 0 == pm.cmd.sidemove
+                && 0 == pm.cmd.upmove)
+            wishvel[2] -= 60; // drift towards bottom
+        else
+            wishvel[2] += pm.cmd.upmove;
+
+        PM_AddCurrents(wishvel);
+
+        Math3D.vectorCopy(wishvel, wishdir);
+        wishspeed = Math3D.vectorNormalize(wishdir);
+
+        if (wishspeed > pm_maxspeed) {
+            Math3D.vectorScale(wishvel, pm_maxspeed / wishspeed, wishvel);
+            wishspeed = pm_maxspeed;
+        }
+        wishspeed *= 0.5;
+
+        PM_Accelerate(wishdir, wishspeed, pm_wateraccelerate);
+
+        PM_StepSlideMove();
+    }
+
+    /**
+     * PM_AirMove.
+     */
+    public static void PM_AirMove() {
+        float[] wishvel = {0, 0, 0};
+        float fmove, smove;
+        float[] wishdir = {0, 0, 0};
+        float wishspeed;
+        float maxspeed;
+
+        fmove = pm.cmd.forwardmove;
+        smove = pm.cmd.sidemove;
+
+        wishvel[0] = pml.forward[0] * fmove + pml.right[0] * smove;
+        wishvel[1] = pml.forward[1] * fmove + pml.right[1] * smove;
+
+        wishvel[2] = 0;
+
+        PM_AddCurrents(wishvel);
+
+        Math3D.vectorCopy(wishvel, wishdir);
+        wishspeed = Math3D.vectorNormalize(wishdir);
+
+
+        // clamp to server defined max speed
+        maxspeed = (pm.s.pm_flags & Move.PMF_DUCKED) != 0 ? pm_duckspeed
+                : pm_maxspeed;
+
+        if (wishspeed > maxspeed) {
+            Math3D.vectorScale(wishvel, maxspeed / wishspeed, wishvel);
+            wishspeed = maxspeed;
+        }
+
+        if (pml.ladder) {
+            PM_Accelerate(wishdir, wishspeed, pm_accelerate);
+            if (0 == wishvel[2]) {
+                if (pml.velocity[2] > 0) {
+                    pml.velocity[2] -= pm.s.gravity * pml.frametime;
+                    if (pml.velocity[2] < 0)
+                        pml.velocity[2] = 0;
+                } else {
+                    pml.velocity[2] += pm.s.gravity * pml.frametime;
+                    if (pml.velocity[2] > 0)
+                        pml.velocity[2] = 0;
+                }
+            }
+            PM_StepSlideMove();
+        } else if (pm.groundentity != null) { // walking on ground
+            pml.velocity[2] = 0; //!!! this is before the accel
+            PM_Accelerate(wishdir, wishspeed, pm_accelerate);
+
+            // PGM -- fix for negative trigger_gravity fields
+            //         pml.velocity[2] = 0;
+            if (pm.s.gravity > 0)
+                pml.velocity[2] = 0;
+            else
+                pml.velocity[2] -= pm.s.gravity * pml.frametime;
+            // PGM
+            if (0 == pml.velocity[0] && 0 == pml.velocity[1])
+                return;
+            PM_StepSlideMove();
+        } else { // not on ground, so little effect on velocity
+            if (pm_airaccelerate != 0)
+                PM_AirAccelerate(wishdir, wishspeed, pm_accelerate);
+            else
+                PM_Accelerate(wishdir, wishspeed, 1);
+            // add gravity
+            pml.velocity[2] -= pm.s.gravity * pml.frametime;
+            PM_StepSlideMove();
+        }
+    }
+
+    /**
+     * PM_CatagorizePosition.
+     */
+    public static void PM_CatagorizePosition() {
+        float[] point = {0, 0, 0};
+        int cont;
+        trace_t trace;
+        int sample1;
+        int sample2;
+
+        // if the player hull point one unit down is solid, the player
+        // is on ground
+
+        // see if standing on something solid
+        point[0] = pml.origin[0];
+        point[1] = pml.origin[1];
+        point[2] = pml.origin[2] - 0.25f;
+        if (pml.velocity[2] > 180) //!!ZOID changed from 100 to 180 (ramp
+        // accel)
+        {
+            pm.s.pm_flags &= ~Move.PMF_ON_GROUND;
+            pm.groundentity = null;
+        } else {
+            trace = pm.trace.trace(pml.origin, pm.mins,
+                    pm.maxs, point);
+            pml.groundsurface = trace.surface;
+            pml.groundcontents = trace.contents;
+
+            if (null == trace.ent
+                    || (trace.plane.normal[2] < 0.7 && !trace.startsolid)) {
+                pm.groundentity = null;
+                pm.s.pm_flags &= ~Move.PMF_ON_GROUND;
+            } else {
+                pm.groundentity = trace.ent;
+                // hitting solid ground will end a waterjump
+                if ((pm.s.pm_flags & Move.PMF_TIME_WATERJUMP) != 0) {
+                    pm.s.pm_flags &= ~(Move.PMF_TIME_WATERJUMP
+                            | Move.PMF_TIME_LAND | Move.PMF_TIME_TELEPORT);
+                    pm.s.pm_time = 0;
+                }
+
+                if (0 == (pm.s.pm_flags & Move.PMF_ON_GROUND)) {
+
+                    // just hit the ground
+                    pm.s.pm_flags |= Move.PMF_ON_GROUND;
+                    // don't do landing time if we were just going down a slope
+                    if (pml.velocity[2] < -200) {
+                        pm.s.pm_flags |= Move.PMF_TIME_LAND;
+                        // don't allow another jump for a little while
+                        if (pml.velocity[2] < -400)
+                            pm.s.pm_time = 25;
+                        else
+                            pm.s.pm_time = 18;
+                    }
+                }
+            }
+
+            if (pm.numtouch < Defines.MAXTOUCH && trace.ent != null) {
+                pm.touchents[pm.numtouch] = trace.ent;
+                pm.numtouch++;
+            }
+        }
+
+
+        // get waterlevel, accounting for ducking
+
+        pm.waterlevel = 0;
+        pm.watertype = 0;
+
+        sample2 = (int) (pm.viewheight - pm.mins[2]);
+        sample1 = sample2 / 2;
+
+        point[2] = pml.origin[2] + pm.mins[2] + 1;
+        cont = pm.pointcontents.pointcontents(point);
+
+        if ((cont & Defines.MASK_WATER) != 0) {
+            pm.watertype = cont;
+            pm.waterlevel = 1;
+            point[2] = pml.origin[2] + pm.mins[2] + sample1;
+            cont = pm.pointcontents.pointcontents(point);
+            if ((cont & Defines.MASK_WATER) != 0) {
+                pm.waterlevel = 2;
+                point[2] = pml.origin[2] + pm.mins[2] + sample2;
+                cont = pm.pointcontents.pointcontents(point);
+                if ((cont & Defines.MASK_WATER) != 0)
+                    pm.waterlevel = 3;
+            }
+        }
+
+    }
+
+    /**
+     * PM_CheckJump.
+     */
+    public static void PM_CheckJump() {
+        if ((pm.s.pm_flags & Move.PMF_TIME_LAND) != 0) {
+            // hasn't been long enough since landing to jump again
+            return;
+        }
+
+        if (pm.cmd.upmove < 10) { // not holding jump
+            pm.s.pm_flags &= ~Move.PMF_JUMP_HELD;
+            return;
+        }
+
+        // must wait for jump to be released
+        if ((pm.s.pm_flags & Move.PMF_JUMP_HELD) != 0)
+            return;
+
+        if (pm.s.pm_type == Defines.PM_DEAD)
+            return;
+
+        if (pm.waterlevel >= 2) { // swimming, not jumping
+            pm.groundentity = null;
+
+            if (pml.velocity[2] <= -300)
+                return;
+
+            if (pm.watertype == Defines.CONTENTS_WATER)
+                pml.velocity[2] = 100;
+            else if (pm.watertype == Defines.CONTENTS_SLIME)
+                pml.velocity[2] = 80;
+            else
+                pml.velocity[2] = 50;
+            return;
+        }
+
+        if (pm.groundentity == null)
+            return; // in air, so no effect
+
+        pm.s.pm_flags |= Move.PMF_JUMP_HELD;
+
+        pm.groundentity = null;
+        pml.velocity[2] += 270;
+        if (pml.velocity[2] < 270)
+            pml.velocity[2] = 270;
+    }
+
+    /**
+     * PM_CheckSpecialMovement.
+     */
+    public static void PM_CheckSpecialMovement() {
+        float[] spot = {0, 0, 0};
+        int cont;
+        float[] flatforward = {0, 0, 0};
+        trace_t trace;
+
+        if (pm.s.pm_time != 0)
+            return;
+
+        pml.ladder = false;
+
+        // check for ladder
+        flatforward[0] = pml.forward[0];
+        flatforward[1] = pml.forward[1];
+        flatforward[2] = 0;
+        Math3D.vectorNormalize(flatforward);
+
+        Math3D.vectorMA(pml.origin, 1, flatforward, spot);
+        trace = pm.trace.trace(pml.origin, pm.mins,
+                pm.maxs, spot);
+        if ((trace.fraction < 1)
+                && (trace.contents & Defines.CONTENTS_LADDER) != 0)
+            pml.ladder = true;
+
+        // check for water jump
+        if (pm.waterlevel != 2)
+            return;
+
+        Math3D.vectorMA(pml.origin, 30, flatforward, spot);
+        spot[2] += 4;
+        cont = pm.pointcontents.pointcontents(spot);
+        if (0 == (cont & Defines.CONTENTS_SOLID))
+            return;
+
+        spot[2] += 16;
+        cont = pm.pointcontents.pointcontents(spot);
+        if (cont != 0)
+            return;
+        // jump out of water
+        Math3D.vectorScale(flatforward, 50, pml.velocity);
+        pml.velocity[2] = 350;
+
+        pm.s.pm_flags |= Move.PMF_TIME_WATERJUMP;
+        pm.s.pm_time = -1; // was 255
+    }
+
+    /**
+     * PM_FlyMove.
+     */
+    public static void PM_FlyMove(boolean doclip) {
+        float speed, drop, friction, control, newspeed;
+        float currentspeed, addspeed, accelspeed;
+        int i;
+        float[] wishvel = {0, 0, 0};
+        float fmove, smove;
+        float[] wishdir = {0, 0, 0};
+        float wishspeed;
+        float[] end = {0, 0, 0};
+        trace_t trace;
+
+        pm.viewheight = 22;
+
+        // friction
+
+        speed = Math3D.vectorLength(pml.velocity);
+        if (speed < 1) {
+            Math3D.vectorCopy(Globals.vec3_origin, pml.velocity);
+        } else {
+            drop = 0;
+
+            friction = pm_friction * 1.5f; // extra friction
+            control = speed < pm_stopspeed ? pm_stopspeed : speed;
+            drop += control * friction * pml.frametime;
+
+            // scale the velocity
+            newspeed = speed - drop;
+            if (newspeed < 0)
+                newspeed = 0;
+            newspeed /= speed;
+
+            Math3D.vectorScale(pml.velocity, newspeed, pml.velocity);
+        }
+
+        // accelerate
+        fmove = pm.cmd.forwardmove;
+        smove = pm.cmd.sidemove;
+
+        Math3D.vectorNormalize(pml.forward);
+        Math3D.vectorNormalize(pml.right);
+
+        for (i = 0; i < 3; i++)
+            wishvel[i] = pml.forward[i] * fmove + pml.right[i]
+                    * smove;
+        wishvel[2] += pm.cmd.upmove;
+
+        Math3D.vectorCopy(wishvel, wishdir);
+        wishspeed = Math3D.vectorNormalize(wishdir);
+
+        // clamp to server defined max speed
+        if (wishspeed > pm_maxspeed) {
+            Math3D.vectorScale(wishvel, pm_maxspeed / wishspeed, wishvel);
+            wishspeed = pm_maxspeed;
+        }
+
+        currentspeed = Math3D.dotProduct(pml.velocity, wishdir);
+        addspeed = wishspeed - currentspeed;
+        if (addspeed <= 0)
+            return;
+        accelspeed = pm_accelerate * pml.frametime * wishspeed;
+        if (accelspeed > addspeed)
+            accelspeed = addspeed;
+
+        for (i = 0; i < 3; i++)
+            pml.velocity[i] += accelspeed * wishdir[i];
+
+        if (doclip) {
+            for (i = 0; i < 3; i++)
+                end[i] = pml.origin[i] + pml.frametime * pml.velocity[i];
+
+            trace = pm.trace.trace(pml.origin, pm.mins, pm.maxs, end);
+
+            Math3D.vectorCopy(trace.endpos, pml.origin);
+        } else {
+            // move
+            Math3D.vectorMA(pml.origin, pml.frametime, pml.velocity, pml.origin);
+        }
+    }
+
+    /**
+     * Sets mins, maxs, and pm.viewheight.
+     */
+    public static void PM_CheckDuck() {
+        trace_t trace;
+
+        pm.mins[0] = -16;
+        pm.mins[1] = -16;
+
+        pm.maxs[0] = 16;
+        pm.maxs[1] = 16;
+
+        if (pm.s.pm_type == Defines.PM_GIB) {
+            pm.mins[2] = 0;
+            pm.maxs[2] = 16;
+            pm.viewheight = 8;
+            return;
+        }
+
+        pm.mins[2] = -24;
+
+        if (pm.s.pm_type == Defines.PM_DEAD) {
+            pm.s.pm_flags |= Move.PMF_DUCKED;
+        } else if (pm.cmd.upmove < 0 && (pm.s.pm_flags & Move.PMF_ON_GROUND) != 0) { // duck
+            pm.s.pm_flags |= Move.PMF_DUCKED;
+        } else { // stand up if possible
+            if ((pm.s.pm_flags & Move.PMF_DUCKED) != 0) {
+                // try to stand up
+                pm.maxs[2] = 32;
+                trace = pm.trace.trace(pml.origin, pm.mins, pm.maxs, pml.origin);
+                if (!trace.allsolid)
+                    pm.s.pm_flags &= ~Move.PMF_DUCKED;
+            }
+        }
+
+        if ((pm.s.pm_flags & Move.PMF_DUCKED) != 0) {
+            pm.maxs[2] = 4;
+            pm.viewheight = -2;
+        } else {
+            pm.maxs[2] = 32;
+            pm.viewheight = 22;
+        }
+    }
+
+    /**
+     * Dead bodies have extra friction.
+     */
+    public static void PM_DeadMove() {
+        float forward;
+
+        if (null == pm.groundentity)
+            return;
+
+        // extra friction
+        forward = Math3D.vectorLength(pml.velocity);
+        forward -= 20;
+        if (forward <= 0) {
+            Math3D.vectorClear(pml.velocity);
+        } else {
+            Math3D.vectorNormalize(pml.velocity);
+            Math3D.vectorScale(pml.velocity, forward, pml.velocity);
+        }
+    }
+
+    public static boolean PM_GoodPosition() {
+        trace_t trace;
+        float[] origin = {0, 0, 0}, end = {0, 0, 0};
+        int i;
+
+        if (pm.s.pm_type == Defines.PM_SPECTATOR)
+            return true;
+
+        for (i = 0; i < 3; i++)
+            origin[i] = end[i] = pm.s.origin[i] * 0.125f;
+        trace = pm.trace.trace(origin, pm.mins, pm.maxs, end);
+
+        return !trace.allsolid;
+    }
+
+    /**
+     * On exit, the origin will have a value that is pre-quantized to the 0.125
+     * precision of the network channel and in a valid position.
+     */
+
+    public static void PM_SnapPosition() {
+        int sign[] = {0, 0, 0};
+        int i, j, bits;
+        short base[] = {0, 0, 0};
+
+        // snap velocity to eigths
+        for (i = 0; i < 3; i++)
+            pm.s.velocity[i] = (short) (pml.velocity[i] * 8);
+
+        for (i = 0; i < 3; i++) {
+            if (pml.origin[i] >= 0)
+                sign[i] = 1;
+            else
+                sign[i] = -1;
+            pm.s.origin[i] = (short) (pml.origin[i] * 8);
+            if (pm.s.origin[i] * 0.125 == pml.origin[i])
+                sign[i] = 0;
+        }
+        Math3D.vectorCopy(pm.s.origin, base);
+
+        // try all combinations
+        for (j = 0; j < 8; j++) {
+            bits = jitterbits[j];
+            Math3D.vectorCopy(base, pm.s.origin);
+            for (i = 0; i < 3; i++)
+                if ((bits & (1 << i)) != 0)
+                    pm.s.origin[i] += sign[i];
+
+            if (PM_GoodPosition())
+                return;
+        }
+
+        // go back to the last position
+        Math3D.vectorCopy(pml.previous_origin, pm.s.origin);
+        // Com.DPrintf("using previous_origin\n");
+    }
+
+    /**
+     * Snaps the origin of the player move to 0.125 grid.
+     */
+    public static void PM_InitialSnapPosition() {
+        int x, y, z;
+        short base[] = {0, 0, 0};
+
+        Math3D.vectorCopy(pm.s.origin, base);
+
+        for (z = 0; z < 3; z++) {
+            pm.s.origin[2] = (short) (base[2] + offset[z]);
+            for (y = 0; y < 3; y++) {
+                pm.s.origin[1] = (short) (base[1] + offset[y]);
+                for (x = 0; x < 3; x++) {
+                    pm.s.origin[0] = (short) (base[0] + offset[x]);
+                    if (PM_GoodPosition()) {
+                        pml.origin[0] = pm.s.origin[0] * 0.125f;
+                        pml.origin[1] = pm.s.origin[1] * 0.125f;
+                        pml.origin[2] = pm.s.origin[2] * 0.125f;
+                        Math3D.vectorCopy(pm.s.origin,
+                                pml.previous_origin);
+                        return;
+                    }
+                }
+            }
+        }
+
+        Com.DPrintf("Bad InitialSnapPosition\n");
+    }
+
+    /**
+     * PM_ClampAngles.
+     */
+    public static void PM_ClampAngles() {
+        short temp;
+        int i;
+
+        if ((pm.s.pm_flags & Move.PMF_TIME_TELEPORT) != 0) {
+            pm.viewangles[Defines.YAW] = Math3D
+                    .short2Angle(pm.cmd.angles[Defines.YAW]
+                            + pm.s.delta_angles[Defines.YAW]);
+            pm.viewangles[Defines.PITCH] = 0;
+            pm.viewangles[Defines.ROLL] = 0;
+        } else {
+            // circularly clamp the angles with deltas
+            for (i = 0; i < 3; i++) {
+                temp = (short) (pm.cmd.angles[i] + pm.s.delta_angles[i]);
+                pm.viewangles[i] = Math3D.short2Angle(temp);
+            }
+
+            // don't let the player look up or down more than 90 degrees
+            if (pm.viewangles[Defines.PITCH] > 89 && pm.viewangles[Defines.PITCH] < 180)
+                pm.viewangles[Defines.PITCH] = 89;
+            else if (pm.viewangles[Defines.PITCH] < 271 && pm.viewangles[Defines.PITCH] >= 180)
+                pm.viewangles[Defines.PITCH] = 271;
+        }
+        Math3D.angleVectors(pm.viewangles, pml.forward, pml.right, pml.up);
+    }
+
+    /**
+     * Can be called by either the server or the client.
+     */
+    public static void Pmove(Move move) {
+        pm = move;
+
+        // clear results
+        pm.numtouch = 0;
+        Math3D.vectorClear(pm.viewangles);
+        pm.viewheight = 0;
+        pm.groundentity = null;
+        pm.watertype = 0;
+        pm.waterlevel = 0;
+
+        pml.groundsurface = null;
+        pml.groundcontents = 0;
+
+        // convert origin and velocity to float values
+        pml.origin[0] = pm.s.origin[0] * 0.125f;
+        pml.origin[1] = pm.s.origin[1] * 0.125f;
+        pml.origin[2] = pm.s.origin[2] * 0.125f;
+
+        pml.velocity[0] = pm.s.velocity[0] * 0.125f;
+        pml.velocity[1] = pm.s.velocity[1] * 0.125f;
+        pml.velocity[2] = pm.s.velocity[2] * 0.125f;
+
+        // save old org in case we get stuck
+        Math3D.vectorCopy(pm.s.origin, pml.previous_origin);
+
+        pml.frametime = (pm.cmd.msec & 0xFF) * 0.001f;
+
+        PM_ClampAngles();
+
+        if (pm.s.pm_type == Defines.PM_SPECTATOR) {
+            PM_FlyMove(false);
+            PM_SnapPosition();
+            return;
+        }
+
+        if (pm.s.pm_type >= Defines.PM_DEAD) {
+            pm.cmd.forwardmove = 0;
+            pm.cmd.sidemove = 0;
+            pm.cmd.upmove = 0;
+        }
+
+        if (pm.s.pm_type == Defines.PM_FREEZE)
+            return; // no movement at all
+
+        // set mins, maxs, and viewheight
+        PM_CheckDuck();
+
+        if (pm.snapinitial)
+            PM_InitialSnapPosition();
+
+        // set groundentity, watertype, and waterlevel
+        PM_CatagorizePosition();
+
+        if (pm.s.pm_type == Defines.PM_DEAD)
+            PM_DeadMove();
+
+        PM_CheckSpecialMovement();
+
+        // drop timing counter
+        if (pm.s.pm_time != 0) {
+            int msec;
+
+            // TOD o bugfix cwei
+            msec = pm.cmd.msec >>> 3;
+            if (msec == 0)
+                msec = 1;
+            if (msec >= (pm.s.pm_time & 0xFF)) {
+                pm.s.pm_flags &= ~(Move.PMF_TIME_WATERJUMP
+                        | Move.PMF_TIME_LAND | Move.PMF_TIME_TELEPORT);
+                pm.s.pm_time = 0;
+            } else
+                pm.s.pm_time = (byte) ((pm.s.pm_time & 0xFF) - msec);
+        }
+
+        if ((pm.s.pm_flags & Move.PMF_TIME_TELEPORT) != 0) {
+            // teleport pause stays exaclty in place
+        } else if ((pm.s.pm_flags & Move.PMF_TIME_WATERJUMP) != 0) {
+            // waterjump has no control, but falls
+            pml.velocity[2] -= pm.s.gravity * pml.frametime;
+            if (pml.velocity[2] < 0) {
+                // cancel as soon as we are falling down again
+                pm.s.pm_flags &= ~(Move.PMF_TIME_WATERJUMP
+                        | Move.PMF_TIME_LAND | Move.PMF_TIME_TELEPORT);
+                pm.s.pm_time = 0;
+            }
+
+            PM_StepSlideMove();
+        } else {
+            PM_CheckJump();
+
+            PM_Friction();
+
+            if (pm.waterlevel >= 2)
+                PM_WaterMove();
+            else {
+                float[] angles = {0, 0, 0};
+
+                Math3D.vectorCopy(pm.viewangles, angles);
+
+                if (angles[Defines.PITCH] > 180)
+                    angles[Defines.PITCH] = angles[Defines.PITCH] - 360;
+
+                angles[Defines.PITCH] /= 3;
+
+                Math3D.angleVectors(angles, pml.forward, pml.right, pml.up);
+
+                PM_AirMove();
+            }
+        }
+
+        // set groundentity, watertype, and waterlevel for final spot
+        PM_CatagorizePosition();
+        PM_SnapPosition();
+    }
+
+    public static class pml_t {
+        public final float[] origin = {0, 0, 0}; // full float precision
+
+        public final float[] velocity = {0, 0, 0}; // full float precision
+
+        public final float[] forward = {0, 0, 0};
+        public final float[] right = {0, 0, 0};
+        public final float[] up = {0, 0,
+                0};
+        public final float[] previous_origin = {0, 0, 0};
+        public float frametime;
+        public csurface_t groundsurface;
+        public int groundcontents;
+        public boolean ladder;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/qcommon/QCommon.java b/src/main/java/lwjake2/qcommon/QCommon.java
new file mode 100644 (file)
index 0000000..84c770a
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.qcommon;
+
+import lwjake2.Globals;
+import lwjake2.client.Client;
+import lwjake2.client.Key;
+import lwjake2.client.SCR;
+import lwjake2.game.Cmd;
+import lwjake2.server.Server;
+import lwjake2.sys.NET;
+import lwjake2.sys.Sys;
+import lwjake2.util.Vargs;
+
+/**
+ * QCommon contains some  basic routines for the game engine
+ * namely initialization, shutdown and frame generation.
+ */
+public final class QCommon extends Globals {
+
+    public static final String BUILDSTRING = "Java " + System.getProperty("java.version");
+    public static final String CPUSTRING = System.getProperty("os.arch");
+
+    /**
+     * This function initializes the different subsystems of
+     * the game engine. The setjmp/longjmp mechanism of the original
+     * was replaced with exceptions.
+     *
+     * @param args the original unmodified command line arguments
+     */
+    public static void init(String[] args) {
+        try {
+
+            // prepare enough of the subsystems to handle
+            // cvar and command buffer management
+            Com.InitArgv(args);
+
+            CommandBuffer.Init();
+
+            Cmd.Init();
+            Cvar.Init();
+
+            Key.Init();
+
+            // we need to add the early commands twice, because
+            // a basedir or cddir needs to be set before execing
+            // config files, but we want other parms to override
+            // the settings of the config files
+            CommandBuffer.addEarlyCommands(false);
+            CommandBuffer.execute();
+
+            FS.initFilesystem();
+
+            reconfigure(false);
+
+            FS.setCDDir(); // use cddir from config.cfg
+            FS.markBaseSearchPaths(); // mark the default search paths
+            FS.checkOverride();
+
+            reconfigure(true); // reload default.cfg and config.cfg
+
+            //
+            // init commands and vars
+            //
+            Cmd.AddCommand("error", Com.Error_f);
+
+            Globals.developer = Cvar.get("developer", "0", CVAR_ARCHIVE);
+            Globals.showtrace = Cvar.get("showtrace", "0", 0);
+            Globals.dedicated = Cvar.get("dedicated", "0", CVAR_NOSET);
+
+            String versionString = Com.sprintf("%4.2f %s %s %s",
+                    new Vargs(4)
+                            .add(Globals.VERSION)
+                            .add(CPUSTRING)
+                            .add(Globals.__DATE__)
+                            .add(BUILDSTRING));
+            Cvar.get("version", versionString, CVAR_SERVERINFO | CVAR_NOSET);
+
+            NET.Init();    //ok
+            Netchan.Netchan_Init();    //ok
+
+            Server.SV_Init();    //ok
+
+            Client.Init();
+
+            // add + commands from command line
+            if (!CommandBuffer.AddLateCommands()) {
+                // if the user didn't give any commands, run default action
+                if (Globals.dedicated.value == 0)
+                    CommandBuffer.AddText("d1\n");
+                else
+                    CommandBuffer.AddText("dedicated_start\n");
+
+                CommandBuffer.execute();
+            } else {
+                // the user asked for something explicit
+                // so drop the loading plaque
+                SCR.EndLoadingPlaque();
+            }
+
+            Com.Printf("====== Quake2 Initialized ======\n\n");
+
+            // save config when configuration is completed
+            Client.WriteConfiguration();
+
+        } catch (longjmpException e) {
+            Sys.Error("Error during initialization");
+        }
+    }
+
+    /**
+     * Trigger generation of a frame for the given time. The setjmp/longjmp
+     * mechanism of the original was replaced with exceptions.
+     *
+     * @param timeSinceLastFrameMs the current game time
+     */
+    public static void doFrame(int timeSinceLastFrameMs) {
+        try {
+
+            logTracing();
+
+            CommandBuffer.execute();
+
+            Com.debugContext = "SV:";
+            Server.doFrame(timeSinceLastFrameMs);
+
+            Com.debugContext = "CL:";
+            Client.doFrame(timeSinceLastFrameMs);
+
+        } catch (longjmpException e) {
+            Com.DPrintf("lonjmp exception:" + e);
+        }
+    }
+
+    private static void logTracing() {
+        if (Globals.showtrace.value != 0.0f) {
+            Com.Printf("%4i traces  %4i points\n",
+                    new Vargs(2).add(Globals.c_traces)
+                            .add(Globals.c_pointcontents));
+
+
+            Globals.c_traces = 0;
+            Globals.c_brush_traces = 0;
+            Globals.c_pointcontents = 0;
+        }
+    }
+
+    static void reconfigure(boolean clear) {
+        String dir = Cvar.get("cddir", "", CVAR_ARCHIVE).string;
+        CommandBuffer.AddText("exec default.cfg\n");
+        CommandBuffer.AddText("bind MWHEELUP weapnext\n");
+        CommandBuffer.AddText("bind MWHEELDOWN weapprev\n");
+        CommandBuffer.AddText("bind w +forward\n");
+        CommandBuffer.AddText("bind s +back\n");
+        CommandBuffer.AddText("bind a +moveleft\n");
+        CommandBuffer.AddText("bind d +moveright\n");
+        CommandBuffer.execute();
+        Cvar.set("vid_fullscreen", "0");
+        CommandBuffer.AddText("exec config.cfg\n");
+
+        CommandBuffer.addEarlyCommands(clear);
+        CommandBuffer.execute();
+        if (!("".equals(dir))) Cvar.set("cddir", dir);
+    }
+}
diff --git a/src/main/java/lwjake2/qcommon/SZ.java b/src/main/java/lwjake2/qcommon/SZ.java
new file mode 100644 (file)
index 0000000..578b99d
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.qcommon;
+
+import lwjake2.Defines;
+import lwjake2.util.Lib;
+
+/**
+ * SZ
+ */
+public final class SZ {
+
+    public static void Clear(sizebuf_t buf) {
+        buf.clear();
+    }
+
+    //===========================================================================
+
+    public static void Init(sizebuf_t buf, byte data[], int length) {
+        // TODO check this. cwei
+        buf.readcount = 0;
+
+        buf.data = data;
+        buf.maxsize = length;
+        buf.cursize = 0;
+        buf.allowoverflow = buf.overflowed = false;
+    }
+
+
+    /**
+     * Ask for the pointer using sizebuf_t.cursize (RST)
+     */
+    public static int GetSpace(sizebuf_t buf, int length) {
+        int oldsize;
+
+        if (buf.cursize + length > buf.maxsize) {
+            if (!buf.allowoverflow)
+                Com.Error(Defines.ERR_FATAL, "SZ_GetSpace: overflow without allowoverflow set");
+
+            if (length > buf.maxsize)
+                Com.Error(Defines.ERR_FATAL, "SZ_GetSpace: " + length + " is > full buffer size");
+
+            Com.Printf("SZ_GetSpace: overflow\n");
+            Clear(buf);
+            buf.overflowed = true;
+        }
+
+        oldsize = buf.cursize;
+        buf.cursize += length;
+
+        return oldsize;
+    }
+
+    public static void Write(sizebuf_t buf, byte data[], int length) {
+        //memcpy(SZ_GetSpace(buf, length), data, length);
+        System.arraycopy(data, 0, buf.data, GetSpace(buf, length), length);
+    }
+
+    public static void Write(sizebuf_t buf, byte data[], int offset, int length) {
+        System.arraycopy(data, offset, buf.data, GetSpace(buf, length), length);
+    }
+
+    public static void Write(sizebuf_t buf, byte data[]) {
+        int length = data.length;
+        //memcpy(SZ_GetSpace(buf, length), data, length);
+        System.arraycopy(data, 0, buf.data, GetSpace(buf, length), length);
+    }
+
+    //
+    public static void Print(sizebuf_t buf, String data) {
+        Com.dprintln("SZ.print():<" + data + ">");
+        int length = data.length();
+        byte str[] = Lib.stringToBytes(data);
+
+        if (buf.cursize != 0) {
+
+            if (buf.data[buf.cursize - 1] != 0) {
+                //memcpy( SZ_GetSpace(buf, len), data, len); // no trailing 0
+                System.arraycopy(str, 0, buf.data, GetSpace(buf, length + 1), length);
+            } else {
+                System.arraycopy(str, 0, buf.data, GetSpace(buf, length) - 1, length);
+                //memcpy(SZ_GetSpace(buf, len - 1) - 1, data, len); // write over trailing 0
+            }
+        } else
+            // first print.
+            System.arraycopy(str, 0, buf.data, GetSpace(buf, length), length);
+        //memcpy(SZ_GetSpace(buf, len), data, len);
+
+        buf.data[buf.cursize - 1] = 0;
+    }
+}
diff --git a/src/main/java/lwjake2/qcommon/cmd_function_t.java b/src/main/java/lwjake2/qcommon/cmd_function_t.java
new file mode 100644 (file)
index 0000000..84e863f
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.qcommon;
+
+/**
+ * cmd_function_t
+ */
+public final class cmd_function_t {
+    public cmd_function_t next = null;
+    public String name = null;
+    public xcommand_t function;
+}
diff --git a/src/main/java/lwjake2/qcommon/longjmpException.java b/src/main/java/lwjake2/qcommon/longjmpException.java
new file mode 100644 (file)
index 0000000..ac69426
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.qcommon;
+
+/**
+ * longjmpException is used to replace the setjmp/longjmp code.
+ */
+@SuppressWarnings("serial")
+public final class longjmpException extends IllegalStateException {
+
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/qcommon/lump_t.java b/src/main/java/lwjake2/qcommon/lump_t.java
new file mode 100644 (file)
index 0000000..510329c
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.qcommon;
+
+public class lump_t {
+    public final int fileofs;
+    public final int filelen;
+
+    public lump_t(int offset, int len) {
+        this.fileofs = offset;
+        this.filelen = len;
+    }
+}
diff --git a/src/main/java/lwjake2/qcommon/netchan_t.java b/src/main/java/lwjake2/qcommon/netchan_t.java
new file mode 100644 (file)
index 0000000..d9c11d5
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.qcommon;
+
+import lwjake2.Defines;
+
+public class netchan_t {
+
+    public boolean fatal_error;
+
+    // was enum {NS_CLIENT, NS_SERVER}
+    public int sock;
+
+    public int dropped; // between last packet and previous
+
+    public int last_received; // for timeouts
+
+    public int last_sent; // for retransmits
+
+    public NetadrT remote_address = new NetadrT();
+
+    public int qport; // qport value to write when transmitting
+
+    // sequencing variables
+    public int incoming_sequence;
+
+    public int incoming_acknowledged;
+
+    public int incoming_reliable_acknowledged; // single bit
+
+    public int incoming_reliable_sequence; // single bit, maintained local
+
+    public int outgoing_sequence;
+
+    public int reliable_sequence; // single bit
+
+    public int last_reliable_sequence; // sequence number of last send
+
+    //    reliable staging and holding areas
+    public sizebuf_t message = new sizebuf_t(); // writing buffer to send to
+    // server
+
+    public byte message_buf[] = new byte[Defines.MAX_MSGLEN - 16]; // leave
+    // space for
+    // header
+
+    //    message is copied to this buffer when it is first transfered
+    public int reliable_length;
+
+    public byte reliable_buf[] = new byte[Defines.MAX_MSGLEN - 16]; // unpcked
+    // reliable
+    // message
+
+    //ok.
+    public void clear() {
+        sock = dropped = last_received = last_sent = 0;
+        remote_address = new NetadrT();
+        qport = incoming_sequence = incoming_acknowledged = incoming_reliable_acknowledged = incoming_reliable_sequence = outgoing_sequence = reliable_sequence = last_reliable_sequence = 0;
+        message = new sizebuf_t();
+
+        message_buf = new byte[Defines.MAX_MSGLEN - 16];
+
+        reliable_length = 0;
+        reliable_buf = new byte[Defines.MAX_MSGLEN - 16];
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/qcommon/qfiles.java b/src/main/java/lwjake2/qcommon/qfiles.java
new file mode 100644 (file)
index 0000000..71cff45
--- /dev/null
@@ -0,0 +1,689 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.qcommon;
+
+import lwjake2.Defines;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+/**
+ * qfiles
+ *
+ * @author cwei
+ */
+public class qfiles {
+    //
+    // qfiles.h: quake file formats
+    // This file must be identical in the quake and utils directories
+    //
+
+       /*
+    ========================================================================
+       
+       The .pak files are just a linear collapse of a directory tree
+       
+       ========================================================================
+       */
+
+    public static final int IDALIASHEADER = (('2' << 24) + ('P' << 16) + ('D' << 8) + 'I');
+    public static final int ALIAS_VERSION = 8;
+
+    /*
+    ========================================================================
+
+    .MD2 triangle model file format
+
+    ========================================================================
+    */
+    public static final int MAX_TRIANGLES = 4096;
+    public static final int MAX_VERTS = 2048;
+    public static final int MAX_FRAMES = 512;
+    public static final int MAX_MD2SKINS = 32;
+    public static final int MAX_SKINNAME = 64;
+    public static final int DTRIVERTX_V0 = 0;
+    public static final int DTRIVERTX_V1 = 1;
+    public static final int DTRIVERTX_V2 = 2;
+    public static final int DTRIVERTX_LNI = 3;
+    public static final int DTRIVERTX_SIZE = 4;
+    /*
+    ========================================================================
+
+    .SP2 sprite file format
+
+    ========================================================================
+    */
+    // little-endian "IDS2"
+    public static final int IDSPRITEHEADER = (('2' << 24) + ('S' << 16) + ('D' << 8) + 'I');
+    public static final int SPRITE_VERSION = 2;
+    public static final int IDBSPHEADER = (('P' << 24) + ('S' << 16) + ('B' << 8) + 'I');
+
+    /*
+    ========================================================================
+
+    PCX files are used for as many images as possible
+
+    ========================================================================
+    */
+    public static class pcx_t {
+
+        // size of byte arrays
+        static final int PALETTE_SIZE = 48;
+        static final int FILLER_SIZE = 58;
+
+        public final byte manufacturer;
+        public final byte version;
+        public final byte encoding;
+        public final byte bits_per_pixel;
+        public final int xmin;
+        public final int ymin;
+        public final int xmax;
+        public final int ymax; // unsigned short
+        public final int hres;
+        public final int vres; // unsigned short
+        public final byte[] palette; //unsigned byte; size 48
+        public final byte reserved;
+        public final byte color_planes;
+        public final int bytes_per_line; // unsigned short
+        public final int palette_type; // unsigned short
+        public final byte[] filler; // size 58
+        public final ByteBuffer data; //unbounded data
+
+        public pcx_t(byte[] dataBytes) {
+            this(ByteBuffer.wrap(dataBytes));
+        }
+
+        public pcx_t(ByteBuffer b) {
+            // is stored as little endian
+            b.order(ByteOrder.LITTLE_ENDIAN);
+
+            // fill header
+            manufacturer = b.get();
+            version = b.get();
+            encoding = b.get();
+            bits_per_pixel = b.get();
+            xmin = b.getShort() & 0xffff;
+            ymin = b.getShort() & 0xffff;
+            xmax = b.getShort() & 0xffff;
+            ymax = b.getShort() & 0xffff;
+            hres = b.getShort() & 0xffff;
+            vres = b.getShort() & 0xffff;
+            b.get(palette = new byte[PALETTE_SIZE]);
+            reserved = b.get();
+            color_planes = b.get();
+            bytes_per_line = b.getShort() & 0xffff;
+            palette_type = b.getShort() & 0xffff;
+            b.get(filler = new byte[FILLER_SIZE]);
+
+            // fill data
+            data = b.slice();
+        }
+    }
+
+    /*
+    ========================================================================
+
+    TGA files are used for sky planes
+
+    ========================================================================
+    */
+    public static class tga_t {
+
+        // targa header
+        public final int id_length;
+        public final int colormap_type;
+        public final int image_type; // unsigned char
+        public final int colormap_index;
+        public final int colormap_length; // unsigned short
+        public final int colormap_size; // unsigned char
+        public final int x_origin;
+        public final int y_origin;
+        public final int width;
+        public final int height; // unsigned short
+        public final int pixel_size;
+        public final int attributes; // unsigned char
+
+        public final ByteBuffer data; // (un)compressed data
+
+        public tga_t(byte[] dataBytes) {
+            this(ByteBuffer.wrap(dataBytes));
+        }
+
+        public tga_t(ByteBuffer b) {
+            // is stored as little endian
+            b.order(ByteOrder.LITTLE_ENDIAN);
+
+            // fill header
+            id_length = b.get() & 0xFF;
+            colormap_type = b.get() & 0xFF;
+            image_type = b.get() & 0xFF;
+            colormap_index = b.getShort() & 0xFFFF;
+            colormap_length = b.getShort() & 0xFFFF;
+            colormap_size = b.get() & 0xFF;
+            x_origin = b.getShort() & 0xFFFF;
+            y_origin = b.getShort() & 0xFFFF;
+            width = b.getShort() & 0xFFFF;
+            height = b.getShort() & 0xFFFF;
+            pixel_size = b.get() & 0xFF;
+            attributes = b.get() & 0xFF;
+
+            // fill data
+            data = b.slice();
+        }
+
+    }
+
+    //    the glcmd format:
+    //    a positive integer starts a tristrip command, followed by that many
+    //    vertex structures.
+    //    a negative integer starts a trifan command, followed by -x vertexes
+    //    a zero indicates the end of the command list.
+    //    a vertex consists of a floating point s, a floating point t,
+    //    and an integer vertex index.
+
+    public static class dstvert_t {
+        public final short s;
+        public final short t;
+
+        public dstvert_t(ByteBuffer b) {
+            s = b.getShort();
+            t = b.getShort();
+        }
+    }
+
+    public static class dtriangle_t {
+        public final short[] index_xyz = {0, 0, 0};
+        public final short[] index_st = {0, 0, 0};
+
+        public dtriangle_t(ByteBuffer b) {
+            index_xyz[0] = b.getShort();
+            index_xyz[1] = b.getShort();
+            index_xyz[2] = b.getShort();
+
+            index_st[0] = b.getShort();
+            index_st[1] = b.getShort();
+            index_st[2] = b.getShort();
+        }
+    }
+
+    public static class daliasframe_t {
+        public final float[] scale = {0, 0, 0}; // multiply byte verts by this
+        public final float[] translate = {0, 0, 0};    // then add this
+        public final String name; // frame name from grabbing (size 16)
+        public int[] verts;    // variable sized
+
+        public daliasframe_t(ByteBuffer b) {
+            scale[0] = b.getFloat();
+            scale[1] = b.getFloat();
+            scale[2] = b.getFloat();
+            translate[0] = b.getFloat();
+            translate[1] = b.getFloat();
+            translate[2] = b.getFloat();
+            byte[] nameBuf = new byte[16];
+            b.get(nameBuf);
+            name = new String(nameBuf).trim();
+        }
+    }
+
+    public static class dmdl_t {
+        public final int ident;
+        public final int version;
+
+        public final int skinwidth;
+        public final int skinheight;
+        public final int framesize; // byte size of each frame
+
+        public final int num_skins;
+        public final int num_xyz;
+        public final int num_st; // greater than num_xyz for seams
+        public final int num_tris;
+        public final int num_glcmds; // dwords in strip/fan command list
+        public final int num_frames;
+
+        public final int ofs_skins; // each skin is a MAX_SKINNAME string
+        public final int ofs_st; // byte offset from start for stverts
+        public final int ofs_tris; // offset for dtriangles
+        public final int ofs_frames; // offset for first frame
+        public final int ofs_glcmds;
+        public final int ofs_end; // end of file
+
+        // wird extra gebraucht
+        public String[] skinNames;
+        public dstvert_t[] stVerts;
+        public dtriangle_t[] triAngles;
+        public int[] glCmds;
+        public daliasframe_t[] aliasFrames;
+        /*
+         * new members for vertex array handling
+         */
+        public FloatBuffer textureCoordBuf = null;
+        public IntBuffer vertexIndexBuf = null;
+        public int[] counts = null;
+        public IntBuffer[] indexElements = null;
+
+        public dmdl_t(ByteBuffer b) {
+            ident = b.getInt();
+            version = b.getInt();
+
+            skinwidth = b.getInt();
+            skinheight = b.getInt();
+            framesize = b.getInt(); // byte size of each frame
+
+            num_skins = b.getInt();
+            num_xyz = b.getInt();
+            num_st = b.getInt(); // greater than num_xyz for seams
+            num_tris = b.getInt();
+            num_glcmds = b.getInt(); // dwords in strip/fan command list
+            num_frames = b.getInt();
+
+            ofs_skins = b.getInt(); // each skin is a MAX_SKINNAME string
+            ofs_st = b.getInt(); // byte offset from start for stverts
+            ofs_tris = b.getInt(); // offset for dtriangles
+            ofs_frames = b.getInt(); // offset for first frame
+            ofs_glcmds = b.getInt();
+            ofs_end = b.getInt(); // end of file
+        }
+    }
+
+    public static class dsprframe_t {
+        public final int width;
+        public final int height;
+        public final int origin_x;
+        public final int origin_y; // raster coordinates inside pic
+        public final String name; // name of pcx file (MAX_SKINNAME)
+
+        public dsprframe_t(ByteBuffer b) {
+            width = b.getInt();
+            height = b.getInt();
+            origin_x = b.getInt();
+            origin_y = b.getInt();
+
+            byte[] nameBuf = new byte[MAX_SKINNAME];
+            b.get(nameBuf);
+            name = new String(nameBuf).trim();
+        }
+    }
+
+    public static class dsprite_t {
+        public final int ident;
+        public final int version;
+        public final int numframes;
+        public final dsprframe_t[] frames; // variable sized
+
+        public dsprite_t(ByteBuffer b) {
+            ident = b.getInt();
+            version = b.getInt();
+            numframes = b.getInt();
+
+            frames = new dsprframe_t[numframes];
+            for (int i = 0; i < numframes; i++) {
+                frames[i] = new dsprframe_t(b);
+            }
+        }
+    }
+
+       /*
+    ==============================================================================
+       
+         .BSP file format
+       
+       ==============================================================================
+       */
+
+    /*
+    ==============================================================================
+
+      .WAL texture file format
+
+    ==============================================================================
+    */
+    public static class miptex_t {
+
+        static final int MIPLEVELS = 4;
+        static final int NAME_SIZE = 32;
+
+        public final String name; // char name[32];
+        public final int width;
+        public final int height;
+        public final int[] offsets = new int[MIPLEVELS]; // 4 mip maps stored
+        // next frame in animation chain
+        public final String animname; //       char    animname[32];
+        public final int flags;
+        public final int contents;
+        public final int value;
+
+        public miptex_t(byte[] dataBytes) {
+            this(ByteBuffer.wrap(dataBytes));
+        }
+
+        public miptex_t(ByteBuffer b) {
+            // is stored as little endian
+            b.order(ByteOrder.LITTLE_ENDIAN);
+
+            byte[] nameBuf = new byte[NAME_SIZE];
+            // fill header
+            b.get(nameBuf);
+            name = new String(nameBuf).trim();
+            width = b.getInt();
+            height = b.getInt();
+            offsets[0] = b.getInt();
+            offsets[1] = b.getInt();
+            offsets[2] = b.getInt();
+            offsets[3] = b.getInt();
+            b.get(nameBuf);
+            animname = new String(nameBuf).trim();
+            flags = b.getInt();
+            contents = b.getInt();
+            value = b.getInt();
+        }
+
+    }
+
+    // =============================================================================
+
+    public static class dheader_t {
+
+        public final int ident;
+        public final int version;
+        public final lump_t[] lumps = new lump_t[Defines.HEADER_LUMPS];
+
+        public dheader_t(ByteBuffer bb) {
+            bb.order(ByteOrder.LITTLE_ENDIAN);
+            this.ident = bb.getInt();
+            this.version = bb.getInt();
+
+            for (int n = 0; n < Defines.HEADER_LUMPS; n++)
+                lumps[n] = new lump_t(bb.getInt(), bb.getInt());
+
+        }
+    }
+
+    public static class dmodel_t {
+
+        public static final int SIZE = 3 * 4 + 3 * 4 + 3 * 4 + 4 + 8;
+        public final float[] mins = {0, 0, 0};
+        public final float[] maxs = {0, 0, 0};
+        public final float[] origin = {0, 0, 0}; // for sounds or lights
+        public final int headnode;
+        public final int firstface;
+        public final int numfaces; // submodels just draw faces
+        // without walking the bsp tree
+
+        public dmodel_t(ByteBuffer bb) {
+            bb.order(ByteOrder.LITTLE_ENDIAN);
+
+            for (int j = 0; j < 3; j++)
+                mins[j] = bb.getFloat();
+
+            for (int j = 0; j < 3; j++)
+                maxs[j] = bb.getFloat();
+
+            for (int j = 0; j < 3; j++)
+                origin[j] = bb.getFloat();
+
+            headnode = bb.getInt();
+            firstface = bb.getInt();
+            numfaces = bb.getInt();
+        }
+    }
+
+    public static class dvertex_t {
+
+        public static final int SIZE = 3 * 4; // 3 mal 32 bit float
+
+        public final float[] point = {0, 0, 0};
+
+        public dvertex_t(ByteBuffer b) {
+            point[0] = b.getFloat();
+            point[1] = b.getFloat();
+            point[2] = b.getFloat();
+        }
+    }
+
+
+    // planes (x&~1) and (x&~1)+1 are always opposites
+    public static class dplane_t {
+
+        public static final int SIZE = 3 * 4 + 4 + 4;
+        public final float[] normal = {0, 0, 0};
+        public final float dist;
+        public final int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate
+
+        public dplane_t(ByteBuffer bb) {
+            bb.order(ByteOrder.LITTLE_ENDIAN);
+
+            normal[0] = (bb.getFloat());
+            normal[1] = (bb.getFloat());
+            normal[2] = (bb.getFloat());
+
+            dist = (bb.getFloat());
+            type = (bb.getInt());
+        }
+    }
+
+    public static class dnode_t {
+
+        public static final int SIZE = 4 + 8 + 6 + 6 + 2 + 2; // counting both sides
+        public final int planenum;
+        public final int[] children = {0, 0};
+        // negative numbers are -(leafs+1), not nodes
+        public final short[] mins = {0, 0, 0}; // for frustom culling
+        public final short[] maxs = {0, 0, 0};
+
+               /*
+        unsigned short firstface;
+               unsigned short  numfaces;       // counting both sides
+               */
+
+        public final int firstface;
+        public final int numfaces;
+
+        public dnode_t(ByteBuffer bb) {
+
+            bb.order(ByteOrder.LITTLE_ENDIAN);
+            planenum = bb.getInt();
+
+            children[0] = bb.getInt();
+            children[1] = bb.getInt();
+
+            for (int j = 0; j < 3; j++)
+                mins[j] = bb.getShort();
+
+            for (int j = 0; j < 3; j++)
+                maxs[j] = bb.getShort();
+
+            firstface = bb.getShort() & 0xffff;
+            numfaces = bb.getShort() & 0xffff;
+
+        }
+    }
+
+
+    // note that edge 0 is never used, because negative edge nums are used for
+    // counterclockwise use of the edge in a face
+
+    public static class dedge_t {
+        // unsigned short v[2];
+        int v[] = {0, 0};
+    }
+
+    public static class dface_t {
+
+        public static final int SIZE =
+                4 * Defines.SIZE_OF_SHORT
+                        + 2 * Defines.SIZE_OF_INT
+                        + Defines.MAXLIGHTMAPS;
+
+        //unsigned short       planenum;
+        public final int planenum;
+        public final short side;
+
+        public final int firstedge; // we must support > 64k edges
+        public final short numedges;
+        public final short texinfo;
+
+        // lighting info
+        public final byte[] styles = new byte[Defines.MAXLIGHTMAPS];
+        public final int lightofs; // start of [numstyles*surfsize] samples
+
+        public dface_t(ByteBuffer b) {
+            planenum = b.getShort() & 0xFFFF;
+            side = b.getShort();
+            firstedge = b.getInt();
+            numedges = b.getShort();
+            texinfo = b.getShort();
+            b.get(styles);
+            lightofs = b.getInt();
+        }
+
+    }
+
+    public static class dleaf_t {
+
+        public static final int SIZE = 4 + 8 * 2 + 4 * 2;
+        public final int contents; // OR of all brushes (not needed?)
+        public final short cluster;
+        public final short area;
+        public final short[] mins = {0, 0, 0}; // for frustum culling
+        public final short[] maxs = {0, 0, 0};
+        public final int firstleafface; // unsigned short
+        public final int numleaffaces; // unsigned short
+        public final int firstleafbrush; // unsigned short
+        public final int numleafbrushes; // unsigned short
+
+        public dleaf_t(byte[] cmod_base, int i, int j) {
+            this(ByteBuffer.wrap(cmod_base, i, j).order(ByteOrder.LITTLE_ENDIAN));
+        }
+
+        public dleaf_t(ByteBuffer bb) {
+            contents = bb.getInt();
+            cluster = bb.getShort();
+            area = bb.getShort();
+
+            mins[0] = bb.getShort();
+            mins[1] = bb.getShort();
+            mins[2] = bb.getShort();
+
+            maxs[0] = bb.getShort();
+            maxs[1] = bb.getShort();
+            maxs[2] = bb.getShort();
+
+            firstleafface = bb.getShort() & 0xffff;
+            numleaffaces = bb.getShort() & 0xffff;
+
+            firstleafbrush = bb.getShort() & 0xffff;
+            numleafbrushes = bb.getShort() & 0xffff;
+        }
+    }
+
+    public static class dbrushside_t {
+
+        public static final int SIZE = 4;
+        //unsigned short planenum;
+        final int planenum; // facing out of the leaf
+
+        final short texinfo;
+
+        public dbrushside_t(ByteBuffer bb) {
+            bb.order(ByteOrder.LITTLE_ENDIAN);
+
+            planenum = bb.getShort() & 0xffff;
+            texinfo = bb.getShort();
+        }
+    }
+
+    public static class dbrush_t {
+
+        public static final int SIZE = 3 * 4;
+        final int firstside;
+        final int numsides;
+        final int contents;
+
+        public dbrush_t(ByteBuffer bb) {
+            bb.order(ByteOrder.LITTLE_ENDIAN);
+            firstside = bb.getInt();
+            numsides = bb.getInt();
+            contents = bb.getInt();
+        }
+    }
+
+    // #define ANGLE_UP        -1
+    // #define ANGLE_DOWN      -2
+
+    // the visibility lump consists of a header with a count, then
+    // byte offsets for the PVS and PHS of each cluster, then the raw
+    // compressed bit vectors
+    // #define DVIS_PVS        0
+    // #define DVIS_PHS        1
+
+    public static class dvis_t {
+
+        public final int numclusters;
+        public int bitofs[][] = new int[8][2]; // bitofs[numclusters][2]
+
+        public dvis_t(ByteBuffer bb) {
+            numclusters = bb.getInt();
+            bitofs = new int[numclusters][2];
+
+            for (int i = 0; i < numclusters; i++) {
+                bitofs[i][0] = bb.getInt();
+                bitofs[i][1] = bb.getInt();
+            }
+        }
+    }
+
+    // each area has a list of portals that lead into other areas
+    // when portals are closed, other areas may not be visible or
+    // hearable even if the vis info says that it should be
+
+    public static class dareaportal_t {
+
+        public static final int SIZE = 8;
+        int portalnum;
+        int otherarea;
+
+        public dareaportal_t() {
+        }
+
+        public dareaportal_t(ByteBuffer bb) {
+            bb.order(ByteOrder.LITTLE_ENDIAN);
+            portalnum = bb.getInt();
+            otherarea = bb.getInt();
+        }
+    }
+
+    public static class darea_t {
+
+        public static final int SIZE = 8;
+        final int numareaportals;
+        final int firstareaportal;
+
+        public darea_t(ByteBuffer bb) {
+
+            bb.order(ByteOrder.LITTLE_ENDIAN);
+
+            numareaportals = bb.getInt();
+            firstareaportal = bb.getInt();
+
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/qcommon/sizebuf_t.java b/src/main/java/lwjake2/qcommon/sizebuf_t.java
new file mode 100644 (file)
index 0000000..eeb6322
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.qcommon;
+
+import java.util.Arrays;
+
+/**
+ * sizebuf_t
+ */
+public final class sizebuf_t {
+    public boolean allowoverflow = false;
+    public boolean overflowed = false;
+    public byte[] data = null;
+    public int maxsize = 0;
+    public int cursize = 0;
+    public int readcount = 0;
+
+    public void clear() {
+        if (data != null)
+            Arrays.fill(data, (byte) 0);
+        cursize = 0;
+        overflowed = false;
+    }
+}
diff --git a/src/main/java/lwjake2/qcommon/texinfo_t.java b/src/main/java/lwjake2/qcommon/texinfo_t.java
new file mode 100644 (file)
index 0000000..1052e8d
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.qcommon;
+
+import lwjake2.util.Lib;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class texinfo_t {
+
+    public static final int SIZE = 32 + 4 + 4 + 32 + 4;
+    //float                    vecs[2][4];             // [s/t][xyz offset]
+    public final float[][] vecs = {
+            {0, 0, 0, 0},
+            {0, 0, 0, 0}
+    };
+    public final int flags; // miptex flags + overrides
+    public final int value; // light emission, etc
+    public final int nexttexinfo; // for animations, -1 = end of chain
+    //char                     texture[32];    // texture name (textures/*.wal)
+    public String texture = "";
+
+    // works fine.
+    public texinfo_t(byte[] cmod_base, int o, int len) {
+        this(ByteBuffer.wrap(cmod_base, o, len).order(ByteOrder.LITTLE_ENDIAN));
+    }
+
+    public texinfo_t(ByteBuffer bb) {
+
+        byte str[] = new byte[32];
+
+        vecs[0] = new float[]{bb.getFloat(), bb.getFloat(), bb.getFloat(), bb.getFloat()};
+        vecs[1] = new float[]{bb.getFloat(), bb.getFloat(), bb.getFloat(), bb.getFloat()};
+
+        flags = bb.getInt();
+        value = bb.getInt();
+
+        bb.get(str);
+        texture = new String(str, 0, Lib.strlen(str));
+        nexttexinfo = bb.getInt();
+    }
+}
diff --git a/src/main/java/lwjake2/qcommon/xcommand_t.java b/src/main/java/lwjake2/qcommon/xcommand_t.java
new file mode 100644 (file)
index 0000000..7850141
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.qcommon;
+
+/**
+ * xcommand_t
+ */
+public abstract class xcommand_t {
+
+    abstract public void execute();
+}
diff --git a/src/main/java/lwjake2/render/DummyRenderer.java b/src/main/java/lwjake2/render/DummyRenderer.java
new file mode 100644 (file)
index 0000000..b00bc2b
--- /dev/null
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render;
+
+import lwjake2.client.refdef_t;
+import lwjake2.client.refexport_t;
+import lwjake2.qcommon.xcommand_t;
+import lwjake2.sys.KBD;
+
+import java.awt.*;
+
+/**
+ * DummyRenderer
+ *
+ * @author cwei
+ */
+public class DummyRenderer implements refexport_t {
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#Init(int, int)
+     */
+    public boolean Init(int vid_xpos, int vid_ypos) {
+        return false;
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#Shutdown()
+     */
+    public void Shutdown() {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#BeginRegistration(java.lang.String)
+     */
+    public void BeginRegistration(String map) {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#RegisterModel(java.lang.String)
+     */
+    public Model RegisterModel(String name) {
+        return null;
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#RegisterSkin(java.lang.String)
+     */
+    public Image RegisterSkin(String name) {
+        return null;
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#RegisterPic(java.lang.String)
+     */
+    public Image RegisterPic(String name) {
+        return null;
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#SetSky(java.lang.String, float, float[])
+     */
+    public void SetSky(String name, float rotate, float[] axis) {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#EndRegistration()
+     */
+    public void EndRegistration() {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#RenderFrame(jake2.client.refdef_t)
+     */
+    public void RenderFrame(refdef_t fd) {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#DrawGetPicSize(java.awt.Dimension, java.lang.String)
+     */
+    public void DrawGetPicSize(Dimension dim, String name) {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#DrawPic(int, int, java.lang.String)
+     */
+    public void DrawPic(int x, int y, String name) {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#DrawStretchPic(int, int, int, int, java.lang.String)
+     */
+    public void DrawStretchPic(int x, int y, int w, int h, String name) {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#DrawChar(int, int, int)
+     */
+    public void DrawChar(int x, int y, int num) {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#DrawTileClear(int, int, int, int, java.lang.String)
+     */
+    public void DrawTileClear(int x, int y, int w, int h, String name) {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#DrawFill(int, int, int, int, int)
+     */
+    public void DrawFill(int x, int y, int w, int h, int c) {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#DrawFadeScreen()
+     */
+    public void DrawFadeScreen() {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#DrawStretchRaw(int, int, int, int, int, int, byte[])
+     */
+    public void DrawStretchRaw(int x, int y, int w, int h, int cols, int rows, byte[] data) {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#CinematicSetPalette(byte[])
+     */
+    public void CinematicSetPalette(byte[] palette) {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#BeginFrame(float)
+     */
+    public void BeginFrame(float camera_separation) {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#EndFrame()
+     */
+    public void EndFrame() {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#AppActivate(boolean)
+     */
+    public void AppActivate(boolean activate) {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#updateScreen(jake2.qcommon.xcommand_t)
+     */
+    public void updateScreen(xcommand_t callback) {
+        callback.execute();
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#apiVersion()
+     */
+    public int apiVersion() {
+        return 0;
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#getModeList()
+     */
+    public DisplayMode[] getModeList() {
+        return null;
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.client.refexport_t#getKeyboardHandler()
+     */
+    public KBD getKeyboardHandler() {
+        return null;
+    }
+
+}
diff --git a/src/main/java/lwjake2/render/Image.java b/src/main/java/lwjake2/render/Image.java
new file mode 100644 (file)
index 0000000..1232dc6
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render;
+
+import lwjake2.Defines;
+
+public class Image {
+
+    public static final int MAX_NAME_SIZE = Defines.MAX_QPATH;
+    // used to get the pos in array
+    // added by cwei
+    private final int id;
+    // quake 2 variables
+    public String name = ""; // game path, including extension
+    // enum imagetype_t
+    public int type;
+    public int width, height; // source image
+    public int upload_width, upload_height; // after power of two and picmip
+    public int registration_sequence; // 0 = free
+    public msurface_t texturechain; // for sort-by-texture world drawing
+    public int texnum; // gl texture binding
+    public float sl, tl, sh, th; // 0,0 - 1,1 unless part of the scrap
+    public boolean scrap;
+    public boolean has_alpha;
+    public boolean paletted;
+
+    public Image(int id) {
+        this.id = id;
+    }
+
+    public void clear() {
+        // don't clear the id
+        // wichtig !!!
+        name = "";
+        type = 0;
+        width = height = 0;
+        upload_width = upload_height = 0;
+        registration_sequence = 0; // 0 = free
+        texturechain = null;
+        texnum = 0; // gl texture binding
+        sl = tl = sh = th = 0;
+        scrap = false;
+        has_alpha = false;
+        paletted = false;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public String toString() {
+        return name + ":" + texnum;
+    }
+}
diff --git a/src/main/java/lwjake2/render/LWJGLRenderer.java b/src/main/java/lwjake2/render/LWJGLRenderer.java
new file mode 100644 (file)
index 0000000..44b3617
--- /dev/null
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render;
+
+import lwjake2.Defines;
+import lwjake2.client.VideoDriver;
+import lwjake2.client.refdef_t;
+import lwjake2.client.refexport_t;
+import lwjake2.render.lwjgl.Misc;
+import lwjake2.sys.KBD;
+import lwjake2.sys.LWJGLKBD;
+
+import java.awt.*;
+
+/**
+ * LWJGLRenderer
+ *
+ * @author dsanders/cwei
+ */
+final class LWJGLRenderer extends Misc implements refexport_t, Ref {
+
+    public static final String DRIVER_NAME = "lwjgl";
+
+    static {
+        Renderer.register(new LWJGLRenderer());
+    }
+
+    private final LWJGLKBD kbd = new LWJGLKBD();
+
+    private LWJGLRenderer() {
+    }
+
+    // ============================================================================
+    // public interface for Renderer implementations
+    //
+    // refexport_t (ref.h)
+    // ============================================================================
+
+    /**
+     * @see jake2.client.refexport_t#Init()
+     */
+    public boolean Init(int vid_xpos, int vid_ypos) {
+
+        // pre init
+        if (!R_Init(vid_xpos, vid_ypos)) return false;
+        // post init
+        boolean ok = R_Init2();
+        if (!ok) {
+            VideoDriver.Printf(Defines.PRINT_ALL, "Missing multi-texturing for LWJGL renderer\n");
+        }
+        return ok;
+    }
+
+    /**
+     * @see jake2.client.refexport_t#Shutdown()
+     */
+    public void Shutdown() {
+        R_Shutdown();
+    }
+
+    /**
+     * @see jake2.client.refexport_t#BeginRegistration(java.lang.String)
+     */
+    public final void BeginRegistration(String map) {
+        R_BeginRegistration(map);
+    }
+
+    /**
+     * @see jake2.client.refexport_t#RegisterModel(java.lang.String)
+     */
+    public final Model RegisterModel(String name) {
+        return R_RegisterModel(name);
+    }
+
+    /**
+     * @see jake2.client.refexport_t#RegisterSkin(java.lang.String)
+     */
+    public final Image RegisterSkin(String name) {
+        return R_RegisterSkin(name);
+    }
+
+    /**
+     * @see jake2.client.refexport_t#RegisterPic(java.lang.String)
+     */
+    public final Image RegisterPic(String name) {
+        return Draw_FindPic(name);
+    }
+
+    /**
+     * @see jake2.client.refexport_t#SetSky(java.lang.String, float, float[])
+     */
+    public final void SetSky(String name, float rotate, float[] axis) {
+        R_SetSky(name, rotate, axis);
+    }
+
+    /**
+     * @see jake2.client.refexport_t#EndRegistration()
+     */
+    public final void EndRegistration() {
+        R_EndRegistration();
+    }
+
+    /**
+     * @see jake2.client.refexport_t#RenderFrame(jake2.client.refdef_t)
+     */
+    public final void RenderFrame(refdef_t fd) {
+        R_RenderFrame(fd);
+    }
+
+    /**
+     * @see jake2.client.refexport_t#DrawGetPicSize(java.awt.Dimension, java.lang.String)
+     */
+    public final void DrawGetPicSize(Dimension dim, String name) {
+        Draw_GetPicSize(dim, name);
+    }
+
+    /**
+     * @see jake2.client.refexport_t#DrawPic(int, int, java.lang.String)
+     */
+    public final void DrawPic(int x, int y, String name) {
+        Draw_Pic(x, y, name);
+    }
+
+    /**
+     * @see jake2.client.refexport_t#DrawStretchPic(int, int, int, int, java.lang.String)
+     */
+    public final void DrawStretchPic(int x, int y, int w, int h, String name) {
+        Draw_StretchPic(x, y, w, h, name);
+    }
+
+    /**
+     * @see jake2.client.refexport_t#DrawChar(int, int, int)
+     */
+    public final void DrawChar(int x, int y, int num) {
+        Draw_Char(x, y, num);
+    }
+
+    /**
+     * @see jake2.client.refexport_t#DrawTileClear(int, int, int, int, java.lang.String)
+     */
+    public final void DrawTileClear(int x, int y, int w, int h, String name) {
+        Draw_TileClear(x, y, w, h, name);
+    }
+
+    /**
+     * @see jake2.client.refexport_t#DrawFill(int, int, int, int, int)
+     */
+    public final void DrawFill(int x, int y, int w, int h, int c) {
+        Draw_Fill(x, y, w, h, c);
+    }
+
+    /**
+     * @see jake2.client.refexport_t#DrawFadeScreen()
+     */
+    public final void DrawFadeScreen() {
+        Draw_FadeScreen();
+    }
+
+    /**
+     * @see jake2.client.refexport_t#DrawStretchRaw(int, int, int, int, int, int, byte[])
+     */
+    public final void DrawStretchRaw(int x, int y, int w, int h, int cols, int rows, byte[] data) {
+        Draw_StretchRaw(x, y, w, h, cols, rows, data);
+    }
+
+    /**
+     * @see jake2.client.refexport_t#CinematicSetPalette(byte[])
+     */
+    public final void CinematicSetPalette(byte[] palette) {
+        R_SetPalette(palette);
+    }
+
+    /**
+     * @see jake2.client.refexport_t#BeginFrame(float)
+     */
+    public final void BeginFrame(float camera_separation) {
+        R_BeginFrame(camera_separation);
+    }
+
+    /**
+     * @see jake2.client.refexport_t#EndFrame()
+     */
+    public final void EndFrame() {
+        GLimp_EndFrame();
+    }
+
+    /**
+     * @see jake2.client.refexport_t#AppActivate(boolean)
+     */
+    public final void AppActivate(boolean activate) {
+        GLimp_AppActivate(activate);
+    }
+
+    public final int apiVersion() {
+        return Defines.API_VERSION;
+    }
+
+    // ============================================================================
+    // Ref interface
+    // ============================================================================
+
+    public final String getName() {
+        return DRIVER_NAME;
+    }
+
+    public final String toString() {
+        return DRIVER_NAME;
+    }
+
+    public final refexport_t GetRefAPI() {
+        return this;
+    }
+
+    public final KBD getKeyboardHandler() {
+        return kbd;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/render/Model.java b/src/main/java/lwjake2/render/Model.java
new file mode 100644 (file)
index 0000000..e409339
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render;
+
+import lwjake2.Defines;
+import lwjake2.game.cplane_t;
+import lwjake2.qcommon.qfiles;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+
+import java.util.Arrays;
+
+public class Model implements Cloneable {
+
+    // for alias models and skins
+    // was image_t *skins[]; (array of pointers)
+    public final Image[] skins = new Image[Defines.MAX_MD2SKINS];
+    public String name = "";
+    public int registration_sequence;
+    // was enum modtype_t
+    public int type;
+    public int numframes;
+    public int flags;
+    //
+    // volume occupied by the model graphics
+    //
+    public float[] mins = {0, 0, 0}, maxs = {0, 0, 0};
+    public float radius;
+    //
+    // solid volume for clipping
+    //
+    public boolean clipbox;
+    public float clipmins[] = {0, 0, 0}, clipmaxs[] = {0, 0, 0};
+    //
+    // brush model
+    //
+    public int firstmodelsurface, nummodelsurfaces;
+    public int lightmap; // only for submodels
+    public int numsubmodels;
+    public mmodel_t submodels[];
+    public int numplanes;
+    public cplane_t planes[];
+    public int numleafs; // number of visible leafs, not counting 0
+    public mleaf_t leafs[];
+    public int numvertexes;
+    public mvertex_t vertexes[];
+    public int numedges;
+    public medge_t edges[];
+    public int numnodes;
+    public int firstnode;
+    public mnode_t nodes[];
+    public int numtexinfo;
+    public mtexinfo_t texinfo[];
+    public int numsurfaces;
+    public msurface_t surfaces[];
+    public int numsurfedges;
+    public int surfedges[];
+    public int nummarksurfaces;
+    public msurface_t marksurfaces[];
+    public qfiles.dvis_t vis;
+    public byte lightdata[];
+    public int extradatasize;
+
+    // or whatever
+    public Object extradata;
+
+    public void clear() {
+        name = "";
+        registration_sequence = 0;
+
+        // was enum modtype_t
+        type = 0;
+        numframes = 0;
+        flags = 0;
+
+        //
+        // volume occupied by the model graphics
+        //
+        Math3D.vectorClear(mins);
+        Math3D.vectorClear(maxs);
+        radius = 0;
+
+        //
+        // solid volume for clipping
+        //
+        clipbox = false;
+        Math3D.vectorClear(clipmins);
+        Math3D.vectorClear(clipmaxs);
+
+        //
+        // brush model
+        //
+        firstmodelsurface = nummodelsurfaces = 0;
+        lightmap = 0; // only for submodels
+
+        numsubmodels = 0;
+        submodels = null;
+
+        numplanes = 0;
+        planes = null;
+
+        numleafs = 0; // number of visible leafs, not counting 0
+        leafs = null;
+
+        numvertexes = 0;
+        vertexes = null;
+
+        numedges = 0;
+        edges = null;
+
+        numnodes = 0;
+        firstnode = 0;
+        nodes = null;
+
+        numtexinfo = 0;
+        texinfo = null;
+
+        numsurfaces = 0;
+        surfaces = null;
+
+        numsurfedges = 0;
+        surfedges = null;
+
+        nummarksurfaces = 0;
+        marksurfaces = null;
+
+        vis = null;
+
+        lightdata = null;
+
+        // for alias models and skins
+        // was image_t *skins[]; (array of pointers)
+        Arrays.fill(skins, null);
+
+        extradatasize = 0;
+        // or whatever
+        extradata = null;
+    }
+
+    // TODO replace with set(model_t from)
+    public Model copy() {
+        Model theClone = null;
+        try {
+            theClone = (Model) super.clone();
+            theClone.mins = Lib.clone(this.mins);
+            theClone.maxs = Lib.clone(this.maxs);
+            theClone.clipmins = Lib.clone(this.clipmins);
+            theClone.clipmaxs = Lib.clone(this.clipmaxs);
+
+        } catch (CloneNotSupportedException ignored) {
+        }
+        return theClone;
+    }
+}
diff --git a/src/main/java/lwjake2/render/Ref.java b/src/main/java/lwjake2/render/Ref.java
new file mode 100644 (file)
index 0000000..4c58289
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render;
+
+import lwjake2.client.refexport_t;
+
+/**
+ * Ref
+ *
+ * @author cwei
+ */
+public interface Ref {
+
+    // ============================================================================
+    // extensions (cwei)
+    // ============================================================================
+    refexport_t GetRefAPI();
+
+    String getName();
+}
diff --git a/src/main/java/lwjake2/render/Renderer.java b/src/main/java/lwjake2/render/Renderer.java
new file mode 100644 (file)
index 0000000..e3448ec
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render;
+
+import lwjake2.client.refexport_t;
+
+import java.util.Vector;
+
+/**
+ * Renderer
+ *
+ * @author cwei
+ */
+public class Renderer {
+
+    static final Vector<Ref> drivers = new Vector<>(1);
+
+    static {
+        try {
+            try {
+                Class.forName("org.lwjgl.opengl.GL11");
+                Class.forName("lwjake2.render.LWJGLRenderer");
+            } catch (ClassNotFoundException e) {
+                // ignore the lwjgl driver if runtime not in classpath
+            }
+        } catch (Throwable e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static void register(Ref impl) {
+        if (impl == null) {
+            throw new IllegalArgumentException("Ref implementation can't be null");
+        }
+        if (!drivers.contains(impl)) {
+            drivers.add(impl);
+        }
+    }
+
+    /**
+     * Factory method to get the Renderer implementation.
+     *
+     * @return refexport_t (Renderer singleton)
+     */
+    public static refexport_t getDriver(String driverName) {
+        // find a driver
+        Ref driver = null;
+        int count = drivers.size();
+        for (Ref driver1 : drivers) {
+            driver = driver1;
+            if (driver.getName().equals(driverName)) {
+                return driver.GetRefAPI();
+            }
+        }
+        // null if driver not found
+        return null;
+    }
+
+    public static String getDefaultName() {
+        return (drivers.isEmpty()) ? null : (drivers.firstElement()).getName();
+    }
+
+    public static String getPreferedName() {
+        return (drivers.isEmpty()) ? null : (drivers.lastElement()).getName();
+    }
+
+    public static String[] getDriverNames() {
+        if (drivers.isEmpty()) return null;
+        int count = drivers.size();
+        String[] names = new String[count];
+        for (int i = 0; i < count; i++) {
+            names[i] = (drivers.get(i)).getName();
+        }
+        return names;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/render/glconfig_t.java b/src/main/java/lwjake2/render/glconfig_t.java
new file mode 100644 (file)
index 0000000..26e8710
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render;
+
+public class glconfig_t {
+
+    public int renderer;
+    public String renderer_string;
+    public String vendor_string;
+    public String version_string;
+    public String extensions_string;
+
+    public boolean allow_cds;
+
+    private float version = 1.1f;
+
+    public void parseOpenGLVersion() {
+        version = Float.parseFloat(version_string.substring(0, 3));
+    }
+
+    public float getOpenGLVersion() {
+        return version;
+    }
+}
diff --git a/src/main/java/lwjake2/render/glpoly_t.java b/src/main/java/lwjake2/render/glpoly_t.java
new file mode 100644 (file)
index 0000000..3e2614b
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render;
+
+import lwjake2.util.Lib;
+
+public abstract class glpoly_t {
+    public final static int STRIDE = 7;
+    public final static int BYTE_STRIDE = 7 * Lib.SIZEOF_FLOAT;
+    public final static int MAX_VERTICES = 64;
+
+    public glpoly_t next;
+    public glpoly_t chain;
+    public int numverts;
+
+    // the array position (glDrawArrays)
+    public int pos = 0;
+
+    public abstract float x(int index);
+
+    public abstract void x(int index, float value);
+
+    public abstract float y(int index);
+
+    public abstract void y(int index, float value);
+
+    public abstract float z(int index);
+
+    public abstract void z(int index, float value);
+
+    public abstract float s1(int index);
+
+    public abstract void s1(int index, float value);
+
+    public abstract float t1(int index);
+
+    public abstract void t1(int index, float value);
+
+    public abstract float s2(int index);
+
+    public abstract void s2(int index, float value);
+
+    public abstract float t2(int index);
+
+    public abstract void t2(int index, float value);
+
+    public abstract void beginScrolling(float s1);
+
+    public abstract void endScrolling();
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/render/glstate_t.java b/src/main/java/lwjake2/render/glstate_t.java
new file mode 100644 (file)
index 0000000..ee6ccc7
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render;
+
+public class glstate_t {
+    public final int[] currenttextures = {0, 0};
+    public float inverse_intensity;
+    public boolean fullscreen;
+    public int prev_mode;
+    public byte d_16to8table[];
+    public int lightmap_textures;
+    public int currenttmu;
+
+    public float camera_separation;
+    public boolean stereo_enabled;
+
+    public byte originalRedGammaTable[] = new byte[256];
+    public byte originalGreenGammaTable[] = new byte[256];
+    public byte originalBlueGammaTable[] = new byte[256];
+
+}
diff --git a/src/main/java/lwjake2/render/lwjgl/Anorms.java b/src/main/java/lwjake2/render/lwjgl/Anorms.java
new file mode 100644 (file)
index 0000000..8f8329d
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render.lwjgl;
+
+/**
+ * Anorms
+ *
+ * @author cwei
+ */
+interface Anorms {
+
+    float[][] VERTEXNORMALS = {
+            {-0.525731f, 0.000000f, 0.850651f},
+            {-0.442863f, 0.238856f, 0.864188f},
+            {-0.295242f, 0.000000f, 0.955423f},
+            {-0.309017f, 0.500000f, 0.809017f},
+            {-0.162460f, 0.262866f, 0.951056f},
+            {0.000000f, 0.000000f, 1.000000f},
+            {0.000000f, 0.850651f, 0.525731f},
+            {-0.147621f, 0.716567f, 0.681718f},
+            {0.147621f, 0.716567f, 0.681718f},
+            {0.000000f, 0.525731f, 0.850651f},
+            {0.309017f, 0.500000f, 0.809017f},
+            {0.525731f, 0.000000f, 0.850651f},
+            {0.295242f, 0.000000f, 0.955423f},
+            {0.442863f, 0.238856f, 0.864188f},
+            {0.162460f, 0.262866f, 0.951056f},
+            {-0.681718f, 0.147621f, 0.716567f},
+            {-0.809017f, 0.309017f, 0.500000f},
+            {-0.587785f, 0.425325f, 0.688191f},
+            {-0.850651f, 0.525731f, 0.000000f},
+            {-0.864188f, 0.442863f, 0.238856f},
+            {-0.716567f, 0.681718f, 0.147621f},
+            {-0.688191f, 0.587785f, 0.425325f},
+            {-0.500000f, 0.809017f, 0.309017f},
+            {-0.238856f, 0.864188f, 0.442863f},
+            {-0.425325f, 0.688191f, 0.587785f},
+            {-0.716567f, 0.681718f, -0.147621f},
+            {-0.500000f, 0.809017f, -0.309017f},
+            {-0.525731f, 0.850651f, 0.000000f},
+            {0.000000f, 0.850651f, -0.525731f},
+            {-0.238856f, 0.864188f, -0.442863f},
+            {0.000000f, 0.955423f, -0.295242f},
+            {-0.262866f, 0.951056f, -0.162460f},
+            {0.000000f, 1.000000f, 0.000000f},
+            {0.000000f, 0.955423f, 0.295242f},
+            {-0.262866f, 0.951056f, 0.162460f},
+            {0.238856f, 0.864188f, 0.442863f},
+            {0.262866f, 0.951056f, 0.162460f},
+            {0.500000f, 0.809017f, 0.309017f},
+            {0.238856f, 0.864188f, -0.442863f},
+            {0.262866f, 0.951056f, -0.162460f},
+            {0.500000f, 0.809017f, -0.309017f},
+            {0.850651f, 0.525731f, 0.000000f},
+            {0.716567f, 0.681718f, 0.147621f},
+            {0.716567f, 0.681718f, -0.147621f},
+            {0.525731f, 0.850651f, 0.000000f},
+            {0.425325f, 0.688191f, 0.587785f},
+            {0.864188f, 0.442863f, 0.238856f},
+            {0.688191f, 0.587785f, 0.425325f},
+            {0.809017f, 0.309017f, 0.500000f},
+            {0.681718f, 0.147621f, 0.716567f},
+            {0.587785f, 0.425325f, 0.688191f},
+            {0.955423f, 0.295242f, 0.000000f},
+            {1.000000f, 0.000000f, 0.000000f},
+            {0.951056f, 0.162460f, 0.262866f},
+            {0.850651f, -0.525731f, 0.000000f},
+            {0.955423f, -0.295242f, 0.000000f},
+            {0.864188f, -0.442863f, 0.238856f},
+            {0.951056f, -0.162460f, 0.262866f},
+            {0.809017f, -0.309017f, 0.500000f},
+            {0.681718f, -0.147621f, 0.716567f},
+            {0.850651f, 0.000000f, 0.525731f},
+            {0.864188f, 0.442863f, -0.238856f},
+            {0.809017f, 0.309017f, -0.500000f},
+            {0.951056f, 0.162460f, -0.262866f},
+            {0.525731f, 0.000000f, -0.850651f},
+            {0.681718f, 0.147621f, -0.716567f},
+            {0.681718f, -0.147621f, -0.716567f},
+            {0.850651f, 0.000000f, -0.525731f},
+            {0.809017f, -0.309017f, -0.500000f},
+            {0.864188f, -0.442863f, -0.238856f},
+            {0.951056f, -0.162460f, -0.262866f},
+            {0.147621f, 0.716567f, -0.681718f},
+            {0.309017f, 0.500000f, -0.809017f},
+            {0.425325f, 0.688191f, -0.587785f},
+            {0.442863f, 0.238856f, -0.864188f},
+            {0.587785f, 0.425325f, -0.688191f},
+            {0.688191f, 0.587785f, -0.425325f},
+            {-0.147621f, 0.716567f, -0.681718f},
+            {-0.309017f, 0.500000f, -0.809017f},
+            {0.000000f, 0.525731f, -0.850651f},
+            {-0.525731f, 0.000000f, -0.850651f},
+            {-0.442863f, 0.238856f, -0.864188f},
+            {-0.295242f, 0.000000f, -0.955423f},
+            {-0.162460f, 0.262866f, -0.951056f},
+            {0.000000f, 0.000000f, -1.000000f},
+            {0.295242f, 0.000000f, -0.955423f},
+            {0.162460f, 0.262866f, -0.951056f},
+            {-0.442863f, -0.238856f, -0.864188f},
+            {-0.309017f, -0.500000f, -0.809017f},
+            {-0.162460f, -0.262866f, -0.951056f},
+            {0.000000f, -0.850651f, -0.525731f},
+            {-0.147621f, -0.716567f, -0.681718f},
+            {0.147621f, -0.716567f, -0.681718f},
+            {0.000000f, -0.525731f, -0.850651f},
+            {0.309017f, -0.500000f, -0.809017f},
+            {0.442863f, -0.238856f, -0.864188f},
+            {0.162460f, -0.262866f, -0.951056f},
+            {0.238856f, -0.864188f, -0.442863f},
+            {0.500000f, -0.809017f, -0.309017f},
+            {0.425325f, -0.688191f, -0.587785f},
+            {0.716567f, -0.681718f, -0.147621f},
+            {0.688191f, -0.587785f, -0.425325f},
+            {0.587785f, -0.425325f, -0.688191f},
+            {0.000000f, -0.955423f, -0.295242f},
+            {0.000000f, -1.000000f, 0.000000f},
+            {0.262866f, -0.951056f, -0.162460f},
+            {0.000000f, -0.850651f, 0.525731f},
+            {0.000000f, -0.955423f, 0.295242f},
+            {0.238856f, -0.864188f, 0.442863f},
+            {0.262866f, -0.951056f, 0.162460f},
+            {0.500000f, -0.809017f, 0.309017f},
+            {0.716567f, -0.681718f, 0.147621f},
+            {0.525731f, -0.850651f, 0.000000f},
+            {-0.238856f, -0.864188f, -0.442863f},
+            {-0.500000f, -0.809017f, -0.309017f},
+            {-0.262866f, -0.951056f, -0.162460f},
+            {-0.850651f, -0.525731f, 0.000000f},
+            {-0.716567f, -0.681718f, -0.147621f},
+            {-0.716567f, -0.681718f, 0.147621f},
+            {-0.525731f, -0.850651f, 0.000000f},
+            {-0.500000f, -0.809017f, 0.309017f},
+            {-0.238856f, -0.864188f, 0.442863f},
+            {-0.262866f, -0.951056f, 0.162460f},
+            {-0.864188f, -0.442863f, 0.238856f},
+            {-0.809017f, -0.309017f, 0.500000f},
+            {-0.688191f, -0.587785f, 0.425325f},
+            {-0.681718f, -0.147621f, 0.716567f},
+            {-0.442863f, -0.238856f, 0.864188f},
+            {-0.587785f, -0.425325f, 0.688191f},
+            {-0.309017f, -0.500000f, 0.809017f},
+            {-0.147621f, -0.716567f, 0.681718f},
+            {-0.425325f, -0.688191f, 0.587785f},
+            {-0.162460f, -0.262866f, 0.951056f},
+            {0.442863f, -0.238856f, 0.864188f},
+            {0.162460f, -0.262866f, 0.951056f},
+            {0.309017f, -0.500000f, 0.809017f},
+            {0.147621f, -0.716567f, 0.681718f},
+            {0.000000f, -0.525731f, 0.850651f},
+            {0.425325f, -0.688191f, 0.587785f},
+            {0.587785f, -0.425325f, 0.688191f},
+            {0.688191f, -0.587785f, 0.425325f},
+            {-0.955423f, 0.295242f, 0.000000f},
+            {-0.951056f, 0.162460f, 0.262866f},
+            {-1.000000f, 0.000000f, 0.000000f},
+            {-0.850651f, 0.000000f, 0.525731f},
+            {-0.955423f, -0.295242f, 0.000000f},
+            {-0.951056f, -0.162460f, 0.262866f},
+            {-0.864188f, 0.442863f, -0.238856f},
+            {-0.951056f, 0.162460f, -0.262866f},
+            {-0.809017f, 0.309017f, -0.500000f},
+            {-0.864188f, -0.442863f, -0.238856f},
+            {-0.951056f, -0.162460f, -0.262866f},
+            {-0.809017f, -0.309017f, -0.500000f},
+            {-0.681718f, 0.147621f, -0.716567f},
+            {-0.681718f, -0.147621f, -0.716567f},
+            {-0.850651f, 0.000000f, -0.525731f},
+            {-0.688191f, 0.587785f, -0.425325f},
+            {-0.587785f, 0.425325f, -0.688191f},
+            {-0.425325f, 0.688191f, -0.587785f},
+            {-0.425325f, -0.688191f, -0.587785f},
+            {-0.587785f, -0.425325f, -0.688191f},
+            {-0.688191f, -0.587785f, -0.425325f}
+    };
+
+    float[][] VERTEXNORMAL_DOTS = {
+            {1.23f, 1.30f, 1.47f, 1.35f, 1.56f, 1.71f, 1.37f, 1.38f, 1.59f, 1.60f, 1.79f, 1.97f, 1.88f, 1.92f, 1.79f, 1.02f, 0.93f, 1.07f, 0.82f, 0.87f, 0.88f, 0.94f, 0.96f, 1.14f, 1.11f, 0.82f, 0.83f, 0.89f, 0.89f, 0.86f, 0.94f, 0.91f, 1.00f, 1.21f, 0.98f, 1.48f, 1.30f, 1.57f, 0.96f, 1.07f, 1.14f, 1.60f, 1.61f, 1.40f, 1.37f, 1.72f, 1.78f, 1.79f, 1.93f, 1.99f, 1.90f, 1.68f, 1.71f, 1.86f, 1.60f, 1.68f, 1.78f, 1.86f, 1.93f, 1.99f, 1.97f, 1.44f, 1.22f, 1.49f, 0.93f, 0.99f, 0.99f, 1.23f, 1.22f, 1.44f, 1.49f, 0.89f, 0.89f, 0.97f, 0.91f, 0.98f, 1.19f, 0.82f, 0.76f, 0.82f, 0.71f, 0.72f, 0.73f, 0.76f, 0.79f, 0.86f, 0.83f, 0.72f, 0.76f, 0.76f, 0.89f, 0.82f, 0.89f, 0.82f, 0.89f, 0.91f, 0.83f, 0.96f, 1.14f, 0.97f, 1.40f, 1.19f, 0.98f, 0.94f, 1.00f, 1.07f, 1.37f, 1.21f, 1.48f, 1.30f, 1.57f, 1.61f, 1.37f, 0.86f, 0.83f, 0.91f, 0.82f, 0.82f, 0.88f, 0.89f, 0.96f, 1.14f, 0.98f, 0.87f, 0.93f, 0.94f, 1.02f, 1.30f, 1.07f, 1.35f, 1.38f, 1.11f, 1.56f, 1.92f, 1.79f, 1.79f, 1.59f, 1.60f, 1.72f, 1.90f, 1.79f, 0.80f, 0.85f, 0.79f, 0.93f, 0.80f, 0.85f, 0.77f, 0.74f, 0.72f, 0.77f, 0.74f, 0.72f, 0.70f, 0.70f, 0.71f, 0.76f, 0.73f, 0.79f, 0.79f, 0.73f, 0.76f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f},
+            {1.26f, 1.26f, 1.48f, 1.23f, 1.50f, 1.71f, 1.14f, 1.19f, 1.38f, 1.46f, 1.64f, 1.94f, 1.87f, 1.84f, 1.71f, 1.02f, 0.92f, 1.00f, 0.79f, 0.85f, 0.84f, 0.91f, 0.90f, 0.98f, 0.99f, 0.77f, 0.77f, 0.83f, 0.82f, 0.79f, 0.86f, 0.84f, 0.92f, 0.99f, 0.91f, 1.24f, 1.03f, 1.33f, 0.88f, 0.94f, 0.97f, 1.41f, 1.39f, 1.18f, 1.11f, 1.51f, 1.61f, 1.59f, 1.80f, 1.91f, 1.76f, 1.54f, 1.65f, 1.76f, 1.70f, 1.70f, 1.85f, 1.85f, 1.97f, 1.99f, 1.93f, 1.28f, 1.09f, 1.39f, 0.92f, 0.97f, 0.99f, 1.18f, 1.26f, 1.52f, 1.48f, 0.83f, 0.85f, 0.90f, 0.88f, 0.93f, 1.00f, 0.77f, 0.73f, 0.78f, 0.72f, 0.71f, 0.74f, 0.75f, 0.79f, 0.86f, 0.81f, 0.75f, 0.81f, 0.79f, 0.96f, 0.88f, 0.94f, 0.86f, 0.93f, 0.92f, 0.85f, 1.08f, 1.33f, 1.05f, 1.55f, 1.31f, 1.01f, 1.05f, 1.27f, 1.31f, 1.60f, 1.47f, 1.70f, 1.54f, 1.76f, 1.76f, 1.57f, 0.93f, 0.90f, 0.99f, 0.88f, 0.88f, 0.95f, 0.97f, 1.11f, 1.39f, 1.20f, 0.92f, 0.97f, 1.01f, 1.10f, 1.39f, 1.22f, 1.51f, 1.58f, 1.32f, 1.64f, 1.97f, 1.85f, 1.91f, 1.77f, 1.74f, 1.88f, 1.99f, 1.91f, 0.79f, 0.86f, 0.80f, 0.94f, 0.84f, 0.88f, 0.74f, 0.74f, 0.71f, 0.82f, 0.77f, 0.76f, 0.70f, 0.73f, 0.72f, 0.73f, 0.70f, 0.74f, 0.85f, 0.77f, 0.82f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f},
+            {1.34f, 1.27f, 1.53f, 1.17f, 1.46f, 1.71f, 0.98f, 1.05f, 1.20f, 1.34f, 1.48f, 1.86f, 1.82f, 1.71f, 1.62f, 1.09f, 0.94f, 0.99f, 0.79f, 0.85f, 0.82f, 0.90f, 0.87f, 0.93f, 0.96f, 0.76f, 0.74f, 0.79f, 0.76f, 0.74f, 0.79f, 0.78f, 0.85f, 0.92f, 0.85f, 1.00f, 0.93f, 1.06f, 0.81f, 0.86f, 0.89f, 1.16f, 1.12f, 0.97f, 0.95f, 1.28f, 1.38f, 1.35f, 1.60f, 1.77f, 1.57f, 1.33f, 1.50f, 1.58f, 1.69f, 1.63f, 1.82f, 1.74f, 1.91f, 1.92f, 1.80f, 1.04f, 0.97f, 1.21f, 0.90f, 0.93f, 0.97f, 1.05f, 1.21f, 1.48f, 1.37f, 0.77f, 0.80f, 0.84f, 0.85f, 0.88f, 0.92f, 0.73f, 0.71f, 0.74f, 0.74f, 0.71f, 0.75f, 0.73f, 0.79f, 0.84f, 0.78f, 0.79f, 0.86f, 0.81f, 1.05f, 0.94f, 0.99f, 0.90f, 0.95f, 0.92f, 0.86f, 1.24f, 1.44f, 1.14f, 1.59f, 1.34f, 1.02f, 1.27f, 1.50f, 1.49f, 1.80f, 1.69f, 1.86f, 1.72f, 1.87f, 1.80f, 1.69f, 1.00f, 0.98f, 1.23f, 0.95f, 0.96f, 1.09f, 1.16f, 1.37f, 1.63f, 1.46f, 0.99f, 1.10f, 1.25f, 1.24f, 1.51f, 1.41f, 1.67f, 1.77f, 1.55f, 1.72f, 1.95f, 1.89f, 1.98f, 1.91f, 1.86f, 1.97f, 1.99f, 1.94f, 0.81f, 0.89f, 0.85f, 0.98f, 0.90f, 0.94f, 0.75f, 0.78f, 0.73f, 0.89f, 0.83f, 0.82f, 0.72f, 0.77f, 0.76f, 0.72f, 0.70f, 0.71f, 0.91f, 0.83f, 0.89f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f},
+            {1.46f, 1.34f, 1.60f, 1.16f, 1.46f, 1.71f, 0.94f, 0.99f, 1.05f, 1.26f, 1.33f, 1.74f, 1.76f, 1.57f, 1.54f, 1.23f, 0.98f, 1.05f, 0.83f, 0.89f, 0.84f, 0.92f, 0.87f, 0.91f, 0.96f, 0.78f, 0.74f, 0.79f, 0.72f, 0.72f, 0.75f, 0.76f, 0.80f, 0.88f, 0.83f, 0.94f, 0.87f, 0.95f, 0.76f, 0.80f, 0.82f, 0.97f, 0.96f, 0.89f, 0.88f, 1.08f, 1.11f, 1.10f, 1.37f, 1.59f, 1.37f, 1.07f, 1.27f, 1.34f, 1.57f, 1.45f, 1.69f, 1.55f, 1.77f, 1.79f, 1.60f, 0.93f, 0.90f, 0.99f, 0.86f, 0.87f, 0.93f, 0.96f, 1.07f, 1.35f, 1.18f, 0.73f, 0.76f, 0.77f, 0.81f, 0.82f, 0.85f, 0.70f, 0.71f, 0.72f, 0.78f, 0.73f, 0.77f, 0.73f, 0.79f, 0.82f, 0.76f, 0.83f, 0.90f, 0.84f, 1.18f, 0.98f, 1.03f, 0.92f, 0.95f, 0.90f, 0.86f, 1.32f, 1.45f, 1.15f, 1.53f, 1.27f, 0.99f, 1.42f, 1.65f, 1.58f, 1.93f, 1.83f, 1.94f, 1.81f, 1.88f, 1.74f, 1.70f, 1.19f, 1.17f, 1.44f, 1.11f, 1.15f, 1.36f, 1.41f, 1.61f, 1.81f, 1.67f, 1.22f, 1.34f, 1.50f, 1.42f, 1.65f, 1.61f, 1.82f, 1.91f, 1.75f, 1.80f, 1.89f, 1.89f, 1.98f, 1.99f, 1.94f, 1.98f, 1.92f, 1.87f, 0.86f, 0.95f, 0.92f, 1.14f, 0.98f, 1.03f, 0.79f, 0.84f, 0.77f, 0.97f, 0.90f, 0.89f, 0.76f, 0.82f, 0.82f, 0.74f, 0.72f, 0.71f, 0.98f, 0.89f, 0.97f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f},
+            {1.60f, 1.44f, 1.68f, 1.22f, 1.49f, 1.71f, 0.93f, 0.99f, 0.99f, 1.23f, 1.22f, 1.60f, 1.68f, 1.44f, 1.49f, 1.40f, 1.14f, 1.19f, 0.89f, 0.96f, 0.89f, 0.97f, 0.89f, 0.91f, 0.98f, 0.82f, 0.76f, 0.82f, 0.71f, 0.72f, 0.73f, 0.76f, 0.79f, 0.86f, 0.83f, 0.91f, 0.83f, 0.89f, 0.72f, 0.76f, 0.76f, 0.89f, 0.89f, 0.82f, 0.82f, 0.98f, 0.96f, 0.97f, 1.14f, 1.40f, 1.19f, 0.94f, 1.00f, 1.07f, 1.37f, 1.21f, 1.48f, 1.30f, 1.57f, 1.61f, 1.37f, 0.86f, 0.83f, 0.91f, 0.82f, 0.82f, 0.88f, 0.89f, 0.96f, 1.14f, 0.98f, 0.70f, 0.72f, 0.73f, 0.77f, 0.76f, 0.79f, 0.70f, 0.72f, 0.71f, 0.82f, 0.77f, 0.80f, 0.74f, 0.79f, 0.80f, 0.74f, 0.87f, 0.93f, 0.85f, 1.23f, 1.02f, 1.02f, 0.93f, 0.93f, 0.87f, 0.85f, 1.30f, 1.35f, 1.07f, 1.38f, 1.11f, 0.94f, 1.47f, 1.71f, 1.56f, 1.97f, 1.88f, 1.92f, 1.79f, 1.79f, 1.59f, 1.60f, 1.30f, 1.35f, 1.56f, 1.37f, 1.38f, 1.59f, 1.60f, 1.79f, 1.92f, 1.79f, 1.48f, 1.57f, 1.72f, 1.61f, 1.78f, 1.79f, 1.93f, 1.99f, 1.90f, 1.86f, 1.78f, 1.86f, 1.93f, 1.99f, 1.97f, 1.90f, 1.79f, 1.72f, 0.94f, 1.07f, 1.00f, 1.37f, 1.21f, 1.30f, 0.86f, 0.91f, 0.83f, 1.14f, 0.98f, 0.96f, 0.82f, 0.88f, 0.89f, 0.79f, 0.76f, 0.73f, 1.07f, 0.94f, 1.11f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f},
+            {1.74f, 1.57f, 1.76f, 1.33f, 1.54f, 1.71f, 0.94f, 1.05f, 0.99f, 1.26f, 1.16f, 1.46f, 1.60f, 1.34f, 1.46f, 1.59f, 1.37f, 1.37f, 0.97f, 1.11f, 0.96f, 1.10f, 0.95f, 0.94f, 1.08f, 0.89f, 0.82f, 0.88f, 0.72f, 0.76f, 0.75f, 0.80f, 0.80f, 0.88f, 0.87f, 0.91f, 0.83f, 0.87f, 0.72f, 0.76f, 0.74f, 0.83f, 0.84f, 0.78f, 0.79f, 0.96f, 0.89f, 0.92f, 0.98f, 1.23f, 1.05f, 0.86f, 0.92f, 0.95f, 1.11f, 0.98f, 1.22f, 1.03f, 1.34f, 1.42f, 1.14f, 0.79f, 0.77f, 0.84f, 0.78f, 0.76f, 0.82f, 0.82f, 0.89f, 0.97f, 0.90f, 0.70f, 0.71f, 0.71f, 0.73f, 0.72f, 0.74f, 0.73f, 0.76f, 0.72f, 0.86f, 0.81f, 0.82f, 0.76f, 0.79f, 0.77f, 0.73f, 0.90f, 0.95f, 0.86f, 1.18f, 1.03f, 0.98f, 0.92f, 0.90f, 0.83f, 0.84f, 1.19f, 1.17f, 0.98f, 1.15f, 0.97f, 0.89f, 1.42f, 1.65f, 1.44f, 1.93f, 1.83f, 1.81f, 1.67f, 1.61f, 1.36f, 1.41f, 1.32f, 1.45f, 1.58f, 1.57f, 1.53f, 1.74f, 1.70f, 1.88f, 1.94f, 1.81f, 1.69f, 1.77f, 1.87f, 1.79f, 1.89f, 1.92f, 1.98f, 1.99f, 1.98f, 1.89f, 1.65f, 1.80f, 1.82f, 1.91f, 1.94f, 1.75f, 1.61f, 1.50f, 1.07f, 1.34f, 1.27f, 1.60f, 1.45f, 1.55f, 0.93f, 0.99f, 0.90f, 1.35f, 1.18f, 1.07f, 0.87f, 0.93f, 0.96f, 0.85f, 0.82f, 0.77f, 1.15f, 0.99f, 1.27f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f},
+            {1.86f, 1.71f, 1.82f, 1.48f, 1.62f, 1.71f, 0.98f, 1.20f, 1.05f, 1.34f, 1.17f, 1.34f, 1.53f, 1.27f, 1.46f, 1.77f, 1.60f, 1.57f, 1.16f, 1.38f, 1.12f, 1.35f, 1.06f, 1.00f, 1.28f, 0.97f, 0.89f, 0.95f, 0.76f, 0.81f, 0.79f, 0.86f, 0.85f, 0.92f, 0.93f, 0.93f, 0.85f, 0.87f, 0.74f, 0.78f, 0.74f, 0.79f, 0.82f, 0.76f, 0.79f, 0.96f, 0.85f, 0.90f, 0.94f, 1.09f, 0.99f, 0.81f, 0.85f, 0.89f, 0.95f, 0.90f, 0.99f, 0.94f, 1.10f, 1.24f, 0.98f, 0.75f, 0.73f, 0.78f, 0.74f, 0.72f, 0.77f, 0.76f, 0.82f, 0.89f, 0.83f, 0.73f, 0.71f, 0.71f, 0.71f, 0.70f, 0.72f, 0.77f, 0.80f, 0.74f, 0.90f, 0.85f, 0.84f, 0.78f, 0.79f, 0.75f, 0.73f, 0.92f, 0.95f, 0.86f, 1.05f, 0.99f, 0.94f, 0.90f, 0.86f, 0.79f, 0.81f, 1.00f, 0.98f, 0.91f, 0.96f, 0.89f, 0.83f, 1.27f, 1.50f, 1.23f, 1.80f, 1.69f, 1.63f, 1.46f, 1.37f, 1.09f, 1.16f, 1.24f, 1.44f, 1.49f, 1.69f, 1.59f, 1.80f, 1.69f, 1.87f, 1.86f, 1.72f, 1.82f, 1.91f, 1.94f, 1.92f, 1.95f, 1.99f, 1.98f, 1.91f, 1.97f, 1.89f, 1.51f, 1.72f, 1.67f, 1.77f, 1.86f, 1.55f, 1.41f, 1.25f, 1.33f, 1.58f, 1.50f, 1.80f, 1.63f, 1.74f, 1.04f, 1.21f, 0.97f, 1.48f, 1.37f, 1.21f, 0.93f, 0.97f, 1.05f, 0.92f, 0.88f, 0.84f, 1.14f, 1.02f, 1.34f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f},
+            {1.94f, 1.84f, 1.87f, 1.64f, 1.71f, 1.71f, 1.14f, 1.38f, 1.19f, 1.46f, 1.23f, 1.26f, 1.48f, 1.26f, 1.50f, 1.91f, 1.80f, 1.76f, 1.41f, 1.61f, 1.39f, 1.59f, 1.33f, 1.24f, 1.51f, 1.18f, 0.97f, 1.11f, 0.82f, 0.88f, 0.86f, 0.94f, 0.92f, 0.99f, 1.03f, 0.98f, 0.91f, 0.90f, 0.79f, 0.84f, 0.77f, 0.79f, 0.84f, 0.77f, 0.83f, 0.99f, 0.85f, 0.91f, 0.92f, 1.02f, 1.00f, 0.79f, 0.80f, 0.86f, 0.88f, 0.84f, 0.92f, 0.88f, 0.97f, 1.10f, 0.94f, 0.74f, 0.71f, 0.74f, 0.72f, 0.70f, 0.73f, 0.72f, 0.76f, 0.82f, 0.77f, 0.77f, 0.73f, 0.74f, 0.71f, 0.70f, 0.73f, 0.83f, 0.85f, 0.78f, 0.92f, 0.88f, 0.86f, 0.81f, 0.79f, 0.74f, 0.75f, 0.92f, 0.93f, 0.85f, 0.96f, 0.94f, 0.88f, 0.86f, 0.81f, 0.75f, 0.79f, 0.93f, 0.90f, 0.85f, 0.88f, 0.82f, 0.77f, 1.05f, 1.27f, 0.99f, 1.60f, 1.47f, 1.39f, 1.20f, 1.11f, 0.95f, 0.97f, 1.08f, 1.33f, 1.31f, 1.70f, 1.55f, 1.76f, 1.57f, 1.76f, 1.70f, 1.54f, 1.85f, 1.97f, 1.91f, 1.99f, 1.97f, 1.99f, 1.91f, 1.77f, 1.88f, 1.85f, 1.39f, 1.64f, 1.51f, 1.58f, 1.74f, 1.32f, 1.22f, 1.01f, 1.54f, 1.76f, 1.65f, 1.93f, 1.70f, 1.85f, 1.28f, 1.39f, 1.09f, 1.52f, 1.48f, 1.26f, 0.97f, 0.99f, 1.18f, 1.00f, 0.93f, 0.90f, 1.05f, 1.01f, 1.31f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f},
+            {1.97f, 1.92f, 1.88f, 1.79f, 1.79f, 1.71f, 1.37f, 1.59f, 1.38f, 1.60f, 1.35f, 1.23f, 1.47f, 1.30f, 1.56f, 1.99f, 1.93f, 1.90f, 1.60f, 1.78f, 1.61f, 1.79f, 1.57f, 1.48f, 1.72f, 1.40f, 1.14f, 1.37f, 0.89f, 0.96f, 0.94f, 1.07f, 1.00f, 1.21f, 1.30f, 1.14f, 0.98f, 0.96f, 0.86f, 0.91f, 0.83f, 0.82f, 0.88f, 0.82f, 0.89f, 1.11f, 0.87f, 0.94f, 0.93f, 1.02f, 1.07f, 0.80f, 0.79f, 0.85f, 0.82f, 0.80f, 0.87f, 0.85f, 0.93f, 1.02f, 0.93f, 0.77f, 0.72f, 0.74f, 0.71f, 0.70f, 0.70f, 0.71f, 0.72f, 0.77f, 0.74f, 0.82f, 0.76f, 0.79f, 0.72f, 0.73f, 0.76f, 0.89f, 0.89f, 0.82f, 0.93f, 0.91f, 0.86f, 0.83f, 0.79f, 0.73f, 0.76f, 0.91f, 0.89f, 0.83f, 0.89f, 0.89f, 0.82f, 0.82f, 0.76f, 0.72f, 0.76f, 0.86f, 0.83f, 0.79f, 0.82f, 0.76f, 0.73f, 0.94f, 1.00f, 0.91f, 1.37f, 1.21f, 1.14f, 0.98f, 0.96f, 0.88f, 0.89f, 0.96f, 1.14f, 1.07f, 1.60f, 1.40f, 1.61f, 1.37f, 1.57f, 1.48f, 1.30f, 1.78f, 1.93f, 1.79f, 1.99f, 1.92f, 1.90f, 1.79f, 1.59f, 1.72f, 1.79f, 1.30f, 1.56f, 1.35f, 1.38f, 1.60f, 1.11f, 1.07f, 0.94f, 1.68f, 1.86f, 1.71f, 1.97f, 1.68f, 1.86f, 1.44f, 1.49f, 1.22f, 1.44f, 1.49f, 1.22f, 0.99f, 0.99f, 1.23f, 1.19f, 0.98f, 0.97f, 0.97f, 0.98f, 1.19f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f},
+            {1.94f, 1.97f, 1.87f, 1.91f, 1.85f, 1.71f, 1.60f, 1.77f, 1.58f, 1.74f, 1.51f, 1.26f, 1.48f, 1.39f, 1.64f, 1.99f, 1.97f, 1.99f, 1.70f, 1.85f, 1.76f, 1.91f, 1.76f, 1.70f, 1.88f, 1.55f, 1.33f, 1.57f, 0.96f, 1.08f, 1.05f, 1.31f, 1.27f, 1.47f, 1.54f, 1.39f, 1.20f, 1.11f, 0.93f, 0.99f, 0.90f, 0.88f, 0.95f, 0.88f, 0.97f, 1.32f, 0.92f, 1.01f, 0.97f, 1.10f, 1.22f, 0.84f, 0.80f, 0.88f, 0.79f, 0.79f, 0.85f, 0.86f, 0.92f, 1.02f, 0.94f, 0.82f, 0.76f, 0.77f, 0.72f, 0.73f, 0.70f, 0.72f, 0.71f, 0.74f, 0.74f, 0.88f, 0.81f, 0.85f, 0.75f, 0.77f, 0.82f, 0.94f, 0.93f, 0.86f, 0.92f, 0.92f, 0.86f, 0.85f, 0.79f, 0.74f, 0.79f, 0.88f, 0.85f, 0.81f, 0.82f, 0.83f, 0.77f, 0.78f, 0.73f, 0.71f, 0.75f, 0.79f, 0.77f, 0.74f, 0.77f, 0.73f, 0.70f, 0.86f, 0.92f, 0.84f, 1.14f, 0.99f, 0.98f, 0.91f, 0.90f, 0.84f, 0.83f, 0.88f, 0.97f, 0.94f, 1.41f, 1.18f, 1.39f, 1.11f, 1.33f, 1.24f, 1.03f, 1.61f, 1.80f, 1.59f, 1.91f, 1.84f, 1.76f, 1.64f, 1.38f, 1.51f, 1.71f, 1.26f, 1.50f, 1.23f, 1.19f, 1.46f, 0.99f, 1.00f, 0.91f, 1.70f, 1.85f, 1.65f, 1.93f, 1.54f, 1.76f, 1.52f, 1.48f, 1.26f, 1.28f, 1.39f, 1.09f, 0.99f, 0.97f, 1.18f, 1.31f, 1.01f, 1.05f, 0.90f, 0.93f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f},
+            {1.86f, 1.95f, 1.82f, 1.98f, 1.89f, 1.71f, 1.80f, 1.91f, 1.77f, 1.86f, 1.67f, 1.34f, 1.53f, 1.51f, 1.72f, 1.92f, 1.91f, 1.99f, 1.69f, 1.82f, 1.80f, 1.94f, 1.87f, 1.86f, 1.97f, 1.59f, 1.44f, 1.69f, 1.05f, 1.24f, 1.27f, 1.49f, 1.50f, 1.69f, 1.72f, 1.63f, 1.46f, 1.37f, 1.00f, 1.23f, 0.98f, 0.95f, 1.09f, 0.96f, 1.16f, 1.55f, 0.99f, 1.25f, 1.10f, 1.24f, 1.41f, 0.90f, 0.85f, 0.94f, 0.79f, 0.81f, 0.85f, 0.89f, 0.94f, 1.09f, 0.98f, 0.89f, 0.82f, 0.83f, 0.74f, 0.77f, 0.72f, 0.76f, 0.73f, 0.75f, 0.78f, 0.94f, 0.86f, 0.91f, 0.79f, 0.83f, 0.89f, 0.99f, 0.95f, 0.90f, 0.90f, 0.92f, 0.84f, 0.86f, 0.79f, 0.75f, 0.81f, 0.85f, 0.80f, 0.78f, 0.76f, 0.77f, 0.73f, 0.74f, 0.71f, 0.71f, 0.73f, 0.74f, 0.74f, 0.71f, 0.76f, 0.72f, 0.70f, 0.79f, 0.85f, 0.78f, 0.98f, 0.92f, 0.93f, 0.85f, 0.87f, 0.82f, 0.79f, 0.81f, 0.89f, 0.86f, 1.16f, 0.97f, 1.12f, 0.95f, 1.06f, 1.00f, 0.93f, 1.38f, 1.60f, 1.35f, 1.77f, 1.71f, 1.57f, 1.48f, 1.20f, 1.28f, 1.62f, 1.27f, 1.46f, 1.17f, 1.05f, 1.34f, 0.96f, 0.99f, 0.90f, 1.63f, 1.74f, 1.50f, 1.80f, 1.33f, 1.58f, 1.48f, 1.37f, 1.21f, 1.04f, 1.21f, 0.97f, 0.97f, 0.93f, 1.05f, 1.34f, 1.02f, 1.14f, 0.84f, 0.88f, 0.92f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f},
+            {1.74f, 1.89f, 1.76f, 1.98f, 1.89f, 1.71f, 1.93f, 1.99f, 1.91f, 1.94f, 1.82f, 1.46f, 1.60f, 1.65f, 1.80f, 1.79f, 1.77f, 1.92f, 1.57f, 1.69f, 1.74f, 1.87f, 1.88f, 1.94f, 1.98f, 1.53f, 1.45f, 1.70f, 1.18f, 1.32f, 1.42f, 1.58f, 1.65f, 1.83f, 1.81f, 1.81f, 1.67f, 1.61f, 1.19f, 1.44f, 1.17f, 1.11f, 1.36f, 1.15f, 1.41f, 1.75f, 1.22f, 1.50f, 1.34f, 1.42f, 1.61f, 0.98f, 0.92f, 1.03f, 0.83f, 0.86f, 0.89f, 0.95f, 0.98f, 1.23f, 1.14f, 0.97f, 0.89f, 0.90f, 0.78f, 0.82f, 0.76f, 0.82f, 0.77f, 0.79f, 0.84f, 0.98f, 0.90f, 0.98f, 0.83f, 0.89f, 0.97f, 1.03f, 0.95f, 0.92f, 0.86f, 0.90f, 0.82f, 0.86f, 0.79f, 0.77f, 0.84f, 0.81f, 0.76f, 0.76f, 0.72f, 0.73f, 0.70f, 0.72f, 0.71f, 0.73f, 0.73f, 0.72f, 0.74f, 0.71f, 0.78f, 0.74f, 0.72f, 0.75f, 0.80f, 0.76f, 0.94f, 0.88f, 0.91f, 0.83f, 0.87f, 0.84f, 0.79f, 0.76f, 0.82f, 0.80f, 0.97f, 0.89f, 0.96f, 0.88f, 0.95f, 0.94f, 0.87f, 1.11f, 1.37f, 1.10f, 1.59f, 1.57f, 1.37f, 1.33f, 1.05f, 1.08f, 1.54f, 1.34f, 1.46f, 1.16f, 0.99f, 1.26f, 0.96f, 1.05f, 0.92f, 1.45f, 1.55f, 1.27f, 1.60f, 1.07f, 1.34f, 1.35f, 1.18f, 1.07f, 0.93f, 0.99f, 0.90f, 0.93f, 0.87f, 0.96f, 1.27f, 0.99f, 1.15f, 0.77f, 0.82f, 0.85f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f},
+            {1.60f, 1.78f, 1.68f, 1.93f, 1.86f, 1.71f, 1.97f, 1.99f, 1.99f, 1.97f, 1.93f, 1.60f, 1.68f, 1.78f, 1.86f, 1.61f, 1.57f, 1.79f, 1.37f, 1.48f, 1.59f, 1.72f, 1.79f, 1.92f, 1.90f, 1.38f, 1.35f, 1.60f, 1.23f, 1.30f, 1.47f, 1.56f, 1.71f, 1.88f, 1.79f, 1.92f, 1.79f, 1.79f, 1.30f, 1.56f, 1.35f, 1.37f, 1.59f, 1.38f, 1.60f, 1.90f, 1.48f, 1.72f, 1.57f, 1.61f, 1.79f, 1.21f, 1.00f, 1.30f, 0.89f, 0.94f, 0.96f, 1.07f, 1.14f, 1.40f, 1.37f, 1.14f, 0.96f, 0.98f, 0.82f, 0.88f, 0.82f, 0.89f, 0.83f, 0.86f, 0.91f, 1.02f, 0.93f, 1.07f, 0.87f, 0.94f, 1.11f, 1.02f, 0.93f, 0.93f, 0.82f, 0.87f, 0.80f, 0.85f, 0.79f, 0.80f, 0.85f, 0.77f, 0.72f, 0.74f, 0.71f, 0.70f, 0.70f, 0.71f, 0.72f, 0.77f, 0.74f, 0.72f, 0.76f, 0.73f, 0.82f, 0.79f, 0.76f, 0.73f, 0.79f, 0.76f, 0.93f, 0.86f, 0.91f, 0.83f, 0.89f, 0.89f, 0.82f, 0.72f, 0.76f, 0.76f, 0.89f, 0.82f, 0.89f, 0.82f, 0.89f, 0.91f, 0.83f, 0.96f, 1.14f, 0.97f, 1.40f, 1.44f, 1.19f, 1.22f, 0.99f, 0.98f, 1.49f, 1.44f, 1.49f, 1.22f, 0.99f, 1.23f, 0.98f, 1.19f, 0.97f, 1.21f, 1.30f, 1.00f, 1.37f, 0.94f, 1.07f, 1.14f, 0.98f, 0.96f, 0.86f, 0.91f, 0.83f, 0.88f, 0.82f, 0.89f, 1.11f, 0.94f, 1.07f, 0.73f, 0.76f, 0.79f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f},
+            {1.46f, 1.65f, 1.60f, 1.82f, 1.80f, 1.71f, 1.93f, 1.91f, 1.99f, 1.94f, 1.98f, 1.74f, 1.76f, 1.89f, 1.89f, 1.42f, 1.34f, 1.61f, 1.11f, 1.22f, 1.36f, 1.50f, 1.61f, 1.81f, 1.75f, 1.15f, 1.17f, 1.41f, 1.18f, 1.19f, 1.42f, 1.44f, 1.65f, 1.83f, 1.67f, 1.94f, 1.81f, 1.88f, 1.32f, 1.58f, 1.45f, 1.57f, 1.74f, 1.53f, 1.70f, 1.98f, 1.69f, 1.87f, 1.77f, 1.79f, 1.92f, 1.45f, 1.27f, 1.55f, 0.97f, 1.07f, 1.11f, 1.34f, 1.37f, 1.59f, 1.60f, 1.35f, 1.07f, 1.18f, 0.86f, 0.93f, 0.87f, 0.96f, 0.90f, 0.93f, 0.99f, 1.03f, 0.95f, 1.15f, 0.90f, 0.99f, 1.27f, 0.98f, 0.90f, 0.92f, 0.78f, 0.83f, 0.77f, 0.84f, 0.79f, 0.82f, 0.86f, 0.73f, 0.71f, 0.73f, 0.72f, 0.70f, 0.73f, 0.72f, 0.76f, 0.81f, 0.76f, 0.76f, 0.82f, 0.77f, 0.89f, 0.85f, 0.82f, 0.75f, 0.80f, 0.80f, 0.94f, 0.88f, 0.94f, 0.87f, 0.95f, 0.96f, 0.88f, 0.72f, 0.74f, 0.76f, 0.83f, 0.78f, 0.84f, 0.79f, 0.87f, 0.91f, 0.83f, 0.89f, 0.98f, 0.92f, 1.23f, 1.34f, 1.05f, 1.16f, 0.99f, 0.96f, 1.46f, 1.57f, 1.54f, 1.33f, 1.05f, 1.26f, 1.08f, 1.37f, 1.10f, 0.98f, 1.03f, 0.92f, 1.14f, 0.86f, 0.95f, 0.97f, 0.90f, 0.89f, 0.79f, 0.84f, 0.77f, 0.82f, 0.76f, 0.82f, 0.97f, 0.89f, 0.98f, 0.71f, 0.72f, 0.74f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f},
+            {1.34f, 1.51f, 1.53f, 1.67f, 1.72f, 1.71f, 1.80f, 1.77f, 1.91f, 1.86f, 1.98f, 1.86f, 1.82f, 1.95f, 1.89f, 1.24f, 1.10f, 1.41f, 0.95f, 0.99f, 1.09f, 1.25f, 1.37f, 1.63f, 1.55f, 0.96f, 0.98f, 1.16f, 1.05f, 1.00f, 1.27f, 1.23f, 1.50f, 1.69f, 1.46f, 1.86f, 1.72f, 1.87f, 1.24f, 1.49f, 1.44f, 1.69f, 1.80f, 1.59f, 1.69f, 1.97f, 1.82f, 1.94f, 1.91f, 1.92f, 1.99f, 1.63f, 1.50f, 1.74f, 1.16f, 1.33f, 1.38f, 1.58f, 1.60f, 1.77f, 1.80f, 1.48f, 1.21f, 1.37f, 0.90f, 0.97f, 0.93f, 1.05f, 0.97f, 1.04f, 1.21f, 0.99f, 0.95f, 1.14f, 0.92f, 1.02f, 1.34f, 0.94f, 0.86f, 0.90f, 0.74f, 0.79f, 0.75f, 0.81f, 0.79f, 0.84f, 0.86f, 0.71f, 0.71f, 0.73f, 0.76f, 0.73f, 0.77f, 0.74f, 0.80f, 0.85f, 0.78f, 0.81f, 0.89f, 0.84f, 0.97f, 0.92f, 0.88f, 0.79f, 0.85f, 0.86f, 0.98f, 0.92f, 1.00f, 0.93f, 1.06f, 1.12f, 0.95f, 0.74f, 0.74f, 0.78f, 0.79f, 0.76f, 0.82f, 0.79f, 0.87f, 0.93f, 0.85f, 0.85f, 0.94f, 0.90f, 1.09f, 1.27f, 0.99f, 1.17f, 1.05f, 0.96f, 1.46f, 1.71f, 1.62f, 1.48f, 1.20f, 1.34f, 1.28f, 1.57f, 1.35f, 0.90f, 0.94f, 0.85f, 0.98f, 0.81f, 0.89f, 0.89f, 0.83f, 0.82f, 0.75f, 0.78f, 0.73f, 0.77f, 0.72f, 0.76f, 0.89f, 0.83f, 0.91f, 0.71f, 0.70f, 0.72f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f},
+            {1.26f, 1.39f, 1.48f, 1.51f, 1.64f, 1.71f, 1.60f, 1.58f, 1.77f, 1.74f, 1.91f, 1.94f, 1.87f, 1.97f, 1.85f, 1.10f, 0.97f, 1.22f, 0.88f, 0.92f, 0.95f, 1.01f, 1.11f, 1.39f, 1.32f, 0.88f, 0.90f, 0.97f, 0.96f, 0.93f, 1.05f, 0.99f, 1.27f, 1.47f, 1.20f, 1.70f, 1.54f, 1.76f, 1.08f, 1.31f, 1.33f, 1.70f, 1.76f, 1.55f, 1.57f, 1.88f, 1.85f, 1.91f, 1.97f, 1.99f, 1.99f, 1.70f, 1.65f, 1.85f, 1.41f, 1.54f, 1.61f, 1.76f, 1.80f, 1.91f, 1.93f, 1.52f, 1.26f, 1.48f, 0.92f, 0.99f, 0.97f, 1.18f, 1.09f, 1.28f, 1.39f, 0.94f, 0.93f, 1.05f, 0.92f, 1.01f, 1.31f, 0.88f, 0.81f, 0.86f, 0.72f, 0.75f, 0.74f, 0.79f, 0.79f, 0.86f, 0.85f, 0.71f, 0.73f, 0.75f, 0.82f, 0.77f, 0.83f, 0.78f, 0.85f, 0.88f, 0.81f, 0.88f, 0.97f, 0.90f, 1.18f, 1.00f, 0.93f, 0.86f, 0.92f, 0.94f, 1.14f, 0.99f, 1.24f, 1.03f, 1.33f, 1.39f, 1.11f, 0.79f, 0.77f, 0.84f, 0.79f, 0.77f, 0.84f, 0.83f, 0.90f, 0.98f, 0.91f, 0.85f, 0.92f, 0.91f, 1.02f, 1.26f, 1.00f, 1.23f, 1.19f, 0.99f, 1.50f, 1.84f, 1.71f, 1.64f, 1.38f, 1.46f, 1.51f, 1.76f, 1.59f, 0.84f, 0.88f, 0.80f, 0.94f, 0.79f, 0.86f, 0.82f, 0.77f, 0.76f, 0.74f, 0.74f, 0.71f, 0.73f, 0.70f, 0.72f, 0.82f, 0.77f, 0.85f, 0.74f, 0.70f, 0.73f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 1.00f}
+    };
+
+}
diff --git a/src/main/java/lwjake2/render/lwjgl/Base.java b/src/main/java/lwjake2/render/lwjgl/Base.java
new file mode 100644 (file)
index 0000000..90769a7
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render.lwjgl;
+
+import org.lwjgl.opengl.GL11;
+
+/**
+ * Base
+ *
+ * @author dsanders/cwei
+ */
+abstract class Base extends LWJGLBase {
+
+    static final int GL_COLOR_INDEX8_EXT = GL11.GL_COLOR_INDEX;
+
+    static final String REF_VERSION = "GL 0.01";
+
+    // up / down
+    static final int PITCH = 0;
+
+    // left / right
+    static final int YAW = 1;
+
+    /*
+     * skins will be outline flood filled and mip mapped pics and sprites with
+     * alpha will be outline flood filled pic won't be mip mapped
+     * 
+     * model skin sprite frame wall texture pic
+     */
+    // enum imagetype_t
+    static final int it_skin = 0;
+
+    static final int it_sprite = 1;
+
+    static final int it_wall = 2;
+
+    static final int it_pic = 3;
+
+    static final int it_sky = 4;
+
+    static final int mod_brush = 1;
+
+    static final int mod_sprite = 2;
+
+    static final int mod_alias = 3;
+
+    static final int TEXNUM_LIGHTMAPS = 1024;
+
+    static final int TEXNUM_SCRAPS = 1152;
+
+    static final int TEXNUM_IMAGES = 1153;
+
+    static final int MAX_GLTEXTURES = 1024;
+
+    static final int MAX_LBM_HEIGHT = 480;
+
+    static final float BACKFACE_EPSILON = 0.01f;
+
+    /*
+     * * GL config stuff
+     */
+    static final int GL_RENDERER_VOODOO = 0x00000001;
+
+    static final int GL_RENDERER_VOODOO2 = 0x00000002;
+
+    static final int GL_RENDERER_VOODOO_RUSH = 0x00000004;
+
+    static final int GL_RENDERER_PCX2 = 0x00000020;
+
+    static final int GL_RENDERER_POWERVR = 0x00000070;
+
+    static final int GL_RENDERER_PERMEDIA2 = 0x00000100;
+
+    static final int GL_RENDERER_GLINT_MX = 0x00000200;
+
+    static final int GL_RENDERER_3DLABS = 0x00000F00;
+
+    static final int GL_RENDERER_REALIZM = 0x00001000;
+
+    static final int GL_RENDERER_RENDITION = 0x001C0000;
+
+    static final int GL_RENDERER_SGI = 0x00F00000;
+
+    static final int GL_RENDERER_MCD = 0x01000000;
+
+    static final int GL_RENDERER_OTHER = 0x80000000;
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/render/lwjgl/Draw.java b/src/main/java/lwjake2/render/lwjgl/Draw.java
new file mode 100644 (file)
index 0000000..95ed2a7
--- /dev/null
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render.lwjgl;
+
+import lwjake2.Defines;
+import lwjake2.client.VideoDriver;
+import lwjake2.qcommon.Com;
+import lwjake2.render.Image;
+import lwjake2.util.Lib;
+import org.lwjgl.opengl.GL11;
+
+import java.awt.*;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+
+/**
+ * Draw
+ * (gl_draw.c)
+ *
+ * @author cwei
+ */
+public abstract class Draw extends lwjake2.render.lwjgl.Image {
+
+    // allocate a 256 * 256 texture buffer
+    private final ByteBuffer image8 = Lib.newByteBuffer(256 * 256 * Defines.SIZE_OF_INT);
+    // share the buffer
+    private final IntBuffer image32 = image8.asIntBuffer();
+
+    /*
+    ===============
+    Draw_InitLocal
+    ===============
+    */
+    void Draw_InitLocal() {
+        // load console characters (don't bilerp characters)
+        draw_chars = GL_FindImage("pics/conchars.pcx", it_pic);
+        GL_Bind(draw_chars.texnum);
+        GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
+        GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
+    }
+
+    /*
+    ================
+    Draw_Char
+
+    Draws one 8*8 graphics character with 0 being transparent.
+    It can be clipped to the top of the screen to allow the console to be
+    smoothly scrolled off.
+    ================
+    */
+    protected void Draw_Char(int x, int y, int num) {
+
+        num &= 255;
+
+        if ((num & 127) == 32) return; // space
+
+        if (y <= -8) return; // totally off screen
+
+        int row = num >> 4;
+        int col = num & 15;
+
+        float frow = row * 0.0625f;
+        float fcol = col * 0.0625f;
+        float size = 0.0625f;
+
+        GL_Bind(draw_chars.texnum);
+
+        GL11.glBegin(GL11.GL_QUADS);
+        GL11.glTexCoord2f(fcol, frow);
+        GL11.glVertex2f(x, y);
+        GL11.glTexCoord2f(fcol + size, frow);
+        GL11.glVertex2f(x + 8, y);
+        GL11.glTexCoord2f(fcol + size, frow + size);
+        GL11.glVertex2f(x + 8, y + 8);
+        GL11.glTexCoord2f(fcol, frow + size);
+        GL11.glVertex2f(x, y + 8);
+        GL11.glEnd();
+    }
+
+    /*
+    =============
+    Draw_FindPic
+    =============
+    */
+    protected Image Draw_FindPic(String name) {
+        Image image = null;
+        String fullname;
+
+        if (!name.startsWith("/") && !name.startsWith("\\")) {
+            fullname = "pics/" + name + ".pcx";
+            image = GL_FindImage(fullname, it_pic);
+        } else {
+            image = GL_FindImage(name.substring(1), it_pic);
+        }
+        return image;
+    }
+
+    /*
+    =============
+    Draw_GetPicSize
+    =============
+    */
+    protected void Draw_GetPicSize(Dimension dim, String pic) {
+
+        Image image = Draw_FindPic(pic);
+        dim.width = (image != null) ? image.width : -1;
+        dim.height = (image != null) ? image.height : -1;
+    }
+
+    /*
+    =============
+    Draw_StretchPic
+    =============
+    */
+    protected void Draw_StretchPic(int x, int y, int w, int h, String pic) {
+
+        Image image;
+
+        image = Draw_FindPic(pic);
+        if (image == null) {
+            VideoDriver.Printf(Defines.PRINT_ALL, "Can't find pic: " + pic + '\n');
+            return;
+        }
+
+        if (scrap_dirty)
+            Scrap_Upload();
+
+        if (((gl_config.renderer == GL_RENDERER_MCD) || ((gl_config.renderer & GL_RENDERER_RENDITION) != 0)) && !image.has_alpha)
+            GL11.glDisable(GL11.GL_ALPHA_TEST);
+
+        GL_Bind(image.texnum);
+        GL11.glBegin(GL11.GL_QUADS);
+        GL11.glTexCoord2f(image.sl, image.tl);
+        GL11.glVertex2f(x, y);
+        GL11.glTexCoord2f(image.sh, image.tl);
+        GL11.glVertex2f(x + w, y);
+        GL11.glTexCoord2f(image.sh, image.th);
+        GL11.glVertex2f(x + w, y + h);
+        GL11.glTexCoord2f(image.sl, image.th);
+        GL11.glVertex2f(x, y + h);
+        GL11.glEnd();
+
+        if (((gl_config.renderer == GL_RENDERER_MCD) || ((gl_config.renderer & GL_RENDERER_RENDITION) != 0)) && !image.has_alpha)
+            GL11.glEnable(GL11.GL_ALPHA_TEST);
+    }
+
+    /*
+    =============
+    Draw_Pic
+    =============
+    */
+    protected void Draw_Pic(int x, int y, String pic) {
+        Image image;
+
+        image = Draw_FindPic(pic);
+        if (image == null) {
+            VideoDriver.Printf(Defines.PRINT_ALL, "Can't find pic: " + pic + '\n');
+            return;
+        }
+        if (scrap_dirty)
+            Scrap_Upload();
+
+        if (((gl_config.renderer == GL_RENDERER_MCD) || ((gl_config.renderer & GL_RENDERER_RENDITION) != 0)) && !image.has_alpha)
+            GL11.glDisable(GL11.GL_ALPHA_TEST);
+
+        GL_Bind(image.texnum);
+
+        GL11.glBegin(GL11.GL_QUADS);
+        GL11.glTexCoord2f(image.sl, image.tl);
+        GL11.glVertex2f(x, y);
+        GL11.glTexCoord2f(image.sh, image.tl);
+        GL11.glVertex2f(x + image.width, y);
+        GL11.glTexCoord2f(image.sh, image.th);
+        GL11.glVertex2f(x + image.width, y + image.height);
+        GL11.glTexCoord2f(image.sl, image.th);
+        GL11.glVertex2f(x, y + image.height);
+        GL11.glEnd();
+
+        if (((gl_config.renderer == GL_RENDERER_MCD) || ((gl_config.renderer & GL_RENDERER_RENDITION) != 0)) && !image.has_alpha)
+            GL11.glEnable(GL11.GL_ALPHA_TEST);
+    }
+
+    //=============================================================================
+
+    /*
+    =============
+    Draw_TileClear
+
+    This repeats a 64*64 tile graphic to fill the screen around a sized down
+    refresh window.
+    =============
+    */
+    protected void Draw_TileClear(int x, int y, int w, int h, String pic) {
+        Image image;
+
+        image = Draw_FindPic(pic);
+        if (image == null) {
+            VideoDriver.Printf(Defines.PRINT_ALL, "Can't find pic: " + pic + '\n');
+            return;
+        }
+
+        if (((gl_config.renderer == GL_RENDERER_MCD) || ((gl_config.renderer & GL_RENDERER_RENDITION) != 0)) && !image.has_alpha)
+            GL11.glDisable(GL11.GL_ALPHA_TEST);
+
+        GL_Bind(image.texnum);
+        GL11.glBegin(GL11.GL_QUADS);
+        GL11.glTexCoord2f(x / 64.0f, y / 64.0f);
+        GL11.glVertex2f(x, y);
+        GL11.glTexCoord2f((x + w) / 64.0f, y / 64.0f);
+        GL11.glVertex2f(x + w, y);
+        GL11.glTexCoord2f((x + w) / 64.0f, (y + h) / 64.0f);
+        GL11.glVertex2f(x + w, y + h);
+        GL11.glTexCoord2f(x / 64.0f, (y + h) / 64.0f);
+        GL11.glVertex2f(x, y + h);
+        GL11.glEnd();
+
+        if (((gl_config.renderer == GL_RENDERER_MCD) || ((gl_config.renderer & GL_RENDERER_RENDITION) != 0)) && !image.has_alpha)
+            GL11.glEnable(GL11.GL_ALPHA_TEST);
+    }
+
+// ====================================================================
+
+    /*
+    =============
+    Draw_Fill
+
+    Fills a box of pixels with a single color
+    =============
+    */
+    protected void Draw_Fill(int x, int y, int w, int h, int colorIndex) {
+
+        if (colorIndex > 255)
+            Com.Error(Defines.ERR_FATAL, "Draw_Fill: bad color");
+
+        GL11.glDisable(GL11.GL_TEXTURE_2D);
+
+        int color = d_8to24table[colorIndex];
+
+        GL11.glColor3ub(
+                (byte) ((color) & 0xff), // r
+                (byte) ((color >> 8) & 0xff), // g
+                (byte) ((color >> 16) & 0xff) // b
+        );
+
+        GL11.glBegin(GL11.GL_QUADS);
+
+        GL11.glVertex2f(x, y);
+        GL11.glVertex2f(x + w, y);
+        GL11.glVertex2f(x + w, y + h);
+        GL11.glVertex2f(x, y + h);
+
+        GL11.glEnd();
+        GL11.glColor3f(1, 1, 1);
+        GL11.glEnable(GL11.GL_TEXTURE_2D);
+    }
+
+    /*
+    ================
+    Draw_FadeScreen
+    ================
+    */
+    protected void Draw_FadeScreen() {
+        GL11.glEnable(GL11.GL_BLEND);
+        GL11.glDisable(GL11.GL_TEXTURE_2D);
+        GL11.glColor4f(0, 0, 0, 0.8f);
+        GL11.glBegin(GL11.GL_QUADS);
+
+        GL11.glVertex2f(0, 0);
+        GL11.glVertex2f(vid.width, 0);
+        GL11.glVertex2f(vid.width, vid.height);
+        GL11.glVertex2f(0, vid.height);
+
+        GL11.glEnd();
+        GL11.glColor4f(1, 1, 1, 1);
+        GL11.glEnable(GL11.GL_TEXTURE_2D);
+        GL11.glDisable(GL11.GL_BLEND);
+    }
+
+    /*
+    =============
+    Draw_StretchRaw
+    =============
+    */
+    protected void Draw_StretchRaw(int x, int y, int w, int h, int cols, int rows, byte[] data) {
+        int i, j, trows;
+        int sourceIndex;
+        int frac, fracstep;
+        float hscale;
+        int row;
+        float t;
+
+        GL_Bind(0);
+
+        if (rows <= 256) {
+            hscale = 1;
+            trows = rows;
+        } else {
+            hscale = rows / 256.0f;
+            trows = 256;
+        }
+        t = rows * hscale / 256;
+
+        if (!qglColorTableEXT) {
+            //int[] image32 = new int[256*256];
+            image32.clear();
+            int destIndex = 0;
+
+            for (i = 0; i < trows; i++) {
+                row = (int) (i * hscale);
+                if (row > rows)
+                    break;
+                sourceIndex = cols * row;
+                destIndex = i * 256;
+                fracstep = cols * 0x10000 / 256;
+                frac = fracstep >> 1;
+                for (j = 0; j < 256; j++) {
+                    image32.put(destIndex + j, r_rawpalette[data[sourceIndex + (frac >> 16)] & 0xff]);
+                    frac += fracstep;
+                }
+            }
+            GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, gl_tex_solid_format, 256, 256, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, image32);
+        } else {
+            //byte[] image8 = new byte[256*256];
+            image8.clear();
+            int destIndex = 0;
+
+            for (i = 0; i < trows; i++) {
+                row = (int) (i * hscale);
+                if (row > rows)
+                    break;
+                sourceIndex = cols * row;
+                destIndex = i * 256;
+                fracstep = cols * 0x10000 / 256;
+                frac = fracstep >> 1;
+                for (j = 0; j < 256; j++) {
+                    image8.put(destIndex + j, data[sourceIndex + (frac >> 16)]);
+                    frac += fracstep;
+                }
+            }
+
+            GL11.glTexImage2D(GL11.GL_TEXTURE_2D,
+                    0,
+                    GL_COLOR_INDEX8_EXT,
+                    256, 256,
+                    0,
+                    GL11.GL_COLOR_INDEX,
+                    GL11.GL_UNSIGNED_BYTE,
+                    image8);
+        }
+        GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
+        GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
+
+        if ((gl_config.renderer == GL_RENDERER_MCD) || ((gl_config.renderer & GL_RENDERER_RENDITION) != 0))
+            GL11.glDisable(GL11.GL_ALPHA_TEST);
+
+        GL11.glBegin(GL11.GL_QUADS);
+        GL11.glTexCoord2f(0, 0);
+        GL11.glVertex2f(x, y);
+        GL11.glTexCoord2f(1, 0);
+        GL11.glVertex2f(x + w, y);
+        GL11.glTexCoord2f(1, t);
+        GL11.glVertex2f(x + w, y + h);
+        GL11.glTexCoord2f(0, t);
+        GL11.glVertex2f(x, y + h);
+        GL11.glEnd();
+
+        if ((gl_config.renderer == GL_RENDERER_MCD) || ((gl_config.renderer & GL_RENDERER_RENDITION) != 0))
+            GL11.glEnable(GL11.GL_ALPHA_TEST);
+    }
+
+}
diff --git a/src/main/java/lwjake2/render/lwjgl/Image.java b/src/main/java/lwjake2/render/lwjgl/Image.java
new file mode 100644 (file)
index 0000000..e7449a5
--- /dev/null
@@ -0,0 +1,1642 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render.lwjgl;
+
+import lwjake2.Defines;
+import lwjake2.client.VideoDriver;
+import lwjake2.client.particle_t;
+import lwjake2.game.CvarT;
+import lwjake2.qcommon.Com;
+import lwjake2.qcommon.Cvar;
+import lwjake2.qcommon.FS;
+import lwjake2.qcommon.qfiles;
+import lwjake2.util.Lib;
+import lwjake2.util.Vargs;
+import org.lwjgl.BufferUtils;
+import org.lwjgl.opengl.ARBImaging;
+import org.lwjgl.opengl.ARBMultitexture;
+import org.lwjgl.opengl.EXTSharedTexturePalette;
+import org.lwjgl.opengl.GL11;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.image.AffineTransformOp;
+import java.awt.image.BufferedImage;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.IntBuffer;
+import java.util.Arrays;
+
+/**
+ * Image
+ *
+ * @author cwei
+ */
+public abstract class Image extends Main {
+
+    static final glmode_t modes[] =
+            {
+                    new glmode_t("GL_NEAREST", GL11.GL_NEAREST, GL11.GL_NEAREST),
+                    new glmode_t("GL_LINEAR", GL11.GL_LINEAR, GL11.GL_LINEAR),
+                    new glmode_t("GL_NEAREST_MIPMAP_NEAREST", GL11.GL_NEAREST_MIPMAP_NEAREST, GL11.GL_NEAREST),
+                    new glmode_t("GL_LINEAR_MIPMAP_NEAREST", GL11.GL_LINEAR_MIPMAP_NEAREST, GL11.GL_LINEAR),
+                    new glmode_t("GL_NEAREST_MIPMAP_LINEAR", GL11.GL_NEAREST_MIPMAP_LINEAR, GL11.GL_NEAREST),
+                    new glmode_t("GL_LINEAR_MIPMAP_LINEAR", GL11.GL_LINEAR_MIPMAP_LINEAR, GL11.GL_LINEAR)};
+    static final int NUM_GL_MODES = modes.length;
+    static final gltmode_t[] gl_alpha_modes =
+            {
+                    new gltmode_t("default", 4),
+                    new gltmode_t("GL_RGBA", GL11.GL_RGBA),
+                    new gltmode_t("GL_RGBA8", GL11.GL_RGBA8),
+                    new gltmode_t("GL_RGB5_A1", GL11.GL_RGB5_A1),
+                    new gltmode_t("GL_RGBA4", GL11.GL_RGBA4),
+                    new gltmode_t("GL_RGBA2", GL11.GL_RGBA2),
+            };
+    static final int NUM_GL_ALPHA_MODES = gl_alpha_modes.length;
+    static final gltmode_t[] gl_solid_modes =
+            {
+                    new gltmode_t("default", 3),
+                    new gltmode_t("GL_RGB", GL11.GL_RGB),
+                    new gltmode_t("GL_RGB8", GL11.GL_RGB8),
+                    new gltmode_t("GL_RGB5", GL11.GL_RGB5),
+                    new gltmode_t("GL_RGB4", GL11.GL_RGB4),
+                    new gltmode_t("GL_R3_G3_B2", GL11.GL_R3_G3_B2),
+                    // #ifdef GL_RGB2_EXT
+                    //new gltmode_t("GL_RGB2", GL.GL_RGB2_EXT)
+                    // #endif
+            };
+    static final int NUM_GL_SOLID_MODES = gl_solid_modes.length;
+    static final int MAX_SCRAPS = 1;
+
+    //
+    // qboolean GL_Upload8 (byte *data, int width, int height,  qboolean mipmap, qboolean is_sky );
+    // qboolean GL_Upload32 (unsigned *data, int width, int height,  qboolean mipmap);
+    //
+    static final int BLOCK_WIDTH = 256;
+    static final int BLOCK_HEIGHT = 256;
+    // must be a power of 2
+    static final int FLOODFILL_FIFO_SIZE = 0x1000;
+    static final int FLOODFILL_FIFO_MASK = FLOODFILL_FIFO_SIZE - 1;
+    // void FLOODFILL_STEP( int off, int dx, int dy )
+    // {
+    //         if (pos[off] == fillcolor)
+    //         {
+    //                 pos[off] = 255;
+    //                 fifo[inpt].x = x + dx; fifo[inpt].y = y + dy;
+    //                 inpt = (inpt + 1) & FLOODFILL_FIFO_MASK;
+    //         }
+    //         else if (pos[off] != 255) fdc = pos[off];
+    // }
+    static final floodfill_t[] fifo = new floodfill_t[FLOODFILL_FIFO_SIZE];
+
+    static {
+        for (int j = 0; j < fifo.length; j++) {
+            fifo[j] = new floodfill_t();
+        }
+    }
+
+    final lwjake2.render.Image[] gltextures = new lwjake2.render.Image[MAX_GLTEXTURES];
+    final byte[] intensitytable = new byte[256];
+    final byte[] gammatable = new byte[256];
+    final int gl_solid_format = 3;
+    final int gl_alpha_format = 4;
+    final int[] lastmodes = {-1, -1};
+    final int[][] scrap_allocated = new int[MAX_SCRAPS][BLOCK_WIDTH];
+    final byte[][] scrap_texels = new byte[MAX_SCRAPS][BLOCK_WIDTH * BLOCK_HEIGHT];
+    /*
+    =============================================================================
+
+      scrap allocation
+
+      Allocate all the little status bar objects into a single texture
+      to crutch up inefficient hardware / drivers
+
+    =============================================================================
+    */
+    /*
+    ===============
+    GL_Upload32
+
+    Returns has_alpha
+    ===============
+    */
+    final int[] scaled = new int[256 * 256];
+    //byte[] paletted_texture = new byte[256 * 256];
+    final ByteBuffer paletted_texture = BufferUtils.createByteBuffer(256 * 256);
+    final IntBuffer tex = Lib.newIntBuffer(512 * 256, ByteOrder.LITTLE_ENDIAN);
+    final int[] trans = new int[512 * 256];
+    final IntBuffer texnumBuffer = BufferUtils.createIntBuffer(1);
+    private final Throwable gotoBreakOut = new Throwable();
+    private final Throwable gotoDone = gotoBreakOut;
+    lwjake2.render.Image draw_chars;
+    //Map gltextures = new Hashtable(MAX_GLTEXTURES); // image_t
+    int numgltextures;
+    int base_textureid; // gltextures[i] = base_textureid+i
+    CvarT intensity;
+    int gl_tex_solid_format = 3;
+    int gl_tex_alpha_format = 4;
+    int gl_filter_min = GL11.GL_LINEAR_MIPMAP_NEAREST;
+    int gl_filter_max = GL11.GL_LINEAR;
+    boolean scrap_dirty;
+    int scrap_uploads = 0;
+    int upload_width, upload_height;
+    boolean uploaded_paletted;
+
+    Image() {
+        // init the texture cache
+        for (int i = 0; i < gltextures.length; i++) {
+            gltextures[i] = new lwjake2.render.Image(i);
+        }
+        numgltextures = 0;
+    }
+
+    void GL_SetTexturePalette(int[] palette) {
+
+        assert (palette != null && palette.length == 256) : "int palette[256] bug";
+
+        int i;
+        //byte[] temptable = new byte[768];
+
+        if (qglColorTableEXT && gl_ext_palettedtexture.value != 0.0f) {
+            ByteBuffer temptable = BufferUtils.createByteBuffer(768);
+            for (i = 0; i < 256; i++) {
+                temptable.put(i * 3, (byte) ((palette[i]) & 0xff));
+                temptable.put(i * 3 + 1, (byte) ((palette[i] >> 8) & 0xff));
+                temptable.put(i * 3 + 2, (byte) ((palette[i] >> 16) & 0xff));
+            }
+
+            ARBImaging.glColorTable(EXTSharedTexturePalette.GL_SHARED_TEXTURE_PALETTE_EXT, GL11.GL_RGB, 256, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, temptable);
+        }
+    }
+
+    void GL_EnableMultitexture(boolean enable) {
+        if (enable) {
+            GL_SelectTexture(GL_TEXTURE1);
+            GL11.glEnable(GL11.GL_TEXTURE_2D);
+            GL_TexEnv(GL11.GL_REPLACE);
+        } else {
+            GL_SelectTexture(GL_TEXTURE1);
+            GL11.glDisable(GL11.GL_TEXTURE_2D);
+            GL_TexEnv(GL11.GL_REPLACE);
+        }
+        GL_SelectTexture(GL_TEXTURE0);
+        GL_TexEnv(GL11.GL_REPLACE);
+    }
+
+       /*
+    =================================================================
+       
+       PCX LOADING
+       
+       =================================================================
+       */
+
+    void GL_SelectTexture(int texture /* GLenum */) {
+        int tmu;
+
+        tmu = (texture == GL_TEXTURE0) ? 0 : 1;
+
+        if (tmu == gl_state.currenttmu) {
+            return;
+        }
+
+        gl_state.currenttmu = tmu;
+
+        ARBMultitexture.glActiveTextureARB(texture);
+        ARBMultitexture.glClientActiveTextureARB(texture);
+    }
+
+    void GL_TexEnv(int mode /* GLenum */
+    ) {
+
+        if (mode != lastmodes[gl_state.currenttmu]) {
+            GL11.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, mode);
+            lastmodes[gl_state.currenttmu] = mode;
+        }
+    }
+
+    void GL_Bind(int texnum) {
+
+        if ((gl_nobind.value != 0) && (draw_chars != null)) {
+            // performance evaluation option
+            texnum = draw_chars.texnum;
+        }
+        if (gl_state.currenttextures[gl_state.currenttmu] == texnum)
+            return;
+
+        gl_state.currenttextures[gl_state.currenttmu] = texnum;
+        GL11.glBindTexture(GL11.GL_TEXTURE_2D, texnum);
+    }
+
+    void GL_MBind(int target /* GLenum */, int texnum) {
+        GL_SelectTexture(target);
+        if (target == GL_TEXTURE0) {
+            if (gl_state.currenttextures[0] == texnum)
+                return;
+        } else {
+            if (gl_state.currenttextures[1] == texnum)
+                return;
+        }
+        GL_Bind(texnum);
+    }
+
+       /*
+    ====================================================================
+       
+       IMAGE FLOOD FILLING
+       
+       ====================================================================
+       */
+
+       /*
+    =================
+       Mod_FloodFillSkin
+       
+       Fill background pixels so mipmapping doesn't have haloes
+       =================
+       */
+
+    /*
+    ===============
+    GL_TextureMode
+    ===============
+    */
+    void GL_TextureMode(String string) {
+
+        int i;
+        for (i = 0; i < NUM_GL_MODES; i++) {
+            if (modes[i].name.equalsIgnoreCase(string))
+                break;
+        }
+
+        if (i == NUM_GL_MODES) {
+            VideoDriver.Printf(Defines.PRINT_ALL, "bad filter name: [" + string + "]\n");
+            return;
+        }
+
+        gl_filter_min = modes[i].minimize;
+        gl_filter_max = modes[i].maximize;
+
+        lwjake2.render.Image glt;
+        // change all the existing mipmap texture objects
+        for (i = 0; i < numgltextures; i++) {
+            glt = gltextures[i];
+
+            if (glt.type != it_pic && glt.type != it_sky) {
+                GL_Bind(glt.texnum);
+                GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, gl_filter_min);
+                GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, gl_filter_max);
+            }
+        }
+    }
+
+    /*
+    ===============
+    GL_TextureAlphaMode
+    ===============
+    */
+    void GL_TextureAlphaMode(String string) {
+
+        int i;
+        for (i = 0; i < NUM_GL_ALPHA_MODES; i++) {
+            if (gl_alpha_modes[i].name.equalsIgnoreCase(string))
+                break;
+        }
+
+        if (i == NUM_GL_ALPHA_MODES) {
+            VideoDriver.Printf(Defines.PRINT_ALL, "bad alpha texture mode name: [" + string + "]\n");
+            return;
+        }
+
+        gl_tex_alpha_format = gl_alpha_modes[i].mode;
+    }
+
+    /*
+    ===============
+    GL_TextureSolidMode
+    ===============
+    */
+    void GL_TextureSolidMode(String string) {
+        int i;
+        for (i = 0; i < NUM_GL_SOLID_MODES; i++) {
+            if (gl_solid_modes[i].name.equalsIgnoreCase(string))
+                break;
+        }
+
+        if (i == NUM_GL_SOLID_MODES) {
+            VideoDriver.Printf(Defines.PRINT_ALL, "bad solid texture mode name: [" + string + "]\n");
+            return;
+        }
+
+        gl_tex_solid_format = gl_solid_modes[i].mode;
+    }
+    //
+    // #define FLOODFILL_STEP( off, dx, dy ) \
+    // { \
+    //         if (pos[off] == fillcolor) \
+    //         { \
+    //                 pos[off] = 255; \
+    //                 fifo[inpt].x = x + (dx), fifo[inpt].y = y + (dy); \
+    //                 inpt = (inpt + 1) & FLOODFILL_FIFO_MASK; \
+    //         } \
+    //         else if (pos[off] != 255) fdc = pos[off]; \
+    // }
+
+    /*
+    ===============
+    GL_ImageList_f
+    ===============
+    */
+    void GL_ImageList_f() {
+
+        lwjake2.render.Image image;
+        int texels;
+        final String[] palstrings = {"RGB", "PAL"};
+
+        VideoDriver.Printf(Defines.PRINT_ALL, "------------------\n");
+        texels = 0;
+
+        for (int i = 0; i < numgltextures; i++) {
+            image = gltextures[i];
+            if (image.texnum <= 0)
+                continue;
+
+            texels += image.upload_width * image.upload_height;
+            switch (image.type) {
+                case it_skin:
+                    VideoDriver.Printf(Defines.PRINT_ALL, "M");
+                    break;
+                case it_sprite:
+                    VideoDriver.Printf(Defines.PRINT_ALL, "S");
+                    break;
+                case it_wall:
+                    VideoDriver.Printf(Defines.PRINT_ALL, "W");
+                    break;
+                case it_pic:
+                    VideoDriver.Printf(Defines.PRINT_ALL, "P");
+                    break;
+                default:
+                    VideoDriver.Printf(Defines.PRINT_ALL, " ");
+                    break;
+            }
+
+            VideoDriver.Printf(
+                    Defines.PRINT_ALL,
+                    " %3i %3i %s: %s\n",
+                    new Vargs(4).add(image.upload_width).add(image.upload_height).add(palstrings[(image.paletted) ? 1 : 0]).add(
+                            image.name));
+        }
+        VideoDriver.Printf(Defines.PRINT_ALL, "Total texel count (not counting mipmaps): " + texels + '\n');
+    }
+
+    // returns a texture number and the position inside it
+    int Scrap_AllocBlock(int w, int h, pos_t pos) {
+        int i, j;
+        int best, best2;
+        int texnum;
+
+        for (texnum = 0; texnum < MAX_SCRAPS; texnum++) {
+            best = BLOCK_HEIGHT;
+
+            for (i = 0; i < BLOCK_WIDTH - w; i++) {
+                best2 = 0;
+
+                for (j = 0; j < w; j++) {
+                    if (scrap_allocated[texnum][i + j] >= best)
+                        break;
+                    if (scrap_allocated[texnum][i + j] > best2)
+                        best2 = scrap_allocated[texnum][i + j];
+                }
+                if (j == w) { // this is a valid spot
+                    pos.x = i;
+                    pos.y = best = best2;
+                }
+            }
+
+            if (best + h > BLOCK_HEIGHT)
+                continue;
+
+            for (i = 0; i < w; i++)
+                scrap_allocated[texnum][pos.x + i] = best + h;
+
+            return texnum;
+        }
+
+        return -1;
+        // Sys_Error ("Scrap_AllocBlock: full");
+    }
+
+    void Scrap_Upload() {
+        scrap_uploads++;
+        GL_Bind(TEXNUM_SCRAPS);
+        GL_Upload8(scrap_texels[0], BLOCK_WIDTH, BLOCK_HEIGHT, false, false);
+        scrap_dirty = false;
+    }
+
+    //   =======================================================
+
+    /*
+    ==============
+    LoadPCX
+    ==============
+    */
+    byte[] LoadPCX(String filename, byte[][] palette, Dimension dim) {
+        qfiles.pcx_t pcx;
+
+        //
+        // load the file
+        //
+        byte[] raw = FS.LoadFile(filename);
+
+        if (raw == null) {
+            VideoDriver.Printf(Defines.PRINT_DEVELOPER, "Bad pcx file " + filename + '\n');
+            return null;
+        }
+
+        //
+        // parse the PCX file
+        //
+        pcx = new qfiles.pcx_t(raw);
+
+        if (pcx.manufacturer != 0x0a
+                || pcx.version != 5
+                || pcx.encoding != 1
+                || pcx.bits_per_pixel != 8
+                || pcx.xmax >= 640
+                || pcx.ymax >= 480) {
+
+            VideoDriver.Printf(Defines.PRINT_ALL, "Bad pcx file " + filename + '\n');
+            return null;
+        }
+
+        int width = pcx.xmax - pcx.xmin + 1;
+        int height = pcx.ymax - pcx.ymin + 1;
+
+        byte[] pix = new byte[width * height];
+
+        if (palette != null) {
+            palette[0] = new byte[768];
+            System.arraycopy(raw, raw.length - 768, palette[0], 0, 768);
+        }
+
+        if (dim != null) {
+            dim.width = width;
+            dim.height = height;
+        }
+
+        //
+        // decode pcx
+        //
+        int count = 0;
+        byte dataByte = 0;
+        int runLength = 0;
+        int x, y;
+
+        for (y = 0; y < height; y++) {
+            for (x = 0; x < width; ) {
+
+                dataByte = pcx.data.get();
+
+                if ((dataByte & 0xC0) == 0xC0) {
+                    runLength = dataByte & 0x3F;
+                    dataByte = pcx.data.get();
+                    // write runLength pixel
+                    while (runLength-- > 0) {
+                        pix[count++] = dataByte;
+                        x++;
+                    }
+                } else {
+                    // write one pixel
+                    pix[count++] = dataByte;
+                    x++;
+                }
+            }
+        }
+        return pix;
+    }
+
+    // /*
+    // =========================================================
+    //
+    // TARGA LOADING
+    //
+    // =========================================================
+    // */
+    /*
+    =============
+       LoadTGA
+       =============
+       */
+    byte[] LoadTGA(String name, Dimension dim) {
+        int columns, rows, numPixels;
+        int pixbuf; // index into pic
+        int row, column;
+        byte[] raw;
+        ByteBuffer buf_p;
+        qfiles.tga_t targa_header;
+        byte[] pic = null;
+
+        //
+        // load the file
+        //
+        raw = FS.LoadFile(name);
+
+        if (raw == null) {
+            VideoDriver.Printf(Defines.PRINT_DEVELOPER, "Bad tga file " + name + '\n');
+            return null;
+        }
+
+        targa_header = new qfiles.tga_t(raw);
+
+        if (targa_header.image_type != 2 && targa_header.image_type != 10)
+            Com.Error(Defines.ERR_DROP, "LoadTGA: Only type 2 and 10 targa RGB images supported\n");
+
+        if (targa_header.colormap_type != 0 || (targa_header.pixel_size != 32 && targa_header.pixel_size != 24))
+            Com.Error(Defines.ERR_DROP, "LoadTGA: Only 32 or 24 bit images supported (no colormaps)\n");
+
+        columns = targa_header.width;
+        rows = targa_header.height;
+        numPixels = columns * rows;
+
+        if (dim != null) {
+            dim.width = columns;
+            dim.height = rows;
+        }
+
+        pic = new byte[numPixels * 4]; // targa_rgba;
+
+        if (targa_header.id_length != 0)
+            targa_header.data.position(targa_header.id_length);  // skip TARGA image comment
+
+        buf_p = targa_header.data;
+
+        byte red, green, blue, alphabyte;
+        red = green = blue = alphabyte = 0;
+        int packetHeader, packetSize, j;
+
+        if (targa_header.image_type == 2) {  // Uncompressed, RGB images
+            for (row = rows - 1; row >= 0; row--) {
+
+                pixbuf = row * columns * 4;
+
+                for (column = 0; column < columns; column++) {
+                    switch (targa_header.pixel_size) {
+                        case 24:
+
+                            blue = buf_p.get();
+                            green = buf_p.get();
+                            red = buf_p.get();
+                            pic[pixbuf++] = red;
+                            pic[pixbuf++] = green;
+                            pic[pixbuf++] = blue;
+                            pic[pixbuf++] = (byte) 255;
+                            break;
+                        case 32:
+                            blue = buf_p.get();
+                            green = buf_p.get();
+                            red = buf_p.get();
+                            alphabyte = buf_p.get();
+                            pic[pixbuf++] = red;
+                            pic[pixbuf++] = green;
+                            pic[pixbuf++] = blue;
+                            pic[pixbuf++] = alphabyte;
+                            break;
+                    }
+                }
+            }
+        } else if (targa_header.image_type == 10) {   // Runlength encoded RGB images
+            for (row = rows - 1; row >= 0; row--) {
+
+                pixbuf = row * columns * 4;
+                try {
+
+                    for (column = 0; column < columns; ) {
+
+                        packetHeader = buf_p.get() & 0xFF;
+                        packetSize = 1 + (packetHeader & 0x7f);
+
+                        if ((packetHeader & 0x80) != 0) {        // run-length packet
+                            switch (targa_header.pixel_size) {
+                                case 24:
+                                    blue = buf_p.get();
+                                    green = buf_p.get();
+                                    red = buf_p.get();
+                                    alphabyte = (byte) 255;
+                                    break;
+                                case 32:
+                                    blue = buf_p.get();
+                                    green = buf_p.get();
+                                    red = buf_p.get();
+                                    alphabyte = buf_p.get();
+                                    break;
+                            }
+
+                            for (j = 0; j < packetSize; j++) {
+                                pic[pixbuf++] = red;
+                                pic[pixbuf++] = green;
+                                pic[pixbuf++] = blue;
+                                pic[pixbuf++] = alphabyte;
+                                column++;
+                                if (column == columns) { // run spans across rows
+                                    column = 0;
+                                    if (row > 0)
+                                        row--;
+                                    else
+                                        // goto label breakOut;
+                                        throw gotoBreakOut;
+
+                                    pixbuf = row * columns * 4;
+                                }
+                            }
+                        } else { // non run-length packet
+                            for (j = 0; j < packetSize; j++) {
+                                switch (targa_header.pixel_size) {
+                                    case 24:
+                                        blue = buf_p.get();
+                                        green = buf_p.get();
+                                        red = buf_p.get();
+                                        pic[pixbuf++] = red;
+                                        pic[pixbuf++] = green;
+                                        pic[pixbuf++] = blue;
+                                        pic[pixbuf++] = (byte) 255;
+                                        break;
+                                    case 32:
+                                        blue = buf_p.get();
+                                        green = buf_p.get();
+                                        red = buf_p.get();
+                                        alphabyte = buf_p.get();
+                                        pic[pixbuf++] = red;
+                                        pic[pixbuf++] = green;
+                                        pic[pixbuf++] = blue;
+                                        pic[pixbuf++] = alphabyte;
+                                        break;
+                                }
+                                column++;
+                                if (column == columns) { // pixel packet run spans across rows
+                                    column = 0;
+                                    if (row > 0)
+                                        row--;
+                                    else
+                                        // goto label breakOut;
+                                        throw gotoBreakOut;
+
+                                    pixbuf = row * columns * 4;
+                                }
+                            }
+                        }
+                    }
+                } catch (Throwable e) {
+                    // label breakOut:
+                }
+            }
+        }
+        return pic;
+    }
+
+    // TODO check this: R_FloodFillSkin( byte[] skin, int skinwidth, int skinheight)
+    void R_FloodFillSkin(byte[] skin, int skinwidth, int skinheight) {
+        //             byte                            fillcolor = *skin; // assume this is the pixel to fill
+        int fillcolor = skin[0] & 0xff;
+//             floodfill_t[] fifo = new floodfill_t[FLOODFILL_FIFO_SIZE];
+        int inpt = 0, outpt = 0;
+        int filledcolor = -1;
+        int i;
+
+//             for (int j = 0; j < fifo.length; j++) {
+//                     fifo[j] = new floodfill_t();
+//             }
+
+        if (filledcolor == -1) {
+            filledcolor = 0;
+            // attempt to find opaque black
+            for (i = 0; i < 256; ++i)
+                // TODO check this
+                if (d_8to24table[i] == 0xFF000000) { // alpha 1.0
+                    //if (d_8to24table[i] == (255 << 0)) // alpha 1.0
+                    filledcolor = i;
+                    break;
+                }
+        }
+
+        // can't fill to filled color or to transparent color (used as visited marker)
+        if ((fillcolor == filledcolor) || (fillcolor == 255)) {
+            return;
+        }
+
+        fifo[inpt].x = 0;
+        fifo[inpt].y = 0;
+        inpt = (inpt + 1) & FLOODFILL_FIFO_MASK;
+
+        while (outpt != inpt) {
+            int x = fifo[outpt].x;
+            int y = fifo[outpt].y;
+            int fdc = filledcolor;
+            //                 byte            *pos = &skin[x + skinwidth * y];
+            int pos = x + skinwidth * y;
+            //
+            outpt = (outpt + 1) & FLOODFILL_FIFO_MASK;
+
+            int off, dx, dy;
+
+            if (x > 0) {
+                // FLOODFILL_STEP( -1, -1, 0 );
+                off = -1;
+                dx = -1;
+                dy = 0;
+                if (skin[pos + off] == (byte) fillcolor) {
+                    skin[pos + off] = (byte) 255;
+                    fifo[inpt].x = (short) (x + dx);
+                    fifo[inpt].y = (short) (y + dy);
+                    inpt = (inpt + 1) & FLOODFILL_FIFO_MASK;
+                } else if (skin[pos + off] != (byte) 255)
+                    fdc = skin[pos + off] & 0xff;
+            }
+
+            if (x < skinwidth - 1) {
+                // FLOODFILL_STEP( 1, 1, 0 );
+                off = 1;
+                dx = 1;
+                dy = 0;
+                if (skin[pos + off] == (byte) fillcolor) {
+                    skin[pos + off] = (byte) 255;
+                    fifo[inpt].x = (short) (x + dx);
+                    fifo[inpt].y = (short) (y + dy);
+                    inpt = (inpt + 1) & FLOODFILL_FIFO_MASK;
+                } else if (skin[pos + off] != (byte) 255)
+                    fdc = skin[pos + off] & 0xff;
+            }
+
+            if (y > 0) {
+                // FLOODFILL_STEP( -skinwidth, 0, -1 );
+                off = -skinwidth;
+                dx = 0;
+                dy = -1;
+                if (skin[pos + off] == (byte) fillcolor) {
+                    skin[pos + off] = (byte) 255;
+                    fifo[inpt].x = (short) (x + dx);
+                    fifo[inpt].y = (short) (y + dy);
+                    inpt = (inpt + 1) & FLOODFILL_FIFO_MASK;
+                } else if (skin[pos + off] != (byte) 255)
+                    fdc = skin[pos + off] & 0xff;
+            }
+
+            if (y < skinheight - 1) {
+                // FLOODFILL_STEP( skinwidth, 0, 1 );
+                off = skinwidth;
+                dx = 0;
+                dy = 1;
+                if (skin[pos + off] == (byte) fillcolor) {
+                    skin[pos + off] = (byte) 255;
+                    fifo[inpt].x = (short) (x + dx);
+                    fifo[inpt].y = (short) (y + dy);
+                    inpt = (inpt + 1) & FLOODFILL_FIFO_MASK;
+                } else if (skin[pos + off] != (byte) 255)
+                    fdc = skin[pos + off] & 0xff;
+
+            }
+
+            skin[x + skinwidth * y] = (byte) fdc;
+        }
+    }
+
+    /*
+    ================
+    GL_ResampleTexture
+    ================
+    */
+    // cwei :-)
+    void GL_ResampleTexture(int[] in, int inwidth, int inheight, int[] out, int outwidth, int outheight) {
+        //             int             i, j;
+        //             unsigned        *inrow, *inrow2;
+        //             int frac, fracstep;
+        //             int[] p1 = new int[1024];
+        //             int[] p2 = new int[1024];
+        //
+
+        // *** this source do the same ***
+        BufferedImage image = new BufferedImage(inwidth, inheight, BufferedImage.TYPE_INT_ARGB);
+
+        image.setRGB(0, 0, inwidth, inheight, in, 0, inwidth);
+
+        AffineTransformOp op =
+                new AffineTransformOp(
+                        AffineTransform.getScaleInstance(outwidth * 1.0 / inwidth, outheight * 1.0 / inheight),
+                        AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
+        BufferedImage tmp = op.filter(image, null);
+
+        tmp.getRGB(0, 0, outwidth, outheight, out, 0, outwidth);
+
+        // *** end ***
+
+        //             byte            *pix1, *pix2, *pix3, *pix4;
+        //
+        //             fracstep = inwidth*0x10000/outwidth;
+        //
+        //             frac = fracstep>>2;
+        //             for (i=0 ; i<outwidth ; i++)
+        //             {
+        //                     p1[i] = 4*(frac>>16);
+        //                     frac += fracstep;
+        //             }
+        //             frac = 3*(fracstep>>2);
+        //             for (i=0 ; i<outwidth ; i++)
+        //             {
+        //                     p2[i] = 4*(frac>>16);
+        //                     frac += fracstep;
+        //             }
+        //
+        //             for (i=0 ; i<outheight ; i++, out += outwidth)
+        //             {
+        //                     inrow = in + inwidth*(int)((i+0.25)*inheight/outheight);
+        //                     inrow2 = in + inwidth*(int)((i+0.75)*inheight/outheight);
+        //                     frac = fracstep >> 1;
+        //                     for (j=0 ; j<outwidth ; j++)
+        //                     {
+        //                             pix1 = (byte *)inrow + p1[j];
+        //                             pix2 = (byte *)inrow + p2[j];
+        //                             pix3 = (byte *)inrow2 + p1[j];
+        //                             pix4 = (byte *)inrow2 + p2[j];
+        //                             ((byte *)(out+j))[0] = (pix1[0] + pix2[0] + pix3[0] + pix4[0])>>2;
+        //                             ((byte *)(out+j))[1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1])>>2;
+        //                             ((byte *)(out+j))[2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2])>>2;
+        //                             ((byte *)(out+j))[3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3])>>2;
+        //                     }
+        //             }
+    }
+
+    /*
+    ================
+    GL_LightScaleTexture
+
+    Scale up the pixel values in a texture to increase the
+    lighting range
+    ================
+    */
+    void GL_LightScaleTexture(int[] in, int inwidth, int inheight, boolean only_gamma) {
+        if (only_gamma) {
+            int i, c;
+            int r, g, b, color;
+
+            c = inwidth * inheight;
+            for (i = 0; i < c; i++) {
+                color = in[i];
+                r = (color) & 0xFF;
+                g = (color >> 8) & 0xFF;
+                b = (color >> 16) & 0xFF;
+
+                r = gammatable[r] & 0xFF;
+                g = gammatable[g] & 0xFF;
+                b = gammatable[b] & 0xFF;
+
+                in[i] = (r) | (g << 8) | (b << 16) | (color & 0xFF000000);
+            }
+        } else {
+            int i, c;
+            int r, g, b, color;
+
+            c = inwidth * inheight;
+            for (i = 0; i < c; i++) {
+                color = in[i];
+                r = (color) & 0xFF;
+                g = (color >> 8) & 0xFF;
+                b = (color >> 16) & 0xFF;
+
+                r = gammatable[intensitytable[r] & 0xFF] & 0xFF;
+                g = gammatable[intensitytable[g] & 0xFF] & 0xFF;
+                b = gammatable[intensitytable[b] & 0xFF] & 0xFF;
+
+                in[i] = (r) | (g << 8) | (b << 16) | (color & 0xFF000000);
+            }
+
+        }
+    }
+
+    /*
+    ================
+    GL_MipMap
+
+    Operates in place, quartering the size of the texture
+    ================
+    */
+    void GL_MipMap(int[] in, int width, int height) {
+        int i, j;
+        int[] out;
+
+        out = in;
+
+        int inIndex = 0;
+        int outIndex = 0;
+
+        int r, g, b, a;
+        int p1, p2, p3, p4;
+
+        for (i = 0; i < height; i += 2, inIndex += width) {
+            for (j = 0; j < width; j += 2, outIndex += 1, inIndex += 2) {
+
+                p1 = in[inIndex];
+                p2 = in[inIndex + 1];
+                p3 = in[inIndex + width];
+                p4 = in[inIndex + width + 1];
+
+                r = (((p1) & 0xFF) + ((p2) & 0xFF) + ((p3) & 0xFF) + ((p4) & 0xFF)) >> 2;
+                g = (((p1 >> 8) & 0xFF) + ((p2 >> 8) & 0xFF) + ((p3 >> 8) & 0xFF) + ((p4 >> 8) & 0xFF)) >> 2;
+                b = (((p1 >> 16) & 0xFF) + ((p2 >> 16) & 0xFF) + ((p3 >> 16) & 0xFF) + ((p4 >> 16) & 0xFF)) >> 2;
+                a = (((p1 >> 24) & 0xFF) + ((p2 >> 24) & 0xFF) + ((p3 >> 24) & 0xFF) + ((p4 >> 24) & 0xFF)) >> 2;
+
+                out[outIndex] = (r) | (g << 8) | (b << 16) | (a << 24);
+            }
+        }
+    }
+
+    /*
+    ===============
+    GL_Upload32
+
+    Returns has_alpha
+    ===============
+    */
+    void GL_BuildPalettedTexture(ByteBuffer paletted_texture, int[] scaled, int scaled_width, int scaled_height) {
+
+        int r, g, b, c;
+        int size = scaled_width * scaled_height;
+
+        for (int i = 0; i < size; i++) {
+
+            r = (scaled[i] >> 3) & 31;
+            g = (scaled[i] >> 10) & 63;
+            b = (scaled[i] >> 19) & 31;
+
+            c = r | (g << 5) | (b << 11);
+
+            paletted_texture.put(i, gl_state.d_16to8table[c]);
+        }
+    }
+
+    boolean GL_Upload32(int[] data, int width, int height, boolean mipmap) {
+        int samples;
+        int scaled_width, scaled_height;
+        int i, c;
+        int comp;
+
+        Arrays.fill(scaled, 0);
+        // Arrays.fill(paletted_texture, (byte)0);
+        paletted_texture.clear();
+        for (int j = 0; j < 256 * 256; j++) paletted_texture.put(j, (byte) 0);
+
+        uploaded_paletted = false;
+
+        for (scaled_width = 1; scaled_width < width; scaled_width <<= 1) ;
+        if (gl_round_down.value > 0.0f && scaled_width > width && mipmap)
+            scaled_width >>= 1;
+        for (scaled_height = 1; scaled_height < height; scaled_height <<= 1) ;
+        if (gl_round_down.value > 0.0f && scaled_height > height && mipmap)
+            scaled_height >>= 1;
+
+        // let people sample down the world textures for speed
+        if (mipmap) {
+            scaled_width >>= (int) gl_picmip.value;
+            scaled_height >>= (int) gl_picmip.value;
+        }
+
+        // don't ever bother with >256 textures
+        if (scaled_width > 256)
+            scaled_width = 256;
+        if (scaled_height > 256)
+            scaled_height = 256;
+
+        if (scaled_width < 1)
+            scaled_width = 1;
+        if (scaled_height < 1)
+            scaled_height = 1;
+
+        upload_width = scaled_width;
+        upload_height = scaled_height;
+
+        if (scaled_width * scaled_height > 256 * 256)
+            Com.Error(Defines.ERR_DROP, "GL_Upload32: too big");
+
+        // scan the texture for any non-255 alpha
+        c = width * height;
+        samples = gl_solid_format;
+
+        for (i = 0; i < c; i++) {
+            if ((data[i] & 0xff000000) != 0xff000000) {
+                samples = gl_alpha_format;
+                break;
+            }
+        }
+
+        if (samples == gl_solid_format)
+            comp = gl_tex_solid_format;
+        else if (samples == gl_alpha_format)
+            comp = gl_tex_alpha_format;
+        else {
+            VideoDriver.Printf(Defines.PRINT_ALL, "Unknown number of texture components " + samples + '\n');
+            comp = samples;
+        }
+
+        // simulates a goto
+        try {
+            if (scaled_width == width && scaled_height == height) {
+                if (!mipmap) {
+                    if (qglColorTableEXT && gl_ext_palettedtexture.value != 0.0f && samples == gl_solid_format) {
+                        uploaded_paletted = true;
+                        GL_BuildPalettedTexture(paletted_texture, data, scaled_width, scaled_height);
+                        GL11.glTexImage2D(
+                                GL11.GL_TEXTURE_2D,
+                                0,
+                                GL_COLOR_INDEX8_EXT,
+                                scaled_width,
+                                scaled_height,
+                                0,
+                                GL11.GL_COLOR_INDEX,
+                                GL11.GL_UNSIGNED_BYTE,
+                                paletted_texture);
+                    } else {
+                        tex.rewind();
+                        tex.put(data);
+                        tex.rewind();
+                        GL11.glTexImage2D(
+                                GL11.GL_TEXTURE_2D,
+                                0,
+                                comp,
+                                scaled_width,
+                                scaled_height,
+                                0,
+                                GL11.GL_RGBA,
+                                GL11.GL_UNSIGNED_BYTE,
+                                tex);
+                    }
+                    //goto done;
+                    throw gotoDone;
+                }
+                //memcpy (scaled, data, width*height*4); were bytes
+                System.arraycopy(data, 0, scaled, 0, width * height);
+            } else
+                GL_ResampleTexture(data, width, height, scaled, scaled_width, scaled_height);
+
+            GL_LightScaleTexture(scaled, scaled_width, scaled_height, !mipmap);
+
+            if (qglColorTableEXT && gl_ext_palettedtexture.value != 0.0f && (samples == gl_solid_format)) {
+                uploaded_paletted = true;
+                GL_BuildPalettedTexture(paletted_texture, scaled, scaled_width, scaled_height);
+                GL11.glTexImage2D(
+                        GL11.GL_TEXTURE_2D,
+                        0,
+                        GL_COLOR_INDEX8_EXT,
+                        scaled_width,
+                        scaled_height,
+                        0,
+                        GL11.GL_COLOR_INDEX,
+                        GL11.GL_UNSIGNED_BYTE,
+                        paletted_texture);
+            } else {
+                tex.rewind();
+                tex.put(scaled);
+                tex.rewind();
+                GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, comp, scaled_width, scaled_height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, tex);
+            }
+
+            if (mipmap) {
+                int miplevel;
+                miplevel = 0;
+                while (scaled_width > 1 || scaled_height > 1) {
+                    GL_MipMap(scaled, scaled_width, scaled_height);
+                    scaled_width >>= 1;
+                    scaled_height >>= 1;
+                    if (scaled_width < 1)
+                        scaled_width = 1;
+                    if (scaled_height < 1)
+                        scaled_height = 1;
+
+                    miplevel++;
+                    if (qglColorTableEXT && gl_ext_palettedtexture.value != 0.0f && samples == gl_solid_format) {
+                        uploaded_paletted = true;
+                        GL_BuildPalettedTexture(paletted_texture, scaled, scaled_width, scaled_height);
+                        GL11.glTexImage2D(
+                                GL11.GL_TEXTURE_2D,
+                                miplevel,
+                                GL_COLOR_INDEX8_EXT,
+                                scaled_width,
+                                scaled_height,
+                                0,
+                                GL11.GL_COLOR_INDEX,
+                                GL11.GL_UNSIGNED_BYTE,
+                                paletted_texture);
+                    } else {
+                        tex.rewind();
+                        tex.put(scaled);
+                        tex.rewind();
+                        GL11.glTexImage2D(
+                                GL11.GL_TEXTURE_2D,
+                                miplevel,
+                                comp,
+                                scaled_width,
+                                scaled_height,
+                                0,
+                                GL11.GL_RGBA,
+                                GL11.GL_UNSIGNED_BYTE,
+                                tex);
+                    }
+                }
+            }
+            // label done:
+        } catch (Throwable e) {
+            // replaces label done
+        }
+
+        if (mipmap) {
+            GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, gl_filter_min);
+            GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, gl_filter_max);
+        } else {
+            GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, gl_filter_max);
+            GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, gl_filter_max);
+        }
+
+        return (samples == gl_alpha_format);
+    }
+
+    boolean GL_Upload8(byte[] data, int width, int height, boolean mipmap, boolean is_sky) {
+
+        Arrays.fill(trans, 0);
+
+        int s = width * height;
+
+        if (s > trans.length)
+            Com.Error(Defines.ERR_DROP, "GL_Upload8: too large");
+
+        if (qglColorTableEXT && gl_ext_palettedtexture.value != 0.0f && is_sky) {
+            GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL_COLOR_INDEX8_EXT, width, height, 0, GL11.GL_COLOR_INDEX, GL11.GL_UNSIGNED_BYTE, ByteBuffer.wrap(data));
+
+            GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, gl_filter_max);
+            GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, gl_filter_max);
+
+            // TODO check this
+            return false;
+        } else {
+            int p;
+            for (int i = 0; i < s; i++) {
+                p = data[i] & 0xff;
+                trans[i] = d_8to24table[p];
+
+                if (p == 255) { // transparent, so scan around for another color
+                    // to avoid alpha fringes
+                    // FIXME: do a full flood fill so mips work...
+                    if (i > width && (data[i - width] & 0xff) != 255)
+                        p = data[i - width] & 0xff;
+                    else if (i < s - width && (data[i + width] & 0xff) != 255)
+                        p = data[i + width] & 0xff;
+                    else if (i > 0 && (data[i - 1] & 0xff) != 255)
+                        p = data[i - 1] & 0xff;
+                    else if (i < s - 1 && (data[i + 1] & 0xff) != 255)
+                        p = data[i + 1] & 0xff;
+                    else
+                        p = 0;
+                    // copy rgb components
+
+                    // ((byte *)&trans[i])[0] = ((byte *)&d_8to24table[p])[0];
+                    // ((byte *)&trans[i])[1] = ((byte *)&d_8to24table[p])[1];
+                    // ((byte *)&trans[i])[2] = ((byte *)&d_8to24table[p])[2];
+
+                    trans[i] = d_8to24table[p] & 0x00FFFFFF; // only rgb
+                }
+            }
+
+            return GL_Upload32(trans, width, height, mipmap);
+        }
+    }
+
+    /*
+    ================
+    GL_LoadPic
+
+    This is also used as an entry point for the generated r_notexture
+    ================
+    */
+    lwjake2.render.Image GL_LoadPic(String name, byte[] pic, int width, int height, int type, int bits) {
+        lwjake2.render.Image image;
+        int i;
+
+        // find a free image_t
+        for (i = 0; i < numgltextures; i++) {
+            image = gltextures[i];
+            if (image.texnum == 0)
+                break;
+        }
+
+        if (i == numgltextures) {
+            if (numgltextures == MAX_GLTEXTURES)
+                Com.Error(Defines.ERR_DROP, "MAX_GLTEXTURES");
+
+            numgltextures++;
+        }
+        image = gltextures[i];
+
+        if (name.length() > Defines.MAX_QPATH)
+            Com.Error(Defines.ERR_DROP, "Draw_LoadPic: \"" + name + "\" is too long");
+
+        image.name = name;
+        image.registration_sequence = registration_sequence;
+
+        image.width = width;
+        image.height = height;
+        image.type = type;
+
+
+        if (type == it_skin && bits == 8)
+            R_FloodFillSkin(pic, width, height);
+
+        // load little pics into the scrap
+        if (image.type == it_pic && bits == 8 && image.width < 64 && image.height < 64) {
+            pos_t pos = new pos_t(0, 0);
+            int j, k;
+
+            int texnum = Scrap_AllocBlock(image.width, image.height, pos);
+
+            if (texnum == -1) {
+                // replace goto nonscrap
+
+                image.scrap = false;
+
+                image.texnum = TEXNUM_IMAGES + image.getId(); // image pos in array
+                GL_Bind(image.texnum);
+
+                if (bits == 8) {
+                    image.has_alpha =
+                            GL_Upload8(pic, width, height, (image.type != it_pic && image.type != it_sky), image.type == it_sky);
+                } else {
+                    int[] tmp = new int[pic.length / 4];
+
+                    for (i = 0; i < tmp.length; i++) {
+                        tmp[i] = ((pic[4 * i] & 0xFF)); // & 0x000000FF;
+                        tmp[i] |= ((pic[4 * i + 1] & 0xFF) << 8); // & 0x0000FF00;
+                        tmp[i] |= ((pic[4 * i + 2] & 0xFF) << 16); // & 0x00FF0000;
+                        tmp[i] |= ((pic[4 * i + 3] & 0xFF) << 24); // & 0xFF000000;
+                    }
+
+                    image.has_alpha = GL_Upload32(tmp, width, height, (image.type != it_pic && image.type != it_sky));
+                }
+
+                image.upload_width = upload_width; // after power of 2 and scales
+                image.upload_height = upload_height;
+                image.paletted = uploaded_paletted;
+                image.sl = 0;
+                image.sh = 1;
+                image.tl = 0;
+                image.th = 1;
+
+                return image;
+            }
+
+            scrap_dirty = true;
+
+            // copy the texels into the scrap block
+            k = 0;
+            for (i = 0; i < image.height; i++)
+                for (j = 0; j < image.width; j++, k++)
+                    scrap_texels[texnum][(pos.y + i) * BLOCK_WIDTH + pos.x + j] = pic[k];
+
+            image.texnum = TEXNUM_SCRAPS + texnum;
+            image.scrap = true;
+            image.has_alpha = true;
+            image.sl = (pos.x + 0.01f) / (float) BLOCK_WIDTH;
+            image.sh = (pos.x + image.width - 0.01f) / (float) BLOCK_WIDTH;
+            image.tl = (pos.y + 0.01f) / (float) BLOCK_WIDTH;
+            image.th = (pos.y + image.height - 0.01f) / (float) BLOCK_WIDTH;
+
+        } else {
+            // this was label nonscrap
+
+            image.scrap = false;
+
+            image.texnum = TEXNUM_IMAGES + image.getId(); //image pos in array
+            GL_Bind(image.texnum);
+
+            if (bits == 8) {
+                image.has_alpha = GL_Upload8(pic, width, height, (image.type != it_pic && image.type != it_sky), image.type == it_sky);
+            } else {
+                int[] tmp = new int[pic.length / 4];
+
+                for (i = 0; i < tmp.length; i++) {
+                    tmp[i] = ((pic[4 * i] & 0xFF)); // & 0x000000FF;
+                    tmp[i] |= ((pic[4 * i + 1] & 0xFF) << 8); // & 0x0000FF00;
+                    tmp[i] |= ((pic[4 * i + 2] & 0xFF) << 16); // & 0x00FF0000;
+                    tmp[i] |= ((pic[4 * i + 3] & 0xFF) << 24); // & 0xFF000000;
+                }
+
+                image.has_alpha = GL_Upload32(tmp, width, height, (image.type != it_pic && image.type != it_sky));
+            }
+            image.upload_width = upload_width; // after power of 2 and scales
+            image.upload_height = upload_height;
+            image.paletted = uploaded_paletted;
+            image.sl = 0;
+            image.sh = 1;
+            image.tl = 0;
+            image.th = 1;
+        }
+        return image;
+    }
+
+       /*
+    ===============
+       GL_Upload8
+       
+       Returns has_alpha
+       ===============
+       */
+
+    /*
+    ================
+    GL_LoadWal
+    ================
+    */
+    lwjake2.render.Image GL_LoadWal(String name) {
+
+        lwjake2.render.Image image = null;
+
+        byte[] raw = FS.LoadFile(name);
+        if (raw == null) {
+            VideoDriver.Printf(Defines.PRINT_ALL, "GL_FindImage: can't load " + name + '\n');
+            return r_notexture;
+        }
+
+        qfiles.miptex_t mt = new qfiles.miptex_t(raw);
+
+        byte[] pix = new byte[mt.width * mt.height];
+        System.arraycopy(raw, mt.offsets[0], pix, 0, pix.length);
+
+        image = GL_LoadPic(name, pix, mt.width, mt.height, it_wall, 8);
+
+        return image;
+    }
+
+    /*
+    ===============
+    GL_FindImage
+
+    Finds or loads the given image
+    ===============
+    */
+    lwjake2.render.Image GL_FindImage(String name, int type) {
+        lwjake2.render.Image image = null;
+
+//             // TODO loest das grossschreibungs problem
+//             name = name.toLowerCase();
+//             // bughack for bad strings (fuck \0)
+//             int index = name.indexOf('\0');
+//             if (index != -1)
+//                     name = name.substring(0, index);
+
+        if (name == null || name.length() < 5)
+            return null; //    Com.Error (ERR_DROP, "GL_FindImage: NULL name");
+        //     Com.Error (ERR_DROP, "GL_FindImage: bad name: %s", name);
+
+        // look for it
+        for (int i = 0; i < numgltextures; i++) {
+            image = gltextures[i];
+            if (name.equals(image.name)) {
+                image.registration_sequence = registration_sequence;
+                return image;
+            }
+        }
+
+        //
+        // load the pic from disk
+        //
+        image = null;
+        byte[] pic = null;
+        Dimension dim = new Dimension();
+
+        if (name.endsWith(".pcx")) {
+
+            pic = LoadPCX(name, null, dim);
+            if (pic == null)
+                return null;
+            image = GL_LoadPic(name, pic, dim.width, dim.height, type, 8);
+
+        } else if (name.endsWith(".wal")) {
+
+            image = GL_LoadWal(name);
+
+        } else if (name.endsWith(".tga")) {
+
+            pic = LoadTGA(name, dim);
+
+            if (pic == null)
+                return null;
+
+            image = GL_LoadPic(name, pic, dim.width, dim.height, type, 32);
+
+        }
+
+        return image;
+    }
+
+    /*
+    ===============
+    R_RegisterSkin
+    ===============
+    */
+    protected lwjake2.render.Image R_RegisterSkin(String name) {
+        return GL_FindImage(name, it_skin);
+    }
+
+    /*
+    ================
+    GL_FreeUnusedImages
+
+    Any image that was not touched on this registration sequence
+    will be freed.
+    ================
+    */
+    void GL_FreeUnusedImages() {
+
+        // never free r_notexture or particle texture
+        r_notexture.registration_sequence = registration_sequence;
+        r_particletexture.registration_sequence = registration_sequence;
+
+        lwjake2.render.Image image = null;
+
+        for (int i = 0; i < numgltextures; i++) {
+            image = gltextures[i];
+            // used this sequence
+            if (image.registration_sequence == registration_sequence)
+                continue;
+            // free image_t slot
+            if (image.registration_sequence == 0)
+                continue;
+            // don't free pics
+            if (image.type == it_pic)
+                continue;
+
+            // free it
+            // TODO jogl bug
+            texnumBuffer.clear();
+            texnumBuffer.put(0, image.texnum);
+            GL11.glDeleteTextures(texnumBuffer);
+            image.clear();
+        }
+    }
+
+    /*
+    ===============
+    Draw_GetPalette
+    ===============
+    */
+    protected void Draw_GetPalette() {
+        int r, g, b;
+        byte[][] palette = new byte[1][]; //new byte[768];
+
+        // get the palette
+
+        LoadPCX("pics/colormap.pcx", palette, new Dimension());
+
+        if (palette[0] == null || palette[0].length != 768)
+            Com.Error(Defines.ERR_FATAL, "Couldn't load pics/colormap.pcx");
+
+        byte[] pal = palette[0];
+
+        int j = 0;
+        for (int i = 0; i < 256; i++) {
+            r = pal[j++] & 0xFF;
+            g = pal[j++] & 0xFF;
+            b = pal[j++] & 0xFF;
+
+            d_8to24table[i] = (255 << 24) | (b << 16) | (g << 8) | (r);
+        }
+
+        d_8to24table[255] &= 0x00FFFFFF; // 255 is transparent
+
+        particle_t.setColorPalette(d_8to24table);
+    }
+
+    /*
+    ===============
+    GL_InitImages
+    ===============
+    */
+    void GL_InitImages() {
+        int i, j;
+        float g = vid_gamma.value;
+
+        registration_sequence = 1;
+
+        // init intensity conversions
+        intensity = Cvar.get("intensity", "2", 0);
+
+        if (intensity.value <= 1)
+            Cvar.set("intensity", "1");
+
+        gl_state.inverse_intensity = 1 / intensity.value;
+
+        Draw_GetPalette();
+
+        if (qglColorTableEXT) {
+            gl_state.d_16to8table = FS.LoadFile("pics/16to8.dat");
+            if (gl_state.d_16to8table == null)
+                Com.Error(Defines.ERR_FATAL, "Couldn't load pics/16to8.pcx");
+        }
+
+        if ((gl_config.renderer & (GL_RENDERER_VOODOO | GL_RENDERER_VOODOO2)) != 0) {
+            g = 1.0F;
+        }
+
+        for (i = 0; i < 256; i++) {
+
+            if (g == 1.0f) {
+                gammatable[i] = (byte) i;
+            } else {
+
+                int inf = (int) (255.0f * Math.pow((i + 0.5) / 255.5, g) + 0.5);
+                if (inf < 0)
+                    inf = 0;
+                if (inf > 255)
+                    inf = 255;
+                gammatable[i] = (byte) inf;
+            }
+        }
+
+        for (i = 0; i < 256; i++) {
+            j = (int) (i * intensity.value);
+            if (j > 255)
+                j = 255;
+            intensitytable[i] = (byte) j;
+        }
+    }
+
+    /*
+    ===============
+    GL_ShutdownImages
+    ===============
+    */
+    void GL_ShutdownImages() {
+        lwjake2.render.Image image;
+
+        for (int i = 0; i < numgltextures; i++) {
+            image = gltextures[i];
+
+            if (image.registration_sequence == 0)
+                continue; // free image_t slot
+            // free it
+            // TODO jogl bug
+            texnumBuffer.clear();
+            texnumBuffer.put(0, image.texnum);
+            GL11.glDeleteTextures(texnumBuffer);
+            image.clear();
+        }
+    }
+
+    // glmode_t
+    static class glmode_t {
+        final String name;
+        final int minimize;
+        final int maximize;
+
+        glmode_t(String name, int minimize, int maximze) {
+            this.name = name;
+            this.minimize = minimize;
+            this.maximize = maximze;
+        }
+    }
+
+    // gltmode_t
+    static class gltmode_t {
+        final String name;
+        final int mode;
+
+        gltmode_t(String name, int mode) {
+            this.name = name;
+            this.mode = mode;
+        }
+    }
+
+    static class pos_t {
+        int x, y;
+
+        pos_t(int x, int y) {
+            this.x = x;
+            this.y = y;
+        }
+    }
+
+    static class floodfill_t {
+        short x, y;
+    }
+
+}
diff --git a/src/main/java/lwjake2/render/lwjgl/LWJGLBase.java b/src/main/java/lwjake2/render/lwjgl/LWJGLBase.java
new file mode 100644 (file)
index 0000000..e0d562e
--- /dev/null
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render.lwjgl;
+
+import lwjake2.Defines;
+import lwjake2.client.VideoDriver;
+import lwjake2.client.viddef_t;
+import lwjake2.game.CvarT;
+import lwjake2.qcommon.xcommand_t;
+import org.lwjgl.LWJGLException;
+import org.lwjgl.opengl.Display;
+import org.lwjgl.opengl.DisplayMode;
+import org.lwjgl.opengl.GL11;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.LinkedList;
+
+/**
+ * LWJGLBase
+ *
+ * @author dsanders/cwei
+ */
+public abstract class LWJGLBase {
+    // enum rserr_t
+    protected static final int rserr_ok = 0;
+    protected static final int rserr_invalid_fullscreen = 1;
+    protected static final int rserr_invalid_mode = 2;
+    protected static final int rserr_unknown = 3;
+    protected final viddef_t vid = new viddef_t();
+    // IMPORTED FUNCTIONS
+    protected DisplayMode oldDisplayMode;
+    protected CvarT vid_fullscreen;
+    // window position on the screen
+    int window_xpos, window_ypos;
+
+    // handles the post initialization with LWJGLRenderer
+    protected abstract boolean R_Init2();
+
+    private java.awt.DisplayMode toAwtDisplayMode(DisplayMode m) {
+        return new java.awt.DisplayMode(m.getWidth(), m.getHeight(), m.getBitsPerPixel(), m.getFrequency());
+    }
+
+    public java.awt.DisplayMode[] getModeList() {
+        try {
+            DisplayMode[] modes;
+
+            modes = Display.getAvailableDisplayModes();
+
+            LinkedList<java.awt.DisplayMode> l = new LinkedList<>();
+            l.add(toAwtDisplayMode(oldDisplayMode));
+
+            for (DisplayMode m : modes) {
+                if (m.getBitsPerPixel() != oldDisplayMode.getBitsPerPixel()) continue;
+                if (m.getFrequency() > oldDisplayMode.getFrequency()) continue;
+                if (m.getHeight() < 240 || m.getWidth() < 320) continue;
+
+                int j = 0;
+                java.awt.DisplayMode ml = null;
+                for (j = 0; j < l.size(); j++) {
+                    ml = l.get(j);
+                    if (ml.getWidth() > m.getWidth()) break;
+                    if (ml.getWidth() == m.getWidth() && ml.getHeight() >= m.getHeight()) break;
+                }
+                if (j == l.size()) {
+                    l.addLast(toAwtDisplayMode(m));
+                } else if (ml.getWidth() > m.getWidth() || ml.getHeight() > m.getHeight()) {
+                    l.add(j, toAwtDisplayMode(m));
+                } else if (m.getFrequency() > ml.getRefreshRate()) {
+                    l.remove(j);
+                    l.add(j, toAwtDisplayMode(m));
+                }
+            }
+            java.awt.DisplayMode[] ma = new java.awt.DisplayMode[l.size()];
+            l.toArray(ma);
+            return ma;
+        } catch (LWJGLException e) {
+            e.printStackTrace();
+            System.exit(0);
+        }
+        return null;
+    }
+
+    public DisplayMode[] getLWJGLModeList() {
+        try {
+            // Return value storage.
+            ArrayList<DisplayMode> displayModes;
+
+            // Get all possible display modes.
+            DisplayMode[] allDisplayModes = Display.getAvailableDisplayModes();
+
+            // Cut down all the ones with a height below 240.
+            displayModes = new ArrayList<>();
+            for (DisplayMode allDisplayMode : allDisplayModes) {
+                if (allDisplayMode.getHeight() >= 240)
+                    displayModes.add(allDisplayMode);
+            }
+
+            // Gnome sort the display modes by height, width, and refresh rate.
+            int currentSpot = 0;
+            boolean needSwap = false;
+            DisplayMode tempStore;
+            while (currentSpot < displayModes.size() - 1) {
+                // Check DisplayMode heights.
+                if (displayModes.get(currentSpot).getHeight() > displayModes.get(currentSpot + 1).getHeight())
+                    needSwap = true;
+                else if (displayModes.get(currentSpot).getHeight() == displayModes.get(currentSpot + 1).getHeight()) {
+                    // Check DisplayMode widths.
+                    if (displayModes.get(currentSpot).getWidth() > displayModes.get(currentSpot + 1).getWidth())
+                        needSwap = true;
+                    else if (displayModes.get(currentSpot).getWidth() == displayModes.get(currentSpot + 1).getWidth())
+                        // Doesn't sort frequencies, but removes the lesser ones entirely.
+                        if (displayModes.get(currentSpot).getFrequency() < displayModes.get(currentSpot + 1).getFrequency()) {
+                            displayModes.remove(currentSpot);
+                            currentSpot--;
+                        } else if (displayModes.get(currentSpot).getFrequency() > displayModes.get(currentSpot + 1).getFrequency()) {
+                            displayModes.remove(currentSpot + 1);
+                            currentSpot--;
+                        }
+                }
+                if (needSwap) {
+                    needSwap = false;
+                    tempStore = displayModes.get(currentSpot);
+                    displayModes.set(currentSpot, displayModes.get(currentSpot + 1));
+                    displayModes.set(currentSpot + 1, tempStore);
+                    if (currentSpot > 0)
+                        currentSpot--;
+                } else
+                    currentSpot++;
+            }
+
+            // Return the array.
+            return displayModes.toArray(new DisplayMode[displayModes.size()]);
+
+        } catch (LWJGLException e) {
+            e.printStackTrace();
+            System.exit(0);
+        }
+        return null;
+    }
+
+    private DisplayMode findDisplayMode(Dimension dim) {
+        DisplayMode mode = null;
+        DisplayMode m = null;
+        DisplayMode[] modes = getLWJGLModeList();
+        int w = dim.width;
+        int h = dim.height;
+
+        for (DisplayMode mode1 : modes) {
+            m = mode1;
+            if (m.getWidth() == w && m.getHeight() == h) {
+                mode = m;
+                break;
+            }
+        }
+        if (mode == null) mode = oldDisplayMode;
+        return mode;
+    }
+
+    String getModeString(DisplayMode m) {
+        return String.valueOf(m.getWidth()) +
+                'x' +
+                m.getHeight() +
+                'x' +
+                m.getBitsPerPixel() +
+                '@' +
+                m.getFrequency() +
+                "Hz";
+    }
+
+    /**
+     * @param dim
+     * @param mode
+     * @param fullscreen
+     * @return enum rserr_t
+     */
+    protected int GLimp_SetMode(Dimension dim, int mode, boolean fullscreen) {
+
+        Dimension newDim = new Dimension();
+
+        VideoDriver.Printf(Defines.PRINT_ALL, "Initializing OpenGL display\n");
+
+        VideoDriver.Printf(Defines.PRINT_ALL, "...setting mode " + mode + ":");
+
+               /*
+         * fullscreen handling
+                */
+        if (oldDisplayMode == null) {
+            oldDisplayMode = Display.getDisplayMode();
+        }
+
+        if (!VideoDriver.GetModeInfo(newDim, mode)) {
+            VideoDriver.Printf(Defines.PRINT_ALL, " invalid mode\n");
+            return rserr_invalid_mode;
+        }
+
+        VideoDriver.Printf(Defines.PRINT_ALL, " " + newDim.width + " " + newDim.height + '\n');
+
+        // destroy the existing window
+        GLimp_Shutdown();
+
+        Display.setTitle("LWJake2");
+
+        DisplayMode displayMode = findDisplayMode(newDim);
+        newDim.width = displayMode.getWidth();
+        newDim.height = displayMode.getHeight();
+
+        if (fullscreen) {
+            try {
+                Display.setDisplayMode(displayMode);
+            } catch (LWJGLException e) {
+                return rserr_invalid_mode;
+            }
+
+            Display.setLocation(0, 0);
+
+            try {
+                Display.setFullscreen(true);
+            } catch (LWJGLException e) {
+                return rserr_invalid_fullscreen;
+            }
+
+            VideoDriver.Printf(Defines.PRINT_ALL, "...setting fullscreen " + getModeString(displayMode) + '\n');
+
+        } else {
+            try {
+                Display.setFullscreen(false);
+            } catch (LWJGLException e) {
+                return rserr_invalid_fullscreen;
+            }
+
+            try {
+                Display.setDisplayMode(displayMode);
+            } catch (LWJGLException e) {
+                return rserr_invalid_mode;
+            }
+            Display.setLocation(window_xpos, window_ypos);
+        }
+
+        vid.width = newDim.width;
+        vid.height = newDim.height;
+
+        try {
+            Display.create();
+        } catch (LWJGLException e) {
+            return rserr_unknown;
+        }
+
+        // let the sound and input subsystems know about the new window
+        VideoDriver.NewWindow(vid.width, vid.height);
+        return rserr_ok;
+    }
+
+    protected void GLimp_Shutdown() {
+        if (oldDisplayMode != null && Display.isFullscreen()) {
+            try {
+                Display.setDisplayMode(oldDisplayMode);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+
+        while (Display.isCreated()) {
+            Display.destroy();
+        }
+    }
+
+    /**
+     * @return true
+     */
+    protected boolean GLimp_Init(int xpos, int ypos) {
+        // do nothing
+        window_xpos = xpos;
+        window_ypos = ypos;
+        return true;
+    }
+
+    protected void GLimp_EndFrame() {
+        GL11.glFlush();
+        // swap buffers
+        Display.update();
+    }
+
+    protected void GLimp_BeginFrame(float camera_separation) {
+        // do nothing
+    }
+
+    protected void GLimp_AppActivate(boolean activate) {
+        // do nothing
+    }
+
+    protected void GLimp_EnableLogging(boolean enable) {
+        // do nothing
+    }
+
+    protected void GLimp_LogNewFrame() {
+        // do nothing
+    }
+
+    /**
+     * this is a hack for jogl renderers.
+     *
+     * @param callback
+     */
+    public final void updateScreen(xcommand_t callback) {
+        callback.execute();
+    }
+}
diff --git a/src/main/java/lwjake2/render/lwjgl/Light.java b/src/main/java/lwjake2/render/lwjgl/Light.java
new file mode 100644 (file)
index 0000000..d29d1a0
--- /dev/null
@@ -0,0 +1,714 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render.lwjgl;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.client.dlight_t;
+import lwjake2.game.cplane_t;
+import lwjake2.qcommon.Com;
+import lwjake2.render.mnode_t;
+import lwjake2.render.msurface_t;
+import lwjake2.render.mtexinfo_t;
+import lwjake2.util.Math3D;
+import lwjake2.util.Vec3Cache;
+import org.lwjgl.opengl.GL11;
+
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+import java.util.Arrays;
+
+/**
+ * Light
+ *
+ * @author cwei
+ */
+public abstract class Light extends Warp {
+    // r_light.c
+
+    static final int DLIGHT_CUTOFF = 64;
+    /*
+    =============================================================================
+
+    DYNAMIC LIGHTS
+
+    =============================================================================
+    */
+    final float[] pointcolor = {0, 0, 0}; // vec3_t
+    /*
+    =============================================================================
+
+    LIGHT SAMPLING
+
+    =============================================================================
+    */
+    final float[] lightspot = {0, 0, 0}; // vec3_t
+    final float[] s_blocklights = new float[34 * 34 * 3];
+    // stack variable
+    private final float[] v = {0, 0, 0};
+    /*
+    =============================================================================
+
+    DYNAMIC LIGHTS BLEND RENDERING
+
+    =============================================================================
+    */
+    // stack variable
+    private final float[] end = {0, 0, 0};
+    // TODO sync with jogl renderer. hoz
+    private final float[] impact = {0, 0, 0};
+    private final Throwable gotoStore = new Throwable();
+    int r_dlightframecount;
+    cplane_t lightplane; // used as shadow plane
+
+    /**
+     * R_RenderDlight
+     */
+    void R_RenderDlight(dlight_t light) {
+        float rad = light.intensity * 0.35f;
+
+        Math3D.vectorSubtract(light.origin, r_origin, v);
+
+        GL11.glBegin(GL11.GL_TRIANGLE_FAN);
+        GL11.glColor3f(light.color[0] * 0.2f, light.color[1] * 0.2f, light.color[2] * 0.2f);
+        int i;
+        for (i = 0; i < 3; i++)
+            v[i] = light.origin[i] - vpn[i] * rad;
+
+        GL11.glVertex3f(v[0], v[1], v[2]);
+        GL11.glColor3f(0, 0, 0);
+
+        int j;
+        float a;
+        for (i = 16; i >= 0; i--) {
+            a = (float) (i / 16.0f * Math.PI * 2);
+            for (j = 0; j < 3; j++)
+                v[j] = (float) (light.origin[j] + vright[j] * Math.cos(a) * rad
+                        + vup[j] * Math.sin(a) * rad);
+            GL11.glVertex3f(v[0], v[1], v[2]);
+        }
+        GL11.glEnd();
+    }
+
+    /**
+     * R_RenderDlights
+     */
+    void R_RenderDlights() {
+        if (gl_flashblend.value == 0)
+            return;
+
+        r_dlightframecount = r_framecount + 1;    // because the count hasn't
+        //  advanced yet for this frame
+        GL11.glDepthMask(false);
+        GL11.glDisable(GL11.GL_TEXTURE_2D);
+        GL11.glShadeModel(GL11.GL_SMOOTH);
+        GL11.glEnable(GL11.GL_BLEND);
+        GL11.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
+
+        for (int i = 0; i < r_newrefdef.num_dlights; i++) {
+            R_RenderDlight(r_newrefdef.dlights[i]);
+        }
+
+        GL11.glColor3f(1, 1, 1);
+        GL11.glDisable(GL11.GL_BLEND);
+        GL11.glEnable(GL11.GL_TEXTURE_2D);
+        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
+        GL11.glDepthMask(true);
+    }
+
+    /**
+     * R_MarkLights
+     */
+    void R_MarkLights(dlight_t light, int bit, mnode_t node) {
+        if (node.contents != -1)
+            return;
+
+        cplane_t splitplane = node.plane;
+        float dist = Math3D.dotProduct(light.origin, splitplane.normal) - splitplane.dist;
+
+        if (dist > light.intensity - DLIGHT_CUTOFF) {
+            R_MarkLights(light, bit, node.children[0]);
+            return;
+        }
+        if (dist < -light.intensity + DLIGHT_CUTOFF) {
+            R_MarkLights(light, bit, node.children[1]);
+            return;
+        }
+
+        // mark the polygons
+        msurface_t surf;
+        int sidebit;
+        for (int i = 0; i < node.numsurfaces; i++) {
+
+            surf = r_worldmodel.surfaces[node.firstsurface + i];
+
+                       /*
+             * cwei
+                        * bugfix for dlight behind the walls
+                        */
+            dist = Math3D.dotProduct(light.origin, surf.plane.normal) - surf.plane.dist;
+            sidebit = (dist >= 0) ? 0 : Defines.SURF_PLANEBACK;
+            if ((surf.flags & Defines.SURF_PLANEBACK) != sidebit)
+                continue;
+            /*
+             * cwei
+                        * bugfix end
+                        */
+
+            if (surf.dlightframe != r_dlightframecount) {
+                surf.dlightbits = 0;
+                surf.dlightframe = r_dlightframecount;
+            }
+            surf.dlightbits |= bit;
+        }
+
+        R_MarkLights(light, bit, node.children[0]);
+        R_MarkLights(light, bit, node.children[1]);
+    }
+
+//       ===================================================================
+
+    /**
+     * R_PushDlights
+     */
+    void R_PushDlights() {
+        if (gl_flashblend.value != 0)
+            return;
+
+        r_dlightframecount = r_framecount + 1;    // because the count hasn't
+        //  advanced yet for this frame
+        dlight_t l;
+        for (int i = 0; i < r_newrefdef.num_dlights; i++) {
+            l = r_newrefdef.dlights[i];
+            R_MarkLights(l, 1 << i, r_worldmodel.nodes[0]);
+        }
+    }
+
+    /**
+     * RecursiveLightPoint
+     *
+     * @param node
+     * @param start
+     * @param end
+     * @return
+     */
+    int RecursiveLightPoint(mnode_t node, float[] start, float[] end) {
+        if (node.contents != -1)
+            return -1;        // didn't hit anything
+
+        // calculate mid point
+
+        // FIXME: optimize for axial
+        cplane_t plane = node.plane;
+        float front = Math3D.dotProduct(start, plane.normal) - plane.dist;
+        float back = Math3D.dotProduct(end, plane.normal) - plane.dist;
+        boolean side = (front < 0);
+        int sideIndex = (side) ? 1 : 0;
+
+        if ((back < 0) == side)
+            return RecursiveLightPoint(node.children[sideIndex], start, end);
+
+        float frac = front / (front - back);
+        float[] mid = Vec3Cache.get();
+        mid[0] = start[0] + (end[0] - start[0]) * frac;
+        mid[1] = start[1] + (end[1] - start[1]) * frac;
+        mid[2] = start[2] + (end[2] - start[2]) * frac;
+
+        // go down front side
+        int r = RecursiveLightPoint(node.children[sideIndex], start, mid);
+        if (r >= 0) {
+            Vec3Cache.release(); // mid
+            return r;        // hit something
+        }
+
+        if ((back < 0) == side) {
+            Vec3Cache.release(); // mid
+            return -1; // didn't hit anuthing
+        }
+
+        // check for impact on this node
+        Math3D.vectorCopy(mid, lightspot);
+        lightplane = plane;
+        int surfIndex = node.firstsurface;
+
+        msurface_t surf;
+        int s, t, ds, dt;
+        mtexinfo_t tex;
+        ByteBuffer lightmap;
+        int maps;
+        for (int i = 0; i < node.numsurfaces; i++, surfIndex++) {
+            surf = r_worldmodel.surfaces[surfIndex];
+
+            if ((surf.flags & (Defines.SURF_DRAWTURB | Defines.SURF_DRAWSKY)) != 0)
+                continue;    // no lightmaps
+
+            tex = surf.texinfo;
+
+            s = (int) (Math3D.dotProduct(mid, tex.vecs[0]) + tex.vecs[0][3]);
+            t = (int) (Math3D.dotProduct(mid, tex.vecs[1]) + tex.vecs[1][3]);
+
+            if (s < surf.texturemins[0] || t < surf.texturemins[1])
+                continue;
+
+            ds = s - surf.texturemins[0];
+            dt = t - surf.texturemins[1];
+
+            if (ds > surf.extents[0] || dt > surf.extents[1])
+                continue;
+
+            if (surf.samples == null)
+                return 0;
+
+            ds >>= 4;
+            dt >>= 4;
+
+            lightmap = surf.samples;
+            int lightmapIndex = 0;
+
+            Math3D.vectorCopy(Globals.vec3_origin, pointcolor);
+            if (lightmap != null) {
+                float[] rgb;
+                lightmapIndex += 3 * (dt * ((surf.extents[0] >> 4) + 1) + ds);
+
+                float scale0, scale1, scale2;
+                for (maps = 0; maps < Defines.MAXLIGHTMAPS && surf.styles[maps] != (byte) 255; maps++) {
+                    rgb = r_newrefdef.lightstyles[surf.styles[maps] & 0xFF].rgb;
+                    scale0 = gl_modulate.value * rgb[0];
+                    scale1 = gl_modulate.value * rgb[1];
+                    scale2 = gl_modulate.value * rgb[2];
+
+                    pointcolor[0] += (lightmap.get(lightmapIndex) & 0xFF) * scale0 * (1.0f / 255);
+                    pointcolor[1] += (lightmap.get(lightmapIndex + 1) & 0xFF) * scale1 * (1.0f / 255);
+                    pointcolor[2] += (lightmap.get(lightmapIndex + 2) & 0xFF) * scale2 * (1.0f / 255);
+                    lightmapIndex += 3 * ((surf.extents[0] >> 4) + 1) * ((surf.extents[1] >> 4) + 1);
+                }
+            }
+            Vec3Cache.release(); // mid
+            return 1;
+        }
+
+        // go down back side
+        r = RecursiveLightPoint(node.children[1 - sideIndex], mid, end);
+        Vec3Cache.release(); // mid
+        return r;
+    }
+
+    /**
+     * R_LightPoint
+     */
+    void R_LightPoint(float[] p, float[] color) {
+        assert (p.length == 3) : "vec3_t bug";
+        assert (color.length == 3) : "rgb bug";
+
+        if (r_worldmodel.lightdata == null) {
+            color[0] = color[1] = color[2] = 1.0f;
+            return;
+        }
+
+        end[0] = p[0];
+        end[1] = p[1];
+        end[2] = p[2] - 2048;
+
+        float r = RecursiveLightPoint(r_worldmodel.nodes[0], p, end);
+
+        if (r == -1) {
+            Math3D.vectorCopy(Globals.vec3_origin, color);
+        } else {
+            Math3D.vectorCopy(pointcolor, color);
+        }
+
+        //
+        // add dynamic lights
+        //
+        dlight_t dl;
+        float add;
+        for (int lnum = 0; lnum < r_newrefdef.num_dlights; lnum++) {
+            dl = r_newrefdef.dlights[lnum];
+
+            Math3D.vectorSubtract(currententity.origin, dl.origin, end);
+            add = dl.intensity - Math3D.vectorLength(end);
+            add *= (1.0f / 256);
+            if (add > 0) {
+                Math3D.vectorMA(color, add, dl.color, color);
+            }
+        }
+        Math3D.vectorScale(color, gl_modulate.value, color);
+    }
+
+    /**
+     * R_AddDynamicLights
+     */
+    void R_AddDynamicLights(msurface_t surf) {
+        int sd, td;
+        float fdist, frad, fminlight;
+        int s, t;
+        dlight_t dl;
+        float[] pfBL;
+        float fsacc, ftacc;
+
+        int smax = (surf.extents[0] >> 4) + 1;
+        int tmax = (surf.extents[1] >> 4) + 1;
+        mtexinfo_t tex = surf.texinfo;
+
+        float local0, local1;
+        for (int lnum = 0; lnum < r_newrefdef.num_dlights; lnum++) {
+            if ((surf.dlightbits & (1 << lnum)) == 0)
+                continue;        // not lit by this light
+
+            dl = r_newrefdef.dlights[lnum];
+            frad = dl.intensity;
+            fdist = Math3D.dotProduct(dl.origin, surf.plane.normal) -
+                    surf.plane.dist;
+            frad -= Math.abs(fdist);
+            // rad is now the highest intensity on the plane
+
+            fminlight = DLIGHT_CUTOFF;    // FIXME: make configurable?
+            if (frad < fminlight)
+                continue;
+            fminlight = frad - fminlight;
+
+            for (int i = 0; i < 3; i++) {
+                impact[i] = dl.origin[i] -
+                        surf.plane.normal[i] * fdist;
+            }
+
+            local0 = Math3D.dotProduct(impact, tex.vecs[0]) + tex.vecs[0][3] - surf.texturemins[0];
+            local1 = Math3D.dotProduct(impact, tex.vecs[1]) + tex.vecs[1][3] - surf.texturemins[1];
+
+            pfBL = s_blocklights;
+            int pfBLindex = 0;
+            for (t = 0, ftacc = 0; t < tmax; t++, ftacc += 16) {
+                td = (int) (local1 - ftacc);
+                if (td < 0)
+                    td = -td;
+
+                for (s = 0, fsacc = 0; s < smax; s++, fsacc += 16, pfBLindex += 3) {
+                    sd = (int) (local0 - fsacc);
+
+                    if (sd < 0)
+                        sd = -sd;
+
+                    if (sd > td)
+                        fdist = sd + (td >> 1);
+                    else
+                        fdist = td + (sd >> 1);
+
+                    if (fdist < fminlight) {
+                        pfBL[pfBLindex] += (frad - fdist) * dl.color[0];
+                        pfBL[pfBLindex + 1] += (frad - fdist) * dl.color[1];
+                        pfBL[pfBLindex + 2] += (frad - fdist) * dl.color[2];
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * R_SetCacheState
+     */
+    void R_SetCacheState(msurface_t surf) {
+        for (int maps = 0; maps < Defines.MAXLIGHTMAPS && surf.styles[maps] != (byte) 255; maps++) {
+            surf.cached_light[maps] = r_newrefdef.lightstyles[surf.styles[maps] & 0xFF].white;
+        }
+    }
+
+//     TODO sync with jogl renderer. hoz
+
+    /**
+     * R_BuildLightMap
+     * <p>
+     * Combine and scale multiple lightmaps into the floating format in blocklights
+     */
+    void R_BuildLightMap(msurface_t surf, IntBuffer dest, int stride) {
+        int r, g, b, a, max;
+        int i, j;
+        int nummaps;
+        float[] bl;
+        //lightstyle_t style;
+
+        if ((surf.texinfo.flags & (Defines.SURF_SKY | Defines.SURF_TRANS33
+                | Defines.SURF_TRANS66 | Defines.SURF_WARP)) != 0)
+            Com.Error(Defines.ERR_DROP,
+                    "R_BuildLightMap called for non-lit surface");
+
+        int smax = (surf.extents[0] >> 4) + 1;
+        int tmax = (surf.extents[1] >> 4) + 1;
+        int size = smax * tmax;
+        if (size > ((s_blocklights.length * Defines.SIZE_OF_FLOAT) >> 4))
+            Com.Error(Defines.ERR_DROP, "Bad s_blocklights size");
+
+        try {
+            // set to full bright if no light data
+            if (surf.samples == null) {
+                // int maps;
+
+                for (i = 0; i < size * 3; i++)
+                    s_blocklights[i] = 255;
+
+                // TODO useless? hoz
+                //                             for (maps = 0 ; maps < Defines.MAXLIGHTMAPS &&
+                // surf.styles[maps] != (byte)255; maps++)
+                //                             {
+                //                                     style = r_newrefdef.lightstyles[surf.styles[maps] & 0xFF];
+                //                             }
+
+                // goto store;
+                throw gotoStore;
+            }
+
+            // count the # of maps
+            for (nummaps = 0; nummaps < Defines.MAXLIGHTMAPS
+                    && surf.styles[nummaps] != (byte) 255; nummaps++)
+                ;
+
+            ByteBuffer lightmap = surf.samples;
+            int lightmapIndex = 0;
+
+            // add all the lightmaps
+            float scale0;
+            float scale1;
+            float scale2;
+            if (nummaps == 1) {
+                int maps;
+
+                for (maps = 0; maps < Defines.MAXLIGHTMAPS
+                        && surf.styles[maps] != (byte) 255; maps++) {
+                    bl = s_blocklights;
+                    int blp = 0;
+
+//                    for (i = 0; i < 3; i++)
+//                        scale[i] = gl_modulate.value
+//                                * r_newrefdef.lightstyles[surf.styles[maps] & 0xFF].rgb[i];
+                    scale0 = gl_modulate.value * r_newrefdef.lightstyles[surf.styles[maps] & 0xFF].rgb[0];
+                    scale1 = gl_modulate.value * r_newrefdef.lightstyles[surf.styles[maps] & 0xFF].rgb[1];
+                    scale2 = gl_modulate.value * r_newrefdef.lightstyles[surf.styles[maps] & 0xFF].rgb[2];
+
+                    if (scale0 == 1.0F && scale1 == 1.0F
+                            && scale2 == 1.0F) {
+                        for (i = 0; i < size; i++) {
+                            bl[blp++] = lightmap.get(lightmapIndex++) & 0xFF;
+                            bl[blp++] = lightmap.get(lightmapIndex++) & 0xFF;
+                            bl[blp++] = lightmap.get(lightmapIndex++) & 0xFF;
+                        }
+                    } else {
+                        for (i = 0; i < size; i++) {
+                            bl[blp++] = (lightmap.get(lightmapIndex++) & 0xFF)
+                                    * scale0;
+                            bl[blp++] = (lightmap.get(lightmapIndex++) & 0xFF)
+                                    * scale1;
+                            bl[blp++] = (lightmap.get(lightmapIndex++) & 0xFF)
+                                    * scale2;
+                        }
+                    }
+                    //lightmap += size*3; // skip to next lightmap
+                }
+            } else {
+                int maps;
+
+                //                     memset( s_blocklights, 0, sizeof( s_blocklights[0] ) * size *
+                // 3 );
+
+                Arrays.fill(s_blocklights, 0, size * 3, 0.0f);
+
+                for (maps = 0; maps < Defines.MAXLIGHTMAPS
+                        && surf.styles[maps] != (byte) 255; maps++) {
+                    bl = s_blocklights;
+                    int blp = 0;
+
+//                    for (i = 0; i < 3; i++)
+//                        scale[i] = gl_modulate.value
+//                                * r_newrefdef.lightstyles[surf.styles[maps] & 0xFF].rgb[i];
+                    scale0 = gl_modulate.value
+                            * r_newrefdef.lightstyles[surf.styles[maps] & 0xFF].rgb[0];
+                    scale1 = gl_modulate.value
+                            * r_newrefdef.lightstyles[surf.styles[maps] & 0xFF].rgb[1];
+                    scale2 = gl_modulate.value
+                            * r_newrefdef.lightstyles[surf.styles[maps] & 0xFF].rgb[2];
+
+
+                    if (scale0 == 1.0F && scale1 == 1.0F
+                            && scale2 == 1.0F) {
+                        for (i = 0; i < size; i++) {
+                            bl[blp++] += lightmap.get(lightmapIndex++) & 0xFF;
+                            bl[blp++] += lightmap.get(lightmapIndex++) & 0xFF;
+                            bl[blp++] += lightmap.get(lightmapIndex++) & 0xFF;
+                        }
+                    } else {
+                        for (i = 0; i < size; i++) {
+                            bl[blp++] += (lightmap.get(lightmapIndex++) & 0xFF)
+                                    * scale0;
+                            bl[blp++] += (lightmap.get(lightmapIndex++) & 0xFF)
+                                    * scale1;
+                            bl[blp++] += (lightmap.get(lightmapIndex++) & 0xFF)
+                                    * scale2;
+                        }
+                    }
+                    //lightmap += size*3; // skip to next lightmap
+                }
+            }
+
+            // add all the dynamic lights
+            if (surf.dlightframe == r_framecount)
+                R_AddDynamicLights(surf);
+
+            // label store:
+        } catch (Throwable ignored) {
+        }
+
+        // put into texture format
+        stride -= smax;
+        bl = s_blocklights;
+        int blp = 0;
+
+        int monolightmap = gl_monolightmap.string.charAt(0);
+
+        int destp = 0;
+
+        if (monolightmap == '0') {
+            for (i = 0; i < tmax; i++, destp += stride) {
+                //dest.position(destp);
+
+                for (j = 0; j < smax; j++) {
+
+                    r = (int) bl[blp++];
+                    g = (int) bl[blp++];
+                    b = (int) bl[blp++];
+
+                    // catch negative lights
+                    if (r < 0)
+                        r = 0;
+                    if (g < 0)
+                        g = 0;
+                    if (b < 0)
+                        b = 0;
+
+                    /*
+                     * * determine the brightest of the three color components
+                     */
+                    if (r > g)
+                        max = r;
+                    else
+                        max = g;
+                    if (b > max)
+                        max = b;
+
+                    /*
+                     * * alpha is ONLY used for the mono lightmap case. For this
+                     * reason * we set it to the brightest of the color
+                     * components so that * things don't get too dim.
+                     */
+                    a = max;
+
+                    /*
+                     * * rescale all the color components if the intensity of
+                     * the greatest * channel exceeds 1.0
+                     */
+                    if (max > 255) {
+                        float t = 255.0F / max;
+
+                        r = (int) (r * t);
+                        g = (int) (g * t);
+                        b = (int) (b * t);
+                        a = (int) (a * t);
+                    }
+                    //r &= 0xFF; g &= 0xFF; b &= 0xFF; a &= 0xFF;
+                    dest.put(destp++, (a << 24) | (b << 16) | (g << 8) | r);
+                }
+            }
+        } else {
+            for (i = 0; i < tmax; i++, destp += stride) {
+                //dest.position(destp);
+
+                for (j = 0; j < smax; j++) {
+
+                    r = (int) bl[blp++];
+                    g = (int) bl[blp++];
+                    b = (int) bl[blp++];
+
+                    // catch negative lights
+                    if (r < 0)
+                        r = 0;
+                    if (g < 0)
+                        g = 0;
+                    if (b < 0)
+                        b = 0;
+
+                    /*
+                     * * determine the brightest of the three color components
+                     */
+                    if (r > g)
+                        max = r;
+                    else
+                        max = g;
+                    if (b > max)
+                        max = b;
+
+                    /*
+                     * * alpha is ONLY used for the mono lightmap case. For this
+                     * reason * we set it to the brightest of the color
+                     * components so that * things don't get too dim.
+                     */
+                    a = max;
+
+                    /*
+                     * * rescale all the color components if the intensity of
+                     * the greatest * channel exceeds 1.0
+                     */
+                    if (max > 255) {
+                        float t = 255.0F / max;
+
+                        r = (int) (r * t);
+                        g = (int) (g * t);
+                        b = (int) (b * t);
+                        a = (int) (a * t);
+                    }
+
+                    /*
+                     * * So if we are doing alpha lightmaps we need to set the
+                     * R, G, and B * components to 0 and we need to set alpha to
+                     * 1-alpha.
+                     */
+                    switch (monolightmap) {
+                        case 'L':
+                        case 'I':
+                            r = a;
+                            g = b = 0;
+                            break;
+                        case 'C':
+                            // try faking colored lighting
+                            a = 255 - ((r + g + b) / 3);
+                            float af = a / 255.0f;
+                            r *= af;
+                            g *= af;
+                            b *= af;
+                            break;
+                        case 'A':
+                        default:
+                            r = g = b = 0;
+                            a = 255 - a;
+                            break;
+                    }
+                    //r &= 0xFF; g &= 0xFF; b &= 0xFF; a &= 0xFF;
+                    dest.put(destp++, (a << 24) | (b << 16) | (g << 8) | r);
+                }
+            }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/render/lwjgl/Main.java b/src/main/java/lwjake2/render/lwjgl/Main.java
new file mode 100644 (file)
index 0000000..b481e7c
--- /dev/null
@@ -0,0 +1,1475 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render.lwjgl;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.client.VideoDriver;
+import lwjake2.client.entity_t;
+import lwjake2.client.particle_t;
+import lwjake2.client.refdef_t;
+import lwjake2.game.Cmd;
+import lwjake2.game.CvarT;
+import lwjake2.game.cplane_t;
+import lwjake2.qcommon.Com;
+import lwjake2.qcommon.Cvar;
+import lwjake2.qcommon.qfiles;
+import lwjake2.qcommon.xcommand_t;
+import lwjake2.render.Image;
+import lwjake2.render.Model;
+import lwjake2.render.*;
+import lwjake2.util.Math3D;
+import lwjake2.util.Vargs;
+import org.lwjgl.BufferUtils;
+import org.lwjgl.opengl.ARBMultitexture;
+import org.lwjgl.opengl.GL11;
+import org.lwjgl.opengl.GL13;
+
+import java.awt.*;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+/**
+ * Main
+ *
+ * @author cwei
+ */
+public abstract class Main extends Base {
+
+    public static final int[] d_8to24table = new int[256];
+    static final int NUM_BEAM_SEGS = 6;
+    final glconfig_t gl_config = new glconfig_t();
+    final glstate_t gl_state = new glstate_t();
+    final cplane_t[] frustum = {new cplane_t(), new cplane_t(), new cplane_t(), new cplane_t()};
+    final float[] v_blend = {0, 0, 0, 0}; // final blending color
+    //
+    //    view origin
+    //
+    final float[] vup = {0, 0, 0};
+    /*
+    ====================================================================
+
+    from gl_rmain.c
+
+    ====================================================================
+    */
+    final float[] vpn = {0, 0, 0};
+    final float[] vright = {0, 0, 0};
+    final float[] r_origin = {0, 0, 0};
+    //float r_world_matrix[] = new float[16];
+    final FloatBuffer r_world_matrix = BufferUtils.createFloatBuffer(16);
+    final float[] r_turbsin = new float[256];
+    final int[] r_rawpalette = new int[256];
+    final float[][] start_points = new float[NUM_BEAM_SEGS][3];
+    // array of vec3_t
+    final float[][] end_points = new float[NUM_BEAM_SEGS][3]; // array of vec3_t
+    // stack variable
+    private final float[] point = {0, 0, 0};
+    // stack variable
+    private final float[] shadelight = {0, 0, 0};
+    // stack variable
+    private final float[] up = {0, 0, 0};
+    private final float[] right = {0, 0, 0};
+    // stack variable
+    private final float[] temp = {0, 0, 0};
+    // stack variable
+    private final float[] light = {0, 0, 0};
+    // stack variable
+    private final float[] perpvec = {0, 0, 0}; // vec3_t
+    private final float[] direction = {0, 0, 0}; // vec3_t
+    private final float[] normalized_direction = {0, 0, 0}; // vec3_t
+    private final float[] oldorigin = {0, 0, 0}; // vec3_t
+    private final float[] origin = {0, 0, 0}; // vec3_t
+    int c_visible_lightmaps;
+    int c_visible_textures;
+    int registration_sequence;
+    // this a hack for function pointer test
+    // default disabled
+    boolean qglColorTableEXT = false;
+    boolean qglActiveTextureARB = false;
+    boolean qglPointParameterfEXT = false;
+    boolean qglLockArraysEXT = false;
+    boolean qwglSwapIntervalEXT = false;
+    int GL_TEXTURE0 = GL13.GL_TEXTURE0;
+    int GL_TEXTURE1 = GL13.GL_TEXTURE1;
+    Model r_worldmodel;
+    float gldepthmin, gldepthmax;
+    Image r_notexture; // use for bad textures
+    Image r_particletexture; // little dot for particles
+    entity_t currententity;
+    Model currentmodel;
+    int r_visframecount; // bumped when going to a new PVS
+    int r_framecount; // used for dlight push checking
+    int c_brush_polys, c_alias_polys;
+    float r_base_world_matrix[] = new float[16];
+    //
+    //    screen size info
+    //
+    refdef_t r_newrefdef = new refdef_t();
+    int r_viewcluster, r_viewcluster2, r_oldviewcluster, r_oldviewcluster2;
+    CvarT r_norefresh;
+    CvarT r_drawentities;
+    CvarT r_drawworld;
+    CvarT r_speeds;
+    CvarT r_fullbright;
+    CvarT r_novis;
+    CvarT r_nocull;
+    CvarT r_lerpmodels;
+    CvarT r_lefthand;
+    CvarT r_lightlevel;
+    CvarT gl_nosubimage;
+    CvarT gl_allow_software;
+    CvarT gl_vertex_arrays;
+    CvarT gl_particle_min_size;
+    CvarT gl_particle_max_size;
+    CvarT gl_particle_size;
+    CvarT gl_particle_att_a;
+    CvarT gl_particle_att_b;
+    CvarT gl_particle_att_c;
+    CvarT gl_ext_swapinterval;
+    CvarT gl_ext_palettedtexture;
+    CvarT gl_ext_multitexture;
+    CvarT gl_ext_pointparameters;
+    CvarT gl_ext_compiled_vertex_array;
+    CvarT gl_log;
+    CvarT gl_bitdepth;
+    // FIXME: This is a HACK to get the client's light level
+    CvarT gl_drawbuffer;
+    CvarT gl_driver;
+    CvarT gl_lightmap;
+    CvarT gl_shadows;
+    CvarT gl_mode;
+    CvarT gl_dynamic;
+    CvarT gl_monolightmap;
+    CvarT gl_modulate;
+    CvarT gl_nobind;
+    CvarT gl_round_down;
+    CvarT gl_picmip;
+    CvarT gl_skymip;
+    CvarT gl_showtris;
+    CvarT gl_ztrick;
+    CvarT gl_finish;
+    CvarT gl_clear;
+    CvarT gl_cull;
+    CvarT gl_polyblend;
+    CvarT gl_flashblend;
+    CvarT gl_playermip;
+    CvarT gl_saturatelighting;
+    CvarT gl_swapinterval;
+    CvarT gl_texturemode;
+    CvarT gl_texturealphamode;
+    CvarT gl_texturesolidmode;
+    CvarT gl_lockpvs;
+    CvarT gl_3dlabs_broken;
+    CvarT vid_gamma;
+    CvarT vid_ref;
+    int trickframe = 0;
+
+    // =================
+    //  abstract methods
+    // =================
+    protected abstract void Draw_GetPalette();
+
+    abstract void GL_ImageList_f();
+
+    abstract void GL_ScreenShot_f();
+
+    abstract void GL_SetTexturePalette(int[] palette);
+
+    abstract void GL_Strings_f();
+
+    abstract void Mod_Modellist_f();
+
+    abstract mleaf_t Mod_PointInLeaf(float[] point, Model model);
+
+    abstract void GL_SetDefaultState();
+
+    abstract void GL_InitImages();
+
+    abstract void Mod_Init(); // Model.java
+
+    abstract void R_InitParticleTexture(); // MIsc.java
+
+    // ============================================================================
+    // to port from gl_rmain.c, ...
+    // ============================================================================
+
+    abstract void R_DrawAliasModel(entity_t e); // Mesh.java
+
+    abstract void R_DrawBrushModel(entity_t e); // Surf.java
+
+       /*
+    =============================================================
+       
+          SPRITE MODELS
+       
+       =============================================================
+       */
+
+    abstract void Draw_InitLocal();
+
+    abstract void R_LightPoint(float[] p, float[] color);
+
+    // ==================================================================================
+
+    abstract void R_PushDlights();
+
+    abstract void R_MarkLeaves();
+
+    abstract void R_DrawWorld();
+
+    abstract void R_RenderDlights();
+
+    abstract void R_DrawAlphaSurfaces();
+
+    abstract void Mod_FreeAll();
+
+    abstract void GL_ShutdownImages();
+
+    abstract void GL_Bind(int texnum);
+
+    // =======================================================================
+
+    abstract void GL_TexEnv(int mode);
+
+    abstract void GL_TextureMode(String string);
+
+    // =======================================================================
+
+    abstract void GL_TextureAlphaMode(String string);
+
+    abstract void GL_TextureSolidMode(String string);
+
+    abstract void GL_UpdateSwapInterval();
+
+    /**
+     * R_CullBox
+     * Returns true if the box is completely outside the frustum
+     */
+    final boolean R_CullBox(float[] mins, float[] maxs) {
+        assert (mins.length == 3 && maxs.length == 3) : "vec3_t bug";
+
+        if (r_nocull.value != 0)
+            return false;
+
+        for (int i = 0; i < 4; i++) {
+            if (Math3D.boxOnPlaneSide(mins, maxs, frustum[i]) == 2)
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * R_RotateForEntity
+     */
+    final void R_RotateForEntity(entity_t e) {
+        GL11.glTranslatef(e.origin[0], e.origin[1], e.origin[2]);
+
+        GL11.glRotatef(e.angles[1], 0, 0, 1);
+        GL11.glRotatef(-e.angles[0], 0, 1, 0);
+        GL11.glRotatef(-e.angles[2], 1, 0, 0);
+    }
+
+    /**
+     * R_DrawSpriteModel
+     */
+    void R_DrawSpriteModel(entity_t e) {
+        float alpha = 1.0F;
+
+        qfiles.dsprframe_t frame;
+        qfiles.dsprite_t psprite;
+
+        // don't even bother culling, because it's just a single
+        // polygon without a surface cache
+
+        psprite = (qfiles.dsprite_t) currentmodel.extradata;
+
+        e.frame %= psprite.numframes;
+
+        frame = psprite.frames[e.frame];
+
+        if ((e.flags & Defines.RF_TRANSLUCENT) != 0)
+            alpha = e.alpha;
+
+        if (alpha != 1.0F)
+            GL11.glEnable(GL11.GL_BLEND);
+
+        GL11.glColor4f(1, 1, 1, alpha);
+
+        GL_Bind(currentmodel.skins[e.frame].texnum);
+
+        GL_TexEnv(GL11.GL_MODULATE);
+
+        if (alpha == 1.0)
+            GL11.glEnable(GL11.GL_ALPHA_TEST);
+        else
+            GL11.glDisable(GL11.GL_ALPHA_TEST);
+
+        GL11.glBegin(GL11.GL_QUADS);
+
+        GL11.glTexCoord2f(0, 1);
+        Math3D.vectorMA(e.origin, -frame.origin_y, vup, point);
+        Math3D.vectorMA(point, -frame.origin_x, vright, point);
+        GL11.glVertex3f(point[0], point[1], point[2]);
+
+        GL11.glTexCoord2f(0, 0);
+        Math3D.vectorMA(e.origin, frame.height - frame.origin_y, vup, point);
+        Math3D.vectorMA(point, -frame.origin_x, vright, point);
+        GL11.glVertex3f(point[0], point[1], point[2]);
+
+        GL11.glTexCoord2f(1, 0);
+        Math3D.vectorMA(e.origin, frame.height - frame.origin_y, vup, point);
+        Math3D.vectorMA(point, frame.width - frame.origin_x, vright, point);
+        GL11.glVertex3f(point[0], point[1], point[2]);
+
+        GL11.glTexCoord2f(1, 1);
+        Math3D.vectorMA(e.origin, -frame.origin_y, vup, point);
+        Math3D.vectorMA(point, frame.width - frame.origin_x, vright, point);
+        GL11.glVertex3f(point[0], point[1], point[2]);
+
+        GL11.glEnd();
+
+        GL11.glDisable(GL11.GL_ALPHA_TEST);
+        GL_TexEnv(GL11.GL_REPLACE);
+
+        if (alpha != 1.0F)
+            GL11.glDisable(GL11.GL_BLEND);
+
+        GL11.glColor4f(1, 1, 1, 1);
+    }
+
+    /**
+     * R_DrawNullModel
+     */
+    void R_DrawNullModel() {
+        if ((currententity.flags & Defines.RF_FULLBRIGHT) != 0) {
+            // cwei wollte blau: shadelight[0] = shadelight[1] = shadelight[2] = 1.0F;
+            shadelight[0] = shadelight[1] = shadelight[2] = 0.0F;
+            shadelight[2] = 0.8F;
+        } else {
+            R_LightPoint(currententity.origin, shadelight);
+        }
+
+        GL11.glPushMatrix();
+        R_RotateForEntity(currententity);
+
+        GL11.glDisable(GL11.GL_TEXTURE_2D);
+        GL11.glColor3f(shadelight[0], shadelight[1], shadelight[2]);
+
+        // this replaces the TRIANGLE_FAN
+        //glut.glutWireCube(gl, 20);
+
+        GL11.glBegin(GL11.GL_TRIANGLE_FAN);
+        GL11.glVertex3f(0, 0, -16);
+        int i;
+        for (i = 0; i <= 4; i++) {
+            GL11.glVertex3f((float) (16.0f * Math.cos(i * Math.PI / 2)), (float) (16.0f * Math.sin(i * Math.PI / 2)), 0.0f);
+        }
+        GL11.glEnd();
+
+        GL11.glBegin(GL11.GL_TRIANGLE_FAN);
+        GL11.glVertex3f(0, 0, 16);
+        for (i = 4; i >= 0; i--) {
+            GL11.glVertex3f((float) (16.0f * Math.cos(i * Math.PI / 2)), (float) (16.0f * Math.sin(i * Math.PI / 2)), 0.0f);
+        }
+        GL11.glEnd();
+
+
+        GL11.glColor3f(1, 1, 1);
+        GL11.glPopMatrix();
+        GL11.glEnable(GL11.GL_TEXTURE_2D);
+    }
+
+    /**
+     * R_DrawEntitiesOnList
+     */
+    void R_DrawEntitiesOnList() {
+        if (r_drawentities.value == 0.0f)
+            return;
+
+        // draw non-transparent first
+        int i;
+        for (i = 0; i < r_newrefdef.num_entities; i++) {
+            currententity = r_newrefdef.entities[i];
+            if ((currententity.flags & Defines.RF_TRANSLUCENT) != 0)
+                continue; // solid
+
+            currentmodel = currententity.model;
+            if (currentmodel == null) {
+                R_DrawNullModel();
+                continue;
+            }
+            switch (currentmodel.type) {
+                case mod_alias:
+                    R_DrawAliasModel(currententity);
+                    break;
+                case mod_brush:
+                    R_DrawBrushModel(currententity);
+                    break;
+                case mod_sprite:
+                    R_DrawSpriteModel(currententity);
+                    break;
+                default:
+                    Com.Error(Defines.ERR_DROP, "Bad modeltype");
+                    break;
+            }
+        }
+        // draw transparent entities
+        // we could sort these if it ever becomes a problem...
+        GL11.glDepthMask(false); // no z writes
+        for (i = 0; i < r_newrefdef.num_entities; i++) {
+            currententity = r_newrefdef.entities[i];
+            if ((currententity.flags & Defines.RF_TRANSLUCENT) == 0)
+                continue; // solid
+
+            currentmodel = currententity.model;
+
+            if (currentmodel == null) {
+                R_DrawNullModel();
+                continue;
+            }
+            switch (currentmodel.type) {
+                case mod_alias:
+                    R_DrawAliasModel(currententity);
+                    break;
+                case mod_brush:
+                    R_DrawBrushModel(currententity);
+                    break;
+                case mod_sprite:
+                    R_DrawSpriteModel(currententity);
+                    break;
+                default:
+                    Com.Error(Defines.ERR_DROP, "Bad modeltype");
+                    break;
+            }
+        }
+        GL11.glDepthMask(true); // back to writing
+    }
+
+    /**
+     * GL_DrawParticles
+     */
+    void GL_DrawParticles(int num_particles) {
+        float origin_x, origin_y, origin_z;
+
+        Math3D.vectorScale(vup, 1.5f, up);
+        Math3D.vectorScale(vright, 1.5f, right);
+
+        GL_Bind(r_particletexture.texnum);
+        GL11.glDepthMask(false); // no z buffering
+        GL11.glEnable(GL11.GL_BLEND);
+        GL_TexEnv(GL11.GL_MODULATE);
+
+        GL11.glBegin(GL11.GL_TRIANGLES);
+
+        FloatBuffer sourceVertices = particle_t.vertexArray;
+        IntBuffer sourceColors = particle_t.colorArray;
+        float scale;
+        int color;
+        for (int j = 0, i = 0; i < num_particles; i++) {
+            origin_x = sourceVertices.get(j++);
+            origin_y = sourceVertices.get(j++);
+            origin_z = sourceVertices.get(j++);
+
+            // hack a scale up to keep particles from disapearing
+            scale =
+                    (origin_x - r_origin[0]) * vpn[0]
+                            + (origin_y - r_origin[1]) * vpn[1]
+                            + (origin_z - r_origin[2]) * vpn[2];
+
+            scale = (scale < 20) ? 1 : 1 + scale * 0.004f;
+
+            color = sourceColors.get(i);
+
+            GL11.glColor4ub(
+                    (byte) ((color) & 0xFF),
+                    (byte) ((color >> 8) & 0xFF),
+                    (byte) ((color >> 16) & 0xFF),
+                    (byte) ((color >>> 24))
+            );
+            // first vertex
+            GL11.glTexCoord2f(0.0625f, 0.0625f);
+            GL11.glVertex3f(origin_x, origin_y, origin_z);
+            // second vertex
+            GL11.glTexCoord2f(1.0625f, 0.0625f);
+            GL11.glVertex3f(origin_x + up[0] * scale, origin_y + up[1] * scale, origin_z + up[2] * scale);
+            // third vertex
+            GL11.glTexCoord2f(0.0625f, 1.0625f);
+            GL11.glVertex3f(origin_x + right[0] * scale, origin_y + right[1] * scale, origin_z + right[2] * scale);
+        }
+        GL11.glEnd();
+
+        GL11.glDisable(GL11.GL_BLEND);
+        GL11.glColor4f(1, 1, 1, 1);
+        GL11.glDepthMask(true); // back to normal Z buffering
+        GL_TexEnv(GL11.GL_REPLACE);
+    }
+
+    /**
+     * R_DrawParticles
+     */
+    void R_DrawParticles() {
+
+        if (gl_ext_pointparameters.value != 0.0f && qglPointParameterfEXT) {
+
+            //GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
+            GL11.glVertexPointer(3, 0, particle_t.vertexArray);
+            GL11.glEnableClientState(GL11.GL_COLOR_ARRAY);
+            GL11.glColorPointer(4, true, 0, particle_t.getColorAsByteBuffer());
+
+            GL11.glDepthMask(false);
+            GL11.glEnable(GL11.GL_BLEND);
+            GL11.glDisable(GL11.GL_TEXTURE_2D);
+            GL11.glPointSize(gl_particle_size.value);
+
+            GL11.glDrawArrays(GL11.GL_POINTS, 0, r_newrefdef.num_particles);
+
+            GL11.glDisableClientState(GL11.GL_COLOR_ARRAY);
+            //GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY);
+
+            GL11.glDisable(GL11.GL_BLEND);
+            GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
+            GL11.glDepthMask(true);
+            GL11.glEnable(GL11.GL_TEXTURE_2D);
+
+        } else {
+            GL_DrawParticles(r_newrefdef.num_particles);
+        }
+    }
+
+    /**
+     * R_PolyBlend
+     */
+    void R_PolyBlend() {
+        if (gl_polyblend.value == 0.0f)
+            return;
+
+        if (v_blend[3] == 0.0f)
+            return;
+
+        GL11.glDisable(GL11.GL_ALPHA_TEST);
+        GL11.glEnable(GL11.GL_BLEND);
+        GL11.glDisable(GL11.GL_DEPTH_TEST);
+        GL11.glDisable(GL11.GL_TEXTURE_2D);
+
+        GL11.glLoadIdentity();
+
+        // FIXME: get rid of these
+        GL11.glRotatef(-90, 1, 0, 0); // put Z going up
+        GL11.glRotatef(90, 0, 0, 1); // put Z going up
+
+        GL11.glColor4f(v_blend[0], v_blend[1], v_blend[2], v_blend[3]);
+
+        GL11.glBegin(GL11.GL_QUADS);
+
+        GL11.glVertex3f(10, 100, 100);
+        GL11.glVertex3f(10, -100, 100);
+        GL11.glVertex3f(10, -100, -100);
+        GL11.glVertex3f(10, 100, -100);
+        GL11.glEnd();
+
+        GL11.glDisable(GL11.GL_BLEND);
+        GL11.glEnable(GL11.GL_TEXTURE_2D);
+        GL11.glEnable(GL11.GL_ALPHA_TEST);
+
+        GL11.glColor4f(1, 1, 1, 1);
+    }
+
+    /**
+     * SignbitsForPlane
+     */
+    int SignbitsForPlane(cplane_t out) {
+        // for fast box on planeside test
+        int bits = 0;
+        for (int j = 0; j < 3; j++) {
+            if (out.normal[j] < 0)
+                bits |= (1 << j);
+        }
+        return bits;
+    }
+
+    /**
+     * R_SetFrustum
+     */
+    void R_SetFrustum() {
+        // rotate VPN right by FOV_X/2 degrees
+        Math3D.rotatePointAroundVector(frustum[0].normal, vup, vpn, -(90f - r_newrefdef.fov_x / 2f));
+        // rotate VPN left by FOV_X/2 degrees
+        Math3D.rotatePointAroundVector(frustum[1].normal, vup, vpn, 90f - r_newrefdef.fov_x / 2f);
+        // rotate VPN up by FOV_X/2 degrees
+        Math3D.rotatePointAroundVector(frustum[2].normal, vright, vpn, 90f - r_newrefdef.fov_y / 2f);
+        // rotate VPN down by FOV_X/2 degrees
+        Math3D.rotatePointAroundVector(frustum[3].normal, vright, vpn, -(90f - r_newrefdef.fov_y / 2f));
+
+        for (int i = 0; i < 4; i++) {
+            frustum[i].type = Defines.PLANE_ANYZ;
+            frustum[i].dist = Math3D.dotProduct(r_origin, frustum[i].normal);
+            frustum[i].signbits = (byte) SignbitsForPlane(frustum[i]);
+        }
+    }
+
+    /**
+     * R_SetupFrame
+     */
+    void R_SetupFrame() {
+        r_framecount++;
+
+        //     build the transformation matrix for the given view angles
+        Math3D.vectorCopy(r_newrefdef.vieworg, r_origin);
+
+        Math3D.angleVectors(r_newrefdef.viewangles, vpn, vright, vup);
+
+        //     current viewcluster
+        mleaf_t leaf;
+        if ((r_newrefdef.rdflags & Defines.RDF_NOWORLDMODEL) == 0) {
+            r_oldviewcluster = r_viewcluster;
+            r_oldviewcluster2 = r_viewcluster2;
+            leaf = Mod_PointInLeaf(r_origin, r_worldmodel);
+            r_viewcluster = r_viewcluster2 = leaf.cluster;
+
+            // check above and below so crossing solid water doesn't draw wrong
+            if (leaf.contents == 0) { // look down a bit
+                Math3D.vectorCopy(r_origin, temp);
+                temp[2] -= 16;
+                leaf = Mod_PointInLeaf(temp, r_worldmodel);
+                if ((leaf.contents & Defines.CONTENTS_SOLID) == 0 && (leaf.cluster != r_viewcluster2))
+                    r_viewcluster2 = leaf.cluster;
+            } else { // look up a bit
+                Math3D.vectorCopy(r_origin, temp);
+                temp[2] += 16;
+                leaf = Mod_PointInLeaf(temp, r_worldmodel);
+                if ((leaf.contents & Defines.CONTENTS_SOLID) == 0 && (leaf.cluster != r_viewcluster2))
+                    r_viewcluster2 = leaf.cluster;
+            }
+        }
+
+        System.arraycopy(r_newrefdef.blend, 0, v_blend, 0, 4);
+
+        c_brush_polys = 0;
+        c_alias_polys = 0;
+
+        // clear out the portion of the screen that the NOWORLDMODEL defines
+        if ((r_newrefdef.rdflags & Defines.RDF_NOWORLDMODEL) != 0) {
+            GL11.glEnable(GL11.GL_SCISSOR_TEST);
+            GL11.glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
+            GL11.glScissor(
+                    r_newrefdef.x,
+                    vid.height - r_newrefdef.height - r_newrefdef.y,
+                    r_newrefdef.width,
+                    r_newrefdef.height);
+            GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
+            GL11.glClearColor(1.0f, 0.0f, 0.5f, 0.5f);
+            GL11.glDisable(GL11.GL_SCISSOR_TEST);
+        }
+    }
+
+    /**
+     * MYgluPerspective
+     *
+     * @param fovy
+     * @param aspect
+     */
+    void MYgluPerspective(double fovy, double aspect) {
+        double ymax = (double) 4 * Math.tan(fovy * Math.PI / 360.0);
+        double ymin = -ymax;
+
+        double xmin = ymin * aspect;
+        double xmax = ymax * aspect;
+
+        xmin += -(2 * gl_state.camera_separation) / (double) 4;
+        xmax += -(2 * gl_state.camera_separation) / (double) 4;
+
+        GL11.glFrustum(xmin, xmax, ymin, ymax, (double) 4, (double) 4096);
+    }
+
+    /**
+     * R_SetupGL
+     */
+    void R_SetupGL() {
+
+        //
+        // set up viewport
+        //
+        //int x = (int) Math.floor(r_newrefdef.x * vid.width / vid.width);
+        int x = r_newrefdef.x;
+        //int x2 = (int) Math.ceil((r_newrefdef.x + r_newrefdef.width) * vid.width / vid.width);
+        int x2 = r_newrefdef.x + r_newrefdef.width;
+        //int y = (int) Math.floor(vid.height - r_newrefdef.y * vid.height / vid.height);
+        int y = vid.height - r_newrefdef.y;
+        //int y2 = (int) Math.ceil(vid.height - (r_newrefdef.y + r_newrefdef.height) * vid.height / vid.height);
+        int y2 = vid.height - (r_newrefdef.y + r_newrefdef.height);
+
+        int w = x2 - x;
+        int h = y - y2;
+
+        GL11.glViewport(x, y2, w, h);
+
+        //
+        // set up projection matrix
+        //
+        float screenaspect = (float) r_newrefdef.width / r_newrefdef.height;
+        GL11.glMatrixMode(GL11.GL_PROJECTION);
+        GL11.glLoadIdentity();
+        MYgluPerspective(r_newrefdef.fov_y, screenaspect);
+
+        GL11.glCullFace(GL11.GL_FRONT);
+
+        GL11.glMatrixMode(GL11.GL_MODELVIEW);
+        GL11.glLoadIdentity();
+
+        GL11.glRotatef(-90, 1, 0, 0); // put Z going up
+        GL11.glRotatef(90, 0, 0, 1); // put Z going up
+        GL11.glRotatef(-r_newrefdef.viewangles[2], 1, 0, 0);
+        GL11.glRotatef(-r_newrefdef.viewangles[0], 0, 1, 0);
+        GL11.glRotatef(-r_newrefdef.viewangles[1], 0, 0, 1);
+        GL11.glTranslatef(-r_newrefdef.vieworg[0], -r_newrefdef.vieworg[1], -r_newrefdef.vieworg[2]);
+
+        GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, r_world_matrix);
+        r_world_matrix.clear();
+
+        //
+        // set drawing parms
+        //
+        if (gl_cull.value != 0.0f)
+            GL11.glEnable(GL11.GL_CULL_FACE);
+        else
+            GL11.glDisable(GL11.GL_CULL_FACE);
+
+        GL11.glDisable(GL11.GL_BLEND);
+        GL11.glDisable(GL11.GL_ALPHA_TEST);
+        GL11.glEnable(GL11.GL_DEPTH_TEST);
+    }
+
+    /**
+     * R_Clear
+     */
+    void R_Clear() {
+        if (gl_ztrick.value != 0.0f) {
+
+            if (gl_clear.value != 0.0f) {
+                GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
+            }
+
+            trickframe++;
+            if ((trickframe & 1) != 0) {
+                gldepthmin = 0;
+                gldepthmax = 0.49999f;
+                GL11.glDepthFunc(GL11.GL_LEQUAL);
+            } else {
+                gldepthmin = 1;
+                gldepthmax = 0.5f;
+                GL11.glDepthFunc(GL11.GL_GEQUAL);
+            }
+        } else {
+            if (gl_clear.value != 0.0f)
+                GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
+            else
+                GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT);
+
+            gldepthmin = 0;
+            gldepthmax = 1;
+            GL11.glDepthFunc(GL11.GL_LEQUAL);
+        }
+        GL11.glDepthRange(gldepthmin, gldepthmax);
+    }
+
+    /**
+     * R_Flash
+     */
+    void R_Flash() {
+        R_PolyBlend();
+    }
+
+    /**
+     * R_RenderView
+     * r_newrefdef must be set before the first call
+     */
+    void R_RenderView(refdef_t fd) {
+
+        if (r_norefresh.value != 0.0f)
+            return;
+
+        r_newrefdef = fd;
+
+        // included by cwei
+        if (r_newrefdef == null) {
+            Com.Error(Defines.ERR_DROP, "R_RenderView: refdef_t fd is null");
+        }
+
+        if (r_worldmodel == null && (r_newrefdef.rdflags & Defines.RDF_NOWORLDMODEL) == 0)
+            Com.Error(Defines.ERR_DROP, "R_RenderView: NULL worldmodel");
+
+        if (r_speeds.value != 0.0f) {
+            c_brush_polys = 0;
+            c_alias_polys = 0;
+        }
+
+        R_PushDlights();
+
+        if (gl_finish.value != 0.0f)
+            GL11.glFinish();
+
+        R_SetupFrame();
+
+        R_SetFrustum();
+
+        R_SetupGL();
+
+        R_MarkLeaves(); // done here so we know if we're in water
+
+        R_DrawWorld();
+
+        R_DrawEntitiesOnList();
+
+        R_RenderDlights();
+
+        R_DrawParticles();
+
+        R_DrawAlphaSurfaces();
+
+        R_Flash();
+
+        if (r_speeds.value != 0.0f) {
+            VideoDriver.Printf(
+                    Defines.PRINT_ALL,
+                    "%4i wpoly %4i epoly %i tex %i lmaps\n",
+                    new Vargs(4).add(c_brush_polys).add(c_alias_polys).add(c_visible_textures).add(c_visible_lightmaps));
+        }
+    }
+
+    /**
+     * R_SetGL2D
+     */
+    void R_SetGL2D() {
+        // set 2D virtual screen size
+        GL11.glViewport(0, 0, vid.width, vid.height);
+        GL11.glMatrixMode(GL11.GL_PROJECTION);
+        GL11.glLoadIdentity();
+        GL11.glOrtho(0, vid.width, vid.height, 0, -99999, 99999);
+        GL11.glMatrixMode(GL11.GL_MODELVIEW);
+        GL11.glLoadIdentity();
+        GL11.glDisable(GL11.GL_DEPTH_TEST);
+        GL11.glDisable(GL11.GL_CULL_FACE);
+        GL11.glDisable(GL11.GL_BLEND);
+        GL11.glEnable(GL11.GL_ALPHA_TEST);
+        GL11.glColor4f(1, 1, 1, 1);
+    }
+
+    /**
+     * R_SetLightLevel
+     */
+    void R_SetLightLevel() {
+        if ((r_newrefdef.rdflags & Defines.RDF_NOWORLDMODEL) != 0)
+            return;
+
+        // save off light value for server to look at (BIG HACK!)
+
+        R_LightPoint(r_newrefdef.vieworg, light);
+
+        // pick the greatest component, which should be the same
+        // as the mono value returned by software
+        if (light[0] > light[1]) {
+            if (light[0] > light[2])
+                r_lightlevel.value = 150 * light[0];
+            else
+                r_lightlevel.value = 150 * light[2];
+        } else {
+            if (light[1] > light[2])
+                r_lightlevel.value = 150 * light[1];
+            else
+                r_lightlevel.value = 150 * light[2];
+        }
+    }
+
+    /**
+     * R_RenderFrame
+     */
+    protected void R_RenderFrame(refdef_t fd) {
+        R_RenderView(fd);
+        R_SetLightLevel();
+        R_SetGL2D();
+    }
+
+    /**
+     * R_Register
+     */
+    protected void R_Register() {
+        r_lefthand = Cvar.get("hand", "0", Globals.CVAR_USERINFO | Globals.CVAR_ARCHIVE);
+        r_norefresh = Cvar.get("r_norefresh", "0", 0);
+        r_fullbright = Cvar.get("r_fullbright", "0", 0);
+        r_drawentities = Cvar.get("r_drawentities", "1", 0);
+        r_drawworld = Cvar.get("r_drawworld", "1", 0);
+        r_novis = Cvar.get("r_novis", "0", 0);
+        r_nocull = Cvar.get("r_nocull", "0", 0);
+        r_lerpmodels = Cvar.get("r_lerpmodels", "1", 0);
+        r_speeds = Cvar.get("r_speeds", "0", 0);
+
+        r_lightlevel = Cvar.get("r_lightlevel", "1", 0);
+
+        gl_nosubimage = Cvar.get("gl_nosubimage", "0", 0);
+        gl_allow_software = Cvar.get("gl_allow_software", "0", 0);
+
+        gl_particle_min_size = Cvar.get("gl_particle_min_size", "2", Globals.CVAR_ARCHIVE);
+        gl_particle_max_size = Cvar.get("gl_particle_max_size", "40", Globals.CVAR_ARCHIVE);
+        gl_particle_size = Cvar.get("gl_particle_size", "40", Globals.CVAR_ARCHIVE);
+        gl_particle_att_a = Cvar.get("gl_particle_att_a", "0.01", Globals.CVAR_ARCHIVE);
+        gl_particle_att_b = Cvar.get("gl_particle_att_b", "0.0", Globals.CVAR_ARCHIVE);
+        gl_particle_att_c = Cvar.get("gl_particle_att_c", "0.01", Globals.CVAR_ARCHIVE);
+
+        gl_modulate = Cvar.get("gl_modulate", "1.5", Globals.CVAR_ARCHIVE);
+        gl_log = Cvar.get("gl_log", "0", 0);
+        gl_bitdepth = Cvar.get("gl_bitdepth", "0", 0);
+        gl_mode = Cvar.get("gl_mode", "3", Globals.CVAR_ARCHIVE); // 640x480
+        gl_lightmap = Cvar.get("gl_lightmap", "0", 0);
+        gl_shadows = Cvar.get("gl_shadows", "0", Globals.CVAR_ARCHIVE);
+        gl_dynamic = Cvar.get("gl_dynamic", "1", 0);
+        gl_nobind = Cvar.get("gl_nobind", "0", 0);
+        gl_round_down = Cvar.get("gl_round_down", "1", 0);
+        gl_picmip = Cvar.get("gl_picmip", "0", 0);
+        gl_skymip = Cvar.get("gl_skymip", "0", 0);
+        gl_showtris = Cvar.get("gl_showtris", "0", 0);
+        gl_ztrick = Cvar.get("gl_ztrick", "0", 0);
+        gl_finish = Cvar.get("gl_finish", "0", Globals.CVAR_ARCHIVE);
+        gl_clear = Cvar.get("gl_clear", "0", 0);
+        gl_cull = Cvar.get("gl_cull", "1", 0);
+        gl_polyblend = Cvar.get("gl_polyblend", "1", 0);
+        gl_flashblend = Cvar.get("gl_flashblend", "0", 0);
+        gl_playermip = Cvar.get("gl_playermip", "0", 0);
+        gl_monolightmap = Cvar.get("gl_monolightmap", "0", 0);
+        gl_driver = Cvar.get("gl_driver", "opengl32", Globals.CVAR_ARCHIVE);
+        gl_texturemode = Cvar.get("gl_texturemode", "GL_LINEAR_MIPMAP_NEAREST", Globals.CVAR_ARCHIVE);
+        gl_texturealphamode = Cvar.get("gl_texturealphamode", "default", Globals.CVAR_ARCHIVE);
+        gl_texturesolidmode = Cvar.get("gl_texturesolidmode", "default", Globals.CVAR_ARCHIVE);
+        gl_lockpvs = Cvar.get("gl_lockpvs", "0", 0);
+
+        gl_vertex_arrays = Cvar.get("gl_vertex_arrays", "1", Globals.CVAR_ARCHIVE);
+
+        gl_ext_swapinterval = Cvar.get("gl_ext_swapinterval", "1", Globals.CVAR_ARCHIVE);
+        gl_ext_palettedtexture = Cvar.get("gl_ext_palettedtexture", "0", Globals.CVAR_ARCHIVE);
+        gl_ext_multitexture = Cvar.get("gl_ext_multitexture", "1", Globals.CVAR_ARCHIVE);
+        gl_ext_pointparameters = Cvar.get("gl_ext_pointparameters", "1", Globals.CVAR_ARCHIVE);
+        gl_ext_compiled_vertex_array = Cvar.get("gl_ext_compiled_vertex_array", "1", Globals.CVAR_ARCHIVE);
+
+        gl_drawbuffer = Cvar.get("gl_drawbuffer", "GL_BACK", 0);
+        gl_swapinterval = Cvar.get("gl_swapinterval", "0", Globals.CVAR_ARCHIVE);
+
+        gl_saturatelighting = Cvar.get("gl_saturatelighting", "0", 0);
+
+        gl_3dlabs_broken = Cvar.get("gl_3dlabs_broken", "1", Globals.CVAR_ARCHIVE);
+
+        vid_fullscreen = Cvar.get("vid_fullscreen", "0", Globals.CVAR_ARCHIVE);
+        vid_gamma = Cvar.get("vid_gamma", "1.0", Globals.CVAR_ARCHIVE);
+        vid_ref = Cvar.get("vid_ref", "lwjgl", Globals.CVAR_ARCHIVE);
+
+        Cmd.AddCommand("imagelist", new xcommand_t() {
+            public void execute() {
+                GL_ImageList_f();
+            }
+        });
+
+        Cmd.AddCommand("screenshot", new xcommand_t() {
+            public void execute() {
+                GL_ScreenShot_f();
+            }
+        });
+        Cmd.AddCommand("modellist", new xcommand_t() {
+            public void execute() {
+                Mod_Modellist_f();
+            }
+        });
+        Cmd.AddCommand("gl_strings", new xcommand_t() {
+            public void execute() {
+                GL_Strings_f();
+            }
+        });
+    }
+
+    /**
+     * R_SetMode
+     */
+    protected boolean R_SetMode() {
+        boolean fullscreen = (vid_fullscreen.value > 0.0f);
+
+        vid_fullscreen.modified = false;
+        gl_mode.modified = false;
+
+        Dimension dim = new Dimension(vid.width, vid.height);
+
+        int err; //  enum rserr_t
+        if ((err = GLimp_SetMode(dim, (int) gl_mode.value, fullscreen)) == rserr_ok) {
+            gl_state.prev_mode = (int) gl_mode.value;
+        } else {
+            if (err == rserr_invalid_fullscreen) {
+                Cvar.setValue("vid_fullscreen", 0);
+                vid_fullscreen.modified = false;
+                VideoDriver.Printf(Defines.PRINT_ALL, "ref_gl::R_SetMode() - fullscreen unavailable in this mode\n");
+                if ((err = GLimp_SetMode(dim, (int) gl_mode.value, false)) == rserr_ok)
+                    return true;
+            } else if (err == rserr_invalid_mode) {
+                Cvar.setValue("gl_mode", gl_state.prev_mode);
+                gl_mode.modified = false;
+                VideoDriver.Printf(Defines.PRINT_ALL, "ref_gl::R_SetMode() - invalid mode\n");
+            }
+
+            // try setting it back to something safe
+            if ((err = GLimp_SetMode(dim, gl_state.prev_mode, false)) != rserr_ok) {
+                VideoDriver.Printf(Defines.PRINT_ALL, "ref_gl::R_SetMode() - could not revert to safe mode\n");
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * R_Init
+     */
+    protected boolean R_Init(int vid_xpos, int vid_ypos) {
+
+        assert (Warp.SIN.length == 256) : "warpsin table bug";
+
+        // fill r_turbsin
+        for (int j = 0; j < 256; j++) {
+            r_turbsin[j] = Warp.SIN[j] * 0.5f;
+        }
+
+        VideoDriver.Printf(Defines.PRINT_ALL, "ref_gl version: " + REF_VERSION + '\n');
+
+        Draw_GetPalette();
+
+        R_Register();
+
+        // set our "safe" modes
+        gl_state.prev_mode = 3;
+
+        // create the window and set up the context
+        if (!R_SetMode()) {
+            VideoDriver.Printf(Defines.PRINT_ALL, "ref_gl::R_Init() - could not R_SetMode()\n");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * R_Init2
+     */
+    protected boolean R_Init2() {
+        VideoDriver.MenuInit();
+
+               /*
+        ** get our various GL strings
+               */
+        gl_config.vendor_string = GL11.glGetString(GL11.GL_VENDOR);
+        VideoDriver.Printf(Defines.PRINT_ALL, "GL_VENDOR: " + gl_config.vendor_string + '\n');
+        gl_config.renderer_string = GL11.glGetString(GL11.GL_RENDERER);
+        VideoDriver.Printf(Defines.PRINT_ALL, "GL_RENDERER: " + gl_config.renderer_string + '\n');
+        gl_config.version_string = GL11.glGetString(GL11.GL_VERSION);
+        VideoDriver.Printf(Defines.PRINT_ALL, "GL_VERSION: " + gl_config.version_string + '\n');
+        gl_config.extensions_string = GL11.glGetString(GL11.GL_EXTENSIONS);
+        VideoDriver.Printf(Defines.PRINT_ALL, "GL_EXTENSIONS: " + gl_config.extensions_string + '\n');
+
+        gl_config.parseOpenGLVersion();
+
+        String renderer_buffer = gl_config.renderer_string.toLowerCase();
+        String vendor_buffer = gl_config.vendor_string.toLowerCase();
+
+        if (renderer_buffer.contains("voodoo")) {
+            if (!renderer_buffer.contains("rush"))
+                gl_config.renderer = GL_RENDERER_VOODOO;
+            else
+                gl_config.renderer = GL_RENDERER_VOODOO_RUSH;
+        } else if (vendor_buffer.contains("sgi"))
+            gl_config.renderer = GL_RENDERER_SGI;
+        else if (renderer_buffer.contains("permedia"))
+            gl_config.renderer = GL_RENDERER_PERMEDIA2;
+        else if (renderer_buffer.contains("glint"))
+            gl_config.renderer = GL_RENDERER_GLINT_MX;
+        else if (renderer_buffer.contains("glzicd"))
+            gl_config.renderer = GL_RENDERER_REALIZM;
+        else if (renderer_buffer.contains("gdi"))
+            gl_config.renderer = GL_RENDERER_MCD;
+        else if (renderer_buffer.contains("pcx2"))
+            gl_config.renderer = GL_RENDERER_PCX2;
+        else if (renderer_buffer.contains("verite"))
+            gl_config.renderer = GL_RENDERER_RENDITION;
+        else
+            gl_config.renderer = GL_RENDERER_OTHER;
+
+        String monolightmap = gl_monolightmap.string.toUpperCase();
+        if (monolightmap.length() < 2 || monolightmap.charAt(1) != 'F') {
+            if (gl_config.renderer == GL_RENDERER_PERMEDIA2) {
+                Cvar.set("gl_monolightmap", "A");
+                VideoDriver.Printf(Defines.PRINT_ALL, "...using gl_monolightmap 'a'\n");
+            } else if ((gl_config.renderer & GL_RENDERER_POWERVR) != 0) {
+                Cvar.set("gl_monolightmap", "0");
+            } else {
+                Cvar.set("gl_monolightmap", "0");
+            }
+        }
+
+        // power vr can't have anything stay in the framebuffer, so
+        // the screen needs to redraw the tiled background every frame
+        if ((gl_config.renderer & GL_RENDERER_POWERVR) != 0) {
+            Cvar.set("scr_drawall", "1");
+        } else {
+            Cvar.set("scr_drawall", "0");
+        }
+
+        // MCD has buffering issues
+        if (gl_config.renderer == GL_RENDERER_MCD) {
+            Cvar.setValue("gl_finish", 1);
+        }
+
+        if ((gl_config.renderer & GL_RENDERER_3DLABS) != 0) {
+            gl_config.allow_cds = gl_3dlabs_broken.value == 0.0f;
+        } else {
+            gl_config.allow_cds = true;
+        }
+
+        if (gl_config.allow_cds)
+            VideoDriver.Printf(Defines.PRINT_ALL, "...allowing CDS\n");
+        else
+            VideoDriver.Printf(Defines.PRINT_ALL, "...disabling CDS\n");
+
+               /*
+        ** grab extensions
+               */
+        if (gl_config.extensions_string.contains("GL_EXT_compiled_vertex_array")
+                || gl_config.extensions_string.contains("GL_SGI_compiled_vertex_array")) {
+            VideoDriver.Printf(Defines.PRINT_ALL, "...enabling GL_EXT_compiled_vertex_array\n");
+            //          qglLockArraysEXT = ( void * ) qwglGetProcAddress( "glLockArraysEXT" );
+            qglLockArraysEXT = gl_ext_compiled_vertex_array.value != 0.0f;
+            //          qglUnlockArraysEXT = ( void * ) qwglGetProcAddress( "glUnlockArraysEXT" );
+            //qglUnlockArraysEXT = true;
+        } else {
+            VideoDriver.Printf(Defines.PRINT_ALL, "...GL_EXT_compiled_vertex_array not found\n");
+            qglLockArraysEXT = false;
+        }
+
+        if (gl_config.extensions_string.contains("WGL_EXT_swap_control")) {
+            qwglSwapIntervalEXT = true;
+            VideoDriver.Printf(Defines.PRINT_ALL, "...enabling WGL_EXT_swap_control\n");
+        } else {
+            qwglSwapIntervalEXT = false;
+            VideoDriver.Printf(Defines.PRINT_ALL, "...WGL_EXT_swap_control not found\n");
+        }
+
+        if (gl_config.extensions_string.contains("GL_EXT_point_parameters")) {
+            if (gl_ext_pointparameters.value != 0.0f) {
+                //                      qglPointParameterfEXT = ( void (APIENTRY *)( GLenum, GLfloat ) ) qwglGetProcAddress( "glPointParameterfEXT" );
+                qglPointParameterfEXT = true;
+                //                      qglPointParameterfvEXT = ( void (APIENTRY *)( GLenum, const GLfloat * ) ) qwglGetProcAddress( "glPointParameterfvEXT" );
+                VideoDriver.Printf(Defines.PRINT_ALL, "...using GL_EXT_point_parameters\n");
+            } else {
+                VideoDriver.Printf(Defines.PRINT_ALL, "...ignoring GL_EXT_point_parameters\n");
+            }
+        } else {
+            VideoDriver.Printf(Defines.PRINT_ALL, "...GL_EXT_point_parameters not found\n");
+        }
+
+        // #ifdef __linux__
+        //      if ( strstr( gl_config.extensions_string, "3DFX_set_global_palette" ))
+        //      {
+        //              if ( gl_ext_palettedtexture->value )
+        //              {
+        //                      VideoDriver.Printf( Defines.PRINT_ALL, "...using 3DFX_set_global_palette\n" );
+        //                      qgl3DfxSetPaletteEXT = ( void ( APIENTRY * ) (GLuint *) )qwglGetProcAddress( "gl3DfxSetPaletteEXT" );
+        ////                    qglColorTableEXT = Fake_glColorTableEXT;
+        //              }
+        //              else
+        //              {
+        //                      VideoDriver.Printf( Defines.PRINT_ALL, "...ignoring 3DFX_set_global_palette\n" );
+        //              }
+        //      }
+        //      else
+        //      {
+        //              VideoDriver.Printf( Defines.PRINT_ALL, "...3DFX_set_global_palette not found\n" );
+        //      }
+        // #endif
+
+        if (!qglColorTableEXT
+                && gl_config.extensions_string.contains("GL_EXT_paletted_texture")
+                && gl_config.extensions_string.contains("GL_EXT_shared_texture_palette")) {
+            if (gl_ext_palettedtexture.value != 0.0f) {
+                VideoDriver.Printf(Defines.PRINT_ALL, "...using GL_EXT_shared_texture_palette\n");
+                qglColorTableEXT = false; // true; TODO jogl bug
+            } else {
+                VideoDriver.Printf(Defines.PRINT_ALL, "...ignoring GL_EXT_shared_texture_palette\n");
+                qglColorTableEXT = false;
+            }
+        } else {
+            VideoDriver.Printf(Defines.PRINT_ALL, "...GL_EXT_shared_texture_palette not found\n");
+        }
+
+        if (gl_config.extensions_string.contains("GL_ARB_multitexture")) {
+            VideoDriver.Printf(Defines.PRINT_ALL, "...using GL_ARB_multitexture\n");
+            qglActiveTextureARB = true;
+            GL_TEXTURE0 = ARBMultitexture.GL_TEXTURE0_ARB;
+            GL_TEXTURE1 = ARBMultitexture.GL_TEXTURE1_ARB;
+        } else {
+            VideoDriver.Printf(Defines.PRINT_ALL, "...GL_ARB_multitexture not found\n");
+        }
+
+        if (!(qglActiveTextureARB))
+            return false;
+
+        GL_SetDefaultState();
+
+        GL_InitImages();
+        Mod_Init();
+        R_InitParticleTexture();
+        Draw_InitLocal();
+
+        int err = GL11.glGetError();
+        if (err != GL11.GL_NO_ERROR)
+            VideoDriver.Printf(
+                    Defines.PRINT_ALL,
+                    "glGetError() = 0x%x\n\t%s\n",
+                    new Vargs(2).add(err).add("" + GL11.glGetString(err)));
+
+        return true;
+    }
+
+    /**
+     * R_Shutdown
+     */
+    protected void R_Shutdown() {
+        Cmd.RemoveCommand("modellist");
+        Cmd.RemoveCommand("screenshot");
+        Cmd.RemoveCommand("imagelist");
+        Cmd.RemoveCommand("gl_strings");
+
+        Mod_FreeAll();
+
+        GL_ShutdownImages();
+
+               /*
+         * shut down OS specific OpenGL stuff like contexts, etc.
+                */
+        GLimp_Shutdown();
+    }
+
+    /**
+     * R_BeginFrame
+     */
+    protected void R_BeginFrame(float camera_separation) {
+
+        gl_state.camera_separation = camera_separation;
+
+               /*
+        ** change modes if necessary
+               */
+        if (gl_mode.modified || vid_fullscreen.modified) {
+            // FIXME: only restart if CDS is required
+            CvarT ref;
+
+            ref = Cvar.get("vid_ref", "lwjgl", 0);
+            ref.modified = true;
+        }
+
+        if (gl_log.modified) {
+            GLimp_EnableLogging((gl_log.value != 0.0f));
+            gl_log.modified = false;
+        }
+
+        if (gl_log.value != 0.0f) {
+            GLimp_LogNewFrame();
+        }
+
+               /*
+        ** update 3Dfx gamma -- it is expected that a user will do a vid_restart
+               ** after tweaking this value
+               */
+        if (vid_gamma.modified) {
+            vid_gamma.modified = false;
+
+            if ((gl_config.renderer & GL_RENDERER_VOODOO) != 0) {
+                // wird erstmal nicht gebraucht
+
+                               /*
+                char envbuffer[1024];
+                               float g;
+
+                               g = 2.00 * ( 0.8 - ( vid_gamma->value - 0.5 ) ) + 1.0F;
+                               Com_sprintf( envbuffer, sizeof(envbuffer), "SSTV2_GAMMA=%f", g );
+                               putenv( envbuffer );
+                               Com_sprintf( envbuffer, sizeof(envbuffer), "SST_GAMMA=%f", g );
+                               putenv( envbuffer );
+                               */
+                VideoDriver.Printf(Defines.PRINT_DEVELOPER, "gamma anpassung fuer VOODOO nicht gesetzt");
+            }
+        }
+
+        GLimp_BeginFrame(camera_separation);
+
+               /*
+               ** go into 2D mode
+               */
+        GL11.glViewport(0, 0, vid.width, vid.height);
+        GL11.glMatrixMode(GL11.GL_PROJECTION);
+        GL11.glLoadIdentity();
+        GL11.glOrtho(0, vid.width, vid.height, 0, -99999, 99999);
+        GL11.glMatrixMode(GL11.GL_MODELVIEW);
+        GL11.glLoadIdentity();
+        GL11.glDisable(GL11.GL_DEPTH_TEST);
+        GL11.glDisable(GL11.GL_CULL_FACE);
+        GL11.glDisable(GL11.GL_BLEND);
+        GL11.glEnable(GL11.GL_ALPHA_TEST);
+        GL11.glColor4f(1, 1, 1, 1);
+
+               /*
+               ** draw buffer stuff
+               */
+        if (gl_drawbuffer.modified) {
+            gl_drawbuffer.modified = false;
+
+            if (gl_state.camera_separation == 0 || !gl_state.stereo_enabled) {
+                if (gl_drawbuffer.string.equalsIgnoreCase("GL_FRONT"))
+                    GL11.glDrawBuffer(GL11.GL_FRONT);
+                else
+                    GL11.glDrawBuffer(GL11.GL_BACK);
+            }
+        }
+
+               /*
+               ** texturemode stuff
+               */
+        if (gl_texturemode.modified) {
+            GL_TextureMode(gl_texturemode.string);
+            gl_texturemode.modified = false;
+        }
+
+        if (gl_texturealphamode.modified) {
+            GL_TextureAlphaMode(gl_texturealphamode.string);
+            gl_texturealphamode.modified = false;
+        }
+
+        if (gl_texturesolidmode.modified) {
+            GL_TextureSolidMode(gl_texturesolidmode.string);
+            gl_texturesolidmode.modified = false;
+        }
+
+               /*
+               ** swapinterval stuff
+               */
+        GL_UpdateSwapInterval();
+
+        //
+        // clear screen if desired
+        //
+        R_Clear();
+    }
+
+    /**
+     * R_SetPalette
+     */
+    protected void R_SetPalette(byte[] palette) {
+        // 256 RGB values (768 bytes)
+        // or null
+        int i;
+        int color = 0;
+
+        if (palette != null) {
+            int j = 0;
+            for (i = 0; i < 256; i++) {
+                color = (palette[j++] & 0xFF);
+                color |= (palette[j++] & 0xFF) << 8;
+                color |= (palette[j++] & 0xFF) << 16;
+                color |= 0xFF000000;
+                r_rawpalette[i] = color;
+            }
+        } else {
+            for (i = 0; i < 256; i++) {
+                r_rawpalette[i] = d_8to24table[i] | 0xff000000;
+            }
+        }
+        GL_SetTexturePalette(r_rawpalette);
+
+        GL11.glClearColor(0, 0, 0, 0);
+        GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
+        GL11.glClearColor(1f, 0f, 0.5f, 0.5f);
+    }
+
+    /**
+     * R_DrawBeam
+     */
+    void R_DrawBeam(entity_t e) {
+        oldorigin[0] = e.oldorigin[0];
+        oldorigin[1] = e.oldorigin[1];
+        oldorigin[2] = e.oldorigin[2];
+
+        origin[0] = e.origin[0];
+        origin[1] = e.origin[1];
+        origin[2] = e.origin[2];
+
+        normalized_direction[0] = direction[0] = oldorigin[0] - origin[0];
+        normalized_direction[1] = direction[1] = oldorigin[1] - origin[1];
+        normalized_direction[2] = direction[2] = oldorigin[2] - origin[2];
+
+        if (Math3D.vectorNormalize(normalized_direction) == 0.0f)
+            return;
+
+        Math3D.perpendicularVector(perpvec, normalized_direction);
+        Math3D.vectorScale(perpvec, e.frame / 2, perpvec);
+
+        for (int i = 0; i < 6; i++) {
+            Math3D.rotatePointAroundVector(
+                    start_points[i],
+                    normalized_direction,
+                    perpvec,
+                    (360.0f / NUM_BEAM_SEGS) * i);
+
+            Math3D.vectorAdd(start_points[i], origin, start_points[i]);
+            Math3D.vectorAdd(start_points[i], direction, end_points[i]);
+        }
+
+        GL11.glDisable(GL11.GL_TEXTURE_2D);
+        GL11.glEnable(GL11.GL_BLEND);
+        GL11.glDepthMask(false);
+
+        float r = (d_8to24table[e.skinnum & 0xFF]) & 0xFF;
+        float g = (d_8to24table[e.skinnum & 0xFF] >> 8) & 0xFF;
+        float b = (d_8to24table[e.skinnum & 0xFF] >> 16) & 0xFF;
+
+        r *= 1 / 255.0f;
+        g *= 1 / 255.0f;
+        b *= 1 / 255.0f;
+
+        GL11.glColor4f(r, g, b, e.alpha);
+
+        GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
+
+        float[] v;
+
+        for (int i = 0; i < NUM_BEAM_SEGS; i++) {
+            v = start_points[i];
+            GL11.glVertex3f(v[0], v[1], v[2]);
+            v = end_points[i];
+            GL11.glVertex3f(v[0], v[1], v[2]);
+            v = start_points[(i + 1) % NUM_BEAM_SEGS];
+            GL11.glVertex3f(v[0], v[1], v[2]);
+            v = end_points[(i + 1) % NUM_BEAM_SEGS];
+            GL11.glVertex3f(v[0], v[1], v[2]);
+        }
+        GL11.glEnd();
+
+        GL11.glEnable(GL11.GL_TEXTURE_2D);
+        GL11.glDisable(GL11.GL_BLEND);
+        GL11.glDepthMask(true);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/render/lwjgl/Mesh.java b/src/main/java/lwjake2/render/lwjgl/Mesh.java
new file mode 100644 (file)
index 0000000..d00934b
--- /dev/null
@@ -0,0 +1,555 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render.lwjgl;
+
+import lwjake2.Defines;
+import lwjake2.client.VideoDriver;
+import lwjake2.client.entity_t;
+import lwjake2.qcommon.qfiles;
+import lwjake2.render.Image;
+import lwjake2.util.Math3D;
+import org.lwjgl.BufferUtils;
+import org.lwjgl.opengl.ARBMultitexture;
+import org.lwjgl.opengl.GL11;
+
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+/**
+ * Mesh
+ *
+ * @author cwei
+ */
+public abstract class Mesh extends Light {
+
+    // g_mesh.c: triangle model functions
+    /*
+    =============================================================
+
+         ALIAS MODELS
+
+       =============================================================
+       */
+
+    static final int NUMVERTEXNORMALS = 162;
+    // precalculated dot products for quantized angles
+    static final int SHADEDOT_QUANT = 16;
+    final float[][] r_avertexnormals = Anorms.VERTEXNORMALS;
+    final float[] shadevector = {0, 0, 0};
+    final float[] shadelight = {0, 0, 0};
+    final float[][] r_avertexnormal_dots = Anorms.VERTEXNORMAL_DOTS;
+    final FloatBuffer colorArrayBuf = BufferUtils.createFloatBuffer(qfiles.MAX_VERTS * 4);
+    final FloatBuffer vertexArrayBuf = BufferUtils.createFloatBuffer(qfiles.MAX_VERTS * 3);
+    final FloatBuffer textureArrayBuf = BufferUtils.createFloatBuffer(qfiles.MAX_VERTS * 2);
+    final float[][] vectors = {
+            {0, 0, 0}, {0, 0, 0}, {0, 0, 0} // 3 mal vec3_t
+    };
+    // bounding box
+    final float[][] bbox = {
+            {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0},
+            {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}
+    };
+    // stack variable
+    private final float[] move = {0, 0, 0}; // vec3_t
+    private final float[] frontv = {0, 0, 0}; // vec3_t
+    private final float[] backv = {0, 0, 0}; // vec3_t
+    private final float[] point = {0, 0, 0};
+    // TODO sync with jogl renderer. hoz
+    // stack variable
+    private final float[] mins = {0, 0, 0};
+    private final float[] maxs = {0, 0, 0};
+    float[] shadedots = r_avertexnormal_dots[0];
+    boolean isFilled = false;
+    float[] tmpVec = {0, 0, 0};
+
+    /**
+     * GL_LerpVerts
+     *
+     * @param nverts
+     * @param ov
+     * @param move
+     * @param frontv
+     * @param backv
+     */
+    void GL_LerpVerts(int nverts, int[] ov, int[] v, float[] move, float[] frontv, float[] backv) {
+        FloatBuffer lerp = vertexArrayBuf;
+        lerp.limit((nverts << 2) - nverts); // nverts * 3
+
+        int ovv, vv;
+        //PMM -- added RF_SHELL_DOUBLE, RF_SHELL_HALF_DAM
+        if ((currententity.flags & (Defines.RF_SHELL_RED | Defines.RF_SHELL_GREEN | Defines.RF_SHELL_BLUE | Defines.RF_SHELL_DOUBLE | Defines.RF_SHELL_HALF_DAM)) != 0) {
+            float[] normal;
+            int j = 0;
+            for (int i = 0; i < nverts; i++/* , v++, ov++, lerp+=4 */) {
+                vv = v[i];
+                normal = r_avertexnormals[(vv >>> 24) & 0xFF];
+                ovv = ov[i];
+                lerp.put(j, move[0] + (ovv & 0xFF) * backv[0] + (vv & 0xFF) * frontv[0] + normal[0] * Defines.POWERSUIT_SCALE);
+                lerp.put(j + 1, move[1] + ((ovv >>> 8) & 0xFF) * backv[1] + ((vv >>> 8) & 0xFF) * frontv[1] + normal[1] * Defines.POWERSUIT_SCALE);
+                lerp.put(j + 2, move[2] + ((ovv >>> 16) & 0xFF) * backv[2] + ((vv >>> 16) & 0xFF) * frontv[2] + normal[2] * Defines.POWERSUIT_SCALE);
+                j += 3;
+            }
+        } else {
+            int j = 0;
+            for (int i = 0; i < nverts; i++ /* , v++, ov++, lerp+=4 */) {
+                ovv = ov[i];
+                vv = v[i];
+
+                lerp.put(j, move[0] + (ovv & 0xFF) * backv[0] + (vv & 0xFF) * frontv[0]);
+                lerp.put(j + 1, move[1] + ((ovv >>> 8) & 0xFF) * backv[1] + ((vv >>> 8) & 0xFF) * frontv[1]);
+                lerp.put(j + 2, move[2] + ((ovv >>> 16) & 0xFF) * backv[2] + ((vv >>> 16) & 0xFF) * frontv[2]);
+                j += 3;
+            }
+        }
+    }
+
+    /**
+     * GL_DrawAliasFrameLerp
+     * <p>
+     * interpolates between two frames and origins
+     * FIXME: batch lerp all vertexes
+     */
+    void GL_DrawAliasFrameLerp(qfiles.dmdl_t paliashdr, float backlerp) {
+        qfiles.daliasframe_t frame = paliashdr.aliasFrames[currententity.frame];
+
+        int[] verts = frame.verts;
+
+        qfiles.daliasframe_t oldframe = paliashdr.aliasFrames[currententity.oldframe];
+
+        int[] ov = oldframe.verts;
+
+        float alpha;
+        if ((currententity.flags & Defines.RF_TRANSLUCENT) != 0)
+            alpha = currententity.alpha;
+        else
+            alpha = 1.0f;
+
+        // PMM - added double shell
+        if ((currententity.flags & (Defines.RF_SHELL_RED | Defines.RF_SHELL_GREEN | Defines.RF_SHELL_BLUE | Defines.RF_SHELL_DOUBLE | Defines.RF_SHELL_HALF_DAM)) != 0)
+            GL11.glDisable(GL11.GL_TEXTURE_2D);
+
+        float frontlerp = 1.0f - backlerp;
+
+        // move should be the delta back to the previous frame * backlerp
+        Math3D.vectorSubtract(currententity.oldorigin, currententity.origin, frontv);
+        Math3D.angleVectors(currententity.angles, vectors[0], vectors[1], vectors[2]);
+
+        move[0] = Math3D.dotProduct(frontv, vectors[0]);    // forward
+        move[1] = -Math3D.dotProduct(frontv, vectors[1]);    // left
+        move[2] = Math3D.dotProduct(frontv, vectors[2]);    // up
+
+        Math3D.vectorAdd(move, oldframe.translate, move);
+
+        for (int i = 0; i < 3; i++) {
+            move[i] = backlerp * move[i] + frontlerp * frame.translate[i];
+            frontv[i] = frontlerp * frame.scale[i];
+            backv[i] = backlerp * oldframe.scale[i];
+        }
+
+        // ab hier wird optimiert
+
+        GL_LerpVerts(paliashdr.num_xyz, ov, verts, move, frontv, backv);
+
+        //GL11.glEnableClientState( GL11.GL_VERTEX_ARRAY );
+        GL11.glVertexPointer(3, 0, vertexArrayBuf);
+
+        // PMM - added double damage shell
+        if ((currententity.flags & (Defines.RF_SHELL_RED | Defines.RF_SHELL_GREEN | Defines.RF_SHELL_BLUE | Defines.RF_SHELL_DOUBLE | Defines.RF_SHELL_HALF_DAM)) != 0) {
+            GL11.glColor4f(shadelight[0], shadelight[1], shadelight[2], alpha);
+        } else {
+            GL11.glEnableClientState(GL11.GL_COLOR_ARRAY);
+            GL11.glColorPointer(4, 0, colorArrayBuf);
+
+            //
+            // pre light everything
+            //
+            FloatBuffer color = colorArrayBuf;
+            float l;
+            int size = paliashdr.num_xyz;
+            int j = 0;
+            for (int i = 0; i < size; i++) {
+                l = shadedots[(verts[i] >>> 24) & 0xFF];
+                color.put(j, l * shadelight[0]);
+                color.put(j + 1, l * shadelight[1]);
+                color.put(j + 2, l * shadelight[2]);
+                color.put(j + 3, alpha);
+                j += 4;
+            }
+        }
+
+        ARBMultitexture.glClientActiveTextureARB(GL_TEXTURE0);
+        GL11.glTexCoordPointer(2, 0, textureArrayBuf);
+        //GL11.glEnableClientState( GL11.GL_TEXTURE_COORD_ARRAY);
+
+        int pos = 0;
+        int[] counts = paliashdr.counts;
+
+        IntBuffer srcIndexBuf = null;
+
+        FloatBuffer dstTextureCoords = textureArrayBuf;
+        FloatBuffer srcTextureCoords = paliashdr.textureCoordBuf;
+
+        int dstIndex = 0;
+        int srcIndex = 0;
+        int count;
+        int mode;
+        int size = counts.length;
+        for (int j = 0; j < size; j++) {
+
+            // get the vertex count and primitive type
+            count = counts[j];
+            if (count == 0)
+                break;        // done
+
+            srcIndexBuf = paliashdr.indexElements[j];
+
+            mode = GL11.GL_TRIANGLE_STRIP;
+            if (count < 0) {
+                mode = GL11.GL_TRIANGLE_FAN;
+                count = -count;
+            }
+            srcIndex = pos << 1;
+            srcIndex--;
+            for (int k = 0; k < count; k++) {
+                dstIndex = srcIndexBuf.get(k) << 1;
+                dstTextureCoords.put(dstIndex, srcTextureCoords.get(++srcIndex));
+                dstTextureCoords.put(++dstIndex, srcTextureCoords.get(++srcIndex));
+            }
+
+            GL11.glDrawElements(mode, srcIndexBuf);
+            pos += count;
+        }
+
+        // PMM - added double damage shell
+        if ((currententity.flags & (Defines.RF_SHELL_RED | Defines.RF_SHELL_GREEN | Defines.RF_SHELL_BLUE | Defines.RF_SHELL_DOUBLE | Defines.RF_SHELL_HALF_DAM)) != 0)
+            GL11.glEnable(GL11.GL_TEXTURE_2D);
+
+        GL11.glDisableClientState(GL11.GL_COLOR_ARRAY);
+    }
+
+    /**
+     * GL_DrawAliasShadow
+     */
+    void GL_DrawAliasShadow(qfiles.dmdl_t paliashdr, int posenum) {
+        float lheight = currententity.origin[2] - lightspot[2];
+        // qfiles.daliasframe_t frame = paliashdr.aliasFrames[currententity.frame];
+        int[] order = paliashdr.glCmds;
+        float height = -lheight + 1.0f;
+
+        int orderIndex = 0;
+        int index = 0;
+
+        // TODO shadow drawing with vertex arrays
+
+        int count;
+        while (true) {
+            // get the vertex count and primitive type
+            count = order[orderIndex++];
+            if (count == 0)
+                break;        // done
+            if (count < 0) {
+                count = -count;
+                GL11.glBegin(GL11.GL_TRIANGLE_FAN);
+            } else
+                GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
+
+            do {
+                index = order[orderIndex + 2] * 3;
+                point[0] = vertexArrayBuf.get(index);
+                point[1] = vertexArrayBuf.get(index + 1);
+                point[2] = vertexArrayBuf.get(index + 2);
+
+                point[0] -= shadevector[0] * (point[2] + lheight);
+                point[1] -= shadevector[1] * (point[2] + lheight);
+                point[2] = height;
+                GL11.glVertex3f(point[0], point[1], point[2]);
+
+                orderIndex += 3;
+
+            } while (--count != 0);
+
+            GL11.glEnd();
+        }
+    }
+
+    /**
+     * R_CullAliasModel
+     */
+    boolean R_CullAliasModel(entity_t e) {
+        qfiles.dmdl_t paliashdr = (qfiles.dmdl_t) currentmodel.extradata;
+
+        if ((e.frame >= paliashdr.num_frames) || (e.frame < 0)) {
+            VideoDriver.Printf(Defines.PRINT_ALL, "R_CullAliasModel " + currentmodel.name + ": no such frame " + e.frame + '\n');
+            e.frame = 0;
+        }
+        if ((e.oldframe >= paliashdr.num_frames) || (e.oldframe < 0)) {
+            VideoDriver.Printf(Defines.PRINT_ALL, "R_CullAliasModel " + currentmodel.name + ": no such oldframe " + e.oldframe + '\n');
+            e.oldframe = 0;
+        }
+
+        qfiles.daliasframe_t pframe = paliashdr.aliasFrames[e.frame];
+        qfiles.daliasframe_t poldframe = paliashdr.aliasFrames[e.oldframe];
+
+               /*
+        ** compute axially aligned mins and maxs
+               */
+        if (pframe == poldframe) {
+            for (int i = 0; i < 3; i++) {
+                mins[i] = pframe.translate[i];
+                maxs[i] = mins[i] + pframe.scale[i] * 255;
+            }
+        } else {
+            float thismaxs, oldmaxs;
+            for (int i = 0; i < 3; i++) {
+                thismaxs = pframe.translate[i] + pframe.scale[i] * 255;
+
+                oldmaxs = poldframe.translate[i] + poldframe.scale[i] * 255;
+
+                if (pframe.translate[i] < poldframe.translate[i])
+                    mins[i] = pframe.translate[i];
+                else
+                    mins[i] = poldframe.translate[i];
+
+                if (thismaxs > oldmaxs)
+                    maxs[i] = thismaxs;
+                else
+                    maxs[i] = oldmaxs;
+            }
+        }
+
+               /*
+        ** compute a full bounding box
+               */
+        float[] tmp;
+        for (int i = 0; i < 8; i++) {
+            tmp = bbox[i];
+            if ((i & 1) != 0)
+                tmp[0] = mins[0];
+            else
+                tmp[0] = maxs[0];
+
+            if ((i & 2) != 0)
+                tmp[1] = mins[1];
+            else
+                tmp[1] = maxs[1];
+
+            if ((i & 4) != 0)
+                tmp[2] = mins[2];
+            else
+                tmp[2] = maxs[2];
+        }
+
+               /*
+        ** rotate the bounding box
+               */
+        tmp = mins;
+        Math3D.vectorCopy(e.angles, tmp);
+        tmp[YAW] = -tmp[YAW];
+        Math3D.angleVectors(tmp, vectors[0], vectors[1], vectors[2]);
+
+        for (int i = 0; i < 8; i++) {
+            Math3D.vectorCopy(bbox[i], tmp);
+
+            bbox[i][0] = Math3D.dotProduct(vectors[0], tmp);
+            bbox[i][1] = -Math3D.dotProduct(vectors[1], tmp);
+            bbox[i][2] = Math3D.dotProduct(vectors[2], tmp);
+
+            Math3D.vectorAdd(e.origin, bbox[i], bbox[i]);
+        }
+
+        int f, mask;
+        int aggregatemask = ~0; // 0xFFFFFFFF
+
+        for (int p = 0; p < 8; p++) {
+            mask = 0;
+
+            for (f = 0; f < 4; f++) {
+                float dp = Math3D.dotProduct(frustum[f].normal, bbox[p]);
+
+                if ((dp - frustum[f].dist) < 0) {
+                    mask |= (1 << f);
+                }
+            }
+
+            aggregatemask &= mask;
+        }
+
+        return aggregatemask != 0;
+
+    }
+
+//     TODO sync with jogl renderer. hoz
+
+    /**
+     * R_DrawAliasModel
+     */
+    void R_DrawAliasModel(entity_t e) {
+
+        qfiles.dmdl_t paliashdr = (qfiles.dmdl_t) currentmodel.extradata;
+
+        //
+        // get lighting information
+        //
+        // PMM - rewrote, reordered to handle new shells & mixing
+        // PMM - 3.20 code .. replaced with original way of doing it to keep mod authors happy
+        //
+        int i;
+        if ((currententity.flags & (Defines.RF_SHELL_HALF_DAM | Defines.RF_SHELL_GREEN | Defines.RF_SHELL_RED | Defines.RF_SHELL_BLUE | Defines.RF_SHELL_DOUBLE)) != 0) {
+            Math3D.vectorClear(shadelight);
+            if ((currententity.flags & Defines.RF_SHELL_HALF_DAM) != 0) {
+                shadelight[0] = 0.56f;
+                shadelight[1] = 0.59f;
+                shadelight[2] = 0.45f;
+            }
+            if ((currententity.flags & Defines.RF_SHELL_DOUBLE) != 0) {
+                shadelight[0] = 0.9f;
+                shadelight[1] = 0.7f;
+            }
+            if ((currententity.flags & Defines.RF_SHELL_RED) != 0)
+                shadelight[0] = 1.0f;
+            if ((currententity.flags & Defines.RF_SHELL_GREEN) != 0)
+                shadelight[1] = 1.0f;
+            if ((currententity.flags & Defines.RF_SHELL_BLUE) != 0)
+                shadelight[2] = 1.0f;
+        } else if ((currententity.flags & Defines.RF_FULLBRIGHT) != 0) {
+            for (i = 0; i < 3; i++)
+                shadelight[i] = 1.0f;
+        } else {
+            R_LightPoint(currententity.origin, shadelight);
+
+            // player lighting hack for communication back to server
+            // big hack!
+
+            if (gl_monolightmap.string.charAt(0) != '0') {
+                float s = shadelight[0];
+
+                if (s < shadelight[1])
+                    s = shadelight[1];
+                if (s < shadelight[2])
+                    s = shadelight[2];
+
+                shadelight[0] = s;
+                shadelight[1] = s;
+                shadelight[2] = s;
+            }
+        }
+
+        if ((currententity.flags & Defines.RF_MINLIGHT) != 0) {
+            for (i = 0; i < 3; i++)
+                if (shadelight[i] > 0.1f)
+                    break;
+            if (i == 3) {
+                shadelight[0] = 0.1f;
+                shadelight[1] = 0.1f;
+                shadelight[2] = 0.1f;
+            }
+        }
+
+
+        // =================
+        // PGM ir goggles color override
+        if ((r_newrefdef.rdflags & Defines.RDF_IRGOGGLES) != 0 && (currententity.flags & Defines.RF_IR_VISIBLE) != 0) {
+            shadelight[0] = 1.0f;
+            shadelight[1] = 0.0f;
+            shadelight[2] = 0.0f;
+        }
+        // PGM
+        // =================
+
+        shadedots = r_avertexnormal_dots[((int) (currententity.angles[1] * (SHADEDOT_QUANT / 360.0))) & (SHADEDOT_QUANT - 1)];
+
+        float an = (float) (currententity.angles[1] / 180 * Math.PI);
+        shadevector[0] = (float) Math.cos(-an);
+        shadevector[1] = (float) Math.sin(-an);
+        shadevector[2] = 1;
+        Math3D.vectorNormalize(shadevector);
+
+        //
+        // locate the proper data
+        //
+
+        c_alias_polys += paliashdr.num_tris;
+
+
+        GL11.glPushMatrix();
+        e.angles[PITCH] = -e.angles[PITCH];    // sigh.
+        R_RotateForEntity(e);
+        e.angles[PITCH] = -e.angles[PITCH];    // sigh.
+
+
+        Image skin;
+        // select skin
+        if (currententity.skin != null)
+            skin = currententity.skin;    // custom player skin
+        else {
+            if (currententity.skinnum >= qfiles.MAX_MD2SKINS)
+                skin = currentmodel.skins[0];
+            else {
+                skin = currentmodel.skins[currententity.skinnum];
+                if (skin == null)
+                    skin = currentmodel.skins[0];
+            }
+        }
+        if (skin == null)
+            skin = r_notexture;    // fallback...
+        GL_Bind(skin.texnum);
+
+        // draw it
+
+        GL11.glShadeModel(GL11.GL_SMOOTH);
+
+        GL_TexEnv(GL11.GL_MODULATE);
+        if ((currententity.flags & Defines.RF_TRANSLUCENT) != 0) {
+            GL11.glEnable(GL11.GL_BLEND);
+        }
+
+
+        if ((currententity.frame >= paliashdr.num_frames)
+                || (currententity.frame < 0)) {
+            VideoDriver.Printf(Defines.PRINT_ALL, "R_DrawAliasModel " + currentmodel.name + ": no such frame " + currententity.frame + '\n');
+            currententity.frame = 0;
+            currententity.oldframe = 0;
+        }
+
+        if ((currententity.oldframe >= paliashdr.num_frames)
+                || (currententity.oldframe < 0)) {
+            VideoDriver.Printf(Defines.PRINT_ALL, "R_DrawAliasModel " + currentmodel.name + ": no such oldframe " + currententity.oldframe + '\n');
+            currententity.frame = 0;
+            currententity.oldframe = 0;
+        }
+
+        if (r_lerpmodels.value == 0.0f)
+            currententity.backlerp = 0;
+
+        GL_DrawAliasFrameLerp(paliashdr, currententity.backlerp);
+
+        GL_TexEnv(GL11.GL_REPLACE);
+        GL11.glShadeModel(GL11.GL_FLAT);
+
+        GL11.glPopMatrix();
+
+
+        if ((currententity.flags & Defines.RF_TRANSLUCENT) != 0) {
+            GL11.glDisable(GL11.GL_BLEND);
+        }
+
+
+        GL11.glColor4f(1, 1, 1, 1);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/render/lwjgl/Misc.java b/src/main/java/lwjake2/render/lwjgl/Misc.java
new file mode 100644 (file)
index 0000000..d8bebbc
--- /dev/null
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render.lwjgl;
+
+import lwjake2.Defines;
+import lwjake2.client.VideoDriver;
+import lwjake2.qcommon.FS;
+import org.lwjgl.BufferUtils;
+import org.lwjgl.opengl.*;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.FloatBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+
+/**
+ * Misc
+ *
+ * @author cwei
+ */
+public abstract class Misc extends Mesh {
+
+    private final static int TGA_HEADER_SIZE = 18;
+    /*
+    ==================
+    R_InitParticleTexture
+    ==================
+    */
+    private final byte[][] dottexture =
+            {
+                    {0, 0, 0, 0, 0, 0, 0, 0},
+                    {0, 0, 1, 1, 0, 0, 0, 0},
+                    {0, 1, 1, 1, 1, 0, 0, 0},
+                    {0, 1, 1, 1, 1, 0, 0, 0},
+                    {0, 0, 1, 1, 0, 0, 0, 0},
+                    {0, 0, 0, 0, 0, 0, 0, 0},
+                    {0, 0, 0, 0, 0, 0, 0, 0},
+                    {0, 0, 0, 0, 0, 0, 0, 0},
+            };
+
+//     /* 
+//     ============================================================================== 
+// 
+//                                                     SCREEN SHOTS 
+// 
+//     ============================================================================== 
+//     */ 
+//
+//     typedef struct _TargaHeader {
+//             unsigned char   id_length, colormap_type, image_type;
+//             unsigned short  colormap_index, colormap_length;
+//             unsigned char   colormap_size;
+//             unsigned short  x_origin, y_origin, width, height;
+//             unsigned char   pixel_size, attributes;
+//     } TargaHeader;
+
+    void R_InitParticleTexture() {
+        int x, y;
+        byte[] data = new byte[8 * 8 * 4];
+
+        //
+        // particle texture
+        //
+        for (x = 0; x < 8; x++) {
+            for (y = 0; y < 8; y++) {
+                data[y * 32 + x * 4] = (byte) 255;
+                data[y * 32 + x * 4 + 1] = (byte) 255;
+                data[y * 32 + x * 4 + 2] = (byte) 255;
+                data[y * 32 + x * 4 + 3] = (byte) (dottexture[x][y] * 255);
+
+            }
+        }
+        r_particletexture = GL_LoadPic("***particle***", data, 8, 8, it_sprite, 32);
+
+        //
+        // also use this for bad textures, but without alpha
+        //
+        for (x = 0; x < 8; x++) {
+            for (y = 0; y < 8; y++) {
+                data[y * 32 + x * 4] = (byte) (dottexture[x & 3][y & 3] * 255);
+                data[y * 32 + x * 4 + 1] = 0; // dottexture[x&3][y&3]*255;
+                data[y * 32 + x * 4 + 2] = 0; //dottexture[x&3][y&3]*255;
+                data[y * 32 + x * 4 + 3] = (byte) 255;
+            }
+        }
+        r_notexture = GL_LoadPic("***r_notexture***", data, 8, 8, it_wall, 32);
+    }
+
+    /*
+    ==================
+    GL_ScreenShot_f
+    ==================
+    */
+    void GL_ScreenShot_f() {
+        StringBuilder sb = new StringBuilder(FS.Gamedir() + "/scrshot/jake00.tga");
+        FS.CreatePath(sb.toString());
+        File file = new File(sb.toString());
+        // find a valid file name
+        int i = 0;
+        int offset = sb.length() - 6;
+        while (file.exists() && i++ < 100) {
+            sb.setCharAt(offset, (char) ((i / 10) + '0'));
+            sb.setCharAt(offset + 1, (char) ((i % 10) + '0'));
+            file = new File(sb.toString());
+        }
+        if (i == 100) {
+            VideoDriver.Printf(Defines.PRINT_ALL, "Clean up your screenshots\n");
+            return;
+        }
+
+        try {
+            RandomAccessFile out = new RandomAccessFile(file, "rw");
+            FileChannel ch = out.getChannel();
+            int fileLength = TGA_HEADER_SIZE + vid.width * vid.height * 3;
+            out.setLength(fileLength);
+            MappedByteBuffer image = ch.map(FileChannel.MapMode.READ_WRITE, 0,
+                    fileLength);
+
+            // write the TGA header
+            image.put(0, (byte) 0).put(1, (byte) 0);
+            image.put(2, (byte) 2); // uncompressed type
+            image.put(12, (byte) (vid.width & 0xFF)); // vid.width
+            image.put(13, (byte) (vid.width >> 8)); // vid.width
+            image.put(14, (byte) (vid.height & 0xFF)); // vid.height
+            image.put(15, (byte) (vid.height >> 8)); // vid.height
+            image.put(16, (byte) 24); // pixel size
+
+            // go to image data position
+            image.position(TGA_HEADER_SIZE);
+
+
+            // change pixel alignment for reading
+            if (vid.width % 4 != 0) {
+                GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1);
+            }
+
+            // OpenGL 1.2+ supports the GL_BGR color format
+            // check the GL_VERSION to use the TARGA BGR order if possible
+            // e.g.: 1.5.2 NVIDIA 66.29
+            if (gl_config.getOpenGLVersion() >= 1.2f) {
+                // read the BGR values into the image buffer
+                GL11.glReadPixels(0, 0, vid.width, vid.height, GL12.GL_BGR, GL11.GL_UNSIGNED_BYTE, image);
+            } else {
+                // read the RGB values into the image buffer
+                GL11.glReadPixels(0, 0, vid.width, vid.height, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, image);
+                // flip RGB to BGR
+                byte tmp;
+                for (i = TGA_HEADER_SIZE; i < fileLength; i += 3) {
+                    tmp = image.get(i);
+                    image.put(i, image.get(i + 2));
+                    image.put(i + 2, tmp);
+                }
+            }
+            // reset to default alignment
+            GL11.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
+            // close the file channel
+            ch.close();
+        } catch (IOException e) {
+            VideoDriver.Printf(Defines.PRINT_ALL, e.getMessage() + '\n');
+        }
+
+        VideoDriver.Printf(Defines.PRINT_ALL, "Wrote " + file + '\n');
+    }
+
+    /*
+    ** GL_Strings_f
+    */
+    void GL_Strings_f() {
+        VideoDriver.Printf(Defines.PRINT_ALL, "GL_VENDOR: " + gl_config.vendor_string + '\n');
+        VideoDriver.Printf(Defines.PRINT_ALL, "GL_RENDERER: " + gl_config.renderer_string + '\n');
+        VideoDriver.Printf(Defines.PRINT_ALL, "GL_VERSION: " + gl_config.version_string + '\n');
+        VideoDriver.Printf(Defines.PRINT_ALL, "GL_EXTENSIONS: " + gl_config.extensions_string + '\n');
+    }
+
+    /*
+    ** GL_SetDefaultState
+    */
+    void GL_SetDefaultState() {
+        GL11.glClearColor(1f, 0f, 0.5f, 0.5f); // original quake2
+        //GL11.glClearColor(0, 0, 0, 0); // replaced with black
+        GL11.glCullFace(GL11.GL_FRONT);
+        GL11.glEnable(GL11.GL_TEXTURE_2D);
+
+        GL11.glEnable(GL11.GL_ALPHA_TEST);
+        GL11.glAlphaFunc(GL11.GL_GREATER, 0.666f);
+
+        GL11.glDisable(GL11.GL_DEPTH_TEST);
+        GL11.glDisable(GL11.GL_CULL_FACE);
+        GL11.glDisable(GL11.GL_BLEND);
+
+        GL11.glColor4f(1, 1, 1, 1);
+
+        GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
+        GL11.glShadeModel(GL11.GL_FLAT);
+
+        GL_TextureMode(gl_texturemode.string);
+        GL_TextureAlphaMode(gl_texturealphamode.string);
+        GL_TextureSolidMode(gl_texturesolidmode.string);
+
+        GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, gl_filter_min);
+        GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, gl_filter_max);
+
+        GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
+        GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
+
+        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
+
+        GL_TexEnv(GL11.GL_REPLACE);
+
+        if (qglPointParameterfEXT) {
+            // float[] attenuations = { gl_particle_att_a.value, gl_particle_att_b.value, gl_particle_att_c.value };
+            FloatBuffer att_buffer = BufferUtils.createFloatBuffer(4);
+            att_buffer.put(0, gl_particle_att_a.value);
+            att_buffer.put(1, gl_particle_att_b.value);
+            att_buffer.put(2, gl_particle_att_c.value);
+
+            GL11.glEnable(GL11.GL_POINT_SMOOTH);
+            EXTPointParameters.glPointParameterfEXT(EXTPointParameters.GL_POINT_SIZE_MIN_EXT, gl_particle_min_size.value);
+            EXTPointParameters.glPointParameterfEXT(EXTPointParameters.GL_POINT_SIZE_MAX_EXT, gl_particle_max_size.value);
+            EXTPointParameters.glPointParameterEXT(EXTPointParameters.GL_DISTANCE_ATTENUATION_EXT, att_buffer);
+        }
+
+        if (qglColorTableEXT && gl_ext_palettedtexture.value != 0.0f) {
+            GL11.glEnable(EXTSharedTexturePalette.GL_SHARED_TEXTURE_PALETTE_EXT);
+
+            GL_SetTexturePalette(d_8to24table);
+        }
+
+        GL_UpdateSwapInterval();
+
+               /*
+         * vertex array extension
+                */
+        GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
+        ARBMultitexture.glClientActiveTextureARB(GL_TEXTURE0);
+        GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
+    }
+
+    void GL_UpdateSwapInterval() {
+        if (gl_swapinterval.modified) {
+            gl_swapinterval.modified = false;
+            if (!gl_state.stereo_enabled) {
+                if (qwglSwapIntervalEXT) {
+                    // ((WGL)gl).wglSwapIntervalEXT((int)gl_swapinterval.value);
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/lwjake2/render/lwjgl/Model.java b/src/main/java/lwjake2/render/lwjgl/Model.java
new file mode 100644 (file)
index 0000000..93b27fc
--- /dev/null
@@ -0,0 +1,1261 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render.lwjgl;
+
+import lwjake2.Defines;
+import lwjake2.client.VideoDriver;
+import lwjake2.game.CvarT;
+import lwjake2.game.cplane_t;
+import lwjake2.qcommon.*;
+import lwjake2.render.*;
+import lwjake2.util.Math3D;
+import lwjake2.util.Vargs;
+import org.lwjgl.BufferUtils;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.util.Arrays;
+import java.util.Vector;
+
+/**
+ * Model
+ *
+ * @author cwei
+ */
+public abstract class Model extends Surf {
+
+    // models.c -- model loading and caching
+
+    static final int MAX_MOD_KNOWN = 512;
+    /*
+     * new functions for vertex array handling
+     */
+    static final int MODEL_BUFFER_SIZE = 50000;
+    static final FloatBuffer globalModelTextureCoordBuf = BufferUtils.createFloatBuffer(MODEL_BUFFER_SIZE * 2);
+    static final IntBuffer globalModelVertexIndexBuf = BufferUtils.createIntBuffer(MODEL_BUFFER_SIZE);
+    final byte[] mod_novis = new byte[Defines.MAX_MAP_LEAFS / 8];
+    final lwjake2.render.Model[] mod_known = new lwjake2.render.Model[MAX_MOD_KNOWN];
+    // the inline * models from the current map are kept seperate
+    final lwjake2.render.Model[] mod_inline = new lwjake2.render.Model[MAX_MOD_KNOWN];
+    final byte[] decompressed = new byte[Defines.MAX_MAP_LEAFS / 8];
+    final byte[] model_visibility = new byte[Defines.MAX_MAP_VISIBILITY];
+    lwjake2.render.Model loadmodel;
+    int modfilelen;
+    int mod_numknown;
+    byte[] fileBuffer;
+
+
+    //   ===============================================================================
+    byte[] mod_base;
+
+    static void resetModelArrays() {
+        globalModelTextureCoordBuf.rewind();
+        globalModelVertexIndexBuf.rewind();
+    }
+
+    static void modelMemoryUsage() {
+        System.out.println("AliasModels: globalVertexBuffer size " + globalModelVertexIndexBuf.position());
+    }
+
+    abstract void GL_SubdivideSurface(msurface_t surface); // Warp.java
+
+       /*
+    ===============================================================================
+
+                                               BRUSHMODEL LOADING
+
+       ===============================================================================
+       */
+
+    /*
+    ===============
+    Mod_PointInLeaf
+    ===============
+    */
+    mleaf_t Mod_PointInLeaf(float[] p, lwjake2.render.Model model) {
+        mnode_t node;
+        float d;
+        cplane_t plane;
+
+        if (model == null || model.nodes == null)
+            Com.Error(Defines.ERR_DROP, "Mod_PointInLeaf: bad model");
+
+        node = model.nodes[0]; // root node
+        while (true) {
+            if (node.contents != -1)
+                return (mleaf_t) node;
+
+            plane = node.plane;
+            d = Math3D.dotProduct(p, plane.normal) - plane.dist;
+            if (d > 0)
+                node = node.children[0];
+            else
+                node = node.children[1];
+        }
+        // never reached
+    }
+
+    /*
+    ===================
+    Mod_DecompressVis
+    ===================
+    */
+    byte[] Mod_DecompressVis(byte[] in, int offset, lwjake2.render.Model model) {
+        int c;
+        byte[] out;
+        int outp, inp;
+        int row;
+
+        row = (model.vis.numclusters + 7) >> 3;
+        out = decompressed;
+        outp = 0;
+        inp = offset;
+
+        if (in == null) {    // no vis info, so make all visible
+            while (row != 0) {
+                out[outp++] = (byte) 0xFF;
+                row--;
+            }
+            return decompressed;
+        }
+
+        do {
+            if (in[inp] != 0) {
+                out[outp++] = in[inp++];
+                continue;
+            }
+
+            c = in[inp + 1] & 0xFF;
+            inp += 2;
+            while (c != 0) {
+                out[outp++] = 0;
+                c--;
+            }
+        } while (outp < row);
+
+        return decompressed;
+    }
+
+    /*
+    ==============
+    Mod_ClusterPVS
+    ==============
+    */
+    byte[] Mod_ClusterPVS(int cluster, lwjake2.render.Model model) {
+        if (cluster == -1 || model.vis == null)
+            return mod_novis;
+        //return Mod_DecompressVis( (byte *)model.vis + model.vis.bitofs[cluster][Defines.DVIS_PVS], model);
+        return Mod_DecompressVis(model_visibility, model.vis.bitofs[cluster][Defines.DVIS_PVS], model);
+    }
+
+    /*
+    ================
+    Mod_Modellist_f
+    ================
+    */
+    void Mod_Modellist_f() {
+        int i;
+        lwjake2.render.Model mod;
+        int total;
+
+        total = 0;
+        VideoDriver.Printf(Defines.PRINT_ALL, "Loaded models:\n");
+        for (i = 0; i < mod_numknown; i++) {
+            mod = mod_known[i];
+            if (mod.name.length() == 0)
+                continue;
+
+            VideoDriver.Printf(Defines.PRINT_ALL, "%8i : %s\n", new Vargs(2).add(mod.extradatasize).add(mod.name));
+            total += mod.extradatasize;
+        }
+        VideoDriver.Printf(Defines.PRINT_ALL, "Total resident: " + total + '\n');
+    }
+
+    /*
+    ===============
+    Mod_Init
+    ===============
+    */
+    void Mod_Init() {
+        // init mod_known
+        for (int i = 0; i < MAX_MOD_KNOWN; i++) {
+            mod_known[i] = new lwjake2.render.Model();
+        }
+        Arrays.fill(mod_novis, (byte) 0xff);
+    }
+
+    /*
+    ==================
+    Mod_ForName
+
+    Loads in a model for the given name
+    ==================
+    */
+    lwjake2.render.Model Mod_ForName(String name, boolean crash) {
+        lwjake2.render.Model mod = null;
+        int i;
+
+        if (name == null || name.length() == 0)
+            Com.Error(Defines.ERR_DROP, "Mod_ForName: NULL name");
+
+        //
+        // inline models are grabbed only from worldmodel
+        //
+        if (name.charAt(0) == '*') {
+            i = Integer.parseInt(name.substring(1));
+            if (i < 1 || r_worldmodel == null || i >= r_worldmodel.numsubmodels)
+                Com.Error(Defines.ERR_DROP, "bad inline model number");
+            return mod_inline[i];
+        }
+
+        //
+        // search the currently loaded models
+        //
+        for (i = 0; i < mod_numknown; i++) {
+            mod = mod_known[i];
+
+            if (mod.name.length() == 0)
+                continue;
+            if (mod.name.equals(name))
+                return mod;
+        }
+
+        //
+        // find a free model slot spot
+        //
+        for (i = 0; i < mod_numknown; i++) {
+            mod = mod_known[i];
+
+            if (mod.name.length() == 0)
+                break;    // free spot
+        }
+        if (i == mod_numknown) {
+            if (mod_numknown == MAX_MOD_KNOWN)
+                Com.Error(Defines.ERR_DROP, "mod_numknown == MAX_MOD_KNOWN");
+            mod_numknown++;
+            mod = mod_known[i];
+        }
+
+        mod.name = name;
+
+        //
+        // load the file
+        //
+        fileBuffer = FS.LoadFile(name);
+
+        if (fileBuffer == null) {
+            if (crash)
+                Com.Error(Defines.ERR_DROP, "Mod_NumForName: " + mod.name + " not found");
+
+            mod.name = "";
+            return null;
+        }
+
+        modfilelen = fileBuffer.length;
+
+        loadmodel = mod;
+
+        //
+        // fill it in
+        //
+        ByteBuffer bb = ByteBuffer.wrap(fileBuffer);
+        bb.order(ByteOrder.LITTLE_ENDIAN);
+
+        // call the apropriate loader
+
+        bb.mark();
+        int ident = bb.getInt();
+
+        bb.reset();
+
+        switch (ident) {
+            case qfiles.IDALIASHEADER:
+                Mod_LoadAliasModel(mod, bb);
+                break;
+            case qfiles.IDSPRITEHEADER:
+                Mod_LoadSpriteModel(mod, bb);
+                break;
+            case qfiles.IDBSPHEADER:
+                Mod_LoadBrushModel(mod, bb);
+                break;
+            default:
+                Com.Error(Defines.ERR_DROP, "Mod_NumForName: unknown fileid for " + mod.name);
+                break;
+        }
+
+        this.fileBuffer = null; // free it for garbage collection
+        return mod;
+    }
+
+    /*
+    =================
+    Mod_LoadLighting
+    =================
+    */
+    void Mod_LoadLighting(lump_t l) {
+        if (l.filelen == 0) {
+            loadmodel.lightdata = null;
+            return;
+        }
+        // memcpy (loadmodel.lightdata, mod_base + l.fileofs, l.filelen);
+        loadmodel.lightdata = new byte[l.filelen];
+        System.arraycopy(mod_base, l.fileofs, loadmodel.lightdata, 0, l.filelen);
+    }
+
+    /*
+    =================
+    Mod_LoadVisibility
+    =================
+    */
+    void Mod_LoadVisibility(lump_t l) {
+
+        if (l.filelen == 0) {
+            loadmodel.vis = null;
+            return;
+        }
+
+        System.arraycopy(mod_base, l.fileofs, model_visibility, 0, l.filelen);
+
+        ByteBuffer bb = ByteBuffer.wrap(model_visibility, 0, l.filelen);
+
+        loadmodel.vis = new qfiles.dvis_t(bb.order(ByteOrder.LITTLE_ENDIAN));
+
+               /* done:
+        memcpy (loadmodel.vis, mod_base + l.fileofs, l.filelen);
+
+               loadmodel.vis.numclusters = LittleLong (loadmodel.vis.numclusters);
+               for (i=0 ; i<loadmodel.vis.numclusters ; i++)
+               {
+                       loadmodel.vis.bitofs[i][0] = LittleLong (loadmodel.vis.bitofs[i][0]);
+                       loadmodel.vis.bitofs[i][1] = LittleLong (loadmodel.vis.bitofs[i][1]);
+               }
+               */
+    }
+
+    /*
+    =================
+    Mod_LoadVertexes
+    =================
+    */
+    void Mod_LoadVertexes(lump_t l) {
+        mvertex_t[] vertexes;
+        int i, count;
+
+        if ((l.filelen % mvertex_t.DISK_SIZE) != 0)
+            Com.Error(Defines.ERR_DROP, "MOD_LoadBmodel: funny lump size in " + loadmodel.name);
+
+        count = l.filelen / mvertex_t.DISK_SIZE;
+
+        vertexes = new mvertex_t[count];
+
+        loadmodel.vertexes = vertexes;
+        loadmodel.numvertexes = count;
+
+        ByteBuffer bb = ByteBuffer.wrap(mod_base, l.fileofs, l.filelen);
+        bb.order(ByteOrder.LITTLE_ENDIAN);
+
+        for (i = 0; i < count; i++) {
+            vertexes[i] = new mvertex_t(bb);
+        }
+    }
+
+    /*
+    =================
+    RadiusFromBounds
+    =================
+    */
+    float RadiusFromBounds(float[] mins, float[] maxs) {
+        float[] corner = {0, 0, 0};
+
+        for (int i = 0; i < 3; i++) {
+            corner[i] = Math.abs(mins[i]) > Math.abs(maxs[i]) ? Math.abs(mins[i]) : Math.abs(maxs[i]);
+        }
+        return Math3D.vectorLength(corner);
+    }
+
+    /*
+    =================
+    Mod_LoadSubmodels
+    =================
+    */
+    void Mod_LoadSubmodels(lump_t l) {
+
+        if ((l.filelen % qfiles.dmodel_t.SIZE) != 0)
+            Com.Error(Defines.ERR_DROP, "MOD_LoadBmodel: funny lump size in "
+                    + loadmodel.name);
+
+        int i, j;
+
+        int count = l.filelen / qfiles.dmodel_t.SIZE;
+        // out = Hunk_Alloc ( count*sizeof(*out));
+        mmodel_t out;
+        mmodel_t[] outs = new mmodel_t[count];
+        for (i = 0; i < count; i++) {
+            outs[i] = new mmodel_t();
+        }
+
+        loadmodel.submodels = outs;
+        loadmodel.numsubmodels = count;
+
+        ByteBuffer bb = ByteBuffer.wrap(mod_base, l.fileofs, l.filelen);
+        bb.order(ByteOrder.LITTLE_ENDIAN);
+
+        qfiles.dmodel_t in;
+
+        for (i = 0; i < count; i++) {
+            in = new qfiles.dmodel_t(bb);
+            out = outs[i];
+            for (j = 0; j < 3; j++) { // spread the mins / maxs by a
+                // pixel
+                out.mins[j] = in.mins[j] - 1;
+                out.maxs[j] = in.maxs[j] + 1;
+                out.origin[j] = in.origin[j];
+            }
+            out.radius = RadiusFromBounds(out.mins, out.maxs);
+            out.headnode = in.headnode;
+            out.firstface = in.firstface;
+            out.numfaces = in.numfaces;
+        }
+    }
+
+    /*
+    =================
+    Mod_LoadEdges
+    =================
+    */
+    void Mod_LoadEdges(lump_t l) {
+        medge_t[] edges;
+        int i, count;
+
+        if ((l.filelen % medge_t.DISK_SIZE) != 0)
+            Com.Error(Defines.ERR_DROP, "MOD_LoadBmodel: funny lump size in " + loadmodel.name);
+
+        count = l.filelen / medge_t.DISK_SIZE;
+        // out = Hunk_Alloc ( (count + 1) * sizeof(*out));
+        edges = new medge_t[count + 1];
+
+        loadmodel.edges = edges;
+        loadmodel.numedges = count;
+
+        ByteBuffer bb = ByteBuffer.wrap(mod_base, l.fileofs, l.filelen);
+        bb.order(ByteOrder.LITTLE_ENDIAN);
+
+        for (i = 0; i < count; i++) {
+            edges[i] = new medge_t(bb);
+        }
+    }
+
+    /*
+    =================
+    Mod_LoadTexinfo
+    =================
+    */
+    void Mod_LoadTexinfo(lump_t l) {
+        texinfo_t in;
+        mtexinfo_t[] out;
+        mtexinfo_t step;
+        int i, count;
+        int next;
+        String name;
+
+        if ((l.filelen % texinfo_t.SIZE) != 0)
+            Com.Error(Defines.ERR_DROP, "MOD_LoadBmodel: funny lump size in " + loadmodel.name);
+
+        count = l.filelen / texinfo_t.SIZE;
+        // out = Hunk_Alloc ( count*sizeof(*out));
+        out = new mtexinfo_t[count];
+        for (i = 0; i < count; i++) {
+            out[i] = new mtexinfo_t();
+        }
+
+        loadmodel.texinfo = out;
+        loadmodel.numtexinfo = count;
+
+        ByteBuffer bb = ByteBuffer.wrap(mod_base, l.fileofs, l.filelen);
+        bb.order(ByteOrder.LITTLE_ENDIAN);
+
+        for (i = 0; i < count; i++) {
+
+            in = new texinfo_t(bb);
+            out[i].vecs = in.vecs;
+            out[i].flags = in.flags;
+            next = in.nexttexinfo;
+            if (next > 0)
+                out[i].next = loadmodel.texinfo[next];
+            else
+                out[i].next = null;
+
+            name = "textures/" + in.texture + ".wal";
+
+            out[i].image = GL_FindImage(name, it_wall);
+            if (out[i].image == null) {
+                VideoDriver.Printf(Defines.PRINT_ALL, "Couldn't load " + name + '\n');
+                out[i].image = r_notexture;
+            }
+        }
+
+        // count animation frames
+        for (i = 0; i < count; i++) {
+            out[i].numframes = 1;
+            for (step = out[i].next; (step != null) && (step != out[i]); step = step.next)
+                out[i].numframes++;
+        }
+    }
+
+    /*
+    ================
+    CalcSurfaceExtents
+
+    Fills in s.texturemins[] and s.extents[]
+    ================
+    */
+    void CalcSurfaceExtents(msurface_t s) {
+        float[] mins = {0, 0};
+        float[] maxs = {0, 0};
+        float val;
+
+        int j, e;
+        mvertex_t v;
+        int[] bmins = {0, 0};
+        int[] bmaxs = {0, 0};
+
+        mins[0] = mins[1] = 999999;
+        maxs[0] = maxs[1] = -99999;
+
+        mtexinfo_t tex = s.texinfo;
+
+        for (int i = 0; i < s.numedges; i++) {
+            e = loadmodel.surfedges[s.firstedge + i];
+            if (e >= 0)
+                v = loadmodel.vertexes[loadmodel.edges[e].v[0]];
+            else
+                v = loadmodel.vertexes[loadmodel.edges[-e].v[1]];
+
+            for (j = 0; j < 2; j++) {
+                val = v.position[0] * tex.vecs[j][0] +
+                        v.position[1] * tex.vecs[j][1] +
+                        v.position[2] * tex.vecs[j][2] +
+                        tex.vecs[j][3];
+                if (val < mins[j])
+                    mins[j] = val;
+                if (val > maxs[j])
+                    maxs[j] = val;
+            }
+        }
+
+        for (int i = 0; i < 2; i++) {
+            bmins[i] = (int) Math.floor(mins[i] / 16);
+            bmaxs[i] = (int) Math.ceil(maxs[i] / 16);
+
+            s.texturemins[i] = (short) (bmins[i] * 16);
+            s.extents[i] = (short) ((bmaxs[i] - bmins[i]) * 16);
+
+        }
+    }
+
+    /*
+    =================
+    Mod_LoadFaces
+    =================
+    */
+    void Mod_LoadFaces(lump_t l) {
+
+        int i, surfnum;
+        int planenum, side;
+        int ti;
+
+        if ((l.filelen % qfiles.dface_t.SIZE) != 0)
+            Com.Error(Defines.ERR_DROP, "MOD_LoadBmodel: funny lump size in "
+                    + loadmodel.name);
+
+        int count = l.filelen / qfiles.dface_t.SIZE;
+        // out = Hunk_Alloc ( count*sizeof(*out));
+        msurface_t[] outs = new msurface_t[count];
+        for (i = 0; i < count; i++) {
+            outs[i] = new msurface_t();
+        }
+
+        loadmodel.surfaces = outs;
+        loadmodel.numsurfaces = count;
+
+        ByteBuffer bb = ByteBuffer.wrap(mod_base, l.fileofs, l.filelen);
+        bb.order(ByteOrder.LITTLE_ENDIAN);
+
+        currentmodel = loadmodel;
+
+        GL_BeginBuildingLightmaps(loadmodel);
+
+        qfiles.dface_t in;
+        msurface_t out;
+
+        for (surfnum = 0; surfnum < count; surfnum++) {
+            in = new qfiles.dface_t(bb);
+            out = outs[surfnum];
+            out.firstedge = in.firstedge;
+            out.numedges = in.numedges;
+            out.flags = 0;
+            out.polys = null;
+
+            planenum = in.planenum;
+            side = in.side;
+            if (side != 0)
+                out.flags |= Defines.SURF_PLANEBACK;
+
+            out.plane = loadmodel.planes[planenum];
+
+            ti = in.texinfo;
+            if (ti < 0 || ti >= loadmodel.numtexinfo)
+                Com.Error(Defines.ERR_DROP,
+                        "MOD_LoadBmodel: bad texinfo number");
+
+            out.texinfo = loadmodel.texinfo[ti];
+
+            CalcSurfaceExtents(out);
+
+            // lighting info
+
+            for (i = 0; i < Defines.MAXLIGHTMAPS; i++)
+                out.styles[i] = in.styles[i];
+
+            i = in.lightofs;
+            if (i == -1)
+                out.samples = null;
+            else {
+                ByteBuffer pointer = ByteBuffer.wrap(loadmodel.lightdata);
+                pointer.position(i);
+                pointer = pointer.slice();
+                pointer.mark();
+                out.samples = pointer; // subarray
+            }
+
+            // set the drawing flags
+
+            if ((out.texinfo.flags & Defines.SURF_WARP) != 0) {
+                out.flags |= Defines.SURF_DRAWTURB;
+                for (i = 0; i < 2; i++) {
+                    out.extents[i] = 16384;
+                    out.texturemins[i] = -8192;
+                }
+                GL_SubdivideSurface(out); // cut up polygon for warps
+            }
+
+            // create lightmaps and polygons
+            if ((out.texinfo.flags & (Defines.SURF_SKY | Defines.SURF_TRANS33
+                    | Defines.SURF_TRANS66 | Defines.SURF_WARP)) == 0)
+                GL_CreateSurfaceLightmap(out);
+
+            if ((out.texinfo.flags & Defines.SURF_WARP) == 0)
+                GL_BuildPolygonFromSurface(out);
+
+        }
+        GL_EndBuildingLightmaps();
+    }
+
+    /*
+    =================
+    Mod_SetParent
+    =================
+    */
+    void Mod_SetParent(mnode_t node, mnode_t parent) {
+        node.parent = parent;
+        if (node.contents != -1) return;
+        Mod_SetParent(node.children[0], node);
+        Mod_SetParent(node.children[1], node);
+    }
+
+    /*
+    =================
+    Mod_LoadNodes
+    =================
+    */
+    void Mod_LoadNodes(lump_t l) {
+        int i, j, count, p;
+        qfiles.dnode_t in;
+        mnode_t[] out;
+
+        if ((l.filelen % qfiles.dnode_t.SIZE) != 0)
+            Com.Error(Defines.ERR_DROP, "MOD_LoadBmodel: funny lump size in " + loadmodel.name);
+
+        count = l.filelen / qfiles.dnode_t.SIZE;
+        // out = Hunk_Alloc ( count*sizeof(*out));
+        out = new mnode_t[count];
+
+        loadmodel.nodes = out;
+        loadmodel.numnodes = count;
+
+        ByteBuffer bb = ByteBuffer.wrap(mod_base, l.fileofs, l.filelen);
+        bb.order(ByteOrder.LITTLE_ENDIAN);
+
+        // initialize the tree array
+        for (i = 0; i < count; i++) out[i] = new mnode_t(); // do first before linking
+
+        // fill and link the nodes
+        for (i = 0; i < count; i++) {
+            in = new qfiles.dnode_t(bb);
+            for (j = 0; j < 3; j++) {
+                out[i].mins[j] = in.mins[j];
+                out[i].maxs[j] = in.maxs[j];
+            }
+
+            p = in.planenum;
+            out[i].plane = loadmodel.planes[p];
+
+            out[i].firstsurface = in.firstface;
+            out[i].numsurfaces = in.numfaces;
+            out[i].contents = -1;    // differentiate from leafs
+
+            for (j = 0; j < 2; j++) {
+                p = in.children[j];
+                if (p >= 0)
+                    out[i].children[j] = loadmodel.nodes[p];
+                else
+                    out[i].children[j] = loadmodel.leafs[-1 - p]; // mleaf_t extends mnode_t
+            }
+        }
+
+        Mod_SetParent(loadmodel.nodes[0], null);    // sets nodes and leafs
+    }
+
+       /*
+    ==============================================================================
+
+       ALIAS MODELS
+
+       ==============================================================================
+       */
+
+    /*
+    =================
+    Mod_LoadLeafs
+    =================
+    */
+    void Mod_LoadLeafs(lump_t l) {
+        qfiles.dleaf_t in;
+        mleaf_t[] out;
+        int i, j, count;
+
+        if ((l.filelen % qfiles.dleaf_t.SIZE) != 0)
+            Com.Error(Defines.ERR_DROP, "MOD_LoadBmodel: funny lump size in " + loadmodel.name);
+
+        count = l.filelen / qfiles.dleaf_t.SIZE;
+        // out = Hunk_Alloc ( count*sizeof(*out));
+        out = new mleaf_t[count];
+
+        loadmodel.leafs = out;
+        loadmodel.numleafs = count;
+
+        ByteBuffer bb = ByteBuffer.wrap(mod_base, l.fileofs, l.filelen);
+        bb.order(ByteOrder.LITTLE_ENDIAN);
+
+        for (i = 0; i < count; i++) {
+            in = new qfiles.dleaf_t(bb);
+            out[i] = new mleaf_t();
+            for (j = 0; j < 3; j++) {
+                out[i].mins[j] = in.mins[j];
+                out[i].maxs[j] = in.maxs[j];
+
+            }
+
+            out[i].contents = in.contents;
+            out[i].cluster = in.cluster;
+            out[i].area = in.area;
+
+            out[i].setMarkSurface(in.firstleafface, loadmodel.marksurfaces);
+            out[i].nummarksurfaces = in.numleaffaces;
+        }
+    }
+
+       /*
+    ==============================================================================
+
+       SPRITE MODELS
+
+       ==============================================================================
+       */
+
+    /*
+    =================
+    Mod_LoadMarksurfaces
+    =================
+    */
+    void Mod_LoadMarksurfaces(lump_t l) {
+        int i, j, count;
+
+        msurface_t[] out;
+
+        if ((l.filelen % Defines.SIZE_OF_SHORT) != 0)
+            Com.Error(Defines.ERR_DROP, "MOD_LoadBmodel: funny lump size in " + loadmodel.name);
+        count = l.filelen / Defines.SIZE_OF_SHORT;
+        // out = Hunk_Alloc ( count*sizeof(*out));
+        out = new msurface_t[count];
+
+        loadmodel.marksurfaces = out;
+        loadmodel.nummarksurfaces = count;
+
+        ByteBuffer bb = ByteBuffer.wrap(mod_base, l.fileofs, l.filelen);
+        bb.order(ByteOrder.LITTLE_ENDIAN);
+
+        for (i = 0; i < count; i++) {
+            j = bb.getShort();
+            if (j < 0 || j >= loadmodel.numsurfaces)
+                Com.Error(Defines.ERR_DROP, "Mod_ParseMarksurfaces: bad surface number");
+
+            out[i] = loadmodel.surfaces[j];
+        }
+    }
+
+//       =============================================================================
+
+    /*
+    =================
+    Mod_LoadSurfedges
+    =================
+    */
+    void Mod_LoadSurfedges(lump_t l) {
+        int i, count;
+        int[] offsets;
+
+        if ((l.filelen % Defines.SIZE_OF_INT) != 0)
+            Com.Error(Defines.ERR_DROP, "MOD_LoadBmodel: funny lump size in " + loadmodel.name);
+
+        count = l.filelen / Defines.SIZE_OF_INT;
+        if (count < 1 || count >= Defines.MAX_MAP_SURFEDGES)
+            Com.Error(Defines.ERR_DROP, "MOD_LoadBmodel: bad surfedges count in " + loadmodel.name + ": " + count);
+
+        offsets = new int[count];
+
+        loadmodel.surfedges = offsets;
+        loadmodel.numsurfedges = count;
+
+        ByteBuffer bb = ByteBuffer.wrap(mod_base, l.fileofs, l.filelen);
+        bb.order(ByteOrder.LITTLE_ENDIAN);
+
+        for (i = 0; i < count; i++) offsets[i] = bb.getInt();
+    }
+
+    /*
+    =================
+    Mod_LoadPlanes
+    =================
+    */
+    void Mod_LoadPlanes(lump_t l) {
+        int i, j;
+        cplane_t[] out;
+        qfiles.dplane_t in;
+        int count;
+        int bits;
+
+        if ((l.filelen % qfiles.dplane_t.SIZE) != 0)
+            Com.Error(Defines.ERR_DROP, "MOD_LoadBmodel: funny lump size in " + loadmodel.name);
+
+        count = l.filelen / qfiles.dplane_t.SIZE;
+        // out = Hunk_Alloc ( count*2*sizeof(*out));
+        out = new cplane_t[count * 2];
+        for (i = 0; i < count; i++) {
+            out[i] = new cplane_t();
+        }
+
+        loadmodel.planes = out;
+        loadmodel.numplanes = count;
+
+        ByteBuffer bb = ByteBuffer.wrap(mod_base, l.fileofs, l.filelen);
+        bb.order(ByteOrder.LITTLE_ENDIAN);
+
+        for (i = 0; i < count; i++) {
+            bits = 0;
+            in = new qfiles.dplane_t(bb);
+            for (j = 0; j < 3; j++) {
+                out[i].normal[j] = in.normal[j];
+                if (out[i].normal[j] < 0)
+                    bits |= (1 << j);
+            }
+
+            out[i].dist = in.dist;
+            out[i].type = (byte) in.type;
+            out[i].signbits = (byte) bits;
+        }
+    }
+
+    /*
+    =================
+    Mod_LoadBrushModel
+    =================
+    */
+    void Mod_LoadBrushModel(lwjake2.render.Model mod, ByteBuffer buffer) {
+        int i;
+        qfiles.dheader_t header;
+        mmodel_t bm;
+
+        loadmodel.type = mod_brush;
+        if (loadmodel != mod_known[0])
+            Com.Error(Defines.ERR_DROP, "Loaded a brush model after the world");
+
+        header = new qfiles.dheader_t(buffer);
+
+        i = header.version;
+        if (i != Defines.BSPVERSION)
+            Com.Error(Defines.ERR_DROP, "Mod_LoadBrushModel: " + mod.name + " has wrong version number (" + i + " should be " + Defines.BSPVERSION + ")");
+
+        mod_base = fileBuffer; //(byte *)header;
+
+        // load into heap
+        Mod_LoadVertexes(header.lumps[Defines.LUMP_VERTEXES]); // ok
+        Mod_LoadEdges(header.lumps[Defines.LUMP_EDGES]); // ok
+        Mod_LoadSurfedges(header.lumps[Defines.LUMP_SURFEDGES]); // ok
+        Mod_LoadLighting(header.lumps[Defines.LUMP_LIGHTING]); // ok
+        Mod_LoadPlanes(header.lumps[Defines.LUMP_PLANES]); // ok
+        Mod_LoadTexinfo(header.lumps[Defines.LUMP_TEXINFO]); // ok
+        Mod_LoadFaces(header.lumps[Defines.LUMP_FACES]); // ok
+        Mod_LoadMarksurfaces(header.lumps[Defines.LUMP_LEAFFACES]);
+        Mod_LoadVisibility(header.lumps[Defines.LUMP_VISIBILITY]); // ok
+        Mod_LoadLeafs(header.lumps[Defines.LUMP_LEAFS]); // ok
+        Mod_LoadNodes(header.lumps[Defines.LUMP_NODES]); // ok
+        Mod_LoadSubmodels(header.lumps[Defines.LUMP_MODELS]);
+        mod.numframes = 2;        // regular and alternate animation
+
+        //
+        // set up the submodels
+        //
+        lwjake2.render.Model starmod;
+
+        for (i = 0; i < mod.numsubmodels; i++) {
+
+            bm = mod.submodels[i];
+            starmod = mod_inline[i] = loadmodel.copy();
+
+            starmod.firstmodelsurface = bm.firstface;
+            starmod.nummodelsurfaces = bm.numfaces;
+            starmod.firstnode = bm.headnode;
+            if (starmod.firstnode >= loadmodel.numnodes)
+                Com.Error(Defines.ERR_DROP, "Inline model " + i + " has bad firstnode");
+
+            Math3D.vectorCopy(bm.maxs, starmod.maxs);
+            Math3D.vectorCopy(bm.mins, starmod.mins);
+            starmod.radius = bm.radius;
+
+            if (i == 0)
+                loadmodel = starmod.copy();
+
+            starmod.numleafs = bm.visleafs;
+        }
+    }
+
+
+//       =============================================================================
+
+    /*
+    =================
+    Mod_LoadAliasModel
+    =================
+    */
+    void Mod_LoadAliasModel(lwjake2.render.Model mod, ByteBuffer buffer) {
+        int i;
+        qfiles.dmdl_t pheader;
+        qfiles.dstvert_t[] poutst;
+        qfiles.dtriangle_t[] pouttri;
+        qfiles.daliasframe_t[] poutframe;
+        int[] poutcmd;
+
+        pheader = new qfiles.dmdl_t(buffer);
+
+        if (pheader.version != qfiles.ALIAS_VERSION)
+            Com.Error(Defines.ERR_DROP, "%s has wrong version number (%i should be %i)",
+                    new Vargs(3).add(mod.name).add(pheader.version).add(qfiles.ALIAS_VERSION));
+
+        if (pheader.skinheight > MAX_LBM_HEIGHT)
+            Com.Error(Defines.ERR_DROP, "model " + mod.name + " has a skin taller than " + MAX_LBM_HEIGHT);
+
+        if (pheader.num_xyz <= 0)
+            Com.Error(Defines.ERR_DROP, "model " + mod.name + " has no vertices");
+
+        if (pheader.num_xyz > qfiles.MAX_VERTS)
+            Com.Error(Defines.ERR_DROP, "model " + mod.name + " has too many vertices");
+
+        if (pheader.num_st <= 0)
+            Com.Error(Defines.ERR_DROP, "model " + mod.name + " has no st vertices");
+
+        if (pheader.num_tris <= 0)
+            Com.Error(Defines.ERR_DROP, "model " + mod.name + " has no triangles");
+
+        if (pheader.num_frames <= 0)
+            Com.Error(Defines.ERR_DROP, "model " + mod.name + " has no frames");
+
+        //
+        // load base s and t vertices (not used in gl version)
+        //
+        poutst = new qfiles.dstvert_t[pheader.num_st];
+        buffer.position(pheader.ofs_st);
+        for (i = 0; i < pheader.num_st; i++) {
+            poutst[i] = new qfiles.dstvert_t(buffer);
+        }
+
+        //
+        //        load triangle lists
+        //
+        pouttri = new qfiles.dtriangle_t[pheader.num_tris];
+        buffer.position(pheader.ofs_tris);
+        for (i = 0; i < pheader.num_tris; i++) {
+            pouttri[i] = new qfiles.dtriangle_t(buffer);
+        }
+
+        //
+        //        load the frames
+        //
+        poutframe = new qfiles.daliasframe_t[pheader.num_frames];
+        buffer.position(pheader.ofs_frames);
+        for (i = 0; i < pheader.num_frames; i++) {
+            poutframe[i] = new qfiles.daliasframe_t(buffer);
+            // verts are all 8 bit, so no swapping needed
+            poutframe[i].verts = new int[pheader.num_xyz];
+            for (int k = 0; k < pheader.num_xyz; k++) {
+                poutframe[i].verts[k] = buffer.getInt();
+            }
+        }
+
+        mod.type = mod_alias;
+
+        //
+        // load the glcmds
+        //
+        poutcmd = new int[pheader.num_glcmds];
+        buffer.position(pheader.ofs_glcmds);
+        for (i = 0; i < pheader.num_glcmds; i++)
+            poutcmd[i] = buffer.getInt(); // LittleLong (pincmd[i]);
+
+        // register all skins
+        String[] skinNames = new String[pheader.num_skins];
+        byte[] nameBuf = new byte[qfiles.MAX_SKINNAME];
+        buffer.position(pheader.ofs_skins);
+        for (i = 0; i < pheader.num_skins; i++) {
+            buffer.get(nameBuf);
+            skinNames[i] = new String(nameBuf);
+            int n = skinNames[i].indexOf('\0');
+            if (n > -1) {
+                skinNames[i] = skinNames[i].substring(0, n);
+            }
+            mod.skins[i] = GL_FindImage(skinNames[i], it_skin);
+        }
+
+        // set the model arrays
+        pheader.skinNames = skinNames; // skin names
+        pheader.stVerts = poutst; // textur koordinaten
+        pheader.triAngles = pouttri; // dreiecke
+        pheader.glCmds = poutcmd; // STRIP or FAN
+        pheader.aliasFrames = poutframe; // frames mit vertex array
+
+        mod.extradata = pheader;
+
+        mod.mins[0] = -32;
+        mod.mins[1] = -32;
+        mod.mins[2] = -32;
+        mod.maxs[0] = 32;
+        mod.maxs[1] = 32;
+        mod.maxs[2] = 32;
+
+        precompileGLCmds(pheader);
+    }
+
+    /*
+    =================
+    Mod_LoadSpriteModel
+    =================
+    */
+    void Mod_LoadSpriteModel(lwjake2.render.Model mod, ByteBuffer buffer) {
+        qfiles.dsprite_t sprout = new qfiles.dsprite_t(buffer);
+
+        if (sprout.version != qfiles.SPRITE_VERSION)
+            Com.Error(Defines.ERR_DROP, "%s has wrong version number (%i should be %i)",
+                    new Vargs(3).add(mod.name).add(sprout.version).add(qfiles.SPRITE_VERSION));
+
+        if (sprout.numframes > qfiles.MAX_MD2SKINS)
+            Com.Error(Defines.ERR_DROP, "%s has too many frames (%i > %i)",
+                    new Vargs(3).add(mod.name).add(sprout.numframes).add(qfiles.MAX_MD2SKINS));
+
+        for (int i = 0; i < sprout.numframes; i++) {
+            mod.skins[i] = GL_FindImage(sprout.frames[i].name, it_sprite);
+        }
+
+        mod.type = mod_sprite;
+        mod.extradata = sprout;
+    }
+
+    /*
+    @@@@@@@@@@@@@@@@@@@@@
+    R_BeginRegistration
+
+    Specifies the model that will be used as the world
+    @@@@@@@@@@@@@@@@@@@@@
+    */
+    protected void R_BeginRegistration(String model) {
+        resetModelArrays();
+        Polygon.reset();
+
+        CvarT flushmap;
+
+        registration_sequence++;
+        r_oldviewcluster = -1;        // force markleafs
+
+        String fullname = "maps/" + model + ".bsp";
+
+        // explicitly free the old map if different
+        // this guarantees that mod_known[0] is the world map
+        flushmap = Cvar.get("flushmap", "0", 0);
+        if (!mod_known[0].name.equals(fullname) || flushmap.value != 0.0f)
+            Mod_Free(mod_known[0]);
+        r_worldmodel = Mod_ForName(fullname, true);
+
+        r_viewcluster = -1;
+    }
+
+    /*
+    @@@@@@@@@@@@@@@@@@@@@
+    R_RegisterModel
+
+    @@@@@@@@@@@@@@@@@@@@@
+    */
+    protected lwjake2.render.Model R_RegisterModel(String name) {
+        lwjake2.render.Model mod = null;
+        int i;
+        qfiles.dsprite_t sprout;
+        qfiles.dmdl_t pheader;
+
+        mod = Mod_ForName(name, false);
+        if (mod != null) {
+            mod.registration_sequence = registration_sequence;
+
+            // register any images used by the models
+            if (mod.type == mod_sprite) {
+                sprout = (qfiles.dsprite_t) mod.extradata;
+                for (i = 0; i < sprout.numframes; i++)
+                    mod.skins[i] = GL_FindImage(sprout.frames[i].name, it_sprite);
+            } else if (mod.type == mod_alias) {
+                pheader = (qfiles.dmdl_t) mod.extradata;
+                for (i = 0; i < pheader.num_skins; i++)
+                    mod.skins[i] = GL_FindImage(pheader.skinNames[i], it_skin);
+                // PGM
+                mod.numframes = pheader.num_frames;
+                // PGM
+            } else if (mod.type == mod_brush) {
+                for (i = 0; i < mod.numtexinfo; i++)
+                    mod.texinfo[i].image.registration_sequence = registration_sequence;
+            }
+        }
+        return mod;
+    }
+
+    /*
+    @@@@@@@@@@@@@@@@@@@@@
+    R_EndRegistration
+
+    @@@@@@@@@@@@@@@@@@@@@
+    */
+    protected void R_EndRegistration() {
+        lwjake2.render.Model mod;
+
+        for (int i = 0; i < mod_numknown; i++) {
+            mod = mod_known[i];
+            if (mod.name.length() == 0)
+                continue;
+            if (mod.registration_sequence != registration_sequence) {    // don't need this model
+                Mod_Free(mod);
+            } else {
+                // precompile AliasModels
+                if (mod.type == mod_alias)
+                    precompileGLCmds((qfiles.dmdl_t) mod.extradata);
+            }
+        }
+        GL_FreeUnusedImages();
+        //modelMemoryUsage();
+    }
+
+    /*
+    ================
+    Mod_Free
+    ================
+    */
+    void Mod_Free(lwjake2.render.Model mod) {
+        mod.clear();
+    }
+
+    /*
+    ================
+    Mod_FreeAll
+    ================
+    */
+    void Mod_FreeAll() {
+        for (int i = 0; i < mod_numknown; i++) {
+            if (mod_known[i].extradata != null)
+                Mod_Free(mod_known[i]);
+        }
+    }
+
+    void precompileGLCmds(qfiles.dmdl_t model) {
+        model.textureCoordBuf = globalModelTextureCoordBuf.slice();
+        model.vertexIndexBuf = globalModelVertexIndexBuf.slice();
+        Vector<Integer> tmp = new Vector<>();
+
+        int count = 0;
+        int[] order = model.glCmds;
+        int orderIndex = 0;
+        while (true) {
+            // get the vertex count and primitive type
+            count = order[orderIndex++];
+            if (count == 0)
+                break;        // done
+
+            tmp.addElement(count);
+
+            if (count < 0) {
+                count = -count;
+                //gl.glBegin (GL.GL_TRIANGLE_FAN);
+            } else {
+                //gl.glBegin (GL.GL_TRIANGLE_STRIP);
+            }
+
+            do {
+                // texture coordinates come from the draw list
+                globalModelTextureCoordBuf.put(Float.intBitsToFloat(order[orderIndex]));
+                globalModelTextureCoordBuf.put(Float.intBitsToFloat(order[orderIndex + 1]));
+                globalModelVertexIndexBuf.put(order[orderIndex + 2]);
+
+                orderIndex += 3;
+            } while (--count != 0);
+        }
+
+        int size = tmp.size();
+
+        model.counts = new int[size];
+        model.indexElements = new IntBuffer[size];
+
+        count = 0;
+        int pos = 0;
+        for (int i = 0; i < model.counts.length; i++) {
+            count = tmp.get(i);
+            model.counts[i] = count;
+
+            count = (count < 0) ? -count : count;
+            model.vertexIndexBuf.position(pos);
+            model.indexElements[i] = model.vertexIndexBuf.slice();
+            model.indexElements[i].limit(count);
+            pos += count;
+        }
+    }
+}
diff --git a/src/main/java/lwjake2/render/lwjgl/Polygon.java b/src/main/java/lwjake2/render/lwjgl/Polygon.java
new file mode 100644 (file)
index 0000000..506c59d
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render.lwjgl;
+
+import lwjake2.render.glpoly_t;
+import lwjake2.util.Lib;
+
+import java.nio.FloatBuffer;
+
+/**
+ * Polygon
+ *
+ * @author cwei
+ */
+public final class Polygon extends glpoly_t {
+
+    private final static int MAX_POLYS = 20000;
+    private final static int MAX_BUFFER_VERTICES = 120000;
+
+    // backup for s1 scrolling
+    private static final float[] s1_old = new float[MAX_VERTICES];
+
+    private static final FloatBuffer buffer = Lib.newFloatBuffer(MAX_BUFFER_VERTICES * STRIDE);
+    private static final Polygon[] polyCache = new Polygon[MAX_POLYS];
+    private static int bufferIndex = 0;
+    private static int polyCount = 0;
+
+    static {
+        for (int i = 0; i < polyCache.length; i++) {
+            polyCache[i] = new Polygon();
+        }
+    }
+
+    private Polygon() {
+    }
+
+    static glpoly_t create(int numverts) {
+        Polygon poly = polyCache[polyCount++];
+        poly.clear();
+        poly.numverts = numverts;
+        poly.pos = bufferIndex;
+        bufferIndex += numverts;
+        return poly;
+    }
+
+    static void reset() {
+        polyCount = 0;
+        bufferIndex = 0;
+    }
+
+    static FloatBuffer getInterleavedBuffer() {
+        return (FloatBuffer) buffer.rewind();
+    }
+
+    private void clear() {
+        next = null;
+        chain = null;
+        numverts = 0;
+    }
+
+    // the interleaved buffer has the format:
+    // textureCoord0 (index 0, 1)
+    // vertex (index 2, 3, 4)
+    // textureCoord1 (index 5, 6)
+
+    public final float x(int index) {
+        return buffer.get((index + pos) * 7 + 2);
+    }
+
+    public final void x(int index, float value) {
+        buffer.put((index + pos) * 7 + 2, value);
+    }
+
+    public final float y(int index) {
+        return buffer.get((index + pos) * 7 + 3);
+    }
+
+    public final void y(int index, float value) {
+        buffer.put((index + pos) * 7 + 3, value);
+    }
+
+    public final float z(int index) {
+        return buffer.get((index + pos) * 7 + 4);
+    }
+
+    public final void z(int index, float value) {
+        buffer.put((index + pos) * 7 + 4, value);
+    }
+
+    public final float s1(int index) {
+        return buffer.get((index + pos) * 7);
+    }
+
+    public final void s1(int index, float value) {
+        buffer.put((index + pos) * 7, value);
+    }
+
+    public final float t1(int index) {
+        return buffer.get((index + pos) * 7 + 1);
+    }
+
+    public final void t1(int index, float value) {
+        buffer.put((index + pos) * 7 + 1, value);
+    }
+
+    public final float s2(int index) {
+        return buffer.get((index + pos) * 7 + 5);
+    }
+
+    public final void s2(int index, float value) {
+        buffer.put((index + pos) * 7 + 5, value);
+    }
+
+    public final float t2(int index) {
+        return buffer.get((index + pos) * 7 + 6);
+    }
+
+    public final void t2(int index, float value) {
+        buffer.put((index + pos) * 7 + 6, value);
+    }
+
+    public final void beginScrolling(float scroll) {
+        int index = pos * 7;
+        for (int i = 0; i < numverts; i++, index += 7) {
+            scroll += s1_old[i] = buffer.get(index);
+            buffer.put(index, scroll);
+        }
+    }
+
+    public final void endScrolling() {
+        int index = pos * 7;
+        for (int i = 0; i < numverts; i++, index += 7) {
+            buffer.put(index, s1_old[i]);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/render/lwjgl/Surf.java b/src/main/java/lwjake2/render/lwjgl/Surf.java
new file mode 100644 (file)
index 0000000..7eb0834
--- /dev/null
@@ -0,0 +1,1184 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render.lwjgl;
+
+import lwjake2.Defines;
+import lwjake2.client.dlight_t;
+import lwjake2.client.entity_t;
+import lwjake2.client.lightstyle_t;
+import lwjake2.game.cplane_t;
+import lwjake2.qcommon.Com;
+import lwjake2.render.Image;
+import lwjake2.render.Model;
+import lwjake2.render.*;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+import org.lwjgl.BufferUtils;
+import org.lwjgl.opengl.ARBMultitexture;
+import org.lwjgl.opengl.GL11;
+
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.util.Arrays;
+
+/**
+ * Surf
+ *
+ * @author cwei
+ */
+public abstract class Surf extends Draw {
+
+    static final int DYNAMIC_LIGHT_WIDTH = 128;
+    static final int DYNAMIC_LIGHT_HEIGHT = 128;
+    static final int LIGHTMAP_BYTES = 4;
+    static final int BLOCK_WIDTH = 128;
+    static final int BLOCK_HEIGHT = 128;
+    static final int MAX_LIGHTMAPS = 128;
+    static final int GL_LIGHTMAP_FORMAT = GL11.GL_RGBA;
+    /*
+     * new buffers for vertex array handling
+     */
+    static final FloatBuffer globalPolygonInterleavedBuf = Polygon.getInterleavedBuffer();
+    static final FloatBuffer globalPolygonTexCoord1Buf;
+
+    static {
+        globalPolygonInterleavedBuf.position(Polygon.STRIDE - 2);
+        globalPolygonTexCoord1Buf = globalPolygonInterleavedBuf.slice();
+        globalPolygonInterleavedBuf.position(0);
+    }
+
+    final byte[] fatvis = new byte[Defines.MAX_MAP_LEAFS / 8];
+    /*
+    =============================================================
+
+        BRUSH MODELS
+
+    =============================================================
+    */
+    // GL_RSURF.C: surface-related refresh code
+    final float[] modelorg = {0, 0, 0};        // relative to viewpoint
+    final gllightmapstate_t gl_lms = new gllightmapstate_t();
+    private final IntBuffer temp2 = Lib.newIntBuffer(34 * 34, ByteOrder.LITTLE_ENDIAN);
+    // direct buffer
+    private final IntBuffer temp = Lib.newIntBuffer(128 * 128, ByteOrder.LITTLE_ENDIAN);
+    // stack variable
+    private final float[] mins = {0, 0, 0};
+    private final float[] maxs = {0, 0, 0};
+    private final float[] org = {0, 0, 0};
+    private final float[] forward = {0, 0, 0};
+    private final float[] right = {0, 0, 0};
+    private final float[] up = {0, 0, 0};
+    private final entity_t worldEntity = new entity_t();
+    private final IntBuffer dummy = BufferUtils.createIntBuffer(128 * 128);
+    msurface_t r_alpha_surfaces;
+    int c_visible_lightmaps;
+    int c_visible_textures;
+    lightstyle_t[] lightstyles;
+
+    // Model.java
+    abstract byte[] Mod_ClusterPVS(int cluster, Model model);
+
+    // Warp.java
+    abstract void R_DrawSkyBox();
+
+    abstract void R_AddSkySurface(msurface_t surface);
+
+    abstract void R_ClearSkyBox();
+
+    abstract void EmitWaterPolys(msurface_t fa);
+
+    // Light.java
+    abstract void R_MarkLights(dlight_t light, int bit, mnode_t node);
+
+    abstract void R_SetCacheState(msurface_t surf);
+
+    abstract void R_BuildLightMap(msurface_t surf, IntBuffer dest, int stride);
+
+    /**
+     * R_TextureAnimation
+     * Returns the proper texture for a given time and base texture
+     */
+    Image R_TextureAnimation(mtexinfo_t tex) {
+        if (tex.next == null)
+            return tex.image;
+
+        int c = currententity.frame % tex.numframes;
+        while (c != 0) {
+            tex = tex.next;
+            c--;
+        }
+
+        return tex.image;
+    }
+
+    /**
+     * DrawGLPoly
+     */
+    void DrawGLPoly(glpoly_t p) {
+        GL11.glDrawArrays(GL11.GL_POLYGON, p.pos, p.numverts);
+    }
+
+    /**
+     * DrawGLFlowingPoly
+     * version that handles scrolling texture
+     */
+    void DrawGLFlowingPoly(glpoly_t p) {
+        float scroll = -64 * ((r_newrefdef.time / 40.0f) - (int) (r_newrefdef.time / 40.0f));
+        if (scroll == 0.0f)
+            scroll = -64.0f;
+        p.beginScrolling(scroll);
+        GL11.glDrawArrays(GL11.GL_POLYGON, p.pos, p.numverts);
+        p.endScrolling();
+    }
+
+    /**
+     * R_DrawTriangleOutlines
+     */
+    void R_DrawTriangleOutlines() {
+        if (gl_showtris.value == 0)
+            return;
+
+        GL11.glDisable(GL11.GL_TEXTURE_2D);
+        GL11.glDisable(GL11.GL_DEPTH_TEST);
+        GL11.glColor4f(1, 1, 1, 1);
+
+        msurface_t surf;
+        glpoly_t p;
+        int j;
+        for (int i = 0; i < MAX_LIGHTMAPS; i++) {
+            for (surf = gl_lms.lightmap_surfaces[i]; surf != null; surf = surf.lightmapchain) {
+                for (p = surf.polys; p != null; p = p.chain) {
+                    for (j = 2; j < p.numverts; j++) {
+                        GL11.glBegin(GL11.GL_LINE_STRIP);
+                        GL11.glVertex3f(p.x(0), p.y(0), p.z(0));
+                        GL11.glVertex3f(p.x(j - 1), p.y(j - 1), p.z(j - 1));
+                        GL11.glVertex3f(p.x(j), p.y(j), p.z(j));
+                        GL11.glVertex3f(p.x(0), p.y(0), p.z(0));
+                        GL11.glEnd();
+                    }
+                }
+            }
+        }
+
+        GL11.glEnable(GL11.GL_DEPTH_TEST);
+        GL11.glEnable(GL11.GL_TEXTURE_2D);
+    }
+
+       /*
+    =============================================================
+
+               WORLD MODEL
+
+       =============================================================
+       */
+
+    /**
+     * R_RenderBrushPoly
+     */
+    void R_RenderBrushPoly(msurface_t fa) {
+        c_brush_polys++;
+
+        Image image = R_TextureAnimation(fa.texinfo);
+
+        if ((fa.flags & Defines.SURF_DRAWTURB) != 0) {
+            GL_Bind(image.texnum);
+
+            // warp texture, no lightmaps
+            GL_TexEnv(GL11.GL_MODULATE);
+            GL11.glColor4f(gl_state.inverse_intensity,
+                    gl_state.inverse_intensity,
+                    gl_state.inverse_intensity,
+                    1.0F);
+            EmitWaterPolys(fa);
+            GL_TexEnv(GL11.GL_REPLACE);
+
+            return;
+        } else {
+            GL_Bind(image.texnum);
+            GL_TexEnv(GL11.GL_REPLACE);
+        }
+
+        //       ======
+        //       PGM
+        if ((fa.texinfo.flags & Defines.SURF_FLOWING) != 0)
+            DrawGLFlowingPoly(fa.polys);
+        else
+            DrawGLPoly(fa.polys);
+        //       PGM
+        //       ======
+
+        // ersetzt goto
+        boolean gotoDynamic = false;
+        /*
+        ** check for lightmap modification
+               */
+        int maps;
+        for (maps = 0; maps < Defines.MAXLIGHTMAPS && fa.styles[maps] != (byte) 255; maps++) {
+            if (r_newrefdef.lightstyles[fa.styles[maps] & 0xFF].white != fa.cached_light[maps]) {
+                gotoDynamic = true;
+                break;
+            }
+        }
+
+        // this is a hack from cwei
+        if (maps == 4) maps--;
+
+        // dynamic this frame or dynamic previously
+        boolean is_dynamic = false;
+        if (gotoDynamic || (fa.dlightframe == r_framecount)) {
+            // label dynamic:
+            if (gl_dynamic.value != 0) {
+                if ((fa.texinfo.flags & (Defines.SURF_SKY | Defines.SURF_TRANS33 | Defines.SURF_TRANS66 | Defines.SURF_WARP)) == 0) {
+                    is_dynamic = true;
+                }
+            }
+        }
+
+        if (is_dynamic) {
+            if (((fa.styles[maps] & 0xFF) >= 32 || fa.styles[maps] == 0) && (fa.dlightframe != r_framecount)) {
+                // ist ersetzt durch temp2:    unsigned        temp[34*34];
+                int smax, tmax;
+
+                smax = (fa.extents[0] >> 4) + 1;
+                tmax = (fa.extents[1] >> 4) + 1;
+
+                R_BuildLightMap(fa, temp2, smax);
+                R_SetCacheState(fa);
+
+                GL_Bind(gl_state.lightmap_textures + fa.lightmaptexturenum);
+
+                GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0,
+                        fa.light_s, fa.light_t,
+                        smax, tmax,
+                        GL_LIGHTMAP_FORMAT,
+                        GL11.GL_UNSIGNED_BYTE, temp2);
+
+                fa.lightmapchain = gl_lms.lightmap_surfaces[fa.lightmaptexturenum];
+                gl_lms.lightmap_surfaces[fa.lightmaptexturenum] = fa;
+            } else {
+                fa.lightmapchain = gl_lms.lightmap_surfaces[0];
+                gl_lms.lightmap_surfaces[0] = fa;
+            }
+        } else {
+            fa.lightmapchain = gl_lms.lightmap_surfaces[fa.lightmaptexturenum];
+            gl_lms.lightmap_surfaces[fa.lightmaptexturenum] = fa;
+        }
+    }
+
+    /**
+     * R_DrawAlphaSurfaces
+     * Draw water surfaces and windows.
+     * The BSP tree is waled front to back, so unwinding the chain
+     * of alpha_surfaces will draw back to front, giving proper ordering.
+     */
+    void R_DrawAlphaSurfaces() {
+        r_world_matrix.clear();
+        //
+        // go back to the world matrix
+        //
+        GL11.glLoadMatrix(r_world_matrix);
+
+        GL11.glEnable(GL11.GL_BLEND);
+        GL_TexEnv(GL11.GL_MODULATE);
+
+
+        // the textures are prescaled up for a better lighting range,
+        // so scale it back down
+        float intens = gl_state.inverse_intensity;
+
+        GL11.glInterleavedArrays(GL11.GL_T2F_V3F, Polygon.BYTE_STRIDE, globalPolygonInterleavedBuf);
+
+        for (msurface_t s = r_alpha_surfaces; s != null; s = s.texturechain) {
+            GL_Bind(s.texinfo.image.texnum);
+            c_brush_polys++;
+            if ((s.texinfo.flags & Defines.SURF_TRANS33) != 0)
+                GL11.glColor4f(intens, intens, intens, 0.33f);
+            else if ((s.texinfo.flags & Defines.SURF_TRANS66) != 0)
+                GL11.glColor4f(intens, intens, intens, 0.66f);
+            else
+                GL11.glColor4f(intens, intens, intens, 1);
+            if ((s.flags & Defines.SURF_DRAWTURB) != 0)
+                EmitWaterPolys(s);
+            else if ((s.texinfo.flags & Defines.SURF_FLOWING) != 0)            // PGM  9/16/98
+                DrawGLFlowingPoly(s.polys);                            // PGM
+            else
+                DrawGLPoly(s.polys);
+        }
+
+        GL_TexEnv(GL11.GL_REPLACE);
+        GL11.glColor4f(1, 1, 1, 1);
+        GL11.glDisable(GL11.GL_BLEND);
+
+        r_alpha_surfaces = null;
+    }
+
+    /**
+     * DrawTextureChains
+     */
+    void DrawTextureChains() {
+        c_visible_textures = 0;
+
+        msurface_t s;
+        Image image;
+        int i;
+        for (i = 0; i < numgltextures; i++) {
+            image = gltextures[i];
+
+            if (image.registration_sequence == 0)
+                continue;
+            if (image.texturechain == null)
+                continue;
+            c_visible_textures++;
+
+            for (s = image.texturechain; s != null; s = s.texturechain) {
+                if ((s.flags & Defines.SURF_DRAWTURB) == 0)
+                    R_RenderBrushPoly(s);
+            }
+        }
+
+        GL_EnableMultitexture(false);
+        for (i = 0; i < numgltextures; i++) {
+            image = gltextures[i];
+
+            if (image.registration_sequence == 0)
+                continue;
+            s = image.texturechain;
+            if (s == null)
+                continue;
+
+            for (; s != null; s = s.texturechain) {
+                if ((s.flags & Defines.SURF_DRAWTURB) != 0)
+                    R_RenderBrushPoly(s);
+            }
+
+            image.texturechain = null;
+        }
+
+        GL_TexEnv(GL11.GL_REPLACE);
+    }
+
+    /**
+     * GL_RenderLightmappedPoly
+     *
+     * @param surf
+     */
+    void GL_RenderLightmappedPoly(msurface_t surf) {
+
+        // ersetzt goto
+        boolean gotoDynamic = false;
+        int map;
+        for (map = 0; map < Defines.MAXLIGHTMAPS && (surf.styles[map] != (byte) 255); map++) {
+            if (r_newrefdef.lightstyles[surf.styles[map] & 0xFF].white != surf.cached_light[map]) {
+                gotoDynamic = true;
+                break;
+            }
+        }
+
+        // this is a hack from cwei
+        if (map == 4) map--;
+
+        // dynamic this frame or dynamic previously
+        boolean is_dynamic = false;
+        if (gotoDynamic || (surf.dlightframe == r_framecount)) {
+            // label dynamic:
+            if (gl_dynamic.value != 0) {
+                if ((surf.texinfo.flags & (Defines.SURF_SKY | Defines.SURF_TRANS33 | Defines.SURF_TRANS66 | Defines.SURF_WARP)) == 0) {
+                    is_dynamic = true;
+                }
+            }
+        }
+
+        glpoly_t p;
+        Image image = R_TextureAnimation(surf.texinfo);
+        int lmtex = surf.lightmaptexturenum;
+
+        if (is_dynamic) {
+            // ist raus gezogen worden int[] temp = new int[128*128];
+            int smax, tmax;
+
+            if (((surf.styles[map] & 0xFF) >= 32 || surf.styles[map] == 0) && (surf.dlightframe != r_framecount)) {
+                smax = (surf.extents[0] >> 4) + 1;
+                tmax = (surf.extents[1] >> 4) + 1;
+
+                R_BuildLightMap(surf, temp, smax);
+                R_SetCacheState(surf);
+
+                GL_MBind(GL_TEXTURE1, gl_state.lightmap_textures + surf.lightmaptexturenum);
+
+                lmtex = surf.lightmaptexturenum;
+
+                GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0,
+                        surf.light_s, surf.light_t,
+                        smax, tmax,
+                        GL_LIGHTMAP_FORMAT,
+                        GL11.GL_UNSIGNED_BYTE, temp);
+
+            } else {
+                smax = (surf.extents[0] >> 4) + 1;
+                tmax = (surf.extents[1] >> 4) + 1;
+
+                R_BuildLightMap(surf, temp, smax);
+
+                GL_MBind(GL_TEXTURE1, gl_state.lightmap_textures);
+
+                lmtex = 0;
+
+                GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0,
+                        surf.light_s, surf.light_t,
+                        smax, tmax,
+                        GL_LIGHTMAP_FORMAT,
+                        GL11.GL_UNSIGNED_BYTE, temp);
+
+            }
+
+            c_brush_polys++;
+
+            GL_MBind(GL_TEXTURE0, image.texnum);
+            GL_MBind(GL_TEXTURE1, gl_state.lightmap_textures + lmtex);
+
+            // ==========
+            //   PGM
+            if ((surf.texinfo.flags & Defines.SURF_FLOWING) != 0) {
+                float scroll;
+
+                scroll = -64 * ((r_newrefdef.time / 40.0f) - (int) (r_newrefdef.time / 40.0f));
+                if (scroll == 0.0f)
+                    scroll = -64.0f;
+
+                for (p = surf.polys; p != null; p = p.chain) {
+                    p.beginScrolling(scroll);
+                    GL11.glDrawArrays(GL11.GL_POLYGON, p.pos, p.numverts);
+                    p.endScrolling();
+                }
+            } else {
+                for (p = surf.polys; p != null; p = p.chain) {
+                    GL11.glDrawArrays(GL11.GL_POLYGON, p.pos, p.numverts);
+                }
+            }
+            // PGM
+            // ==========
+        } else {
+            c_brush_polys++;
+
+            GL_MBind(GL_TEXTURE0, image.texnum);
+            GL_MBind(GL_TEXTURE1, gl_state.lightmap_textures + lmtex);
+
+            // ==========
+            //   PGM
+            if ((surf.texinfo.flags & Defines.SURF_FLOWING) != 0) {
+                float scroll;
+
+                scroll = -64 * ((r_newrefdef.time / 40.0f) - (int) (r_newrefdef.time / 40.0f));
+                if (scroll == 0.0)
+                    scroll = -64.0f;
+
+                for (p = surf.polys; p != null; p = p.chain) {
+                    p.beginScrolling(scroll);
+                    GL11.glDrawArrays(GL11.GL_POLYGON, p.pos, p.numverts);
+                    p.endScrolling();
+                }
+            } else {
+                // PGM
+                //  ==========
+                for (p = surf.polys; p != null; p = p.chain) {
+                    GL11.glDrawArrays(GL11.GL_POLYGON, p.pos, p.numverts);
+                }
+
+                // ==========
+                // PGM
+            }
+            // PGM
+            // ==========
+        }
+    }
+
+    /**
+     * R_DrawInlineBModel
+     */
+    void R_DrawInlineBModel() {
+        // calculate dynamic lighting for bmodel
+        if (gl_flashblend.value == 0) {
+            dlight_t lt;
+            for (int k = 0; k < r_newrefdef.num_dlights; k++) {
+                lt = r_newrefdef.dlights[k];
+                R_MarkLights(lt, 1 << k, currentmodel.nodes[currentmodel.firstnode]);
+            }
+        }
+
+        // psurf = &currentmodel->surfaces[currentmodel->firstmodelsurface];
+        int psurfp = currentmodel.firstmodelsurface;
+        msurface_t[] surfaces = currentmodel.surfaces;
+        //psurf = surfaces[psurfp];
+
+        if ((currententity.flags & Defines.RF_TRANSLUCENT) != 0) {
+            GL11.glEnable(GL11.GL_BLEND);
+            GL11.glColor4f(1, 1, 1, 0.25f);
+            GL_TexEnv(GL11.GL_MODULATE);
+        }
+
+        //
+        // draw texture
+        //
+        msurface_t psurf;
+        cplane_t pplane;
+        float dot;
+        for (int i = 0; i < currentmodel.nummodelsurfaces; i++) {
+            psurf = surfaces[psurfp++];
+            // find which side of the node we are on
+            pplane = psurf.plane;
+
+            dot = Math3D.dotProduct(modelorg, pplane.normal) - pplane.dist;
+
+            // draw the polygon
+            if (((psurf.flags & Defines.SURF_PLANEBACK) != 0 && (dot < -BACKFACE_EPSILON)) ||
+                    ((psurf.flags & Defines.SURF_PLANEBACK) == 0 && (dot > BACKFACE_EPSILON))) {
+                if ((psurf.texinfo.flags & (Defines.SURF_TRANS33 | Defines.SURF_TRANS66)) != 0) {    // add to the translucent chain
+                    psurf.texturechain = r_alpha_surfaces;
+                    r_alpha_surfaces = psurf;
+                } else if ((psurf.flags & Defines.SURF_DRAWTURB) == 0) {
+                    GL_RenderLightmappedPoly(psurf);
+                } else {
+                    GL_EnableMultitexture(false);
+                    R_RenderBrushPoly(psurf);
+                    GL_EnableMultitexture(true);
+                }
+            }
+        }
+
+        if ((currententity.flags & Defines.RF_TRANSLUCENT) != 0) {
+            GL11.glDisable(GL11.GL_BLEND);
+            GL11.glColor4f(1, 1, 1, 1);
+            GL_TexEnv(GL11.GL_REPLACE);
+        }
+    }
+
+       /*
+    =============================================================================
+
+         LIGHTMAP ALLOCATION
+
+       =============================================================================
+       */
+
+    /**
+     * R_DrawBrushModel
+     */
+    void R_DrawBrushModel(entity_t e) {
+        if (currentmodel.nummodelsurfaces == 0)
+            return;
+
+        currententity = e;
+        gl_state.currenttextures[0] = gl_state.currenttextures[1] = -1;
+
+        boolean rotated;
+        if (e.angles[0] != 0 || e.angles[1] != 0 || e.angles[2] != 0) {
+            rotated = true;
+            for (int i = 0; i < 3; i++) {
+                mins[i] = e.origin[i] - currentmodel.radius;
+                maxs[i] = e.origin[i] + currentmodel.radius;
+            }
+        } else {
+            rotated = false;
+            Math3D.vectorAdd(e.origin, currentmodel.mins, mins);
+            Math3D.vectorAdd(e.origin, currentmodel.maxs, maxs);
+        }
+
+        if (R_CullBox(mins, maxs)) return;
+
+        GL11.glColor3f(1, 1, 1);
+
+        // memset (gl_lms.lightmap_surfaces, 0, sizeof(gl_lms.lightmap_surfaces));
+
+        // TODO wird beim multitexturing nicht gebraucht
+        //gl_lms.clearLightmapSurfaces();
+
+        Math3D.vectorSubtract(r_newrefdef.vieworg, e.origin, modelorg);
+        if (rotated) {
+            Math3D.vectorCopy(modelorg, org);
+            Math3D.angleVectors(e.angles, forward, right, up);
+            modelorg[0] = Math3D.dotProduct(org, forward);
+            modelorg[1] = -Math3D.dotProduct(org, right);
+            modelorg[2] = Math3D.dotProduct(org, up);
+        }
+
+        GL11.glPushMatrix();
+
+        e.angles[0] = -e.angles[0];    // stupid quake bug
+        e.angles[2] = -e.angles[2];    // stupid quake bug
+        R_RotateForEntity(e);
+        e.angles[0] = -e.angles[0];    // stupid quake bug
+        e.angles[2] = -e.angles[2];    // stupid quake bug
+
+        GL_EnableMultitexture(true);
+        GL_SelectTexture(GL_TEXTURE0);
+        GL_TexEnv(GL11.GL_REPLACE);
+        GL11.glInterleavedArrays(GL11.GL_T2F_V3F, Polygon.BYTE_STRIDE, globalPolygonInterleavedBuf);
+        GL_SelectTexture(GL_TEXTURE1);
+        GL_TexEnv(GL11.GL_MODULATE);
+        GL11.glTexCoordPointer(2, Polygon.BYTE_STRIDE, globalPolygonTexCoord1Buf);
+        GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
+
+        R_DrawInlineBModel();
+
+        ARBMultitexture.glClientActiveTextureARB(GL_TEXTURE1);
+        GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
+
+        GL_EnableMultitexture(false);
+
+        GL11.glPopMatrix();
+    }
+
+    /**
+     * R_RecursiveWorldNode
+     */
+    void R_RecursiveWorldNode(mnode_t node) {
+        if (node.contents == Defines.CONTENTS_SOLID)
+            return;        // solid
+
+        if (node.visframe != r_visframecount)
+            return;
+
+        if (R_CullBox(node.mins, node.maxs))
+            return;
+
+        int c;
+        msurface_t mark;
+        // if a leaf node, draw stuff
+        if (node.contents != -1) {
+            mleaf_t pleaf = (mleaf_t) node;
+
+            // check for door connected areas
+            if (r_newrefdef.areabits != null) {
+                if (((r_newrefdef.areabits[pleaf.area >> 3] & 0xFF) & (1 << (pleaf.area & 7))) == 0)
+                    return;        // not visible
+            }
+
+            int markp = 0;
+
+            mark = pleaf.getMarkSurface(markp); // first marked surface
+            c = pleaf.nummarksurfaces;
+
+            if (c != 0) {
+                do {
+                    mark.visframe = r_framecount;
+                    mark = pleaf.getMarkSurface(++markp); // next surface
+                } while (--c != 0);
+            }
+
+            return;
+        }
+
+        // node is just a decision point, so go down the apropriate sides
+
+        // find which side of the node we are on
+        cplane_t plane = node.plane;
+        float dot;
+        switch (plane.type) {
+            case Defines.PLANE_X:
+                dot = modelorg[0] - plane.dist;
+                break;
+            case Defines.PLANE_Y:
+                dot = modelorg[1] - plane.dist;
+                break;
+            case Defines.PLANE_Z:
+                dot = modelorg[2] - plane.dist;
+                break;
+            default:
+                dot = Math3D.dotProduct(modelorg, plane.normal) - plane.dist;
+                break;
+        }
+
+        int side, sidebit;
+        if (dot >= 0.0f) {
+            side = 0;
+            sidebit = 0;
+        } else {
+            side = 1;
+            sidebit = Defines.SURF_PLANEBACK;
+        }
+
+        // recurse down the children, front side first
+        R_RecursiveWorldNode(node.children[side]);
+
+        // draw stuff
+        msurface_t surf;
+        Image image;
+        //for ( c = node.numsurfaces, surf = r_worldmodel.surfaces[node.firstsurface]; c != 0 ; c--, surf++)
+        for (c = 0; c < node.numsurfaces; c++) {
+            surf = r_worldmodel.surfaces[node.firstsurface + c];
+            if (surf.visframe != r_framecount)
+                continue;
+
+            if ((surf.flags & Defines.SURF_PLANEBACK) != sidebit)
+                continue;        // wrong side
+
+            if ((surf.texinfo.flags & Defines.SURF_SKY) != 0) {    // just adds to visible sky bounds
+                R_AddSkySurface(surf);
+            } else if ((surf.texinfo.flags & (Defines.SURF_TRANS33 | Defines.SURF_TRANS66)) != 0) {    // add to the translucent chain
+                surf.texturechain = r_alpha_surfaces;
+                r_alpha_surfaces = surf;
+            } else {
+                if ((surf.flags & Defines.SURF_DRAWTURB) == 0) {
+                    GL_RenderLightmappedPoly(surf);
+                } else {
+                    // the polygon is visible, so add it to the texture
+                    // sorted chain
+                    // FIXME: this is a hack for animation
+                    image = R_TextureAnimation(surf.texinfo);
+                    surf.texturechain = image.texturechain;
+                    image.texturechain = surf;
+                }
+            }
+        }
+        // recurse down the back side
+        R_RecursiveWorldNode(node.children[1 - side]);
+    }
+
+    /**
+     * R_DrawWorld
+     */
+    void R_DrawWorld() {
+        if (r_drawworld.value == 0)
+            return;
+
+        if ((r_newrefdef.rdflags & Defines.RDF_NOWORLDMODEL) != 0)
+            return;
+
+        currentmodel = r_worldmodel;
+
+        Math3D.vectorCopy(r_newrefdef.vieworg, modelorg);
+
+        entity_t ent = worldEntity;
+        // auto cycle the world frame for texture animation
+        ent.clear();
+        ent.frame = (int) (r_newrefdef.time * 2);
+        currententity = ent;
+
+        gl_state.currenttextures[0] = gl_state.currenttextures[1] = -1;
+
+        GL11.glColor3f(1, 1, 1);
+        // memset (gl_lms.lightmap_surfaces, 0, sizeof(gl_lms.lightmap_surfaces));
+        // TODO wird bei multitexture nicht gebraucht
+        //gl_lms.clearLightmapSurfaces();
+
+        R_ClearSkyBox();
+
+        GL_EnableMultitexture(true);
+
+        GL_SelectTexture(GL_TEXTURE0);
+        GL_TexEnv(GL11.GL_REPLACE);
+        GL11.glInterleavedArrays(GL11.GL_T2F_V3F, Polygon.BYTE_STRIDE, globalPolygonInterleavedBuf);
+        GL_SelectTexture(GL_TEXTURE1);
+        GL11.glTexCoordPointer(2, Polygon.BYTE_STRIDE, globalPolygonTexCoord1Buf);
+        GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
+
+        if (gl_lightmap.value != 0)
+            GL_TexEnv(GL11.GL_REPLACE);
+        else
+            GL_TexEnv(GL11.GL_MODULATE);
+
+        R_RecursiveWorldNode(r_worldmodel.nodes[0]); // root node
+
+        ARBMultitexture.glClientActiveTextureARB(GL_TEXTURE1);
+        GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
+
+        GL_EnableMultitexture(false);
+
+        DrawTextureChains();
+        R_DrawSkyBox();
+        R_DrawTriangleOutlines();
+    }
+
+    /**
+     * R_MarkLeaves
+     * Mark the leaves and nodes that are in the PVS for the current
+     * cluster
+     */
+    void R_MarkLeaves() {
+        if (r_oldviewcluster == r_viewcluster && r_oldviewcluster2 == r_viewcluster2 && r_novis.value == 0 && r_viewcluster != -1)
+            return;
+
+        // development aid to let you run around and see exactly where
+        // the pvs ends
+        if (gl_lockpvs.value != 0)
+            return;
+
+        r_visframecount++;
+        r_oldviewcluster = r_viewcluster;
+        r_oldviewcluster2 = r_viewcluster2;
+
+        int i;
+        if (r_novis.value != 0 || r_viewcluster == -1 || r_worldmodel.vis == null) {
+            // mark everything
+            for (i = 0; i < r_worldmodel.numleafs; i++)
+                r_worldmodel.leafs[i].visframe = r_visframecount;
+            for (i = 0; i < r_worldmodel.numnodes; i++)
+                r_worldmodel.nodes[i].visframe = r_visframecount;
+            return;
+        }
+
+        byte[] vis = Mod_ClusterPVS(r_viewcluster, r_worldmodel);
+        int c;
+        // may have to combine two clusters because of solid water boundaries
+        if (r_viewcluster2 != r_viewcluster) {
+            // memcpy (fatvis, vis, (r_worldmodel.numleafs+7)/8);
+            System.arraycopy(vis, 0, fatvis, 0, (r_worldmodel.numleafs + 7) >> 3);
+            vis = Mod_ClusterPVS(r_viewcluster2, r_worldmodel);
+            c = (r_worldmodel.numleafs + 31) >> 5;
+            c <<= 2;
+            for (int k = 0; k < c; k += 4) {
+                fatvis[k] |= vis[k];
+                fatvis[k + 1] |= vis[k + 1];
+                fatvis[k + 2] |= vis[k + 2];
+                fatvis[k + 3] |= vis[k + 3];
+            }
+
+            vis = fatvis;
+        }
+
+        mnode_t node;
+        mleaf_t leaf;
+        int cluster;
+        for (i = 0; i < r_worldmodel.numleafs; i++) {
+            leaf = r_worldmodel.leafs[i];
+            cluster = leaf.cluster;
+            if (cluster == -1)
+                continue;
+            if (((vis[cluster >> 3] & 0xFF) & (1 << (cluster & 7))) != 0) {
+                node = leaf;
+                do {
+                    if (node.visframe == r_visframecount)
+                        break;
+                    node.visframe = r_visframecount;
+                    node = node.parent;
+                } while (node != null);
+            }
+        }
+    }
+
+    /**
+     * LM_InitBlock
+     */
+    void LM_InitBlock() {
+        Arrays.fill(gl_lms.allocated, 0);
+    }
+
+    /**
+     * LM_UploadBlock
+     */
+    void LM_UploadBlock() {
+        int texture = gl_lms.current_lightmap_texture;
+
+        GL_Bind(gl_state.lightmap_textures + texture);
+        GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
+        GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
+
+        gl_lms.lightmap_buffer.rewind();
+        GL11.glTexImage2D(GL11.GL_TEXTURE_2D,
+                0,
+                gl_lms.internal_format,
+                BLOCK_WIDTH, BLOCK_HEIGHT,
+                0,
+                GL_LIGHTMAP_FORMAT,
+                GL11.GL_UNSIGNED_BYTE,
+                gl_lms.lightmap_buffer);
+        if (++gl_lms.current_lightmap_texture == MAX_LIGHTMAPS)
+            Com.Error(Defines.ERR_DROP, "LM_UploadBlock() - MAX_LIGHTMAPS exceeded\n");
+
+        //debugLightmap(gl_lms.lightmap_buffer, 128, 128, 4);
+    }
+
+    /**
+     * LM_AllocBlock
+     *
+     * @param w
+     * @param h
+     * @param pos
+     * @return a texture number and the position inside it
+     */
+    boolean LM_AllocBlock(int w, int h, pos_t pos) {
+        int best = BLOCK_HEIGHT;
+
+        int best2;
+        int i, j;
+        for (i = 0; i < BLOCK_WIDTH - w; i++) {
+            best2 = 0;
+
+            for (j = 0; j < w; j++) {
+                if (gl_lms.allocated[i + j] >= best)
+                    break;
+                if (gl_lms.allocated[i + j] > best2)
+                    best2 = gl_lms.allocated[i + j];
+            }
+            if (j == w) {    // this is a valid spot
+                pos.x = i;
+                pos.y = best = best2;
+            }
+        }
+
+        if (best + h > BLOCK_HEIGHT)
+            return false;
+
+        for (i = 0; i < w; i++)
+            gl_lms.allocated[pos.x + i] = best + h;
+
+        return true;
+    }
+
+    /**
+     * GL_BuildPolygonFromSurface
+     */
+    void GL_BuildPolygonFromSurface(msurface_t fa) {
+        // reconstruct the polygon
+        medge_t[] pedges = currentmodel.edges;
+        int lnumverts = fa.numedges;
+        //
+        // draw texture
+        //
+        // poly = Hunk_Alloc (sizeof(glpoly_t) + (lnumverts-4) * VERTEXSIZE*sizeof(float));
+        glpoly_t poly = Polygon.create(lnumverts);
+
+        poly.next = fa.polys;
+        fa.polys = poly;
+
+        int lindex;
+        float[] vec;
+        medge_t r_pedge;
+        float s, t;
+        for (int i = 0; i < lnumverts; i++) {
+            lindex = currentmodel.surfedges[fa.firstedge + i];
+
+            if (lindex > 0) {
+                r_pedge = pedges[lindex];
+                vec = currentmodel.vertexes[r_pedge.v[0]].position;
+            } else {
+                r_pedge = pedges[-lindex];
+                vec = currentmodel.vertexes[r_pedge.v[1]].position;
+            }
+            s = Math3D.dotProduct(vec, fa.texinfo.vecs[0]) + fa.texinfo.vecs[0][3];
+            s /= fa.texinfo.image.width;
+
+            t = Math3D.dotProduct(vec, fa.texinfo.vecs[1]) + fa.texinfo.vecs[1][3];
+            t /= fa.texinfo.image.height;
+
+            poly.x(i, vec[0]);
+            poly.y(i, vec[1]);
+            poly.z(i, vec[2]);
+
+            poly.s1(i, s);
+            poly.t1(i, t);
+
+            //
+            // lightmap texture coordinates
+            //
+            s = Math3D.dotProduct(vec, fa.texinfo.vecs[0]) + fa.texinfo.vecs[0][3];
+            s -= fa.texturemins[0];
+            s += fa.light_s * 16;
+            s += 8;
+            s /= BLOCK_WIDTH * 16; //fa.texinfo.texture.width;
+
+            t = Math3D.dotProduct(vec, fa.texinfo.vecs[1]) + fa.texinfo.vecs[1][3];
+            t -= fa.texturemins[1];
+            t += fa.light_t * 16;
+            t += 8;
+            t /= BLOCK_HEIGHT * 16; //fa.texinfo.texture.height;
+
+            poly.s2(i, s);
+            poly.t2(i, t);
+        }
+    }
+
+    /**
+     * GL_CreateSurfaceLightmap
+     */
+    void GL_CreateSurfaceLightmap(msurface_t surf) {
+        if ((surf.flags & (Defines.SURF_DRAWSKY | Defines.SURF_DRAWTURB)) != 0)
+            return;
+
+        int smax = (surf.extents[0] >> 4) + 1;
+        int tmax = (surf.extents[1] >> 4) + 1;
+
+        pos_t lightPos = new pos_t(surf.light_s, surf.light_t);
+
+        if (!LM_AllocBlock(smax, tmax, lightPos)) {
+            LM_UploadBlock();
+            LM_InitBlock();
+            lightPos = new pos_t(surf.light_s, surf.light_t);
+            if (!LM_AllocBlock(smax, tmax, lightPos)) {
+                Com.Error(Defines.ERR_FATAL, "Consecutive calls to LM_AllocBlock(" + smax + "," + tmax + ") failed\n");
+            }
+        }
+
+        // kopiere die koordinaten zurueck
+        surf.light_s = lightPos.x;
+        surf.light_t = lightPos.y;
+
+        surf.lightmaptexturenum = gl_lms.current_lightmap_texture;
+
+        IntBuffer base = gl_lms.lightmap_buffer;
+        base.position(surf.light_t * BLOCK_WIDTH + surf.light_s);
+
+        R_SetCacheState(surf);
+        R_BuildLightMap(surf, base.slice(), BLOCK_WIDTH);
+    }
+
+    /**
+     * GL_BeginBuildingLightmaps
+     */
+    void GL_BeginBuildingLightmaps(Model m) {
+        // static lightstyle_t lightstyles[MAX_LIGHTSTYLES];
+        int i;
+
+        // init lightstyles
+        if (lightstyles == null) {
+            lightstyles = new lightstyle_t[Defines.MAX_LIGHTSTYLES];
+            for (i = 0; i < lightstyles.length; i++) {
+                lightstyles[i] = new lightstyle_t();
+            }
+        }
+
+        // memset( gl_lms.allocated, 0, sizeof(gl_lms.allocated) );
+        Arrays.fill(gl_lms.allocated, 0);
+
+        r_framecount = 1;        // no dlightcache
+
+        GL_EnableMultitexture(true);
+        GL_SelectTexture(GL_TEXTURE1);
+
+               /*
+        ** setup the base lightstyles so the lightmaps won't have to be regenerated
+               ** the first time they're seen
+               */
+        for (i = 0; i < Defines.MAX_LIGHTSTYLES; i++) {
+            lightstyles[i].rgb[0] = 1;
+            lightstyles[i].rgb[1] = 1;
+            lightstyles[i].rgb[2] = 1;
+            lightstyles[i].white = 3;
+        }
+        r_newrefdef.lightstyles = lightstyles;
+
+        if (gl_state.lightmap_textures == 0) {
+            gl_state.lightmap_textures = TEXNUM_LIGHTMAPS;
+        }
+
+        gl_lms.current_lightmap_texture = 1;
+
+               /*
+        ** if mono lightmaps are enabled and we want to use alpha
+               ** blending (a,1-a) then we're likely running on a 3DLabs
+               ** Permedia2.  In a perfect world we'd use a GL_ALPHA lightmap
+               ** in order to conserve space and maximize bandwidth, however
+               ** this isn't a perfect world.
+               **
+               ** So we have to use alpha lightmaps, but stored in GL_RGBA format,
+               ** which means we only get 1/16th the color resolution we should when
+               ** using alpha lightmaps.  If we find another board that supports
+               ** only alpha lightmaps but that can at least support the GL_ALPHA
+               ** format then we should change this code to use real alpha maps.
+               */
+
+        char format = gl_monolightmap.string.toUpperCase().charAt(0);
+
+        if (format == 'A') {
+            gl_lms.internal_format = gl_tex_alpha_format;
+        }
+        /*
+        ** try to do hacked colored lighting with a blended texture
+               */
+        else if (format == 'C') {
+            gl_lms.internal_format = gl_tex_alpha_format;
+        } else if (format == 'I') {
+            gl_lms.internal_format = GL11.GL_INTENSITY8;
+        } else if (format == 'L') {
+            gl_lms.internal_format = GL11.GL_LUMINANCE8;
+        } else {
+            gl_lms.internal_format = gl_tex_solid_format;
+        }
+
+               /*
+               ** initialize the dynamic lightmap texture
+               */
+        GL_Bind(gl_state.lightmap_textures);
+        GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
+        GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
+        GL11.glTexImage2D(GL11.GL_TEXTURE_2D,
+                0,
+                gl_lms.internal_format,
+                BLOCK_WIDTH, BLOCK_HEIGHT,
+                0,
+                GL_LIGHTMAP_FORMAT,
+                GL11.GL_UNSIGNED_BYTE,
+                dummy);
+    }
+
+    /**
+     * GL_EndBuildingLightmaps
+     */
+    void GL_EndBuildingLightmaps() {
+        LM_UploadBlock();
+        GL_EnableMultitexture(false);
+    }
+
+    static class gllightmapstate_t {
+        final msurface_t[] lightmap_surfaces = new msurface_t[MAX_LIGHTMAPS];
+        final int[] allocated = new int[BLOCK_WIDTH];
+        // the lightmap texture data needs to be kept in
+        // main memory so texsubimage can update properly
+        //byte[] lightmap_buffer = new byte[4 * BLOCK_WIDTH * BLOCK_HEIGHT];
+        final IntBuffer lightmap_buffer = Lib.newIntBuffer(BLOCK_WIDTH * BLOCK_HEIGHT, ByteOrder.LITTLE_ENDIAN);
+        int internal_format;
+        int current_lightmap_texture;
+
+        public gllightmapstate_t() {
+            for (int i = 0; i < MAX_LIGHTMAPS; i++)
+                lightmap_surfaces[i] = new msurface_t();
+        }
+
+        public void clearLightmapSurfaces() {
+            for (int i = 0; i < MAX_LIGHTMAPS; i++)
+                // TODO lightmap_surfaces[i].clear();
+                lightmap_surfaces[i] = new msurface_t();
+        }
+
+    }
+
+    //ImageFrame frame;
+
+//     void debugLightmap(byte[] buf, int w, int h, float scale) {
+//             IntBuffer pix = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
+//             
+//             int[] pixel = new int[w * h];
+//             
+//             pix.get(pixel);
+//             
+//             BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
+//             image.setRGB(0,  0, w, h, pixel, 0, w);
+//             AffineTransformOp op = new AffineTransformOp(AffineTransform.getScaleInstance(scale, scale), AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
+//             BufferedImage tmp = op.filter(image, null);
+//             
+//             if (frame == null) {
+//                     frame = new ImageFrame(null);
+//                     frame.show();
+//             } 
+//             frame.showImage(tmp);
+//             
+//     }
+
+}
diff --git a/src/main/java/lwjake2/render/lwjgl/Warp.java b/src/main/java/lwjake2/render/lwjgl/Warp.java
new file mode 100644 (file)
index 0000000..2f56534
--- /dev/null
@@ -0,0 +1,661 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render.lwjgl;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.qcommon.Com;
+import lwjake2.render.Image;
+import lwjake2.render.glpoly_t;
+import lwjake2.render.msurface_t;
+import lwjake2.util.Math3D;
+import lwjake2.util.Vec3Cache;
+import org.lwjgl.opengl.GL11;
+
+/**
+ * Warp
+ *
+ * @author cwei
+ */
+public abstract class Warp extends Model {
+    // warpsin.h
+    public static final float[] SIN = {
+            0f, 0.19633f, 0.392541f, 0.588517f, 0.784137f, 0.979285f, 1.17384f, 1.3677f,
+            1.56072f, 1.75281f, 1.94384f, 2.1337f, 2.32228f, 2.50945f, 2.69512f, 2.87916f,
+            3.06147f, 3.24193f, 3.42044f, 3.59689f, 3.77117f, 3.94319f, 4.11282f, 4.27998f,
+            4.44456f, 4.60647f, 4.76559f, 4.92185f, 5.07515f, 5.22538f, 5.37247f, 5.51632f,
+            5.65685f, 5.79398f, 5.92761f, 6.05767f, 6.18408f, 6.30677f, 6.42566f, 6.54068f,
+            6.65176f, 6.75883f, 6.86183f, 6.9607f, 7.05537f, 7.14579f, 7.23191f, 7.31368f,
+            7.39104f, 7.46394f, 7.53235f, 7.59623f, 7.65552f, 7.71021f, 7.76025f, 7.80562f,
+            7.84628f, 7.88222f, 7.91341f, 7.93984f, 7.96148f, 7.97832f, 7.99036f, 7.99759f,
+            8f, 7.99759f, 7.99036f, 7.97832f, 7.96148f, 7.93984f, 7.91341f, 7.88222f,
+            7.84628f, 7.80562f, 7.76025f, 7.71021f, 7.65552f, 7.59623f, 7.53235f, 7.46394f,
+            7.39104f, 7.31368f, 7.23191f, 7.14579f, 7.05537f, 6.9607f, 6.86183f, 6.75883f,
+            6.65176f, 6.54068f, 6.42566f, 6.30677f, 6.18408f, 6.05767f, 5.92761f, 5.79398f,
+            5.65685f, 5.51632f, 5.37247f, 5.22538f, 5.07515f, 4.92185f, 4.76559f, 4.60647f,
+            4.44456f, 4.27998f, 4.11282f, 3.94319f, 3.77117f, 3.59689f, 3.42044f, 3.24193f,
+            3.06147f, 2.87916f, 2.69512f, 2.50945f, 2.32228f, 2.1337f, 1.94384f, 1.75281f,
+            1.56072f, 1.3677f, 1.17384f, 0.979285f, 0.784137f, 0.588517f, 0.392541f, 0.19633f,
+            9.79717e-16f, -0.19633f, -0.392541f, -0.588517f, -0.784137f, -0.979285f, -1.17384f, -1.3677f,
+            -1.56072f, -1.75281f, -1.94384f, -2.1337f, -2.32228f, -2.50945f, -2.69512f, -2.87916f,
+            -3.06147f, -3.24193f, -3.42044f, -3.59689f, -3.77117f, -3.94319f, -4.11282f, -4.27998f,
+            -4.44456f, -4.60647f, -4.76559f, -4.92185f, -5.07515f, -5.22538f, -5.37247f, -5.51632f,
+            -5.65685f, -5.79398f, -5.92761f, -6.05767f, -6.18408f, -6.30677f, -6.42566f, -6.54068f,
+            -6.65176f, -6.75883f, -6.86183f, -6.9607f, -7.05537f, -7.14579f, -7.23191f, -7.31368f,
+            -7.39104f, -7.46394f, -7.53235f, -7.59623f, -7.65552f, -7.71021f, -7.76025f, -7.80562f,
+            -7.84628f, -7.88222f, -7.91341f, -7.93984f, -7.96148f, -7.97832f, -7.99036f, -7.99759f,
+            -8f, -7.99759f, -7.99036f, -7.97832f, -7.96148f, -7.93984f, -7.91341f, -7.88222f,
+            -7.84628f, -7.80562f, -7.76025f, -7.71021f, -7.65552f, -7.59623f, -7.53235f, -7.46394f,
+            -7.39104f, -7.31368f, -7.23191f, -7.14579f, -7.05537f, -6.9607f, -6.86183f, -6.75883f,
+            -6.65176f, -6.54068f, -6.42566f, -6.30677f, -6.18408f, -6.05767f, -5.92761f, -5.79398f,
+            -5.65685f, -5.51632f, -5.37247f, -5.22538f, -5.07515f, -4.92185f, -4.76559f, -4.60647f,
+            -4.44456f, -4.27998f, -4.11282f, -3.94319f, -3.77117f, -3.59689f, -3.42044f, -3.24193f,
+            -3.06147f, -2.87916f, -2.69512f, -2.50945f, -2.32228f, -2.1337f, -1.94384f, -1.75281f,
+            -1.56072f, -1.3677f, -1.17384f, -0.979285f, -0.784137f, -0.588517f, -0.392541f, -0.19633f
+    };
+    private static final int SUBDIVIDE_SIZE = 64;
+    // =========================================================
+    private static final float TURBSCALE = (float) (256.0f / (2 * Math.PI));
+    private static final float ON_EPSILON = 0.1f; // point on plane side epsilon
+    private static final int MAX_CLIP_VERTS = 64;
+    private static final int SIDE_BACK = 1;
+    private static final int SIDE_FRONT = 0;
+    private static final int SIDE_ON = 2;
+    private final float[][] tmpVerts = new float[64][3];
+    // stack variable
+    private final float[] v = {0, 0, 0};
+    private final float[] av = {0, 0, 0};
+    // stack variable
+    private final float[] v1 = {0, 0, 0};
+    private final float[] b = {0, 0, 0};
+    private final float[] skyaxis = {0, 0, 0};
+    private final Image[] sky_images = new Image[6];
+    private final float[][] skyclip = {
+            {1, 1, 0},
+            {1, -1, 0},
+            {0, -1, 1},
+            {0, 1, 1},
+            {1, 0, 1},
+            {-1, 0, 1}
+    };
+    // 1 = s, 2 = t, 3 = 2048
+    private final int[][] st_to_vec =
+            {
+                    {3, -1, 2},
+                    {-3, 1, 2},
+
+                    {1, 3, 2},
+                    {-1, -3, 2},
+
+                    {-2, -1, 3},        // 0 degrees yaw, look straight up
+                    {2, -1, -3}        // look straight down
+
+            };
+    private final int[][] vec_to_st =
+            {
+                    {-2, 3, 1},
+                    {2, 3, -1},
+
+                    {1, 3, 2},
+                    {-1, 3, -2},
+
+                    {-2, -1, 3},
+                    {-2, 1, -3}
+
+            };
+    private final float[][] skymins = new float[2][6];
+    private final float[][] skymaxs = new float[2][6];
+    private final float[] dists = new float[MAX_CLIP_VERTS];
+    private final int[] sides = new int[MAX_CLIP_VERTS];
+    private final float[][][][] newv = new float[6][2][MAX_CLIP_VERTS][3];
+    private final float[][] verts = new float[MAX_CLIP_VERTS][3];
+    private final int[] skytexorder = {0, 2, 1, 3, 4, 5};
+    // 3dstudio environment map names
+    private final String[] suf = {"rt", "bk", "lf", "ft", "up", "dn"};
+    private float skyrotate;
+    private msurface_t warpface;
+    private int c_sky;
+    private float sky_min;
+    private float sky_max;
+
+    /**
+     * BoundPoly
+     *
+     * @param numverts
+     * @param verts
+     * @param mins
+     * @param maxs
+     */
+    private void BoundPoly(int numverts, float[][] verts, float[] mins, float[] maxs) {
+        mins[0] = mins[1] = mins[2] = 9999;
+        maxs[0] = maxs[1] = maxs[2] = -9999;
+
+        int j;
+        float[] v;
+        for (int i = 0; i < numverts; i++) {
+            v = verts[i];
+            for (j = 0; j < 3; j++) {
+                if (v[j] < mins[j])
+                    mins[j] = v[j];
+                if (v[j] > maxs[j])
+                    maxs[j] = v[j];
+            }
+        }
+    }
+
+    /**
+     * SubdividePolygon
+     *
+     * @param numverts
+     * @param verts
+     */
+    private void SubdividePolygon(int numverts, float[][] verts) {
+        int i, j, k;
+        float m;
+        float[][] front = new float[64][3];
+        float[][] back = new float[64][3];
+
+        int f, b;
+        float[] dist = new float[64];
+        float frac;
+
+        if (numverts > 60)
+            Com.Error(Defines.ERR_DROP, "numverts = " + numverts);
+
+        float[] mins = Vec3Cache.get();
+        float[] maxs = Vec3Cache.get();
+
+        BoundPoly(numverts, verts, mins, maxs);
+        float[] v;
+        // x,y und z
+        for (i = 0; i < 3; i++) {
+            m = (mins[i] + maxs[i]) * 0.5f;
+            m = SUBDIVIDE_SIZE * (float) Math.floor(m / SUBDIVIDE_SIZE + 0.5f);
+            if (maxs[i] - m < 8)
+                continue;
+            if (m - mins[i] < 8)
+                continue;
+
+            // cut it
+            for (j = 0; j < numverts; j++) {
+                dist[j] = verts[j][i] - m;
+            }
+
+            // wrap cases
+            dist[j] = dist[0];
+
+            Math3D.vectorCopy(verts[0], verts[numverts]);
+
+            f = b = 0;
+            for (j = 0; j < numverts; j++) {
+                v = verts[j];
+                if (dist[j] >= 0) {
+                    Math3D.vectorCopy(v, front[f]);
+                    f++;
+                }
+                if (dist[j] <= 0) {
+                    Math3D.vectorCopy(v, back[b]);
+                    b++;
+                }
+                if (dist[j] == 0 || dist[j + 1] == 0) continue;
+
+                if ((dist[j] > 0) != (dist[j + 1] > 0)) {
+                    // clip point
+                    frac = dist[j] / (dist[j] - dist[j + 1]);
+                    for (k = 0; k < 3; k++)
+                        front[f][k] = back[b][k] = v[k] + frac * (verts[j + 1][k] - v[k]);
+
+                    f++;
+                    b++;
+                }
+            }
+
+            SubdividePolygon(f, front);
+            SubdividePolygon(b, back);
+
+            Vec3Cache.release(2); // mins, maxs
+            return;
+        }
+
+        Vec3Cache.release(2); // mins, maxs
+
+        // add a point in the center to help keep warp valid
+
+        // wird im Konstruktor erschlagen
+        // poly = Hunk_Alloc (sizeof(glpoly_t) + ((numverts-4)+2) * VERTEXSIZE*sizeof(float));
+
+        // init polys
+        glpoly_t poly = Polygon.create(numverts + 2);
+
+        poly.next = warpface.polys;
+        warpface.polys = poly;
+
+        float[] total = Vec3Cache.get();
+        Math3D.vectorClear(total);
+        float total_s = 0;
+        float total_t = 0;
+        float s, t;
+        for (i = 0; i < numverts; i++) {
+            poly.x(i + 1, verts[i][0]);
+            poly.y(i + 1, verts[i][1]);
+            poly.z(i + 1, verts[i][2]);
+            s = Math3D.dotProduct(verts[i], warpface.texinfo.vecs[0]);
+            t = Math3D.dotProduct(verts[i], warpface.texinfo.vecs[1]);
+
+            total_s += s;
+            total_t += t;
+            Math3D.vectorAdd(total, verts[i], total);
+
+            poly.s1(i + 1, s);
+            poly.t1(i + 1, t);
+        }
+
+        float scale = 1.0f / numverts;
+        poly.x(0, total[0] * scale);
+        poly.y(0, total[1] * scale);
+        poly.z(0, total[2] * scale);
+        poly.s1(0, total_s * scale);
+        poly.t1(0, total_t * scale);
+
+        poly.x(i + 1, poly.x(1));
+        poly.y(i + 1, poly.y(1));
+        poly.z(i + 1, poly.z(1));
+        poly.s1(i + 1, poly.s1(1));
+        poly.t1(i + 1, poly.t1(1));
+        poly.s2(i + 1, poly.s2(1));
+        poly.t2(i + 1, poly.t2(1));
+
+        Vec3Cache.release(); // total
+    }
+
+    /**
+     * GL_SubdivideSurface
+     * Breaks a polygon up along axial 64 unit
+     * boundaries so that turbulent and sky warps
+     * can be done reasonably.
+     */
+    void GL_SubdivideSurface(msurface_t fa) {
+        float[][] verts = tmpVerts;
+        float[] vec;
+        warpface = fa;
+        //
+        // convert edges back to a normal polygon
+        //
+        int numverts = 0;
+        for (int i = 0; i < fa.numedges; i++) {
+            int lindex = loadmodel.surfedges[fa.firstedge + i];
+
+            if (lindex > 0)
+                vec = loadmodel.vertexes[loadmodel.edges[lindex].v[0]].position;
+            else
+                vec = loadmodel.vertexes[loadmodel.edges[-lindex].v[1]].position;
+            Math3D.vectorCopy(vec, verts[numverts]);
+            numverts++;
+        }
+        SubdividePolygon(numverts, verts);
+    }
+
+    /**
+     * EmitWaterPolys
+     * Does a water warp on the pre-fragmented glpoly_t chain
+     */
+    void EmitWaterPolys(msurface_t fa) {
+        float rdt = r_newrefdef.time;
+
+        float scroll;
+        if ((fa.texinfo.flags & Defines.SURF_FLOWING) != 0)
+            scroll = -64 * ((r_newrefdef.time * 0.5f) - (int) (r_newrefdef.time * 0.5f));
+        else
+            scroll = 0;
+
+        int i;
+        float s, t, os, ot;
+        glpoly_t p, bp;
+        for (bp = fa.polys; bp != null; bp = bp.next) {
+            p = bp;
+
+            GL11.glBegin(GL11.GL_TRIANGLE_FAN);
+            for (i = 0; i < p.numverts; i++) {
+                os = p.s1(i);
+                ot = p.t1(i);
+
+                s = os
+                        + Warp.SIN[(int) ((ot * 0.125f + r_newrefdef.time) * TURBSCALE) & 255];
+                s += scroll;
+                s *= (1.0f / 64);
+
+                t = ot
+                        + Warp.SIN[(int) ((os * 0.125f + rdt) * TURBSCALE) & 255];
+                t *= (1.0f / 64);
+
+                GL11.glTexCoord2f(s, t);
+                GL11.glVertex3f(p.x(i), p.y(i), p.z(i));
+            }
+            GL11.glEnd();
+        }
+    }
+
+    /**
+     * DrawSkyPolygon
+     *
+     * @param nump
+     * @param vecs
+     */
+    private void DrawSkyPolygon(int nump, float[][] vecs) {
+        c_sky++;
+        // decide which face it maps to
+        Math3D.vectorCopy(Globals.vec3_origin, v);
+        int i, axis;
+        for (i = 0; i < nump; i++) {
+            Math3D.vectorAdd(vecs[i], v, v);
+        }
+        av[0] = Math.abs(v[0]);
+        av[1] = Math.abs(v[1]);
+        av[2] = Math.abs(v[2]);
+        if (av[0] > av[1] && av[0] > av[2]) {
+            if (v[0] < 0)
+                axis = 1;
+            else
+                axis = 0;
+        } else if (av[1] > av[2] && av[1] > av[0]) {
+            if (v[1] < 0)
+                axis = 3;
+            else
+                axis = 2;
+        } else {
+            if (v[2] < 0)
+                axis = 5;
+            else
+                axis = 4;
+        }
+
+        // project new texture coords
+        float s, t, dv;
+        int j;
+        for (i = 0; i < nump; i++) {
+            j = vec_to_st[axis][2];
+            if (j > 0)
+                dv = vecs[i][j - 1];
+            else
+                dv = -vecs[i][-j - 1];
+            if (dv < 0.001f)
+                continue;    // don't divide by zero
+            j = vec_to_st[axis][0];
+            if (j < 0)
+                s = -vecs[i][-j - 1] / dv;
+            else
+                s = vecs[i][j - 1] / dv;
+            j = vec_to_st[axis][1];
+            if (j < 0)
+                t = -vecs[i][-j - 1] / dv;
+            else
+                t = vecs[i][j - 1] / dv;
+
+            if (s < skymins[0][axis])
+                skymins[0][axis] = s;
+            if (t < skymins[1][axis])
+                skymins[1][axis] = t;
+            if (s > skymaxs[0][axis])
+                skymaxs[0][axis] = s;
+            if (t > skymaxs[1][axis])
+                skymaxs[1][axis] = t;
+        }
+    }
+
+    /**
+     * ClipSkyPolygon
+     *
+     * @param nump
+     * @param vecs
+     * @param stage
+     */
+    private void ClipSkyPolygon(int nump, float[][] vecs, int stage) {
+        if (nump > MAX_CLIP_VERTS - 2)
+            Com.Error(Defines.ERR_DROP, "ClipSkyPolygon: MAX_CLIP_VERTS");
+        if (stage == 6) {    // fully clipped, so draw it
+            DrawSkyPolygon(nump, vecs);
+            return;
+        }
+
+        boolean front = false;
+        boolean back = false;
+        float[] norm = skyclip[stage];
+
+        int i;
+        float d;
+        for (i = 0; i < nump; i++) {
+            d = Math3D.dotProduct(vecs[i], norm);
+            if (d > ON_EPSILON) {
+                front = true;
+                sides[i] = SIDE_FRONT;
+            } else if (d < -ON_EPSILON) {
+                back = true;
+                sides[i] = SIDE_BACK;
+            } else
+                sides[i] = SIDE_ON;
+            dists[i] = d;
+        }
+
+        if (!front || !back) {    // not clipped
+            ClipSkyPolygon(nump, vecs, stage + 1);
+            return;
+        }
+
+        // clip it
+        sides[i] = sides[0];
+        dists[i] = dists[0];
+        Math3D.vectorCopy(vecs[0], vecs[i]);
+
+        int newc0 = 0;
+        int newc1 = 0;
+        float[] v;
+        float e;
+        int j;
+        for (i = 0; i < nump; i++) {
+            v = vecs[i];
+            switch (sides[i]) {
+                case SIDE_FRONT:
+                    Math3D.vectorCopy(v, newv[stage][0][newc0]);
+                    newc0++;
+                    break;
+                case SIDE_BACK:
+                    Math3D.vectorCopy(v, newv[stage][1][newc1]);
+                    newc1++;
+                    break;
+                case SIDE_ON:
+                    Math3D.vectorCopy(v, newv[stage][0][newc0]);
+                    newc0++;
+                    Math3D.vectorCopy(v, newv[stage][1][newc1]);
+                    newc1++;
+                    break;
+            }
+
+            if (sides[i] == SIDE_ON || sides[i + 1] == SIDE_ON || sides[i + 1] == sides[i])
+                continue;
+
+            d = dists[i] / (dists[i] - dists[i + 1]);
+            for (j = 0; j < 3; j++) {
+                e = v[j] + d * (vecs[i + 1][j] - v[j]);
+                newv[stage][0][newc0][j] = e;
+                newv[stage][1][newc1][j] = e;
+            }
+            newc0++;
+            newc1++;
+        }
+
+        // continue
+        ClipSkyPolygon(newc0, newv[stage][0], stage + 1);
+        ClipSkyPolygon(newc1, newv[stage][1], stage + 1);
+    }
+
+    /**
+     * R_AddSkySurface
+     */
+    void R_AddSkySurface(msurface_t fa) {
+        // calculate vertex values for sky box
+        for (glpoly_t p = fa.polys; p != null; p = p.next) {
+            for (int i = 0; i < p.numverts; i++) {
+                verts[i][0] = p.x(i) - r_origin[0];
+                verts[i][1] = p.y(i) - r_origin[1];
+                verts[i][2] = p.z(i) - r_origin[2];
+            }
+            ClipSkyPolygon(p.numverts, verts, 0);
+        }
+    }
+
+    /**
+     * R_ClearSkyBox
+     */
+    void R_ClearSkyBox() {
+        float[] skymins0 = skymins[0];
+        float[] skymins1 = skymins[1];
+        float[] skymaxs0 = skymaxs[0];
+        float[] skymaxs1 = skymaxs[1];
+
+        for (int i = 0; i < 6; i++) {
+            skymins0[i] = skymins1[i] = 9999;
+            skymaxs0[i] = skymaxs1[i] = -9999;
+        }
+    }
+
+    /**
+     * MakeSkyVec
+     *
+     * @param s
+     * @param t
+     * @param axis
+     */
+    private void MakeSkyVec(float s, float t, int axis) {
+        b[0] = s * 2300;
+        b[1] = t * 2300;
+        b[2] = 2300;
+
+        int j, k;
+        for (j = 0; j < 3; j++) {
+            k = st_to_vec[axis][j];
+            if (k < 0)
+                v1[j] = -b[-k - 1];
+            else
+                v1[j] = b[k - 1];
+        }
+
+        // avoid bilerp seam
+        s = (s + 1) * 0.5f;
+        t = (t + 1) * 0.5f;
+
+        if (s < sky_min)
+            s = sky_min;
+        else if (s > sky_max)
+            s = sky_max;
+        if (t < sky_min)
+            t = sky_min;
+        else if (t > sky_max)
+            t = sky_max;
+
+        t = 1.0f - t;
+        GL11.glTexCoord2f(s, t);
+        GL11.glVertex3f(v1[0], v1[1], v1[2]);
+    }
+
+    /**
+     * R_DrawSkyBox
+     */
+    void R_DrawSkyBox() {
+        int i;
+
+        if (skyrotate != 0) {    // check for no sky at all
+            for (i = 0; i < 6; i++)
+                if (skymins[0][i] < skymaxs[0][i]
+                        && skymins[1][i] < skymaxs[1][i])
+                    break;
+            if (i == 6)
+                return;        // nothing visible
+        }
+
+        GL11.glPushMatrix();
+        GL11.glTranslatef(r_origin[0], r_origin[1], r_origin[2]);
+        GL11.glRotatef(r_newrefdef.time * skyrotate, skyaxis[0], skyaxis[1], skyaxis[2]);
+
+        for (i = 0; i < 6; i++) {
+            if (skyrotate != 0) {    // hack, forces full sky to draw when rotating
+                skymins[0][i] = -1;
+                skymins[1][i] = -1;
+                skymaxs[0][i] = 1;
+                skymaxs[1][i] = 1;
+            }
+
+            if (skymins[0][i] >= skymaxs[0][i]
+                    || skymins[1][i] >= skymaxs[1][i])
+                continue;
+
+            GL_Bind(sky_images[skytexorder[i]].texnum);
+
+            GL11.glBegin(GL11.GL_QUADS);
+            MakeSkyVec(skymins[0][i], skymins[1][i], i);
+            MakeSkyVec(skymins[0][i], skymaxs[1][i], i);
+            MakeSkyVec(skymaxs[0][i], skymaxs[1][i], i);
+            MakeSkyVec(skymaxs[0][i], skymins[1][i], i);
+            GL11.glEnd();
+        }
+        GL11.glPopMatrix();
+    }
+
+    /**
+     * R_SetSky
+     *
+     * @param name
+     * @param rotate
+     * @param axis
+     */
+    protected void R_SetSky(String name, float rotate, float[] axis) {
+        assert (axis.length == 3) : "vec3_t bug";
+        String pathname;
+
+        skyrotate = rotate;
+        Math3D.vectorCopy(axis, skyaxis);
+
+        for (int i = 0; i < 6; i++) {
+            // chop down rotating skies for less memory
+            if (gl_skymip.value != 0 || skyrotate != 0)
+                gl_picmip.value++;
+
+            if (qglColorTableEXT && gl_ext_palettedtexture.value != 0) {
+                //     Com_sprintf (pathname, sizeof(pathname), "env/%s%s.pcx", skyname, suf[i]);
+                pathname = "env/" + name + suf[i] + ".pcx";
+            } else {
+                // Com_sprintf (pathname, sizeof(pathname), "env/%s%s.tga", skyname, suf[i]);
+                pathname = "env/" + name + suf[i] + ".tga";
+            }
+
+            sky_images[i] = GL_FindImage(pathname, it_sky);
+
+            if (sky_images[i] == null)
+                sky_images[i] = r_notexture;
+
+            if (gl_skymip.value != 0 || skyrotate != 0) {    // take less memory
+                gl_picmip.value--;
+                sky_min = 1.0f / 256;
+                sky_max = 255.0f / 256;
+            } else {
+                sky_min = 1.0f / 512;
+                sky_max = 511.0f / 512;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/render/medge_t.java b/src/main/java/lwjake2/render/medge_t.java
new file mode 100644 (file)
index 0000000..9b88070
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render;
+
+import lwjake2.Defines;
+
+import java.nio.ByteBuffer;
+
+public class medge_t {
+
+    public static final int DISK_SIZE = 2 * Defines.SIZE_OF_SHORT;
+
+    public static final int MEM_SIZE = 3 * Defines.SIZE_OF_INT;
+
+    // unsigned short
+    public final int[] v = new int[2];
+
+    public int cachededgeoffset;
+
+    public medge_t(ByteBuffer b) {
+        v[0] = b.getShort() & 0xFFFF;
+        v[1] = b.getShort() & 0xFFFF;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/render/mleaf_t.java b/src/main/java/lwjake2/render/mleaf_t.java
new file mode 100644 (file)
index 0000000..ca77edf
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render;
+
+public class mleaf_t extends mnode_t {
+
+    // common with node
+    /*
+    public int contents; // wil be a negative contents number
+       public int visframe; // node needs to be traversed if current
+
+       public float minmaxs[] = new float[6]; // for bounding box culling
+
+       public mnode_t parent;
+       */
+
+    // leaf specific
+    public int cluster;
+    public int area;
+
+    //public msurface_t firstmarksurface;
+    public int nummarksurfaces;
+
+    // added by cwei
+    int markIndex;
+    msurface_t[] markSurfaces;
+
+    public void setMarkSurface(int markIndex, msurface_t[] markSurfaces) {
+        this.markIndex = markIndex;
+        this.markSurfaces = markSurfaces;
+    }
+
+    public msurface_t getMarkSurface(int index) {
+        assert (index >= 0 && index <= nummarksurfaces) : "mleaf: markSurface bug (index = " + index + "; num = " + nummarksurfaces + ")";
+        // TODO code in Surf.R_RecursiveWorldNode aendern (der Pointer wird wie in C zu weit gezaehlt)
+        return (index < nummarksurfaces) ? markSurfaces[markIndex + index] : null;
+    }
+
+}
diff --git a/src/main/java/lwjake2/render/mmodel_t.java b/src/main/java/lwjake2/render/mmodel_t.java
new file mode 100644 (file)
index 0000000..f046baa
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render;
+
+public class mmodel_t {
+    public final float[] mins = {0, 0, 0};
+    public final float[] maxs = {0, 0, 0};
+    public final float[] origin = {0, 0, 0}; // for sounds or lights
+    public float radius;
+    public int headnode;
+    public int visleafs; // not including the solid leaf 0
+    public int firstface, numfaces;
+}
diff --git a/src/main/java/lwjake2/render/mnode_t.java b/src/main/java/lwjake2/render/mnode_t.java
new file mode 100644 (file)
index 0000000..207724b
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render;
+
+import lwjake2.game.cplane_t;
+
+public class mnode_t {
+    //public float minmaxs[] = new float[6]; // for bounding box culling
+    public final float[] mins = new float[3]; // for bounding box culling
+    public final float[] maxs = new float[3]; // for bounding box culling
+    public final mnode_t[] children = new mnode_t[2];
+    // common with leaf
+    public int contents; // -1, to differentiate from leafs
+    public int visframe; // node needs to be traversed if current
+    public mnode_t parent;
+    // node specific
+    public cplane_t plane;
+    // unsigned short
+    public int firstsurface;
+    public int numsurfaces;
+
+}
diff --git a/src/main/java/lwjake2/render/msurface_t.java b/src/main/java/lwjake2/render/msurface_t.java
new file mode 100644 (file)
index 0000000..145763f
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render;
+
+import lwjake2.Defines;
+import lwjake2.game.cplane_t;
+
+import java.nio.ByteBuffer;
+
+public class msurface_t {
+
+    public final short[] texturemins = {0, 0};
+    public final short[] extents = {0, 0};
+    public final byte[] styles = new byte[Defines.MAXLIGHTMAPS];
+    public final float[] cached_light = new float[Defines.MAXLIGHTMAPS];
+    public int visframe; // should be drawn when node is crossed
+    public cplane_t plane;
+    public int flags;
+    public int firstedge; // look up in model->surfedges[], negative numbers
+    public int numedges; // are backwards edges
+    // gl lightmap coordinates for dynamic lightmaps
+    public int light_s, light_t; // gl lightmap coordinates
+    public int dlight_s, dlight_t;
+    public glpoly_t polys; // multiple if warped
+    public msurface_t texturechain;
+    public msurface_t lightmapchain;
+    // TODO check this
+    public mtexinfo_t texinfo = new mtexinfo_t();
+    // lighting info
+    public int dlightframe;
+    public int dlightbits;
+    public int lightmaptexturenum;
+    // values currently used in lightmap
+    //public byte samples[]; // [numstyles*surfsize]
+    public ByteBuffer samples; // [numstyles*surfsize]
+
+    public void clear() {
+        visframe = 0;
+        plane.clear();
+        flags = 0;
+
+        firstedge = 0;
+        numedges = 0;
+
+        texturemins[0] = texturemins[1] = -1;
+        extents[0] = extents[1] = 0;
+
+        light_s = light_t = 0;
+        dlight_s = dlight_t = 0;
+
+        polys = null;
+        texturechain = null;
+        lightmapchain = null;
+
+        //texinfo = new mtexinfo_t();
+        texinfo.clear();
+
+        dlightframe = 0;
+        dlightbits = 0;
+
+        lightmaptexturenum = 0;
+
+        for (int i = 0; i < styles.length; i++) {
+            styles[i] = 0;
+        }
+        for (int i = 0; i < cached_light.length; i++) {
+            cached_light[i] = 0;
+        }
+        if (samples != null) samples.clear();
+    }
+}
diff --git a/src/main/java/lwjake2/render/mtexinfo_t.java b/src/main/java/lwjake2/render/mtexinfo_t.java
new file mode 100644 (file)
index 0000000..047e0cf
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render;
+
+import java.util.Arrays;
+
+public class mtexinfo_t {
+    // [s/t][xyz offset]
+    public float vecs[][] = {
+            {0, 0, 0, 0},
+            {0, 0, 0, 0}
+    };
+    public int flags;
+    public int numframes;
+    public mtexinfo_t next; // animation chain
+    public Image image;
+
+    public void clear() {
+        Arrays.fill(vecs[0], 0);
+        Arrays.fill(vecs[1], 0);
+
+        flags = 0;
+        numframes = 0;
+        next = null;
+        image = null;
+    }
+
+}
diff --git a/src/main/java/lwjake2/render/mvertex_t.java b/src/main/java/lwjake2/render/mvertex_t.java
new file mode 100644 (file)
index 0000000..a975513
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.render;
+
+import lwjake2.Defines;
+
+import java.nio.ByteBuffer;
+
+public class mvertex_t {
+    public static final int DISK_SIZE = 3 * Defines.SIZE_OF_FLOAT;
+
+    public static final int MEM_SIZE = 3 * Defines.SIZE_OF_FLOAT;
+
+    public final float[] position = {0, 0, 0};
+
+    public mvertex_t(ByteBuffer b) {
+        position[0] = b.getFloat();
+        position[1] = b.getFloat();
+        position[2] = b.getFloat();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/server/SV.java b/src/main/java/lwjake2/server/SV.java
new file mode 100644 (file)
index 0000000..4d10d09
--- /dev/null
@@ -0,0 +1,780 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.server;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.client.M;
+import lwjake2.game.EDict;
+import lwjake2.game.GameBase;
+import lwjake2.game.pushed_t;
+import lwjake2.game.trace_t;
+import lwjake2.qcommon.Com;
+import lwjake2.util.Math3D;
+
+/**
+ * SV
+ */
+public final class SV {
+
+    /**
+     * SV_FlyMove
+     * <p>
+     * The basic solid body movement clip that slides along multiple planes
+     * Returns the clipflags if the velocity was modified (hit something solid)
+     * 1 = floor 2 = wall / step 4 = dead stop
+     */
+    public final static int MAX_CLIP_PLANES = 5;
+
+    ///////////////////////////////////////
+    private static EDict[] SV_TestEntityPosition(EDict ent) {
+        trace_t trace;
+        int mask;
+
+        if (ent.clipmask != 0)
+            mask = ent.clipmask;
+        else
+            mask = Defines.MASK_SOLID;
+
+        trace = GameBase.gi.trace(ent.s.origin, ent.mins, ent.maxs,
+                ent.s.origin, ent, mask);
+
+        if (trace.startsolid)
+            return GameBase.g_edicts;
+
+        return null;
+    }
+
+    ///////////////////////////////////////
+    private static void SV_CheckVelocity(EDict ent) {
+        int i;
+
+        //
+        //        bound velocity
+        //
+        for (i = 0; i < 3; i++) {
+            if (ent.velocity[i] > GameBase.sv_maxvelocity.value)
+                ent.velocity[i] = GameBase.sv_maxvelocity.value;
+            else if (ent.velocity[i] < -GameBase.sv_maxvelocity.value)
+                ent.velocity[i] = -GameBase.sv_maxvelocity.value;
+        }
+    }
+
+    /**
+     * Runs thinking code for this frame if necessary.
+     */
+    private static boolean SV_RunThink(EDict ent) {
+        float thinktime;
+
+        thinktime = ent.nextthink;
+        if (thinktime <= 0)
+            return true;
+        if (thinktime > GameBase.level.time + 0.001)
+            return true;
+
+        ent.nextthink = 0;
+
+        if (ent.think == null)
+            Com.Error(Defines.ERR_FATAL, "NULL ent.think");
+
+        ent.think.think(ent);
+
+        return false;
+    }
+
+    /**
+     * Two entities have touched, so run their touch functions.
+     */
+    private static void SV_Impact(EDict e1, trace_t trace) {
+        EDict e2;
+
+        e2 = trace.ent;
+
+        if (e1.touch != null && e1.solid != Defines.SOLID_NOT)
+            e1.touch.touch(e1, e2, trace.plane, trace.surface);
+
+        if (e2.touch != null && e2.solid != Defines.SOLID_NOT)
+            e2.touch.touch(e2, e1, GameBase.dummyplane, null);
+    }
+
+    private static void SV_FlyMove(EDict ent, int mask) {
+        EDict hit;
+        int bumpcount, numbumps;
+        float[] dir = {0.0f, 0.0f, 0.0f};
+        float d;
+        int numplanes;
+        float[][] planes = new float[MAX_CLIP_PLANES][3];
+        float[] primal_velocity = {0.0f, 0.0f, 0.0f};
+        float[] original_velocity = {0.0f, 0.0f, 0.0f};
+        float[] new_velocity = {0.0f, 0.0f, 0.0f};
+        int i, j;
+        trace_t trace;
+        float[] end = {0.0f, 0.0f, 0.0f};
+        float time_left;
+        int blocked;
+
+        numbumps = 4;
+
+        blocked = 0;
+        Math3D.vectorCopy(ent.velocity, original_velocity);
+        Math3D.vectorCopy(ent.velocity, primal_velocity);
+        numplanes = 0;
+
+        time_left = Defines.FRAMETIME;
+
+        ent.groundentity = null;
+        for (bumpcount = 0; bumpcount < numbumps; bumpcount++) {
+            for (i = 0; i < 3; i++)
+                end[i] = ent.s.origin[i] + time_left * ent.velocity[i];
+
+            trace = GameBase.gi.trace(ent.s.origin, ent.mins, ent.maxs, end,
+                    ent, mask);
+
+            if (trace.allsolid) { // entity is trapped in another solid
+                Math3D.vectorCopy(Globals.vec3_origin, ent.velocity);
+                return;
+            }
+
+            if (trace.fraction > 0) { // actually covered some distance
+                Math3D.vectorCopy(trace.endpos, ent.s.origin);
+                Math3D.vectorCopy(ent.velocity, original_velocity);
+                numplanes = 0;
+            }
+
+            if (trace.fraction == 1)
+                break; // moved the entire distance
+
+            hit = trace.ent;
+
+            if (trace.plane.normal[2] > 0.7) {
+                blocked |= 1; // floor
+                if (hit.solid == Defines.SOLID_BSP) {
+                    ent.groundentity = hit;
+                    ent.groundentity_linkcount = hit.linkcount;
+                }
+            }
+            if (trace.plane.normal[2] == 0.0f) {
+                blocked |= 2; // step
+            }
+
+            //
+            //    run the impact function
+            //
+            SV_Impact(ent, trace);
+            if (!ent.inuse)
+                break; // removed by the impact function
+
+            time_left -= time_left * trace.fraction;
+
+            // cliped to another plane
+            if (numplanes >= MAX_CLIP_PLANES) { // this shouldn't
+                // really happen
+                Math3D.vectorCopy(Globals.vec3_origin, ent.velocity);
+                return;
+            }
+
+            Math3D.vectorCopy(trace.plane.normal, planes[numplanes]);
+            numplanes++;
+
+            //
+            //    modify original_velocity so it parallels all of the clip planes
+            //
+            for (i = 0; i < numplanes; i++) {
+                GameBase.ClipVelocity(original_velocity, planes[i],
+                        new_velocity, 1);
+
+                for (j = 0; j < numplanes; j++)
+                    if ((j != i)
+                            && !Math3D.vectorEquals(planes[i], planes[j])) {
+                        if (Math3D.dotProduct(new_velocity, planes[j]) < 0)
+                            break; // not ok
+                    }
+                if (j == numplanes)
+                    break;
+            }
+
+            if (i != numplanes) { // go along this plane
+                Math3D.vectorCopy(new_velocity, ent.velocity);
+            } else { // go along the crease
+                if (numplanes != 2) {
+                    //                                 gi.dprintf ("clip velocity, numplanes ==
+                    // %i\n",numplanes);
+                    Math3D.vectorCopy(Globals.vec3_origin, ent.velocity);
+                    return;
+                }
+                Math3D.crossProduct(planes[0], planes[1], dir);
+                d = Math3D.dotProduct(dir, ent.velocity);
+                Math3D.vectorScale(dir, d, ent.velocity);
+            }
+
+            //
+            //    if original velocity is against the original velocity, stop dead
+            //    to avoid tiny occilations in sloping corners
+            //
+            if (Math3D.dotProduct(ent.velocity, primal_velocity) <= 0) {
+                Math3D.vectorCopy(Globals.vec3_origin, ent.velocity);
+                return;
+            }
+        }
+
+    }
+
+    /**
+     * SV_AddGravity.
+     */
+    private static void SV_AddGravity(EDict ent) {
+        ent.velocity[2] -= ent.gravity * GameBase.sv_gravity.value
+                * Defines.FRAMETIME;
+    }
+
+    /**
+     * Does not change the entities velocity at all
+     */
+    private static trace_t SV_PushEntity(EDict ent, float[] push) {
+        trace_t trace;
+        float[] start = {0, 0, 0};
+        float[] end = {0, 0, 0};
+        int mask;
+
+        Math3D.vectorCopy(ent.s.origin, start);
+        Math3D.vectorAdd(start, push, end);
+
+        // FIXME: test this
+        // a goto statement was replaced.
+        boolean retry = false;
+
+        do {
+            if (ent.clipmask != 0)
+                mask = ent.clipmask;
+            else
+                mask = Defines.MASK_SOLID;
+
+            trace = GameBase.gi
+                    .trace(start, ent.mins, ent.maxs, end, ent, mask);
+
+            Math3D.vectorCopy(trace.endpos, ent.s.origin);
+            GameBase.gi.linkentity(ent);
+
+            retry = false;
+            if (trace.fraction != 1.0) {
+                SV_Impact(ent, trace);
+
+                // if the pushed entity went away and the pusher is still there
+                if (!trace.ent.inuse && ent.inuse) {
+                    // move the pusher back and try again
+                    Math3D.vectorCopy(start, ent.s.origin);
+                    GameBase.gi.linkentity(ent);
+                    //goto retry;
+                    retry = true;
+                }
+            }
+        } while (retry);
+
+        if (ent.inuse)
+            GameBase.G_TouchTriggers(ent);
+
+        return trace;
+    }
+
+    /**
+     * Objects need to be moved back on a failed push, otherwise riders would
+     * continue to slide.
+     */
+    private static boolean SV_Push(EDict pusher, float[] move, float[] amove) {
+        int i, e;
+        EDict check, block[];
+        float[] mins = {0, 0, 0};
+        float[] maxs = {0, 0, 0};
+        pushed_t p;
+        float[] org = {0, 0, 0};
+        float[] org2 = {0, 0, 0};
+        float[] move2 = {0, 0, 0};
+        float[] forward = {0, 0, 0};
+        float[] right = {0, 0, 0};
+        float[] up = {0, 0, 0};
+
+        // clamp the move to 1/8 units, so the position will
+        // be accurate for client side prediction
+        for (i = 0; i < 3; i++) {
+            float temp;
+            temp = move[i] * 8.0f;
+            if (temp > 0.0)
+                temp += 0.5;
+            else
+                temp -= 0.5;
+            move[i] = 0.125f * (int) temp;
+        }
+
+        // find the bounding box
+        for (i = 0; i < 3; i++) {
+            mins[i] = pusher.absmin[i] + move[i];
+            maxs[i] = pusher.absmax[i] + move[i];
+        }
+
+        //        we need this for pushing things later
+        Math3D.vectorSubtract(Globals.vec3_origin, amove, org);
+        Math3D.angleVectors(org, forward, right, up);
+
+        //        save the pusher's original position
+        GameBase.pushed[GameBase.pushed_p].ent = pusher;
+        Math3D.vectorCopy(pusher.s.origin,
+                GameBase.pushed[GameBase.pushed_p].origin);
+        Math3D.vectorCopy(pusher.s.angles,
+                GameBase.pushed[GameBase.pushed_p].angles);
+
+        if (pusher.client != null)
+            GameBase.pushed[GameBase.pushed_p].deltayaw = pusher.client.ps.pmove.delta_angles[Defines.YAW];
+
+        GameBase.pushed_p++;
+
+        //        move the pusher to it's final position
+        Math3D.vectorAdd(pusher.s.origin, move, pusher.s.origin);
+        Math3D.vectorAdd(pusher.s.angles, amove, pusher.s.angles);
+        GameBase.gi.linkentity(pusher);
+
+        //        see if any solid entities are inside the final position
+
+        //check= g_edicts + 1;
+        for (e = 1; e < GameBase.num_edicts; e++) {
+            check = GameBase.g_edicts[e];
+            if (!check.inuse)
+                continue;
+            if (check.movetype == Defines.MOVETYPE_PUSH
+                    || check.movetype == Defines.MOVETYPE_STOP
+                    || check.movetype == Defines.MOVETYPE_NONE
+                    || check.movetype == Defines.MOVETYPE_NOCLIP)
+                continue;
+
+            if (check.area.prev == null)
+                continue; // not linked in anywhere
+
+            // if the entity is standing on the pusher, it will definitely be
+            // moved
+            if (check.groundentity != pusher) {
+                // see if the ent needs to be tested
+                if (check.absmin[0] >= maxs[0] || check.absmin[1] >= maxs[1]
+                        || check.absmin[2] >= maxs[2]
+                        || check.absmax[0] <= mins[0]
+                        || check.absmax[1] <= mins[1]
+                        || check.absmax[2] <= mins[2])
+                    continue;
+
+                // see if the ent's bbox is inside the pusher's final position
+                if (SV_TestEntityPosition(check) == null)
+                    continue;
+            }
+
+            if ((pusher.movetype == Defines.MOVETYPE_PUSH)
+                    || (check.groundentity == pusher)) {
+                // move this entity
+                GameBase.pushed[GameBase.pushed_p].ent = check;
+                Math3D.vectorCopy(check.s.origin,
+                        GameBase.pushed[GameBase.pushed_p].origin);
+                Math3D.vectorCopy(check.s.angles,
+                        GameBase.pushed[GameBase.pushed_p].angles);
+                GameBase.pushed_p++;
+
+                // try moving the contacted entity
+                Math3D.vectorAdd(check.s.origin, move, check.s.origin);
+                if (check.client != null) { // FIXME: doesn't rotate monsters?
+                    check.client.ps.pmove.delta_angles[Defines.YAW] += amove[Defines.YAW];
+                }
+
+                // figure movement due to the pusher's amove
+                Math3D.vectorSubtract(check.s.origin, pusher.s.origin, org);
+                org2[0] = Math3D.dotProduct(org, forward);
+                org2[1] = -Math3D.dotProduct(org, right);
+                org2[2] = Math3D.dotProduct(org, up);
+                Math3D.vectorSubtract(org2, org, move2);
+                Math3D.vectorAdd(check.s.origin, move2, check.s.origin);
+
+                // may have pushed them off an edge
+                if (check.groundentity != pusher)
+                    check.groundentity = null;
+
+                block = SV_TestEntityPosition(check);
+                if (block == null) { // pushed ok
+                    GameBase.gi.linkentity(check);
+                    // impact?
+                    continue;
+                }
+
+                // if it is ok to leave in the old position, do it
+                // this is only relevent for riding entities, not pushed
+                // FIXME: this doesn't acount for rotation
+                Math3D.vectorSubtract(check.s.origin, move, check.s.origin);
+                block = SV_TestEntityPosition(check);
+
+                if (block == null) {
+                    GameBase.pushed_p--;
+                    continue;
+                }
+            }
+
+            // save off the obstacle so we can call the block function
+            GameBase.obstacle = check;
+
+            // move back any entities we already moved
+            // go backwards, so if the same entity was pushed
+            // twice, it goes back to the original position
+            for (int ip = GameBase.pushed_p - 1; ip >= 0; ip--) {
+                p = GameBase.pushed[ip];
+                Math3D.vectorCopy(p.origin, p.ent.s.origin);
+                Math3D.vectorCopy(p.angles, p.ent.s.angles);
+                if (p.ent.client != null) {
+                    p.ent.client.ps.pmove.delta_angles[Defines.YAW] = (short) p.deltayaw;
+                }
+                GameBase.gi.linkentity(p.ent);
+            }
+            return false;
+        }
+
+        //       FIXME: is there a better way to handle this?
+        // see if anything we moved has touched a trigger
+        for (int ip = GameBase.pushed_p - 1; ip >= 0; ip--)
+            GameBase.G_TouchTriggers(GameBase.pushed[ip].ent);
+
+        return true;
+    }
+
+    /**
+     * Bmodel objects don't interact with each other, but push all box objects.
+     */
+    public static void SV_Physics_Pusher(EDict ent) {
+        float[] move = {0, 0, 0};
+        float[] amove = {0, 0, 0};
+        EDict part, mv;
+
+        // if not a team captain, so movement will be handled elsewhere
+        if ((ent.flags & Defines.FL_TEAMSLAVE) != 0)
+            return;
+
+        // make sure all team slaves can move before commiting
+        // any moves or calling any think functions
+        // if the move is blocked, all moved objects will be backed out
+        //       retry:
+        GameBase.pushed_p = 0;
+        for (part = ent; part != null; part = part.teamchain) {
+            if (part.velocity[0] != 0 || part.velocity[1] != 0
+                    || part.velocity[2] != 0 || part.avelocity[0] != 0
+                    || part.avelocity[1] != 0 || part.avelocity[2] != 0) { // object
+                // is
+                // moving
+                Math3D.vectorScale(part.velocity, Defines.FRAMETIME, move);
+                Math3D.vectorScale(part.avelocity, Defines.FRAMETIME, amove);
+
+                if (!SV_Push(part, move, amove))
+                    break; // move was blocked
+            }
+        }
+        if (GameBase.pushed_p > Defines.MAX_EDICTS)
+            SV_GAME.PF_error(Defines.ERR_FATAL,
+                    "pushed_p > &pushed[MAX_EDICTS], memory corrupted");
+
+        if (part != null) {
+            // the move failed, bump all nextthink times and back out moves
+            for (mv = ent; mv != null; mv = mv.teamchain) {
+                if (mv.nextthink > 0)
+                    mv.nextthink += Defines.FRAMETIME;
+            }
+
+            // if the pusher has a "blocked" function, call it
+            // otherwise, just stay in place until the obstacle is gone
+            if (part.blocked != null)
+                part.blocked.blocked(part, GameBase.obstacle);
+        } else { // the move succeeded, so call all think functions
+            for (part = ent; part != null; part = part.teamchain) {
+                SV_RunThink(part);
+            }
+        }
+    }
+
+    /**
+     * Non moving objects can only think.
+     */
+    public static void SV_Physics_None(EDict ent) {
+        // regular thinking
+        SV_RunThink(ent);
+    }
+
+    /**
+     * A moving object that doesn't obey physics.
+     */
+    public static void SV_Physics_Noclip(EDict ent) {
+        //        regular thinking
+        if (!SV_RunThink(ent))
+            return;
+
+        Math3D.vectorMA(ent.s.angles, Defines.FRAMETIME, ent.avelocity,
+                ent.s.angles);
+        Math3D.vectorMA(ent.s.origin, Defines.FRAMETIME, ent.velocity,
+                ent.s.origin);
+
+        GameBase.gi.linkentity(ent);
+    }
+
+    /**
+     * Toss, bounce, and fly movement. When onground, do nothing.
+     */
+    public static void SV_Physics_Toss(EDict ent) {
+
+        trace_t trace;
+        float[] move = {0, 0, 0};
+        float backoff;
+        EDict slave;
+        boolean wasinwater;
+        boolean isinwater;
+        float[] old_origin = {0, 0, 0};
+
+        //        regular thinking
+        SV_RunThink(ent);
+
+        // if not a team captain, so movement will be handled elsewhere
+        if ((ent.flags & Defines.FL_TEAMSLAVE) != 0)
+            return;
+
+        if (ent.velocity[2] > 0)
+            ent.groundentity = null;
+
+        //     check for the groundentity going away
+        if (ent.groundentity != null)
+            if (!ent.groundentity.inuse)
+                ent.groundentity = null;
+
+        //        if onground, return without moving
+        if (ent.groundentity != null)
+            return;
+
+        Math3D.vectorCopy(ent.s.origin, old_origin);
+
+        SV_CheckVelocity(ent);
+
+        //        add gravity
+        if (ent.movetype != Defines.MOVETYPE_FLY
+                && ent.movetype != Defines.MOVETYPE_FLYMISSILE)
+            SV_AddGravity(ent);
+
+        //        move angles
+        Math3D.vectorMA(ent.s.angles, Defines.FRAMETIME, ent.avelocity,
+                ent.s.angles);
+
+        //        move origin
+        Math3D.vectorScale(ent.velocity, Defines.FRAMETIME, move);
+        trace = SV_PushEntity(ent, move);
+        if (!ent.inuse)
+            return;
+
+        if (trace.fraction < 1) {
+            if (ent.movetype == Defines.MOVETYPE_BOUNCE)
+                backoff = 1.5f;
+            else
+                backoff = 1;
+
+            GameBase.ClipVelocity(ent.velocity, trace.plane.normal,
+                    ent.velocity, backoff);
+
+            // stop if on ground
+            if (trace.plane.normal[2] > 0.7) {
+                if (ent.velocity[2] < 60
+                        || ent.movetype != Defines.MOVETYPE_BOUNCE) {
+                    ent.groundentity = trace.ent;
+                    ent.groundentity_linkcount = trace.ent.linkcount;
+                    Math3D.vectorCopy(Globals.vec3_origin, ent.velocity);
+                    Math3D.vectorCopy(Globals.vec3_origin, ent.avelocity);
+                }
+            }
+
+            // if (ent.touch)
+            // ent.touch (ent, trace.ent, &trace.plane, trace.surface);
+        }
+
+        //        check for water transition
+        wasinwater = (ent.watertype & Defines.MASK_WATER) != 0;
+        ent.watertype = GameBase.gi.pointcontents.pointcontents(ent.s.origin);
+        isinwater = (ent.watertype & Defines.MASK_WATER) != 0;
+
+        if (isinwater)
+            ent.waterlevel = 1;
+        else
+            ent.waterlevel = 0;
+
+        if (!wasinwater && isinwater)
+            GameBase.gi.positioned_sound(old_origin, ent, Defines.CHAN_AUTO,
+                    GameBase.gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
+        else if (wasinwater && !isinwater)
+            GameBase.gi.positioned_sound(ent.s.origin, ent, Defines.CHAN_AUTO,
+                    GameBase.gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
+
+        //        move teamslaves
+        for (slave = ent.teamchain; slave != null; slave = slave.teamchain) {
+            Math3D.vectorCopy(ent.s.origin, slave.s.origin);
+            GameBase.gi.linkentity(slave);
+        }
+    }
+
+    // FIXME: hacked in for E3 demo
+    private static void SV_AddRotationalFriction(EDict ent) {
+        int n;
+        float adjustment;
+
+        Math3D.vectorMA(ent.s.angles, Defines.FRAMETIME, ent.avelocity,
+                ent.s.angles);
+        adjustment = Defines.FRAMETIME * Defines.sv_stopspeed
+                * Defines.sv_friction;
+        for (n = 0; n < 3; n++) {
+            if (ent.avelocity[n] > 0) {
+                ent.avelocity[n] -= adjustment;
+                if (ent.avelocity[n] < 0)
+                    ent.avelocity[n] = 0;
+            } else {
+                ent.avelocity[n] += adjustment;
+                if (ent.avelocity[n] > 0)
+                    ent.avelocity[n] = 0;
+            }
+        }
+    }
+
+    /**
+     * Monsters freefall when they don't have a ground entity, otherwise all
+     * movement is done with discrete steps.
+     * <p>
+     * This is also used for objects that have become still on the ground, but
+     * will fall if the floor is pulled out from under them. FIXME: is this
+     * true?
+     */
+
+    public static void SV_Physics_Step(EDict ent) {
+        boolean wasonground;
+        boolean hitsound = false;
+        float vel[];
+        float speed, newspeed, control;
+        float friction;
+        EDict groundentity;
+        int mask;
+
+        // airborn monsters should always check for ground
+        if (ent.groundentity == null)
+            M.M_CheckGround(ent);
+
+        groundentity = ent.groundentity;
+
+        SV_CheckVelocity(ent);
+
+        wasonground = groundentity != null;
+
+        if (ent.avelocity[0] != 0 || ent.avelocity[1] != 0
+                || ent.avelocity[2] != 0)
+            SV_AddRotationalFriction(ent);
+
+        // add gravity except:
+        //   flying monsters
+        //   swimming monsters who are in the water
+        if (!wasonground)
+            if (0 == (ent.flags & Defines.FL_FLY))
+                if (!((ent.flags & Defines.FL_SWIM) != 0 && (ent.waterlevel > 2))) {
+                    if (ent.velocity[2] < GameBase.sv_gravity.value * -0.1)
+                        hitsound = true;
+                    if (ent.waterlevel == 0)
+                        SV_AddGravity(ent);
+                }
+
+        // friction for flying monsters that have been given vertical velocity
+        if ((ent.flags & Defines.FL_FLY) != 0 && (ent.velocity[2] != 0)) {
+            speed = Math.abs(ent.velocity[2]);
+            control = speed < Defines.sv_stopspeed ? Defines.sv_stopspeed
+                    : speed;
+            friction = Defines.sv_friction / 3;
+            newspeed = speed - (Defines.FRAMETIME * control * friction);
+            if (newspeed < 0)
+                newspeed = 0;
+            newspeed /= speed;
+            ent.velocity[2] *= newspeed;
+        }
+
+        // friction for flying monsters that have been given vertical velocity
+        if ((ent.flags & Defines.FL_SWIM) != 0 && (ent.velocity[2] != 0)) {
+            speed = Math.abs(ent.velocity[2]);
+            control = speed < Defines.sv_stopspeed ? Defines.sv_stopspeed
+                    : speed;
+            newspeed = speed
+                    - (Defines.FRAMETIME * control * Defines.sv_waterfriction * ent.waterlevel);
+            if (newspeed < 0)
+                newspeed = 0;
+            newspeed /= speed;
+            ent.velocity[2] *= newspeed;
+        }
+
+        if (ent.velocity[2] != 0 || ent.velocity[1] != 0
+                || ent.velocity[0] != 0) {
+            // apply friction
+            // let dead monsters who aren't completely onground slide
+            if ((wasonground)
+                    || 0 != (ent.flags & (Defines.FL_SWIM | Defines.FL_FLY)))
+                if (!(ent.health <= 0.0 && !M.M_CheckBottom(ent))) {
+                    vel = ent.velocity;
+                    speed = (float) Math
+                            .sqrt(vel[0] * vel[0] + vel[1] * vel[1]);
+                    if (speed != 0) {
+                        friction = Defines.sv_friction;
+
+                        control = speed < Defines.sv_stopspeed ? Defines.sv_stopspeed
+                                : speed;
+                        newspeed = speed - Defines.FRAMETIME * control
+                                * friction;
+
+                        if (newspeed < 0)
+                            newspeed = 0;
+                        newspeed /= speed;
+
+                        vel[0] *= newspeed;
+                        vel[1] *= newspeed;
+                    }
+                }
+
+            if ((ent.svflags & Defines.SVF_MONSTER) != 0)
+                mask = Defines.MASK_MONSTERSOLID;
+            else
+                mask = Defines.MASK_SOLID;
+
+            SV_FlyMove(ent, mask);
+
+            GameBase.gi.linkentity(ent);
+            GameBase.G_TouchTriggers(ent);
+            if (!ent.inuse)
+                return;
+
+            if (ent.groundentity != null)
+                if (!wasonground)
+                    if (hitsound)
+                        GameBase.gi.sound(ent, 0,
+                                GameBase.gi.soundindex("world/land.wav"), 1, 1, 0);
+        }
+
+        // regular thinking
+        SV_RunThink(ent);
+    }
+
+    /**
+     * Called by monster program code. The move will be adjusted for slopes and
+     * stairs, but if the move isn't possible, no move is done, false is
+     * returned, and pr_global_struct.trace_normal is set to the normal of the
+     * blocking wall.
+     */
+
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/server/SV_CCMDS.java b/src/main/java/lwjake2/server/SV_CCMDS.java
new file mode 100644 (file)
index 0000000..5e0daa8
--- /dev/null
@@ -0,0 +1,1102 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.server;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.*;
+import lwjake2.qcommon.*;
+import lwjake2.sys.NET;
+import lwjake2.sys.Sys;
+import lwjake2.util.Lib;
+import lwjake2.util.QuakeFile;
+import lwjake2.util.Vargs;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.Calendar;
+
+class SV_CCMDS {
+
+       /*
+    ===============================================================================
+       
+       OPERATOR CONSOLE ONLY COMMANDS
+       
+       These commands can only be entered from stdin or by a remote operator datagram
+       ===============================================================================
+       */
+
+    /*
+    ====================
+    SV_SetMaster_f
+
+    Specify a list of master servers
+    ====================
+    */
+    private static void SV_SetMaster_f() {
+        int i, slot;
+
+        // only dedicated servers send heartbeats
+        if (Globals.dedicated.value == 0) {
+            Com.Printf("Only dedicated servers use masters.\n");
+            return;
+        }
+
+        // make sure the server is listed public
+        Cvar.set("public", "1");
+
+        for (i = 1; i < Defines.MAX_MASTERS; i++)
+            Server.master_adr[i] = new NetadrT();
+
+        slot = 1; // slot 0 will always contain the id master
+        for (i = 1; i < Cmd.Argc(); i++) {
+            if (slot == Defines.MAX_MASTERS)
+                break;
+
+            if (!NET.StringToAdr(Cmd.Argv(i), Server.master_adr[i])) {
+                Com.Printf("Bad address: " + Cmd.Argv(i) + "\n");
+                continue;
+            }
+            if (Server.master_adr[slot].port == 0)
+                Server.master_adr[slot].port = Defines.PORT_MASTER;
+
+            Com.Printf("Master server at " + NET.AdrToString(Server.master_adr[slot]) + "\n");
+            Com.Printf("Sending a ping.\n");
+
+            Netchan.OutOfBandPrint(Defines.NS_SERVER, Server.master_adr[slot], "ping");
+
+            slot++;
+        }
+
+        SV_INIT.svs.last_heartbeat = -9999999;
+    }
+
+    /*
+    ==================
+    SV_SetPlayer
+
+    Sets sv_client and sv_player to the player with idnum Cmd.Argv(1)
+    ==================
+    */
+    private static boolean SV_SetPlayer() {
+        client_t cl;
+        int i;
+        int idnum;
+        String s;
+
+        if (Cmd.Argc() < 2)
+            return false;
+
+        s = Cmd.Argv(1);
+
+        // numeric values are just slot numbers
+        if (s.charAt(0) >= '0' && s.charAt(0) <= '9') {
+            idnum = Lib.atoi(Cmd.Argv(1));
+            if (idnum < 0 || idnum >= Server.maxclients.value) {
+                Com.Printf("Bad client slot: " + idnum + "\n");
+                return false;
+            }
+
+            Server.sv_client = SV_INIT.svs.clients[idnum];
+            SV_USER.sv_player = Server.sv_client.edict;
+            if (0 == Server.sv_client.state) {
+                Com.Printf("Client " + idnum + " is not active\n");
+                return false;
+            }
+            return true;
+        }
+
+        // check for a name match
+        for (i = 0; i < Server.maxclients.value; i++) {
+            cl = SV_INIT.svs.clients[i];
+            if (0 == cl.state)
+                continue;
+            if (0 == Lib.strcmp(cl.name, s)) {
+                Server.sv_client = cl;
+                SV_USER.sv_player = Server.sv_client.edict;
+                return true;
+            }
+        }
+
+        Com.Printf("Userid " + s + " is not on the server\n");
+        return false;
+    }
+    /*
+    ===============================================================================
+       
+       SAVEGAME FILES
+       
+       ===============================================================================
+       */
+
+    private static void remove(String name) {
+        try {
+            new File(name).delete();
+        } catch (Exception ignored) {
+        }
+    }
+
+    /**
+     * Delete save files save/(number)/.
+     */
+    private static void SV_WipeSavegame(String savename) {
+
+        String name;
+
+        Com.DPrintf("SV_WipeSaveGame(" + savename + ")\n");
+
+        name = FS.Gamedir() + "/save/" + savename + "/server.ssv";
+        remove(name);
+
+        name = FS.Gamedir() + "/save/" + savename + "/game.ssv";
+        remove(name);
+
+        name = FS.Gamedir() + "/save/" + savename + "/*.sav";
+
+        File f = Sys.FindFirst(name, 0, 0);
+        while (f != null) {
+            f.delete();
+            f = Sys.FindNext();
+        }
+        Sys.FindClose();
+
+        name = FS.Gamedir() + "/save/" + savename + "/*.sv2";
+
+        f = Sys.FindFirst(name, 0, 0);
+
+        while (f != null) {
+            f.delete();
+            f = Sys.FindNext();
+        }
+        Sys.FindClose();
+    }
+
+    /*
+    ================
+    CopyFile
+    ================
+    */
+    private static void CopyFile(String src, String dst) {
+        RandomAccessFile f1, f2;
+        int l = -1;
+        byte buffer[] = new byte[65536];
+
+        //Com.DPrintf("CopyFile (" + src + ", " + dst + ")\n");
+        try {
+            f1 = new RandomAccessFile(src, "r");
+        } catch (Exception e) {
+            return;
+        }
+        try {
+            f2 = new RandomAccessFile(dst, "rw");
+        } catch (Exception e) {
+            try {
+                f1.close();
+            } catch (IOException e1) {
+                e1.printStackTrace();
+            }
+            return;
+        }
+
+        while (true) {
+
+            try {
+                l = f1.read(buffer, 0, 65536);
+            } catch (IOException e1) {
+
+                e1.printStackTrace();
+            }
+            if (l == -1)
+                break;
+            try {
+                f2.write(buffer, 0, l);
+            } catch (IOException e2) {
+
+                e2.printStackTrace();
+            }
+        }
+
+        try {
+            f1.close();
+        } catch (IOException e1) {
+
+            e1.printStackTrace();
+        }
+        try {
+            f2.close();
+        } catch (IOException e2) {
+
+            e2.printStackTrace();
+        }
+    }
+
+    /*
+    ================
+    SV_CopySaveGame
+    ================
+    */
+    private static void SV_CopySaveGame(String src, String dst) {
+        File found;
+
+        String name, name2;
+
+        Com.DPrintf("SV_CopySaveGame(" + src + "," + dst + ")\n");
+
+        SV_WipeSavegame(dst);
+
+        // copy the savegame over
+        name = FS.Gamedir() + "/save/" + src + "/server.ssv";
+        name2 = FS.Gamedir() + "/save/" + dst + "/server.ssv";
+        FS.CreatePath(name2);
+        CopyFile(name, name2);
+
+        name = FS.Gamedir() + "/save/" + src + "/game.ssv";
+        name2 = FS.Gamedir() + "/save/" + dst + "/game.ssv";
+        CopyFile(name, name2);
+
+        String name1 = FS.Gamedir() + "/save/" + src + "/";
+        name = FS.Gamedir() + "/save/" + src + "/*.sav";
+
+        found = Sys.FindFirst(name, 0, 0);
+
+        while (found != null) {
+            name = name1 + found.getName();
+            name2 = FS.Gamedir() + "/save/" + dst + "/" + found.getName();
+
+            CopyFile(name, name2);
+
+            // change sav to sv2
+            name = name.substring(0, name.length() - 3) + "sv2";
+            name2 = name2.substring(0, name2.length() - 3) + "sv2";
+
+            CopyFile(name, name2);
+
+            found = Sys.FindNext();
+        }
+        Sys.FindClose();
+    }
+
+    /*
+    ==============
+    SV_WriteLevelFile
+
+    ==============
+    */
+    private static void SV_WriteLevelFile() {
+
+        String name;
+        QuakeFile f;
+
+        Com.DPrintf("SV_WriteLevelFile()\n");
+
+        name = FS.Gamedir() + "/save/current/" + SV_INIT.sv.name + ".sv2";
+
+        try {
+            f = new QuakeFile(name, "rw");
+
+            for (int i = 0; i < Defines.MAX_CONFIGSTRINGS; i++)
+                f.writeString(SV_INIT.sv.configstrings[i]);
+
+            CM.CM_WritePortalState(f);
+            f.close();
+        } catch (Exception e) {
+            Com.Printf("Failed to open " + name + "\n");
+            e.printStackTrace();
+        }
+
+        name = FS.Gamedir() + "/save/current/" + SV_INIT.sv.name + ".sav";
+        GameSave.WriteLevel(name);
+    }
+
+    /*
+    ==============
+    SV_ReadLevelFile
+
+    ==============
+    */
+    public static void SV_ReadLevelFile() {
+        //char name[MAX_OSPATH];
+        String name;
+        QuakeFile f;
+
+        Com.DPrintf("SV_ReadLevelFile()\n");
+
+        name = FS.Gamedir() + "/save/current/" + SV_INIT.sv.name + ".sv2";
+        try {
+            f = new QuakeFile(name, "r");
+
+            for (int n = 0; n < Defines.MAX_CONFIGSTRINGS; n++)
+                SV_INIT.sv.configstrings[n] = f.readString();
+
+            CM.CM_ReadPortalState(f);
+
+            f.close();
+        } catch (IOException e1) {
+            Com.Printf("Failed to open " + name + "\n");
+            e1.printStackTrace();
+        }
+
+        name = FS.Gamedir() + "/save/current/" + SV_INIT.sv.name + ".sav";
+        GameSave.ReadLevel(name);
+    }
+
+    /*
+    ==============
+    SV_WriteServerFile
+
+    ==============
+    */
+    private static void SV_WriteServerFile(boolean autosave) {
+        QuakeFile f;
+        CvarT var;
+
+        String filename, name, string, comment;
+
+        Com.DPrintf("SV_WriteServerFile(" + (autosave ? "true" : "false") + ")\n");
+
+        filename = FS.Gamedir() + "/save/current/server.ssv";
+        try {
+            f = new QuakeFile(filename, "rw");
+
+            if (!autosave) {
+                Calendar c = Calendar.getInstance();
+                comment =
+                        Com.sprintf(
+                                "%2i:%2i %2i/%2i  ",
+                                new Vargs().add(c.get(Calendar.HOUR_OF_DAY)).add(c.get(Calendar.MINUTE)).add(
+                                        c.get(Calendar.MONTH) + 1).add(
+                                        c.get(Calendar.DAY_OF_MONTH)));
+                comment += SV_INIT.sv.configstrings[Defines.CS_NAME];
+            } else {
+                // autosaved
+                comment = "ENTERING " + SV_INIT.sv.configstrings[Defines.CS_NAME];
+            }
+
+            f.writeString(comment);
+            f.writeString(SV_INIT.svs.mapcmd);
+
+            // write the mapcmd
+
+            // write all CVAR_LATCH cvars
+            // these will be things like coop, skill, deathmatch, etc
+            for (var = Globals.cvar_vars; var != null; var = var.next) {
+                if (0 == (var.flags & Defines.CVAR_LATCH))
+                    continue;
+                if (var.name.length() >= Defines.MAX_OSPATH - 1 || var.string.length() >= 128 - 1) {
+                    Com.Printf("Cvar too long: " + var.name + " = " + var.string + "\n");
+                    continue;
+                }
+
+                name = var.name;
+                string = var.string;
+                try {
+                    f.writeString(name);
+                    f.writeString(string);
+                } catch (IOException ignored) {
+                }
+
+            }
+            // rst: for termination.
+            f.writeString(null);
+            f.close();
+        } catch (Exception e) {
+            Com.Printf("Couldn't write " + filename + "\n");
+        }
+
+        // write game state
+        filename = FS.Gamedir() + "/save/current/game.ssv";
+        GameSave.WriteGame(filename, autosave);
+    }
+
+    /*
+    ==============
+    SV_ReadServerFile
+
+    ==============
+    */
+    private static void SV_ReadServerFile() {
+        String filename = "", name = "", string, mapcmd;
+        try {
+            QuakeFile f;
+
+            mapcmd = "";
+
+            Com.DPrintf("SV_ReadServerFile()\n");
+
+            filename = FS.Gamedir() + "/save/current/server.ssv";
+
+            f = new QuakeFile(filename, "r");
+
+            // read the mapcmd
+            mapcmd = f.readString();
+
+            // read all CVAR_LATCH cvars
+            // these will be things like coop, skill, deathmatch, etc
+            while (true) {
+                name = f.readString();
+                if (name == null)
+                    break;
+                string = f.readString();
+
+                Com.DPrintf("Set " + name + " = " + string + "\n");
+                Cvar.forceSet(name, string);
+            }
+
+            f.close();
+
+            // start a new game fresh with new cvars
+            SV_INIT.SV_InitGame();
+
+            SV_INIT.svs.mapcmd = mapcmd;
+
+            // read game state
+            filename = FS.Gamedir() + "/save/current/game.ssv";
+            GameSave.ReadGame(filename);
+        } catch (Exception e) {
+            Com.Printf("Couldn't read file " + filename + "\n");
+            e.printStackTrace();
+        }
+    }
+    //=========================================================
+
+    /*
+    ==================
+    SV_DemoMap_f
+
+    Puts the server in demo mode on a specific map/cinematic
+    ==================
+    */
+    private static void SV_DemoMap_f() {
+        SV_INIT.SV_Map(true, Cmd.Argv(1), false);
+    }
+
+    /*
+    ==================
+    SV_GameMap_f
+
+    Saves the state of the map just being exited and goes to a new map.
+
+    If the initial character of the map string is '*', the next map is
+    in a new unit, so the current savegame directory is cleared of
+    map files.
+
+    Example:
+
+    *inter.cin+jail
+
+    Clears the archived maps, plays the inter.cin cinematic, then
+    goes to map jail.bsp.
+    ==================
+    */
+    private static void SV_GameMap_f() {
+        String map;
+        int i;
+        client_t cl;
+        boolean savedInuse[];
+
+        if (Cmd.Argc() != 2) {
+            Com.Printf("USAGE: gamemap <map>\n");
+            return;
+        }
+
+        Com.DPrintf("SV_GameMap(" + Cmd.Argv(1) + ")\n");
+
+        FS.CreatePath(FS.Gamedir() + "/save/current/");
+
+        // check for clearing the current savegame
+        map = Cmd.Argv(1);
+        if (map.charAt(0) == '*') {
+            // wipe all the *.sav files
+            SV_WipeSavegame("current");
+        } else { // save the map just exited
+            if (SV_INIT.sv.state == Defines.ss_game) {
+                // clear all the client inuse flags before saving so that
+                // when the level is re-entered, the clients will spawn
+                // at spawn points instead of occupying body shells
+                savedInuse = new boolean[(int) Server.maxclients.value];
+                for (i = 0; i < Server.maxclients.value; i++) {
+                    cl = SV_INIT.svs.clients[i];
+                    savedInuse[i] = cl.edict.inuse;
+                    cl.edict.inuse = false;
+                }
+
+                SV_WriteLevelFile();
+
+                // we must restore these for clients to transfer over correctly
+                for (i = 0; i < Server.maxclients.value; i++) {
+                    cl = SV_INIT.svs.clients[i];
+                    cl.edict.inuse = savedInuse[i];
+
+                }
+                savedInuse = null;
+            }
+        }
+
+        // start up the next map
+        SV_INIT.SV_Map(false, Cmd.Argv(1), false);
+
+        // archive server state
+        SV_INIT.svs.mapcmd = Cmd.Argv(1);
+
+        // copy off the level to the autosave slot
+        if (0 == Globals.dedicated.value) {
+            SV_WriteServerFile(true);
+            SV_CopySaveGame("current", "save0");
+        }
+    }
+
+    /*
+    ==================
+    SV_Map_f
+
+    Goes directly to a given map without any savegame archiving.
+    For development work
+    ==================
+    */
+    private static void SV_Map_f() {
+        String map;
+        //char expanded[MAX_QPATH];
+        String expanded;
+
+        // if not a pcx, demo, or cinematic, check to make sure the level exists
+        map = Cmd.Argv(1);
+        if (!map.contains(".")) {
+            expanded = "maps/" + map + ".bsp";
+            if (FS.LoadFile(expanded) == null) {
+
+                Com.Printf("Can't find " + expanded + "\n");
+                return;
+            }
+        }
+
+        SV_INIT.sv.state = Defines.ss_dead; // don't save current level when changing
+
+        SV_WipeSavegame("current");
+        SV_GameMap_f();
+    }
+    /*
+    =====================================================================
+       
+         SAVEGAMES
+       
+       =====================================================================
+       */
+
+    /*
+    ==============
+    SV_Loadgame_f
+
+    ==============
+    */
+    private static void SV_Loadgame_f() {
+
+        String name;
+        RandomAccessFile f;
+        String dir;
+
+        if (Cmd.Argc() != 2) {
+            Com.Printf("USAGE: loadgame <directory>\n");
+            return;
+        }
+
+        Com.Printf("Loading game...\n");
+
+        dir = Cmd.Argv(1);
+        if ((dir.contains("..")) || (dir.contains("/")) || (dir.contains("\\"))) {
+            Com.Printf("Bad savedir.\n");
+        }
+
+        // make sure the server.ssv file exists
+        name = FS.Gamedir() + "/save/" + Cmd.Argv(1) + "/server.ssv";
+        try {
+            f = new RandomAccessFile(name, "r");
+        } catch (FileNotFoundException e) {
+            Com.Printf("No such savegame: " + name + "\n");
+            return;
+        }
+
+        try {
+            f.close();
+        } catch (IOException e1) {
+            e1.printStackTrace();
+        }
+
+        SV_CopySaveGame(Cmd.Argv(1), "current");
+        SV_ReadServerFile();
+
+        // go to the map
+        SV_INIT.sv.state = Defines.ss_dead; // don't save current level when changing
+        SV_INIT.SV_Map(false, SV_INIT.svs.mapcmd, true);
+    }
+
+    /*
+    ==============
+    SV_Savegame_f
+
+    ==============
+    */
+    private static void SV_Savegame_f() {
+        String dir;
+
+        if (SV_INIT.sv.state != Defines.ss_game) {
+            Com.Printf("You must be in a game to save.\n");
+            return;
+        }
+
+        if (Cmd.Argc() != 2) {
+            Com.Printf("USAGE: savegame <directory>\n");
+            return;
+        }
+
+        if (Cvar.variableValue("deathmatch") != 0) {
+            Com.Printf("Can't savegame in a deathmatch\n");
+            return;
+        }
+
+        if (0 == Lib.strcmp(Cmd.Argv(1), "current")) {
+            Com.Printf("Can't save to 'current'\n");
+            return;
+        }
+
+        if (Server.maxclients.value == 1 && SV_INIT.svs.clients[0].edict.client.ps.stats[Defines.STAT_HEALTH] <= 0) {
+            Com.Printf("\nCan't savegame while dead!\n");
+            return;
+        }
+
+        dir = Cmd.Argv(1);
+        if ((dir.contains("..")) || (dir.contains("/")) || (dir.contains("\\"))) {
+            Com.Printf("Bad savedir.\n");
+        }
+
+        Com.Printf("Saving game...\n");
+
+        // archive current level, including all client edicts.
+        // when the level is reloaded, they will be shells awaiting
+        // a connecting client
+        SV_WriteLevelFile();
+
+        // save server state
+        try {
+            SV_WriteServerFile(false);
+        } catch (Exception e) {
+            Com.Printf("IOError in SV_WriteServerFile: " + e);
+        }
+
+        // copy it off
+        SV_CopySaveGame("current", dir);
+        Com.Printf("Done.\n");
+    }
+
+    //===============================================================
+    /*
+    ==================
+       SV_Kick_f
+       
+       Kick a user off of the server
+       ==================
+       */
+    private static void SV_Kick_f() {
+        if (!SV_INIT.svs.initialized) {
+            Com.Printf("No server running.\n");
+            return;
+        }
+
+        if (Cmd.Argc() != 2) {
+            Com.Printf("Usage: kick <userid>\n");
+            return;
+        }
+
+        if (!SV_SetPlayer())
+            return;
+
+        SV_SEND.SV_BroadcastPrintf(Defines.PRINT_HIGH, Server.sv_client.name + " was kicked\n");
+        // print directly, because the dropped client won't get the
+        // SV_BroadcastPrintf message
+        SV_SEND.SV_ClientPrintf(Server.sv_client, Defines.PRINT_HIGH, "You were kicked from the game\n");
+        Server.SV_DropClient(Server.sv_client);
+        Server.sv_client.lastmessage = SV_INIT.svs.realtime; // min case there is a funny zombie
+    }
+
+    /*
+    ================
+    SV_Status_f
+    ================
+    */
+    private static void SV_Status_f() {
+        int i, j, l;
+        client_t cl;
+        String s;
+        int ping;
+        if (SV_INIT.svs.clients == null) {
+            Com.Printf("No server running.\n");
+            return;
+        }
+        Com.Printf("map              : " + SV_INIT.sv.name + "\n");
+
+        Com.Printf("num score ping name            lastmsg address               qport \n");
+        Com.Printf("--- ----- ---- --------------- ------- --------------------- ------\n");
+        for (i = 0; i < Server.maxclients.value; i++) {
+            cl = SV_INIT.svs.clients[i];
+            if (0 == cl.state)
+                continue;
+
+            Com.Printf("%3i ", new Vargs().add(i));
+            Com.Printf("%5i ", new Vargs().add(cl.edict.client.ps.stats[Defines.STAT_FRAGS]));
+
+            if (cl.state == Defines.cs_connected)
+                Com.Printf("CNCT ");
+            else if (cl.state == Defines.cs_zombie)
+                Com.Printf("ZMBI ");
+            else {
+                ping = cl.ping < 9999 ? cl.ping : 9999;
+                Com.Printf("%4i ", new Vargs().add(ping));
+            }
+
+            Com.Printf("%s", new Vargs().add(cl.name));
+            l = 16 - cl.name.length();
+            for (j = 0; j < l; j++)
+                Com.Printf(" ");
+
+            Com.Printf("%7i ", new Vargs().add(SV_INIT.svs.realtime - cl.lastmessage));
+
+            s = NET.AdrToString(cl.netchan.remote_address);
+            Com.Printf(s);
+            l = 22 - s.length();
+            for (j = 0; j < l; j++)
+                Com.Printf(" ");
+
+            Com.Printf("%5i", new Vargs().add(cl.netchan.qport));
+
+            Com.Printf("\n");
+        }
+        Com.Printf("\n");
+    }
+
+    /*
+    ==================
+    SV_ConSay_f
+    ==================
+    */
+    private static void SV_ConSay_f() {
+        client_t client;
+        int j;
+        String p;
+        String text; // char[1024];
+
+        if (Cmd.Argc() < 2)
+            return;
+
+        text = "console: ";
+        p = Cmd.Args();
+
+        if (p.charAt(0) == '"') {
+            p = p.substring(1, p.length() - 1);
+        }
+
+        text += p;
+
+        for (j = 0; j < Server.maxclients.value; j++) {
+            client = SV_INIT.svs.clients[j];
+            if (client.state != Defines.cs_spawned)
+                continue;
+            SV_SEND.SV_ClientPrintf(client, Defines.PRINT_CHAT, text + "\n");
+        }
+    }
+
+    /*
+    ==================
+    SV_Heartbeat_f
+    ==================
+    */
+    private static void SV_Heartbeat_f() {
+        SV_INIT.svs.last_heartbeat = -9999999;
+    }
+
+    /*
+    ===========
+    SV_Serverinfo_f
+
+      Examine or change the serverinfo string
+    ===========
+    */
+    private static void SV_Serverinfo_f() {
+        Com.Printf("Server info settings:\n");
+        Info.Print(Cvar.serverinfo());
+    }
+
+    /*
+    ===========
+    SV_DumpUser_f
+
+    Examine all a users info strings
+    ===========
+    */
+    private static void SV_DumpUser_f() {
+        if (Cmd.Argc() != 2) {
+            Com.Printf("Usage: info <userid>\n");
+            return;
+        }
+
+        if (!SV_SetPlayer())
+            return;
+
+        Com.Printf("userinfo\n");
+        Com.Printf("--------\n");
+        Info.Print(Server.sv_client.userinfo);
+
+    }
+
+    /*
+    ==============
+    SV_ServerRecord_f
+
+    Begins server demo recording.  Every entity and every message will be
+    recorded, but no playerinfo will be stored.  Primarily for demo merging.
+    ==============
+    */
+    private static void SV_ServerRecord_f() {
+        //char name[MAX_OSPATH];
+        String name;
+        byte buf_data[] = new byte[32768];
+        sizebuf_t buf = new sizebuf_t();
+        int len;
+        int i;
+
+        if (Cmd.Argc() != 2) {
+            Com.Printf("serverrecord <demoname>\n");
+            return;
+        }
+
+        if (SV_INIT.svs.demofile != null) {
+            Com.Printf("Already recording.\n");
+            return;
+        }
+
+        if (SV_INIT.sv.state != Defines.ss_game) {
+            Com.Printf("You must be in a level to record.\n");
+            return;
+        }
+
+        //
+        // open the demo file
+        //
+        name = FS.Gamedir() + "/demos/" + Cmd.Argv(1) + ".dm2";
+
+        Com.Printf("recording to " + name + ".\n");
+        FS.CreatePath(name);
+        try {
+            SV_INIT.svs.demofile = new RandomAccessFile(name, "rw");
+        } catch (Exception e) {
+            Com.Printf("ERROR: couldn't open.\n");
+            return;
+        }
+
+        // setup a buffer to catch all multicasts
+        SZ.Init(SV_INIT.svs.demo_multicast, SV_INIT.svs.demo_multicast_buf, SV_INIT.svs.demo_multicast_buf.length);
+
+        //
+        // write a single giant fake message with all the startup info
+        //
+        SZ.Init(buf, buf_data, buf_data.length);
+
+        //
+        // serverdata needs to go over for all types of servers
+        // to make sure the protocol is right, and to set the gamedir
+        //
+        // send the serverdata
+        MSG.WriteByte(buf, Defines.svc_serverdata);
+        MSG.WriteLong(buf, Defines.PROTOCOL_VERSION);
+        MSG.WriteLong(buf, SV_INIT.svs.spawncount);
+        // 2 means server demo
+        MSG.WriteByte(buf, 2); // demos are always attract loops
+        MSG.WriteString(buf, Cvar.variableString("gamedir"));
+        MSG.WriteShort(buf, -1);
+        // send full levelname
+        MSG.WriteString(buf, SV_INIT.sv.configstrings[Defines.CS_NAME]);
+
+        for (i = 0; i < Defines.MAX_CONFIGSTRINGS; i++)
+            if (SV_INIT.sv.configstrings[i].length() == 0) {
+                MSG.WriteByte(buf, Defines.svc_configstring);
+                MSG.WriteShort(buf, i);
+                MSG.WriteString(buf, SV_INIT.sv.configstrings[i]);
+            }
+
+        // write it to the demo file
+        Com.DPrintf("signon message length: " + buf.cursize + "\n");
+        len = EndianHandler.swapInt(buf.cursize);
+        //fwrite(len, 4, 1, svs.demofile);
+        //fwrite(buf.data, buf.cursize, 1, svs.demofile);
+        try {
+            SV_INIT.svs.demofile.writeInt(len);
+            SV_INIT.svs.demofile.write(buf.data, 0, buf.cursize);
+        } catch (IOException e1) {
+            // TODO: do quake2 error handling!
+            e1.printStackTrace();
+        }
+
+        // the rest of the demo file will be individual frames
+    }
+
+    /*
+    ==============
+    SV_ServerStop_f
+
+    Ends server demo recording
+    ==============
+    */
+    private static void SV_ServerStop_f() {
+        if (SV_INIT.svs.demofile == null) {
+            Com.Printf("Not doing a serverrecord.\n");
+            return;
+        }
+        try {
+            SV_INIT.svs.demofile.close();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        SV_INIT.svs.demofile = null;
+        Com.Printf("Recording completed.\n");
+    }
+
+    /*
+    ===============
+    SV_KillServer_f
+
+    Kick everyone off, possibly in preparation for a new game
+
+    ===============
+    */
+    private static void SV_KillServer_f() {
+        if (!SV_INIT.svs.initialized)
+            return;
+        Server.SV_Shutdown("Server was killed.\n", false);
+        NET.Config(false); // close network sockets
+    }
+
+    /*
+    ===============
+    SV_ServerCommand_f
+
+    Let the game dll handle a command
+    ===============
+    */
+    private static void SV_ServerCommand_f() {
+
+        GameSVCmds.ServerCommand();
+    }
+    //===========================================================
+
+    /*
+    ==================
+    SV_InitOperatorCommands
+    ==================
+    */
+    public static void SV_InitOperatorCommands() {
+        Cmd.AddCommand("heartbeat", new xcommand_t() {
+            public void execute() {
+                SV_Heartbeat_f();
+            }
+        });
+        Cmd.AddCommand("kick", new xcommand_t() {
+            public void execute() {
+                SV_Kick_f();
+            }
+        });
+        Cmd.AddCommand("status", new xcommand_t() {
+            public void execute() {
+                SV_Status_f();
+            }
+        });
+        Cmd.AddCommand("serverinfo", new xcommand_t() {
+            public void execute() {
+                SV_Serverinfo_f();
+            }
+        });
+        Cmd.AddCommand("dumpuser", new xcommand_t() {
+            public void execute() {
+                SV_DumpUser_f();
+            }
+        });
+
+        Cmd.AddCommand("map", new xcommand_t() {
+            public void execute() {
+                SV_Map_f();
+            }
+        });
+        Cmd.AddCommand("demomap", new xcommand_t() {
+            public void execute() {
+                SV_DemoMap_f();
+            }
+        });
+        Cmd.AddCommand("gamemap", new xcommand_t() {
+            public void execute() {
+                SV_GameMap_f();
+            }
+        });
+        Cmd.AddCommand("setmaster", new xcommand_t() {
+            public void execute() {
+                SV_SetMaster_f();
+            }
+        });
+
+        if (Globals.dedicated.value != 0)
+            Cmd.AddCommand("say", new xcommand_t() {
+                public void execute() {
+                    SV_ConSay_f();
+                }
+            });
+
+        Cmd.AddCommand("serverrecord", new xcommand_t() {
+            public void execute() {
+                SV_ServerRecord_f();
+            }
+        });
+        Cmd.AddCommand("serverstop", new xcommand_t() {
+            public void execute() {
+                SV_ServerStop_f();
+            }
+        });
+
+        Cmd.AddCommand("save", new xcommand_t() {
+            public void execute() {
+                SV_Savegame_f();
+            }
+        });
+        Cmd.AddCommand("load", new xcommand_t() {
+            public void execute() {
+                SV_Loadgame_f();
+            }
+        });
+
+        Cmd.AddCommand("killserver", new xcommand_t() {
+            public void execute() {
+                SV_KillServer_f();
+            }
+        });
+
+        Cmd.AddCommand("sv", new xcommand_t() {
+            public void execute() {
+                SV_ServerCommand_f();
+            }
+        });
+    }
+}
diff --git a/src/main/java/lwjake2/server/SV_ENTS.java b/src/main/java/lwjake2/server/SV_ENTS.java
new file mode 100644 (file)
index 0000000..39982e5
--- /dev/null
@@ -0,0 +1,530 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.server;
+
+import lwjake2.Defines;
+import lwjake2.game.*;
+import lwjake2.qcommon.*;
+
+import java.io.IOException;
+
+public class SV_ENTS {
+
+    /**
+     * =============================================================================
+     * <p>
+     * Build a client frame structure
+     * <p>
+     * =============================================================================
+     */
+
+    public static final byte[] fatpvs = new byte[65536 / 8]; // 32767 is MAX_MAP_LEAFS
+
+    /*
+     * =============================================================================
+     * 
+     * Encode a client frame onto the network channel
+     * 
+     * =============================================================================
+     */
+
+    /**
+     * Writes a delta update of an entity_state_t list to the message.
+     */
+    static void SV_EmitPacketEntities(client_frame_t from, client_frame_t to,
+                                      sizebuf_t msg) {
+        entity_state_t oldent = null, newent = null;
+        int oldindex, newindex;
+        int oldnum, newnum;
+        int from_num_entities;
+        int bits;
+
+        MSG.WriteByte(msg, Defines.svc_packetentities);
+
+        if (from == null)
+            from_num_entities = 0;
+        else
+            from_num_entities = from.num_entities;
+
+        newindex = 0;
+        oldindex = 0;
+        while (newindex < to.num_entities || oldindex < from_num_entities) {
+            if (newindex >= to.num_entities)
+                newnum = 9999;
+            else {
+                newent = SV_INIT.svs.client_entities[(to.first_entity + newindex)
+                        % SV_INIT.svs.num_client_entities];
+                newnum = newent.number;
+            }
+
+            if (oldindex >= from_num_entities)
+                oldnum = 9999;
+            else {
+                oldent = SV_INIT.svs.client_entities[(from.first_entity + oldindex)
+                        % SV_INIT.svs.num_client_entities];
+                oldnum = oldent.number;
+            }
+
+            if (newnum == oldnum) {
+                // delta update from old position
+                // because the force parm is false, this will not result
+                // in any bytes being emited if the entity has not changed at
+                // all note that players are always 'newentities', this updates
+                // their oldorigin always
+                // and prevents warping
+                MSG.WriteDeltaEntity(oldent, newent, msg, false,
+                        newent.number <= Server.maxclients.value);
+                oldindex++;
+                newindex++;
+                continue;
+            }
+
+            if (newnum < oldnum) {
+                // this is a new entity, send it from the baseline
+                MSG.WriteDeltaEntity(SV_INIT.sv.baselines[newnum], newent, msg,
+                        true, true);
+                newindex++;
+                continue;
+            }
+
+            if (newnum > oldnum) {
+                // the old entity isn't present in the new message
+                bits = Defines.U_REMOVE;
+                if (oldnum >= 256)
+                    bits |= Defines.U_NUMBER16 | Defines.U_MOREBITS1;
+
+                MSG.WriteByte(msg, bits & 255);
+                if ((bits & 0x0000ff00) != 0)
+                    MSG.WriteByte(msg, (bits >> 8) & 255);
+
+                if ((bits & Defines.U_NUMBER16) != 0)
+                    MSG.WriteShort(msg, oldnum);
+                else
+                    MSG.WriteByte(msg, oldnum);
+
+                oldindex++;
+            }
+        }
+
+        MSG.WriteShort(msg, 0); // end of packetentities
+
+    }
+
+    /**
+     * Writes the status of a player to a client system.
+     */
+    static void SV_WritePlayerstateToClient(client_frame_t from,
+                                            client_frame_t to, sizebuf_t msg) {
+        int i;
+        int pflags;
+        // ptr
+        player_state_t ps, ops;
+        // mem
+        player_state_t dummy;
+        int statbits;
+
+        ps = to.ps;
+        if (from == null) {
+            //memset (dummy, 0, sizeof(dummy));
+            dummy = new player_state_t();
+            ops = dummy;
+        } else
+            ops = from.ps;
+
+        // determine what needs to be sent
+        pflags = 0;
+
+        if (ps.pmove.pm_type != ops.pmove.pm_type)
+            pflags |= Defines.PS_M_TYPE;
+
+        if (ps.pmove.origin[0] != ops.pmove.origin[0]
+                || ps.pmove.origin[1] != ops.pmove.origin[1]
+                || ps.pmove.origin[2] != ops.pmove.origin[2])
+            pflags |= Defines.PS_M_ORIGIN;
+
+        if (ps.pmove.velocity[0] != ops.pmove.velocity[0]
+                || ps.pmove.velocity[1] != ops.pmove.velocity[1]
+                || ps.pmove.velocity[2] != ops.pmove.velocity[2])
+            pflags |= Defines.PS_M_VELOCITY;
+
+        if (ps.pmove.pm_time != ops.pmove.pm_time)
+            pflags |= Defines.PS_M_TIME;
+
+        if (ps.pmove.pm_flags != ops.pmove.pm_flags)
+            pflags |= Defines.PS_M_FLAGS;
+
+        if (ps.pmove.gravity != ops.pmove.gravity)
+            pflags |= Defines.PS_M_GRAVITY;
+
+        if (ps.pmove.delta_angles[0] != ops.pmove.delta_angles[0]
+                || ps.pmove.delta_angles[1] != ops.pmove.delta_angles[1]
+                || ps.pmove.delta_angles[2] != ops.pmove.delta_angles[2])
+            pflags |= Defines.PS_M_DELTA_ANGLES;
+
+        if (ps.viewoffset[0] != ops.viewoffset[0]
+                || ps.viewoffset[1] != ops.viewoffset[1]
+                || ps.viewoffset[2] != ops.viewoffset[2])
+            pflags |= Defines.PS_VIEWOFFSET;
+
+        if (ps.viewangles[0] != ops.viewangles[0]
+                || ps.viewangles[1] != ops.viewangles[1]
+                || ps.viewangles[2] != ops.viewangles[2])
+            pflags |= Defines.PS_VIEWANGLES;
+
+        if (ps.kick_angles[0] != ops.kick_angles[0]
+                || ps.kick_angles[1] != ops.kick_angles[1]
+                || ps.kick_angles[2] != ops.kick_angles[2])
+            pflags |= Defines.PS_KICKANGLES;
+
+        if (ps.blend[0] != ops.blend[0] || ps.blend[1] != ops.blend[1]
+                || ps.blend[2] != ops.blend[2] || ps.blend[3] != ops.blend[3])
+            pflags |= Defines.PS_BLEND;
+
+        if (ps.fov != ops.fov)
+            pflags |= Defines.PS_FOV;
+
+        if (ps.rdflags != ops.rdflags)
+            pflags |= Defines.PS_RDFLAGS;
+
+
+        pflags |= Defines.PS_WEAPONINDEX;
+
+        // write it
+        MSG.WriteByte(msg, Defines.svc_playerinfo);
+        MSG.WriteShort(msg, pflags);
+
+        // write the pmove_state_t
+        if ((pflags & Defines.PS_M_TYPE) != 0)
+            MSG.WriteByte(msg, ps.pmove.pm_type);
+
+        if ((pflags & Defines.PS_M_ORIGIN) != 0) {
+            MSG.WriteShort(msg, ps.pmove.origin[0]);
+            MSG.WriteShort(msg, ps.pmove.origin[1]);
+            MSG.WriteShort(msg, ps.pmove.origin[2]);
+        }
+
+        if ((pflags & Defines.PS_M_VELOCITY) != 0) {
+            MSG.WriteShort(msg, ps.pmove.velocity[0]);
+            MSG.WriteShort(msg, ps.pmove.velocity[1]);
+            MSG.WriteShort(msg, ps.pmove.velocity[2]);
+        }
+
+        if ((pflags & Defines.PS_M_TIME) != 0)
+            MSG.WriteByte(msg, ps.pmove.pm_time);
+
+        if ((pflags & Defines.PS_M_FLAGS) != 0)
+            MSG.WriteByte(msg, ps.pmove.pm_flags);
+
+        if ((pflags & Defines.PS_M_GRAVITY) != 0)
+            MSG.WriteShort(msg, ps.pmove.gravity);
+
+        if ((pflags & Defines.PS_M_DELTA_ANGLES) != 0) {
+            MSG.WriteShort(msg, ps.pmove.delta_angles[0]);
+            MSG.WriteShort(msg, ps.pmove.delta_angles[1]);
+            MSG.WriteShort(msg, ps.pmove.delta_angles[2]);
+        }
+
+        // write the rest of the player_state_t
+        if ((pflags & Defines.PS_VIEWOFFSET) != 0) {
+            MSG.WriteChar(msg, ps.viewoffset[0] * 4);
+            MSG.WriteChar(msg, ps.viewoffset[1] * 4);
+            MSG.WriteChar(msg, ps.viewoffset[2] * 4);
+        }
+
+        if ((pflags & Defines.PS_VIEWANGLES) != 0) {
+            MSG.WriteAngle16(msg, ps.viewangles[0]);
+            MSG.WriteAngle16(msg, ps.viewangles[1]);
+            MSG.WriteAngle16(msg, ps.viewangles[2]);
+        }
+
+        if ((pflags & Defines.PS_KICKANGLES) != 0) {
+            MSG.WriteChar(msg, ps.kick_angles[0] * 4);
+            MSG.WriteChar(msg, ps.kick_angles[1] * 4);
+            MSG.WriteChar(msg, ps.kick_angles[2] * 4);
+        }
+
+
+        if ((pflags & Defines.PS_BLEND) != 0) {
+            MSG.WriteByte(msg, ps.blend[0] * 255);
+            MSG.WriteByte(msg, ps.blend[1] * 255);
+            MSG.WriteByte(msg, ps.blend[2] * 255);
+            MSG.WriteByte(msg, ps.blend[3] * 255);
+        }
+        if ((pflags & Defines.PS_FOV) != 0)
+            MSG.WriteByte(msg, ps.fov);
+        if ((pflags & Defines.PS_RDFLAGS) != 0)
+            MSG.WriteByte(msg, ps.rdflags);
+
+        // send stats
+        statbits = 0;
+        for (i = 0; i < Defines.MAX_STATS; i++)
+            if (ps.stats[i] != ops.stats[i])
+                statbits |= 1 << i;
+        MSG.WriteLong(msg, statbits);
+        for (i = 0; i < Defines.MAX_STATS; i++)
+            if ((statbits & (1 << i)) != 0)
+                MSG.WriteShort(msg, ps.stats[i]);
+    }
+
+    /**
+     * Writes a frame to a client system.
+     */
+    public static void SV_WriteFrameToClient(client_t client, sizebuf_t msg) {
+        //ptr
+        client_frame_t frame, oldframe;
+        int lastframe;
+
+        //Com.Printf ("%i . %i\n", new
+        // Vargs().add(client.lastframe).add(sv.framenum));
+        // this is the frame we are creating
+        frame = client.frames[SV_INIT.sv.framenum & Defines.UPDATE_MASK];
+        if (client.lastframe <= 0) { // client is asking for a retransmit
+            oldframe = null;
+            lastframe = -1;
+        } else if (SV_INIT.sv.framenum - client.lastframe >= (Defines.UPDATE_BACKUP - 3)) {
+            // client hasn't gotten a good message through in a long time
+            // Com_Printf ("%s: Delta request from out-of-date packet.\n",
+            // client.name);
+            oldframe = null;
+            lastframe = -1;
+        } else { // we have a valid message to delta from
+            oldframe = client.frames[client.lastframe & Defines.UPDATE_MASK];
+            lastframe = client.lastframe;
+        }
+
+        MSG.WriteByte(msg, Defines.svc_frame);
+        MSG.WriteLong(msg, SV_INIT.sv.framenum);
+        MSG.WriteLong(msg, lastframe); // what we are delta'ing from
+        MSG.WriteByte(msg, client.surpressCount); // rate dropped packets
+        client.surpressCount = 0;
+
+        // send over the areabits
+        MSG.WriteByte(msg, frame.areabytes);
+        SZ.Write(msg, frame.areabits, frame.areabytes);
+
+        // delta encode the playerstate
+        SV_WritePlayerstateToClient(oldframe, frame, msg);
+
+        // delta encode the entities
+        SV_EmitPacketEntities(oldframe, frame, msg);
+    }
+
+    /**
+     * The client will interpolate the view position, so we can't use a single
+     * PVS point.
+     */
+    public static void SV_FatPVS(float[] org) {
+        int leafs[] = new int[64];
+        int i, j, count;
+        int longs;
+        byte src[];
+        float[] mins = {0, 0, 0}, maxs = {0, 0, 0};
+
+        for (i = 0; i < 3; i++) {
+            mins[i] = org[i] - 8;
+            maxs[i] = org[i] + 8;
+        }
+
+        count = CM.CM_BoxLeafnums(mins, maxs, leafs, 64, null);
+
+        if (count < 1)
+            Com.Error(Defines.ERR_FATAL, "SV_FatPVS: count < 1");
+
+        longs = (CM.CM_NumClusters() + 31) >> 5;
+
+        // convert leafs to clusters
+        for (i = 0; i < count; i++)
+            leafs[i] = CM.CM_LeafCluster(leafs[i]);
+
+        System.arraycopy(CM.CM_ClusterPVS(leafs[0]), 0, SV_ENTS.fatpvs, 0,
+                longs << 2);
+        // or in all the other leaf bits
+        for (i = 1; i < count; i++) {
+            for (j = 0; j < i; j++)
+                if (leafs[i] == leafs[j])
+                    break;
+            if (j != i)
+                continue; // already have the cluster we want
+
+            src = CM.CM_ClusterPVS(leafs[i]);
+
+            //for (j=0 ; j<longs ; j++)
+            // ((long *)fatpvs)[j] |= ((long *)src)[j];
+            int k = 0;
+            for (j = 0; j < longs; j++) {
+                SV_ENTS.fatpvs[k] |= src[k++];
+                SV_ENTS.fatpvs[k] |= src[k++];
+                SV_ENTS.fatpvs[k] |= src[k++];
+                SV_ENTS.fatpvs[k] |= src[k++];
+            }
+        }
+    }
+
+    /**
+     * Decides which entities are going to be visible to the client, and copies
+     * off the playerstat and areabits.
+     */
+    public static void SV_BuildClientFrame(client_t client) {
+        int e, i;
+        float[] org = {0, 0, 0};
+        EDict ent;
+        EDict clent;
+        client_frame_t frame;
+        entity_state_t state;
+        int l;
+        int clientarea, clientcluster;
+        int leafnum;
+        byte clientphs[];
+        byte bitvector[];
+
+        clent = client.edict;
+        if (clent.client == null)
+            return; // not in game yet
+
+        // this is the frame we are creating
+        frame = client.frames[SV_INIT.sv.framenum & Defines.UPDATE_MASK];
+
+        frame.senttime = SV_INIT.svs.realtime; // save it for ping calc later
+
+        // find the client's PVS
+        for (i = 0; i < 3; i++)
+            org[i] = clent.client.ps.pmove.origin[i] * 0.125f
+                    + clent.client.ps.viewoffset[i];
+
+        leafnum = CM.CM_PointLeafnum(org);
+        clientarea = CM.CM_LeafArea(leafnum);
+        clientcluster = CM.CM_LeafCluster(leafnum);
+
+        // calculate the visible areas
+        frame.areabytes = CM.CM_WriteAreaBits(frame.areabits, clientarea);
+
+        // grab the current player_state_t
+        frame.ps.set(clent.client.ps);
+
+        SV_FatPVS(org);
+        clientphs = CM.CM_ClusterPHS(clientcluster);
+
+        // build up the list of visible entities
+        frame.num_entities = 0;
+        frame.first_entity = SV_INIT.svs.next_client_entities;
+
+        for (e = 1; e < GameBase.num_edicts; e++) {
+            ent = GameBase.g_edicts[e];
+
+            // ignore ents without visible models
+            if ((ent.svflags & Defines.SVF_NOCLIENT) != 0)
+                continue;
+
+            // ignore ents without visible models unless they have an effect
+            if (0 == ent.s.modelindex && 0 == ent.s.effects && 0 == ent.s.sound
+                    && 0 == ent.s.event)
+                continue;
+
+            // ignore if not touching a PV leaf
+            // check area
+            if (ent != clent) {
+                if (!CM.CM_AreasConnected(clientarea, ent.areanum)) {
+                    // doors can legally straddle two areas, so we may need to check another one
+                    if (0 == ent.areanum2 || !CM.CM_AreasConnected(clientarea, ent.areanum2))
+                        continue; // blocked by a door
+                }
+
+            }
+
+            // add it to the circular client_entities array
+            int ix = SV_INIT.svs.next_client_entities
+                    % SV_INIT.svs.num_client_entities;
+            state = SV_INIT.svs.client_entities[ix];
+            if (ent.s.number != e) {
+                Com.DPrintf("FIXING ENT.S.NUMBER!!!\n");
+                ent.s.number = e;
+            }
+
+            //*state = ent.s;
+            SV_INIT.svs.client_entities[ix].set(ent.s);
+
+            // don't mark players missiles as solid
+            if (ent.owner == client.edict)
+                state.solid = 0;
+
+            SV_INIT.svs.next_client_entities++;
+            frame.num_entities++;
+        }
+    }
+
+    /**
+     * Save everything in the world out without deltas. Used for recording
+     * footage for merged or assembled demos.
+     */
+    public static void SV_RecordDemoMessage() {
+        int e;
+        EDict ent;
+        entity_state_t nostate = new entity_state_t(null);
+        sizebuf_t buf = new sizebuf_t();
+        byte buf_data[] = new byte[32768];
+        int len;
+
+        if (SV_INIT.svs.demofile == null)
+            return;
+
+        //memset (nostate, 0, sizeof(nostate));
+        SZ.Init(buf, buf_data, buf_data.length);
+
+        // write a frame message that doesn't contain a player_state_t
+        MSG.WriteByte(buf, Defines.svc_frame);
+        MSG.WriteLong(buf, SV_INIT.sv.framenum);
+
+        MSG.WriteByte(buf, Defines.svc_packetentities);
+
+        e = 1;
+        ent = GameBase.g_edicts[e];
+
+        while (e < GameBase.num_edicts) {
+            // ignore ents without visible models unless they have an effect
+            if (ent.inuse
+                    && ent.s.number != 0
+                    && (ent.s.modelindex != 0 || ent.s.effects != 0
+                    || ent.s.sound != 0 || ent.s.event != 0)
+                    && 0 == (ent.svflags & Defines.SVF_NOCLIENT))
+                MSG.WriteDeltaEntity(nostate, ent.s, buf, false, true);
+
+            e++;
+            ent = GameBase.g_edicts[e];
+        }
+
+        MSG.WriteShort(buf, 0); // end of packetentities
+
+        // now add the accumulated multicast information
+        SZ.Write(buf, SV_INIT.svs.demo_multicast.data,
+                SV_INIT.svs.demo_multicast.cursize);
+        SZ.Clear(SV_INIT.svs.demo_multicast);
+
+        // now write the entire message to the file, prefixed by the length
+        len = EndianHandler.swapInt(buf.cursize);
+
+        try {
+            //fwrite (len, 4, 1, svs.demofile);
+            SV_INIT.svs.demofile.writeInt(len);
+            //fwrite (buf.data, buf.cursize, 1, svs.demofile);
+            SV_INIT.svs.demofile.write(buf.data, 0, buf.cursize);
+        } catch (IOException e1) {
+            Com.Printf("Error writing demo file:" + e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/server/SV_GAME.java b/src/main/java/lwjake2/server/SV_GAME.java
new file mode 100644 (file)
index 0000000..5e248fc
--- /dev/null
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.server;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.*;
+import lwjake2.qcommon.CM;
+import lwjake2.qcommon.Com;
+import lwjake2.qcommon.MSG;
+import lwjake2.qcommon.SZ;
+import lwjake2.util.Math3D;
+
+public class SV_GAME {
+
+    /**
+     * PF_Unicast
+     * <p>
+     * Sends the contents of the mutlicast buffer to a single client.
+     */
+    public static void PF_Unicast(EDict ent, boolean reliable) {
+        int p;
+        client_t client;
+
+        if (ent == null)
+            return;
+
+        p = ent.index;
+        if (p < 1 || p > Server.maxclients.value)
+            return;
+
+        client = SV_INIT.svs.clients[p - 1];
+
+        if (reliable)
+            SZ.Write(client.netchan.message, SV_INIT.sv.multicast.data,
+                    SV_INIT.sv.multicast.cursize);
+        else
+            SZ.Write(client.datagram, SV_INIT.sv.multicast.data,
+                    SV_INIT.sv.multicast.cursize);
+
+        SZ.Clear(SV_INIT.sv.multicast);
+    }
+
+    /**
+     * PF_dprintf
+     * <p>
+     * Debug print to server console.
+     */
+    public static void PF_dprintf(String fmt) {
+        Com.Printf(fmt);
+    }
+
+
+    /**
+     * Centerprintf for critical messages.
+     */
+    public static void PF_cprintfhigh(EDict ent, String fmt) {
+        PF_cprintf(ent, Defines.PRINT_HIGH, fmt);
+    }
+
+    /**
+     * PF_cprintf
+     * <p>
+     * Print to a single client.
+     */
+    public static void PF_cprintf(EDict ent, int level, String fmt) {
+
+        int n = 0;
+
+        if (ent != null) {
+            n = ent.index;
+            if (n < 1 || n > Server.maxclients.value)
+                Com.Error(Defines.ERR_DROP, "cprintf to a non-client");
+        }
+
+        if (ent != null)
+            SV_SEND.SV_ClientPrintf(SV_INIT.svs.clients[n - 1], level, fmt);
+        else
+            Com.Printf(fmt);
+    }
+
+    /**
+     * PF_centerprintf
+     * <p>
+     * centerprint to a single client.
+     */
+    public static void PF_centerprintf(EDict ent, String fmt) {
+        int n;
+
+        n = ent.index;
+        if (n < 1 || n > Server.maxclients.value)
+            return; // Com_Error (ERR_DROP, "centerprintf to a non-client");
+
+        MSG.WriteByte(SV_INIT.sv.multicast, Defines.svc_centerprint);
+        MSG.WriteString(SV_INIT.sv.multicast, fmt);
+        PF_Unicast(ent, true);
+    }
+
+    public static void PF_error(int level, String fmt) {
+        Com.Error(level, fmt);
+    }
+
+    /**
+     * PF_setmodel
+     * <p>
+     * Also sets mins and maxs for inline bmodels.
+     */
+    public static void PF_setmodel(EDict ent, String name) {
+        int i;
+        cmodel_t mod;
+
+        if (name == null)
+            Com.Error(Defines.ERR_DROP, "PF_setmodel: NULL");
+
+        i = SV_INIT.SV_ModelIndex(name);
+
+        ent.s.modelindex = i;
+
+        // if it is an inline model, get the size information for it
+        if (name.startsWith("*")) {
+            mod = CM.InlineModel(name);
+            Math3D.vectorCopy(mod.mins, ent.mins);
+            Math3D.vectorCopy(mod.maxs, ent.maxs);
+            SV_WORLD.SV_LinkEdict(ent);
+        }
+    }
+
+    /**
+     * PF_Configstring
+     */
+    public static void PF_Configstring(int index, String val) {
+        if (index < 0 || index >= Defines.MAX_CONFIGSTRINGS)
+            Com.Error(Defines.ERR_DROP, "configstring: bad index " + index
+                    + "\n");
+
+        if (val == null)
+            val = "";
+
+        // change the string in sv
+        SV_INIT.sv.configstrings[index] = val;
+
+        if (SV_INIT.sv.state != Defines.ss_loading) { // send the update to
+            // everyone
+            SZ.Clear(SV_INIT.sv.multicast);
+            MSG.WriteChar(SV_INIT.sv.multicast, Defines.svc_configstring);
+            MSG.WriteShort(SV_INIT.sv.multicast, index);
+            MSG.WriteString(SV_INIT.sv.multicast, val);
+
+            SV_SEND.SV_Multicast(Globals.vec3_origin, Defines.MULTICAST_ALL_R);
+        }
+    }
+
+    public static void PF_WriteByte(int c) {
+        MSG.WriteByte(SV_INIT.sv.multicast, c);
+    }
+
+    public static void PF_WriteShort(int c) {
+        MSG.WriteShort(SV_INIT.sv.multicast, c);
+    }
+
+    public static void PF_WriteString(String s) {
+        MSG.WriteString(SV_INIT.sv.multicast, s);
+    }
+
+    public static void PF_WritePos(float[] pos) {
+        MSG.WritePos(SV_INIT.sv.multicast, pos);
+    }
+
+    public static void PF_WriteDir(float[] dir) {
+        MSG.WriteDir(SV_INIT.sv.multicast, dir);
+    }
+
+    /**
+     * PF_inPHS.
+     * <p>
+     * Also checks portalareas so that doors block sound.
+     */
+    public static boolean PF_inPHS(float[] p1, float[] p2) {
+        int leafnum;
+        int cluster;
+        int area1, area2;
+        byte mask[];
+
+        leafnum = CM.CM_PointLeafnum(p1);
+        cluster = CM.CM_LeafCluster(leafnum);
+        area1 = CM.CM_LeafArea(leafnum);
+        mask = CM.CM_ClusterPHS(cluster);
+
+        leafnum = CM.CM_PointLeafnum(p2);
+        cluster = CM.CM_LeafCluster(leafnum);
+        area2 = CM.CM_LeafArea(leafnum);
+
+        // quake2 bugfix
+        if (cluster == -1)
+            return false;
+        // more than one bounce away
+        return !(mask != null && (0 == (mask[cluster >> 3] & (1 << (cluster & 7))))) && CM.CM_AreasConnected(area1, area2);
+
+    }
+
+    public static void PF_StartSound(EDict entity, int channel,
+                                     int sound_num, float volume, float attenuation, float timeofs) {
+
+        if (null == entity)
+            return;
+        SV_SEND.SV_StartSound(null, entity, channel, sound_num, volume,
+                attenuation, timeofs);
+
+    }
+
+
+    /**
+     * SV_ShutdownGameProgs
+     * <p>
+     * Called when either the entire server is being killed, or it is changing
+     * to a different game directory.
+     */
+    public static void SV_ShutdownGameProgs() {
+        GameBase.ShutdownGame();
+    }
+
+    /**
+     * SV_InitGameProgs
+     * <p>
+     * Init the game subsystem for a new map.
+     */
+
+    public static void SV_InitGameProgs() {
+
+        // unload anything we have now
+        SV_ShutdownGameProgs();
+
+        game_import_t gimport = new game_import_t();
+
+        // all functions set in game_export_t (rst)
+        GameBase.GetGameApi(gimport);
+
+        GameSave.InitGame();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/server/SV_INIT.java b/src/main/java/lwjake2/server/SV_INIT.java
new file mode 100644 (file)
index 0000000..7ca15ec
--- /dev/null
@@ -0,0 +1,462 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.server;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.client.Client;
+import lwjake2.client.SCR;
+import lwjake2.game.*;
+import lwjake2.qcommon.*;
+import lwjake2.sys.NET;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+public class SV_INIT {
+
+    public static server_static_t svs = new server_static_t(); // persistant
+    public static server_t sv = new server_t(); // local server
+    private static String firstmap = "";
+
+    /**
+     * SV_FindIndex.
+     */
+    private static int SV_FindIndex(String name, int start, int max) {
+        int i;
+
+        if (name == null || name.length() == 0)
+            return 0;
+
+        for (i = 1; i < max && sv.configstrings[start + i] != null; i++)
+            if (0 == Lib.strcmp(sv.configstrings[start + i], name))
+                return i;
+
+        if (i == max)
+            Com.Error(Defines.ERR_DROP, "*Index: overflow");
+
+        sv.configstrings[start + i] = name;
+
+        if (sv.state != Defines.ss_loading) {
+            // send the update to everyone
+            SZ.Clear(sv.multicast);
+            MSG.WriteChar(sv.multicast, Defines.svc_configstring);
+            MSG.WriteShort(sv.multicast, start + i);
+            MSG.WriteString(sv.multicast, name);
+            SV_SEND.SV_Multicast(Globals.vec3_origin, Defines.MULTICAST_ALL_R);
+        }
+
+        return i;
+    }
+
+    public static int SV_ModelIndex(String name) {
+        return SV_FindIndex(name, Defines.CS_MODELS, Defines.MAX_MODELS);
+    }
+
+    public static int SV_SoundIndex(String name) {
+        return SV_FindIndex(name, Defines.CS_SOUNDS, Defines.MAX_SOUNDS);
+    }
+
+    public static int SV_ImageIndex(String name) {
+        return SV_FindIndex(name, Defines.CS_IMAGES, Defines.MAX_IMAGES);
+    }
+
+    /**
+     * SV_CreateBaseline
+     * <p>
+     * Entity baselines are used to compress the update messages to the clients --
+     * only the fields that differ from the baseline will be transmitted.
+     */
+    private static void SV_CreateBaseline() {
+        EDict svent;
+        int entnum;
+
+        for (entnum = 1; entnum < GameBase.num_edicts; entnum++) {
+            svent = GameBase.g_edicts[entnum];
+
+            if (!svent.inuse)
+                continue;
+            if (0 == svent.s.modelindex && 0 == svent.s.sound
+                    && 0 == svent.s.effects)
+                continue;
+
+            svent.s.number = entnum;
+
+            // take current state as baseline
+            Math3D.vectorCopy(svent.s.origin, svent.s.old_origin);
+            sv.baselines[entnum].set(svent.s);
+        }
+    }
+
+    /**
+     * SV_CheckForSavegame.
+     */
+    private static void SV_CheckForSavegame() {
+
+        String name;
+        RandomAccessFile f;
+
+        int i;
+
+        if (Server.sv_noreload.value != 0)
+            return;
+
+        if (Cvar.variableValue("deathmatch") != 0)
+            return;
+
+        name = FS.Gamedir() + "/save/current/" + sv.name + ".sav";
+        try {
+            f = new RandomAccessFile(name, "r");
+        } catch (Exception e) {
+            return;
+        }
+
+        try {
+            f.close();
+        } catch (IOException e1) {
+            e1.printStackTrace();
+        }
+
+        SV_WORLD.SV_ClearWorld();
+
+        // get configstrings and areaportals
+        SV_CCMDS.SV_ReadLevelFile();
+
+        if (!sv.loadgame) {
+            // coming back to a level after being in a different
+            // level, so run it for ten seconds
+
+            // rlava2 was sending too many lightstyles, and overflowing the
+            // reliable data. temporarily changing the server state to loading
+            // prevents these from being passed down.
+            int previousState; // PGM
+
+            previousState = sv.state; // PGM
+            sv.state = Defines.ss_loading; // PGM
+            for (i = 0; i < 100; i++)
+                GameBase.G_RunFrame();
+
+            sv.state = previousState; // PGM
+        }
+    }
+
+    /**
+     * SV_SpawnServer.
+     * <p>
+     * Change the server to a new map, taking all connected clients along with
+     * it.
+     */
+    private static void SV_SpawnServer(String server, String spawnpoint,
+                                       int serverstate, boolean attractloop, boolean loadgame) {
+        int i;
+        int checksum = 0;
+
+        if (attractloop)
+            Cvar.set("paused", "0");
+
+        Com.Printf("------- Server Initialization -------\n");
+
+        Com.DPrintf("SpawnServer: " + server + "\n");
+        if (sv.demofile != null)
+            try {
+                sv.demofile.close();
+            } catch (Exception ignored) {
+            }
+
+        // any partially connected client will be restarted
+        svs.spawncount++;
+
+        sv.state = Defines.ss_dead;
+
+        Globals.server_state = sv.state;
+
+        // wipe the entire per-level structure
+        sv = new server_t();
+
+        svs.realtime = 0;
+        sv.loadgame = loadgame;
+        sv.attractloop = attractloop;
+
+        // save name for levels that don't set message
+        sv.configstrings[Defines.CS_NAME] = server;
+
+        if (Cvar.variableValue("deathmatch") != 0) {
+            sv.configstrings[Defines.CS_AIRACCEL] = ""
+                    + Server.sv_airaccelerate.value;
+            PMove.pm_airaccelerate = Server.sv_airaccelerate.value;
+        } else {
+            sv.configstrings[Defines.CS_AIRACCEL] = "0";
+            PMove.pm_airaccelerate = 0;
+        }
+
+        SZ.Init(sv.multicast, sv.multicast_buf, sv.multicast_buf.length);
+
+        sv.name = server;
+
+        // leave slots at start for clients only
+        for (i = 0; i < Server.maxclients.value; i++) {
+            // needs to reconnect
+            if (svs.clients[i].state > Defines.cs_connected)
+                svs.clients[i].state = Defines.cs_connected;
+            svs.clients[i].lastframe = -1;
+        }
+
+        sv.time = 1000;
+
+        sv.name = server;
+        sv.configstrings[Defines.CS_NAME] = server;
+
+        int iw[] = {checksum};
+
+        if (serverstate != Defines.ss_game) {
+            sv.models[1] = CM.CM_LoadMap("", false, iw); // no real map
+        } else {
+            sv.configstrings[Defines.CS_MODELS + 1] = "maps/" + server + ".bsp";
+            sv.models[1] = CM.CM_LoadMap(
+                    sv.configstrings[Defines.CS_MODELS + 1], false, iw);
+        }
+        checksum = iw[0];
+        sv.configstrings[Defines.CS_MAPCHECKSUM] = "" + checksum;
+
+
+        // clear physics interaction links
+
+        SV_WORLD.SV_ClearWorld();
+
+        for (i = 1; i < CM.CM_NumInlineModels(); i++) {
+            sv.configstrings[Defines.CS_MODELS + 1 + i] = "*" + i;
+
+            // copy references
+            sv.models[i + 1] = CM.InlineModel(sv.configstrings[Defines.CS_MODELS + 1 + i]);
+        }
+
+
+        // spawn the rest of the entities on the map
+
+        // precache and static commands can be issued during
+        // map initialization
+
+        sv.state = Defines.ss_loading;
+        Globals.server_state = sv.state;
+
+        // load and spawn all other entities
+        GameSpawn.SpawnEntities(sv.name, CM.CM_EntityString(), spawnpoint);
+
+        // run two frames to allow everything to settle
+        GameBase.G_RunFrame();
+        GameBase.G_RunFrame();
+
+        // all precaches are complete
+        sv.state = serverstate;
+        Globals.server_state = sv.state;
+
+        // create a baseline for more efficient communications
+        SV_CreateBaseline();
+
+        // check for a savegame
+        SV_CheckForSavegame();
+
+        // set serverinfo variable
+        Cvar.fullSet("mapname", sv.name, Defines.CVAR_SERVERINFO
+                | Defines.CVAR_NOSET);
+    }
+
+    /**
+     * SV_InitGame.
+     * <p>
+     * A brand new game has been started.
+     */
+    public static void SV_InitGame() {
+        int i;
+        EDict ent;
+        //char idmaster[32];
+        String idmaster;
+
+        if (svs.initialized) {
+            // cause any connected clients to reconnect
+            Server.SV_Shutdown("Server restarted\n", true);
+        } else {
+            // make sure the client is down
+            Client.Drop();
+            SCR.BeginLoadingPlaque();
+        }
+
+        // get any latched variable changes (maxclients, etc)
+        Cvar.getLatchedVars();
+
+        svs.initialized = true;
+
+        if (Cvar.variableValue("coop") != 0
+                && Cvar.variableValue("deathmatch") != 0) {
+            Com.Printf("Deathmatch and Coop both set, disabling Coop\n");
+            Cvar.fullSet("coop", "0", Defines.CVAR_SERVERINFO
+                    | Defines.CVAR_LATCH);
+        }
+
+        // dedicated servers are can't be single player and are usually DM
+        // so unless they explicity set coop, force it to deathmatch
+        if (Globals.dedicated.value != 0) {
+            if (0 == Cvar.variableValue("coop"))
+                Cvar.fullSet("deathmatch", "1", Defines.CVAR_SERVERINFO
+                        | Defines.CVAR_LATCH);
+        }
+
+        // init clients
+        if (Cvar.variableValue("deathmatch") != 0) {
+            if (Server.maxclients.value <= 1)
+                Cvar.fullSet("maxclients", "8", Defines.CVAR_SERVERINFO
+                        | Defines.CVAR_LATCH);
+            else if (Server.maxclients.value > Defines.MAX_CLIENTS)
+                Cvar.fullSet("maxclients", "" + Defines.MAX_CLIENTS,
+                        Defines.CVAR_SERVERINFO | Defines.CVAR_LATCH);
+        } else if (Cvar.variableValue("coop") != 0) {
+            if (Server.maxclients.value <= 1 || Server.maxclients.value > 4)
+                Cvar.fullSet("maxclients", "4", Defines.CVAR_SERVERINFO
+                        | Defines.CVAR_LATCH);
+
+        } else // non-deathmatch, non-coop is one player
+        {
+            Cvar.fullSet("maxclients", "1", Defines.CVAR_SERVERINFO
+                    | Defines.CVAR_LATCH);
+        }
+
+        svs.spawncount = Lib.rand();
+        svs.clients = new client_t[(int) Server.maxclients.value];
+        for (int n = 0; n < svs.clients.length; n++) {
+            svs.clients[n] = new client_t();
+            svs.clients[n].serverindex = n;
+        }
+        svs.num_client_entities = ((int) Server.maxclients.value)
+                * Defines.UPDATE_BACKUP * 64; //ok.
+
+        svs.client_entities = new entity_state_t[svs.num_client_entities];
+        for (int n = 0; n < svs.client_entities.length; n++)
+            svs.client_entities[n] = new entity_state_t(null);
+
+        // init network stuff
+        NET.Config((Server.maxclients.value > 1));
+
+        // heartbeats will always be sent to the id master
+        svs.last_heartbeat = -99999; // send immediately
+        idmaster = "192.246.40.37:" + Defines.PORT_MASTER;
+        NET.StringToAdr(idmaster, Server.master_adr[0]);
+
+        // init game
+        SV_GAME.SV_InitGameProgs();
+
+        for (i = 0; i < Server.maxclients.value; i++) {
+            ent = GameBase.g_edicts[i + 1];
+            svs.clients[i].edict = ent;
+            svs.clients[i].lastcmd = new usercmd_t();
+        }
+    }
+    // server info
+
+    /**
+     * SV_Map
+     * <p>
+     * the full syntax is:
+     * <p>
+     * map [*] <map>$ <startspot>+ <nextserver>
+     * <p>
+     * command from the console or progs. Map can also be a.cin, .pcx, or .dm2 file.
+     * <p>
+     * Nextserver is used to allow a cinematic to play, then proceed to
+     * another level:
+     * <p>
+     * map tram.cin+jail_e3
+     */
+    public static void SV_Map(boolean attractloop, String levelstring, boolean loadgame) {
+
+        int l;
+        String level, spawnpoint;
+
+        sv.loadgame = loadgame;
+        sv.attractloop = attractloop;
+
+        if (sv.state == Defines.ss_dead && !sv.loadgame)
+            SV_InitGame(); // the game is just starting
+
+        level = levelstring; // bis hier her ok.
+
+        // if there is a + in the map, set nextserver to the remainder
+
+        int c = level.indexOf('+');
+        if (c != -1) {
+            Cvar.set("nextserver", "gamemap \"" + level.substring(c + 1) + "\"");
+            level = level.substring(0, c);
+        } else {
+            Cvar.set("nextserver", "");
+        }
+
+        // rst: base1 works for full, damo1 works for demo, so we need to store first map.
+        if (firstmap.length() == 0) {
+            if (!levelstring.endsWith(".cin") && !levelstring.endsWith(".pcx") && !levelstring.endsWith(".dm2")) {
+                int pos = levelstring.indexOf('+');
+                firstmap = levelstring.substring(pos + 1);
+            }
+        }
+
+        // ZOID: special hack for end game screen in coop mode
+        if (Cvar.variableValue("coop") != 0 && level.equals("victory.pcx"))
+            Cvar.set("nextserver", "gamemap \"*" + firstmap + "\"");
+
+        // if there is a $, use the remainder as a spawnpoint
+        int pos = level.indexOf('$');
+        if (pos != -1) {
+            spawnpoint = level.substring(pos + 1);
+            level = level.substring(0, pos);
+
+        } else
+            spawnpoint = "";
+
+        // skip the end-of-unit flag * if necessary
+        if (level.charAt(0) == '*')
+            level = level.substring(1);
+
+        l = level.length();
+        if (l > 4 && level.endsWith(".cin")) {
+            SCR.BeginLoadingPlaque(); // for local system
+            SV_SEND.SV_BroadcastCommand("changing\n");
+            SV_SpawnServer(level, spawnpoint, Defines.ss_cinematic,
+                    attractloop, loadgame);
+        } else if (l > 4 && level.endsWith(".dm2")) {
+            SCR.BeginLoadingPlaque(); // for local system
+            SV_SEND.SV_BroadcastCommand("changing\n");
+            SV_SpawnServer(level, spawnpoint, Defines.ss_demo, attractloop,
+                    loadgame);
+        } else if (l > 4 && level.endsWith(".pcx")) {
+            SCR.BeginLoadingPlaque(); // for local system
+            SV_SEND.SV_BroadcastCommand("changing\n");
+            SV_SpawnServer(level, spawnpoint, Defines.ss_pic, attractloop,
+                    loadgame);
+        } else {
+            SCR.BeginLoadingPlaque(); // for local system
+            SV_SEND.SV_BroadcastCommand("changing\n");
+            SV_SEND.SV_SendClientMessages();
+            SV_SpawnServer(level, spawnpoint, Defines.ss_game, attractloop,
+                    loadgame);
+            CommandBuffer.CopyToDefer();
+        }
+
+        SV_SEND.SV_BroadcastCommand("reconnect\n");
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/server/SV_SEND.java b/src/main/java/lwjake2/server/SV_SEND.java
new file mode 100644 (file)
index 0000000..8deef97
--- /dev/null
@@ -0,0 +1,524 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.server;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.EDict;
+import lwjake2.game.EndianHandler;
+import lwjake2.qcommon.*;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+
+import java.io.IOException;
+
+public class SV_SEND {
+    /*
+    =============================================================================
+       
+       Com_Printf redirection
+       
+       =============================================================================
+       */
+
+    public static final StringBuffer sv_outputbuf = new StringBuffer();
+    private static final float[] origin_v = {0, 0, 0};
+    private static final sizebuf_t msg = new sizebuf_t();
+    /*
+    =============================================================================
+
+       EVENT MESSAGES
+
+       =============================================================================
+       */
+    private static final byte msgbuf[] = new byte[Defines.MAX_MSGLEN];
+    private static final byte[] NULLBYTE = {0};
+
+    public static void SV_FlushRedirect(int sv_redirected, byte outputbuf[]) {
+        if (sv_redirected == Defines.RD_PACKET) {
+            String s = ("print\n" + Lib.CtoJava(outputbuf));
+            Netchan.Netchan_OutOfBand(Defines.NS_SERVER, Globals.net_from, s.length(), Lib.stringToBytes(s));
+        } else if (sv_redirected == Defines.RD_CLIENT) {
+            MSG.WriteByte(Server.sv_client.netchan.message, Defines.svc_print);
+            MSG.WriteByte(Server.sv_client.netchan.message, Defines.PRINT_HIGH);
+            MSG.WriteString(Server.sv_client.netchan.message, outputbuf);
+        }
+    }
+
+    /*
+    =================
+    SV_ClientPrintf
+
+    Sends text across to be displayed if the level passes
+    =================
+    */
+    public static void SV_ClientPrintf(client_t cl, int level, String s) {
+
+        if (level < cl.messagelevel)
+            return;
+
+        MSG.WriteByte(cl.netchan.message, Defines.svc_print);
+        MSG.WriteByte(cl.netchan.message, level);
+        MSG.WriteString(cl.netchan.message, s);
+    }
+
+    /*
+    =================
+    SV_BroadcastPrintf
+
+    Sends text to all active clients
+    =================
+    */
+    public static void SV_BroadcastPrintf(int level, String s) {
+
+        client_t cl;
+
+        // echo to console
+        if (Globals.dedicated.value != 0) {
+
+            Com.Printf(s);
+        }
+
+        for (int i = 0; i < Server.maxclients.value; i++) {
+            cl = SV_INIT.svs.clients[i];
+            if (level < cl.messagelevel)
+                continue;
+            if (cl.state != Defines.cs_spawned)
+                continue;
+            MSG.WriteByte(cl.netchan.message, Defines.svc_print);
+            MSG.WriteByte(cl.netchan.message, level);
+            MSG.WriteString(cl.netchan.message, s);
+        }
+    }
+    /*
+    ===============================================================================
+       
+       FRAME UPDATES
+       
+       ===============================================================================
+       */
+
+    /*
+    =================
+    SV_BroadcastCommand
+
+    Sends text to all active clients
+    =================
+    */
+    public static void SV_BroadcastCommand(String s) {
+
+        if (SV_INIT.sv.state == 0)
+            return;
+
+        MSG.WriteByte(SV_INIT.sv.multicast, Defines.svc_stufftext);
+        MSG.WriteString(SV_INIT.sv.multicast, s);
+        SV_Multicast(null, Defines.MULTICAST_ALL_R);
+    }
+
+    /*
+    =================
+    SV_Multicast
+
+    Sends the contents of sv.multicast to a subset of the clients,
+    then clears sv.multicast.
+
+    MULTICAST_ALL      same as broadcast (origin can be null)
+    MULTICAST_PVS      send to clients potentially visible from org
+    MULTICAST_PHS      send to clients potentially hearable from org
+    =================
+    */
+    public static void SV_Multicast(float[] origin, int to) {
+        client_t client;
+        byte mask[];
+        int leafnum, cluster;
+        int j;
+        boolean reliable;
+        int area1, area2;
+
+        reliable = false;
+
+        if (to != Defines.MULTICAST_ALL_R && to != Defines.MULTICAST_ALL) {
+            leafnum = CM.CM_PointLeafnum(origin);
+            area1 = CM.CM_LeafArea(leafnum);
+        } else {
+            leafnum = 0; // just to avoid compiler warnings
+            area1 = 0;
+        }
+
+        // if doing a serverrecord, store everything
+        if (SV_INIT.svs.demofile != null)
+            SZ.Write(SV_INIT.svs.demo_multicast, SV_INIT.sv.multicast.data, SV_INIT.sv.multicast.cursize);
+
+        switch (to) {
+            case Defines.MULTICAST_ALL_R:
+                reliable = true; // intentional fallthrough, no break here
+            case Defines.MULTICAST_ALL:
+                leafnum = 0;
+                mask = null;
+                break;
+
+            case Defines.MULTICAST_PHS_R:
+                reliable = true; // intentional fallthrough
+            case Defines.MULTICAST_PHS:
+                leafnum = CM.CM_PointLeafnum(origin);
+                cluster = CM.CM_LeafCluster(leafnum);
+                mask = CM.CM_ClusterPHS(cluster);
+                break;
+
+            case Defines.MULTICAST_PVS_R:
+                reliable = true; // intentional fallthrough
+            case Defines.MULTICAST_PVS:
+                leafnum = CM.CM_PointLeafnum(origin);
+                cluster = CM.CM_LeafCluster(leafnum);
+                mask = CM.CM_ClusterPVS(cluster);
+                break;
+
+            default:
+                mask = null;
+                Com.Error(Defines.ERR_FATAL, "SV_Multicast: bad to:" + to + "\n");
+        }
+
+        // send the data to all relevent clients
+        for (j = 0; j < Server.maxclients.value; j++) {
+            client = SV_INIT.svs.clients[j];
+
+            if (client.state == Defines.cs_free || client.state == Defines.cs_zombie)
+                continue;
+            if (client.state != Defines.cs_spawned && !reliable)
+                continue;
+
+            if (mask != null) {
+                leafnum = CM.CM_PointLeafnum(client.edict.s.origin);
+                cluster = CM.CM_LeafCluster(leafnum);
+                area2 = CM.CM_LeafArea(leafnum);
+                if (!CM.CM_AreasConnected(area1, area2))
+                    continue;
+
+                // quake2 bugfix
+                if (cluster == -1)
+                    continue;
+                if (mask != null && (0 == (mask[cluster >> 3] & (1 << (cluster & 7)))))
+                    continue;
+            }
+
+            if (reliable)
+                SZ.Write(client.netchan.message, SV_INIT.sv.multicast.data, SV_INIT.sv.multicast.cursize);
+            else
+                SZ.Write(client.datagram, SV_INIT.sv.multicast.data, SV_INIT.sv.multicast.cursize);
+        }
+
+        SZ.Clear(SV_INIT.sv.multicast);
+    }
+
+    /*
+    ==================
+    SV_StartSound
+
+    Each entity can have eight independant sound sources, like voice,
+    weapon, feet, etc.
+
+    If cahnnel & 8, the sound will be sent to everyone, not just
+    things in the PHS.
+
+    FIXME: if entity isn't in PHS, they must be forced to be sent or
+    have the origin explicitly sent.
+
+    Channel 0 is an auto-allocate channel, the others override anything
+    already running on that entity/channel pair.
+
+    An attenuation of 0 will play full volume everywhere in the level.
+    Larger attenuations will drop off.  (max 4 attenuation)
+
+    Timeofs can range from 0.0 to 0.1 to cause sounds to be started
+    later in the frame than they normally would.
+
+    If origin is null, the origin is determined from the entity origin
+    or the midpoint of the entity box for bmodels.
+    ==================
+    */
+    public static void SV_StartSound(
+            float[] origin,
+            EDict entity,
+            int channel,
+            int soundindex,
+            float volume,
+            float attenuation,
+            float timeofs) {
+        int sendchan;
+        int flags;
+        int i;
+        int ent;
+        boolean use_phs;
+
+        if (volume < 0 || volume > 1.0)
+            Com.Error(Defines.ERR_FATAL, "SV_StartSound: volume = " + volume);
+
+        if (attenuation < 0 || attenuation > 4)
+            Com.Error(Defines.ERR_FATAL, "SV_StartSound: attenuation = " + attenuation);
+
+        //     if (channel < 0 || channel > 15)
+        //             Com_Error (ERR_FATAL, "SV_StartSound: channel = %i", channel);
+
+        if (timeofs < 0 || timeofs > 0.255)
+            Com.Error(Defines.ERR_FATAL, "SV_StartSound: timeofs = " + timeofs);
+
+        ent = entity.index;
+
+        // no PHS flag
+        if ((channel & 8) != 0) {
+            use_phs = false;
+            channel &= 7;
+        } else
+            use_phs = true;
+
+        sendchan = (ent << 3) | (channel & 7);
+
+        flags = 0;
+        if (volume != Defines.DEFAULT_SOUND_PACKET_VOLUME)
+            flags |= Defines.SND_VOLUME;
+        if (attenuation != Defines.DEFAULT_SOUND_PACKET_ATTENUATION)
+            flags |= Defines.SND_ATTENUATION;
+
+        // the client doesn't know that bmodels have weird origins
+        // the origin can also be explicitly set
+        if ((entity.svflags & Defines.SVF_NOCLIENT) != 0 || (entity.solid == Defines.SOLID_BSP) || origin != null)
+            flags |= Defines.SND_POS;
+
+        // always send the entity number for channel overrides
+        flags |= Defines.SND_ENT;
+
+        if (timeofs != 0)
+            flags |= Defines.SND_OFFSET;
+
+        // use the entity origin unless it is a bmodel or explicitly specified
+        if (origin == null) {
+            origin = origin_v;
+            if (entity.solid == Defines.SOLID_BSP) {
+                for (i = 0; i < 3; i++)
+                    origin_v[i] = entity.s.origin[i] + 0.5f * (entity.mins[i] + entity.maxs[i]);
+            } else {
+                Math3D.vectorCopy(entity.s.origin, origin_v);
+            }
+        }
+
+        MSG.WriteByte(SV_INIT.sv.multicast, Defines.svc_sound);
+        MSG.WriteByte(SV_INIT.sv.multicast, flags);
+        MSG.WriteByte(SV_INIT.sv.multicast, soundindex);
+
+        if ((flags & Defines.SND_VOLUME) != 0)
+            MSG.WriteByte(SV_INIT.sv.multicast, volume * 255);
+        if ((flags & Defines.SND_ATTENUATION) != 0)
+            MSG.WriteByte(SV_INIT.sv.multicast, attenuation * 64);
+        if ((flags & Defines.SND_OFFSET) != 0)
+            MSG.WriteByte(SV_INIT.sv.multicast, timeofs * 1000);
+
+        if ((flags & Defines.SND_ENT) != 0)
+            MSG.WriteShort(SV_INIT.sv.multicast, sendchan);
+
+        if ((flags & Defines.SND_POS) != 0)
+            MSG.WritePos(SV_INIT.sv.multicast, origin);
+
+        // if the sound doesn't attenuate,send it to everyone
+        // (global radio chatter, voiceovers, etc)
+        if (attenuation == Defines.ATTN_NONE)
+            use_phs = false;
+
+        if ((channel & Defines.CHAN_RELIABLE) != 0) {
+            if (use_phs)
+                SV_Multicast(origin, Defines.MULTICAST_PHS_R);
+            else
+                SV_Multicast(origin, Defines.MULTICAST_ALL_R);
+        } else {
+            if (use_phs)
+                SV_Multicast(origin, Defines.MULTICAST_PHS);
+            else
+                SV_Multicast(origin, Defines.MULTICAST_ALL);
+        }
+    }
+
+    /*
+    =======================
+    SV_SendClientDatagram
+    =======================
+    */
+    private static void SV_SendClientDatagram(client_t client) {
+        //byte msg_buf[] = new byte[Defines.MAX_MSGLEN];
+
+        SV_ENTS.SV_BuildClientFrame(client);
+
+        SZ.Init(msg, msgbuf, msgbuf.length);
+        msg.allowoverflow = true;
+
+        // send over all the relevant entity_state_t
+        // and the player_state_t
+        SV_ENTS.SV_WriteFrameToClient(client, msg);
+
+        // copy the accumulated multicast datagram
+        // for this client out to the message
+        // it is necessary for this to be after the WriteEntities
+        // so that entity references will be current
+        if (client.datagram.overflowed)
+            Com.Printf("WARNING: datagram overflowed for " + client.name + "\n");
+        else
+            SZ.Write(msg, client.datagram.data, client.datagram.cursize);
+        SZ.Clear(client.datagram);
+
+        if (msg.overflowed) { // must have room left for the packet header
+            Com.Printf("WARNING: msg overflowed for " + client.name + "\n");
+            SZ.Clear(msg);
+        }
+
+        // send the datagram
+        Netchan.Transmit(client.netchan, msg.cursize, msg.data);
+
+        // record the size for rate estimation
+        client.message_size[SV_INIT.sv.framenum % Defines.RATE_MESSAGES] = msg.cursize;
+
+    }
+
+    /*
+    ==================
+    SV_DemoCompleted
+    ==================
+    */
+    private static void SV_DemoCompleted() {
+        if (SV_INIT.sv.demofile != null) {
+            try {
+                SV_INIT.sv.demofile.close();
+            } catch (IOException e) {
+                Com.Printf("IOError closing d9emo fiele:" + e);
+            }
+            SV_INIT.sv.demofile = null;
+        }
+        SV_USER.SV_Nextserver();
+    }
+
+    /*
+    =======================
+    SV_RateDrop
+
+    Returns true if the client is over its current
+    bandwidth estimation and should not be sent another packet
+    =======================
+    */
+    private static boolean SV_RateDrop(client_t c) {
+        int total;
+        int i;
+
+        // never drop over the loopback
+        if (c.netchan.remote_address.type == Defines.NA_LOOPBACK)
+            return false;
+
+        total = 0;
+
+        for (i = 0; i < Defines.RATE_MESSAGES; i++) {
+            total += c.message_size[i];
+        }
+
+        if (total > c.rate) {
+            c.surpressCount++;
+            c.message_size[SV_INIT.sv.framenum % Defines.RATE_MESSAGES] = 0;
+            return true;
+        }
+
+        return false;
+    }
+
+    /*
+    =======================
+    SV_SendClientMessages
+    =======================
+    */
+    public static void SV_SendClientMessages() {
+        int i;
+        client_t c;
+        int msglen;
+        int r;
+
+        msglen = 0;
+
+        // read the next demo message if needed
+        if (SV_INIT.sv.state == Defines.ss_demo && SV_INIT.sv.demofile != null) {
+            if (Server.sv_paused.value != 0)
+                msglen = 0;
+            else {
+                // get the next message
+                //r = fread (&msglen, 4, 1, sv.demofile);
+                try {
+                    msglen = EndianHandler.swapInt(SV_INIT.sv.demofile.readInt());
+                } catch (Exception e) {
+                    SV_DemoCompleted();
+                    return;
+                }
+
+                //msglen = LittleLong (msglen);
+                if (msglen == -1) {
+                    SV_DemoCompleted();
+                    return;
+                }
+                if (msglen > Defines.MAX_MSGLEN)
+                    Com.Error(Defines.ERR_DROP, "SV_SendClientMessages: msglen > MAX_MSGLEN");
+
+                //r = fread (msgbuf, msglen, 1, sv.demofile);
+                r = 0;
+                try {
+                    r = SV_INIT.sv.demofile.read(msgbuf, 0, msglen);
+                } catch (IOException e1) {
+                    Com.Printf("IOError: reading demo file, " + e1);
+                }
+                if (r != msglen) {
+                    SV_DemoCompleted();
+                    return;
+                }
+            }
+        }
+
+        // send a message to each connected client
+        for (i = 0; i < Server.maxclients.value; i++) {
+            c = SV_INIT.svs.clients[i];
+
+            if (c.state == 0)
+                continue;
+            // if the reliable message overflowed,
+            // drop the client
+            if (c.netchan.message.overflowed) {
+                SZ.Clear(c.netchan.message);
+                SZ.Clear(c.datagram);
+                SV_BroadcastPrintf(Defines.PRINT_HIGH, c.name + " overflowed\n");
+                Server.SV_DropClient(c);
+            }
+
+            if (SV_INIT.sv.state == Defines.ss_cinematic
+                    || SV_INIT.sv.state == Defines.ss_demo
+                    || SV_INIT.sv.state == Defines.ss_pic)
+                Netchan.Transmit(c.netchan, msglen, msgbuf);
+            else if (c.state == Defines.cs_spawned) {
+                // don't overrun bandwidth
+                if (SV_RateDrop(c))
+                    continue;
+
+                SV_SendClientDatagram(c);
+            } else {
+                // just update reliable        if needed
+                if (c.netchan.message.cursize != 0 || Globals.curtime - c.netchan.last_sent > 1000)
+                    Netchan.Transmit(c.netchan, 0, NULLBYTE);
+            }
+        }
+    }
+}
diff --git a/src/main/java/lwjake2/server/SV_USER.java b/src/main/java/lwjake2/server/SV_USER.java
new file mode 100644 (file)
index 0000000..e4efb94
--- /dev/null
@@ -0,0 +1,637 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.server;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.*;
+import lwjake2.qcommon.*;
+import lwjake2.util.Lib;
+
+import java.io.IOException;
+
+public class SV_USER {
+
+    public static final int MAX_STRINGCMDS = 8;
+    static EDict sv_player;
+    static final ucmd_t[] ucmds = {
+            // auto issued
+            new ucmd_t("new", SV_USER::SV_New_f), new ucmd_t("configstrings", SV_USER::SV_Configstrings_f), new ucmd_t("baselines", SV_USER::SV_Baselines_f), new ucmd_t("begin", SV_USER::SV_Begin_f), new ucmd_t("nextserver", SV_USER::SV_Nextserver_f), new ucmd_t("disconnect", SV_USER::SV_Disconnect_f),
+
+            // issued by hand at client consoles
+            new ucmd_t("info", SV_USER::SV_ShowServerinfo_f), new ucmd_t("download", SV_USER::SV_BeginDownload_f), new ucmd_t("nextdl", SV_USER::SV_NextDownload_f)};
+
+    /*
+     * ================== SV_BeginDemoServer ==================
+     */
+    public static void SV_BeginDemoserver() {
+        String name;
+
+        name = "demos/" + SV_INIT.sv.name;
+        try {
+            SV_INIT.sv.demofile = FS.FOpenFile(name);
+        } catch (IOException e) {
+            Com.Error(Defines.ERR_DROP, "Couldn't open " + name + "\n");
+        }
+        if (SV_INIT.sv.demofile == null)
+            Com.Error(Defines.ERR_DROP, "Couldn't open " + name + "\n");
+    }
+
+    /*
+     * ============================================================
+     * 
+     * USER STRINGCMD EXECUTION
+     * 
+     * sv_client and sv_player will be valid.
+     * ============================================================
+     */
+
+    /*
+     * ================ SV_New_f
+     *
+     * Sends the first message from the server to a connected client. This will
+     * be sent on the initial connection and upon each server load.
+     * ================
+     */
+    public static void SV_New_f() {
+        String gamedir;
+        int playernum;
+        EDict ent;
+
+        Com.DPrintf("New() from " + Server.sv_client.name + "\n");
+
+        if (Server.sv_client.state != Defines.cs_connected) {
+            Com.Printf("New not valid -- already spawned\n");
+            return;
+        }
+
+        // demo servers just dump the file message
+        if (SV_INIT.sv.state == Defines.ss_demo) {
+            SV_BeginDemoserver();
+            return;
+        }
+
+        //
+        // serverdata needs to go over for all types of servers
+        // to make sure the protocol is right, and to set the gamedir
+        //
+        gamedir = Cvar.variableString("gamedir");
+
+        // send the serverdata
+        MSG.WriteByte(Server.sv_client.netchan.message,
+                Defines.svc_serverdata);
+        MSG.WriteInt(Server.sv_client.netchan.message,
+                Defines.PROTOCOL_VERSION);
+
+        MSG.WriteLong(Server.sv_client.netchan.message,
+                SV_INIT.svs.spawncount);
+        MSG.WriteByte(Server.sv_client.netchan.message,
+                SV_INIT.sv.attractloop ? 1 : 0);
+        MSG.WriteString(Server.sv_client.netchan.message, gamedir);
+
+        if (SV_INIT.sv.state == Defines.ss_cinematic
+                || SV_INIT.sv.state == Defines.ss_pic)
+            playernum = -1;
+        else
+            //playernum = sv_client - svs.clients;
+            playernum = Server.sv_client.serverindex;
+
+        MSG.WriteShort(Server.sv_client.netchan.message, playernum);
+
+        // send full levelname
+        MSG.WriteString(Server.sv_client.netchan.message,
+                SV_INIT.sv.configstrings[Defines.CS_NAME]);
+
+        //
+        // game server
+        //
+        if (SV_INIT.sv.state == Defines.ss_game) {
+            // set up the entity for the client
+            ent = GameBase.g_edicts[playernum + 1];
+            ent.s.number = playernum + 1;
+            Server.sv_client.edict = ent;
+            Server.sv_client.lastcmd = new usercmd_t();
+
+            // begin fetching configstrings
+            MSG.WriteByte(Server.sv_client.netchan.message,
+                    Defines.svc_stufftext);
+            MSG.WriteString(Server.sv_client.netchan.message,
+                    "cmd configstrings " + SV_INIT.svs.spawncount + " 0\n");
+        }
+
+    }
+
+    /*
+     * ================== SV_Configstrings_f ==================
+     */
+    public static void SV_Configstrings_f() {
+        int start;
+
+        Com.DPrintf("Configstrings() from " + Server.sv_client.name + "\n");
+
+        if (Server.sv_client.state != Defines.cs_connected) {
+            Com.Printf("configstrings not valid -- already spawned\n");
+            return;
+        }
+
+        // handle the case of a level changing while a client was connecting
+        if (Lib.atoi(Cmd.Argv(1)) != SV_INIT.svs.spawncount) {
+            Com.Printf("SV_Configstrings_f from different level\n");
+            SV_New_f();
+            return;
+        }
+
+        start = Lib.atoi(Cmd.Argv(2));
+
+        // write a packet full of data
+
+        while (Server.sv_client.netchan.message.cursize < Defines.MAX_MSGLEN / 2
+                && start < Defines.MAX_CONFIGSTRINGS) {
+            if (SV_INIT.sv.configstrings[start] != null
+                    && SV_INIT.sv.configstrings[start].length() != 0) {
+                MSG.WriteByte(Server.sv_client.netchan.message,
+                        Defines.svc_configstring);
+                MSG.WriteShort(Server.sv_client.netchan.message, start);
+                MSG.WriteString(Server.sv_client.netchan.message,
+                        SV_INIT.sv.configstrings[start]);
+            }
+            start++;
+        }
+
+        // send next command
+
+        if (start == Defines.MAX_CONFIGSTRINGS) {
+            MSG.WriteByte(Server.sv_client.netchan.message,
+                    Defines.svc_stufftext);
+            MSG.WriteString(Server.sv_client.netchan.message, "cmd baselines "
+                    + SV_INIT.svs.spawncount + " 0\n");
+        } else {
+            MSG.WriteByte(Server.sv_client.netchan.message,
+                    Defines.svc_stufftext);
+            MSG.WriteString(Server.sv_client.netchan.message,
+                    "cmd configstrings " + SV_INIT.svs.spawncount + " " + start
+                            + "\n");
+        }
+    }
+
+    /*
+     * ================== SV_Baselines_f ==================
+     */
+    public static void SV_Baselines_f() {
+        int start;
+        entity_state_t nullstate;
+        entity_state_t base;
+
+        Com.DPrintf("Baselines() from " + Server.sv_client.name + "\n");
+
+        if (Server.sv_client.state != Defines.cs_connected) {
+            Com.Printf("baselines not valid -- already spawned\n");
+            return;
+        }
+
+        // handle the case of a level changing while a client was connecting
+        if (Lib.atoi(Cmd.Argv(1)) != SV_INIT.svs.spawncount) {
+            Com.Printf("SV_Baselines_f from different level\n");
+            SV_New_f();
+            return;
+        }
+
+        start = Lib.atoi(Cmd.Argv(2));
+
+        //memset (&nullstate, 0, sizeof(nullstate));
+        nullstate = new entity_state_t(null);
+
+        // write a packet full of data
+
+        while (Server.sv_client.netchan.message.cursize < Defines.MAX_MSGLEN / 2
+                && start < Defines.MAX_EDICTS) {
+            base = SV_INIT.sv.baselines[start];
+            if (base.modelindex != 0 || base.sound != 0 || base.effects != 0) {
+                MSG.WriteByte(Server.sv_client.netchan.message,
+                        Defines.svc_spawnbaseline);
+                MSG.WriteDeltaEntity(nullstate, base,
+                        Server.sv_client.netchan.message, true, true);
+            }
+            start++;
+        }
+
+        // send next command
+
+        if (start == Defines.MAX_EDICTS) {
+            MSG.WriteByte(Server.sv_client.netchan.message,
+                    Defines.svc_stufftext);
+            MSG.WriteString(Server.sv_client.netchan.message, "precache "
+                    + SV_INIT.svs.spawncount + "\n");
+        } else {
+            MSG.WriteByte(Server.sv_client.netchan.message,
+                    Defines.svc_stufftext);
+            MSG.WriteString(Server.sv_client.netchan.message, "cmd baselines "
+                    + SV_INIT.svs.spawncount + " " + start + "\n");
+        }
+    }
+
+    /*
+     * ================== SV_Begin_f ==================
+     */
+    public static void SV_Begin_f() {
+        Com.DPrintf("Begin() from " + Server.sv_client.name + "\n");
+
+        // handle the case of a level changing while a client was connecting
+        if (Lib.atoi(Cmd.Argv(1)) != SV_INIT.svs.spawncount) {
+            Com.Printf("SV_Begin_f from different level\n");
+            SV_New_f();
+            return;
+        }
+
+        Server.sv_client.state = Defines.cs_spawned;
+
+        // call the game begin function
+        PlayerClient.ClientBegin(SV_USER.sv_player);
+
+        CommandBuffer.InsertFromDefer();
+    }
+
+    /*
+     * ================== SV_NextDownload_f ==================
+     */
+    public static void SV_NextDownload_f() {
+        int r;
+        int percent;
+        int size;
+
+        if (Server.sv_client.download == null)
+            return;
+
+        r = Server.sv_client.downloadsize - Server.sv_client.downloadcount;
+        if (r > 1024)
+            r = 1024;
+
+        MSG.WriteByte(Server.sv_client.netchan.message, Defines.svc_download);
+        MSG.WriteShort(Server.sv_client.netchan.message, r);
+
+        Server.sv_client.downloadcount += r;
+        size = Server.sv_client.downloadsize;
+        if (size == 0)
+            size = 1;
+        percent = Server.sv_client.downloadcount * 100 / size;
+        MSG.WriteByte(Server.sv_client.netchan.message, percent);
+        SZ.Write(Server.sv_client.netchan.message, Server.sv_client.download,
+                Server.sv_client.downloadcount - r, r);
+
+        if (Server.sv_client.downloadcount != Server.sv_client.downloadsize)
+            return;
+
+        FS.FreeFile();
+        Server.sv_client.download = null;
+    }
+
+    //=============================================================================
+
+    /*
+     * ================== SV_BeginDownload_f ==================
+     */
+    public static void SV_BeginDownload_f() {
+        String name;
+        int offset = 0;
+
+        name = Cmd.Argv(1);
+
+        if (Cmd.Argc() > 2)
+            offset = Lib.atoi(Cmd.Argv(2)); // downloaded offset
+
+        // hacked by zoid to allow more conrol over download
+        // first off, no .. or global allow check
+
+        if (name.contains("..")
+                || Server.allow_download.value == 0 // leading dot is no good
+                || name.charAt(0) == '.' // leading slash bad as well, must be
+                // in subdir
+                || name.charAt(0) == '/' // next up, skin check
+                || (name.startsWith("players/") && 0 == Server.allow_download_players.value) // now
+                // models
+                || (name.startsWith("models/") && 0 == Server.allow_download_models.value) // now
+                // sounds
+                || (name.startsWith("sound/") && 0 == Server.allow_download_sounds.value)
+                // now maps (note special case for maps, must not be in pak)
+                || (name.startsWith("maps/") && 0 == Server.allow_download_maps.value) // MUST
+                // be
+                // in a
+                // subdirectory
+                || name.indexOf('/') == -1) { // don't allow anything with ..
+            // path
+            MSG.WriteByte(Server.sv_client.netchan.message,
+                    Defines.svc_download);
+            MSG.WriteShort(Server.sv_client.netchan.message, -1);
+            MSG.WriteByte(Server.sv_client.netchan.message, 0);
+            return;
+        }
+
+        if (Server.sv_client.download != null)
+            FS.FreeFile();
+
+        Server.sv_client.download = FS.LoadFile(name);
+
+        // rst: this handles loading errors, no message yet visible
+        if (Server.sv_client.download == null) {
+            return;
+        }
+
+        Server.sv_client.downloadsize = Server.sv_client.download.length;
+        Server.sv_client.downloadcount = offset;
+
+        if (offset > Server.sv_client.downloadsize)
+            Server.sv_client.downloadcount = Server.sv_client.downloadsize;
+
+        if (Server.sv_client.download == null // special check for maps, if it
+                // came from a pak file, don't
+                // allow
+                // download ZOID
+                || (name.startsWith("maps/") && FS.file_from_pak != 0)) {
+            Com.DPrintf("Couldn't download " + name + " to "
+                    + Server.sv_client.name + "\n");
+            if (Server.sv_client.download != null) {
+                FS.FreeFile();
+                Server.sv_client.download = null;
+            }
+
+            MSG.WriteByte(Server.sv_client.netchan.message,
+                    Defines.svc_download);
+            MSG.WriteShort(Server.sv_client.netchan.message, -1);
+            MSG.WriteByte(Server.sv_client.netchan.message, 0);
+            return;
+        }
+
+        SV_NextDownload_f();
+        Com.DPrintf("Downloading " + name + " to " + Server.sv_client.name
+                + "\n");
+    }
+
+    /*
+     * ================= SV_Disconnect_f
+     *
+     * The client is going to disconnect, so remove the connection immediately
+     * =================
+     */
+    public static void SV_Disconnect_f() {
+        //     SV_EndRedirect ();
+        Server.SV_DropClient(Server.sv_client);
+    }
+
+    //============================================================================
+
+    /*
+     * ================== SV_ShowServerinfo_f
+     *
+     * Dumps the serverinfo info string ==================
+     */
+    public static void SV_ShowServerinfo_f() {
+        Info.Print(Cvar.serverinfo());
+    }
+
+    public static void SV_Nextserver() {
+        String v;
+
+        //ZOID, ss_pic can be nextserver'd in coop mode
+        if (SV_INIT.sv.state == Defines.ss_game
+                || (SV_INIT.sv.state == Defines.ss_pic &&
+                0 == Cvar.variableValue("coop")))
+            return; // can't nextserver while playing a normal game
+
+        SV_INIT.svs.spawncount++; // make sure another doesn't sneak in
+        v = Cvar.variableString("nextserver");
+        //if (!v[0])
+        if (v.length() == 0)
+            CommandBuffer.AddText("killserver\n");
+        else {
+            CommandBuffer.AddText(v);
+            CommandBuffer.AddText("\n");
+        }
+        Cvar.set("nextserver", "");
+    }
+
+    /*
+     * ================== SV_Nextserver_f
+     *
+     * A cinematic has completed or been aborted by a client, so move to the
+     * next server, ==================
+     */
+    public static void SV_Nextserver_f() {
+        if (Lib.atoi(Cmd.Argv(1)) != SV_INIT.svs.spawncount) {
+            Com.DPrintf("Nextserver() from wrong level, from "
+                    + Server.sv_client.name + "\n");
+            return; // leftover from last server
+        }
+
+        Com.DPrintf("Nextserver() from " + Server.sv_client.name + "\n");
+
+        SV_Nextserver();
+    }
+
+    /*
+     * ================== SV_ExecuteUserCommand ==================
+     */
+    public static void SV_ExecuteUserCommand(String s) {
+
+        Com.dprintln("SV_ExecuteUserCommand:" + s);
+        SV_USER.ucmd_t u = null;
+
+        Cmd.TokenizeString(s.toCharArray(), true);
+        SV_USER.sv_player = Server.sv_client.edict;
+
+        //     SV_BeginRedirect (RD_CLIENT);
+
+        int i = 0;
+        for (; i < SV_USER.ucmds.length; i++) {
+            u = SV_USER.ucmds[i];
+            if (Cmd.Argv(0).equals(u.name)) {
+                u.r.run();
+                break;
+            }
+        }
+
+        if (i == SV_USER.ucmds.length && SV_INIT.sv.state == Defines.ss_game)
+            Cmd.ClientCommand(SV_USER.sv_player);
+
+        //     SV_EndRedirect ();
+    }
+
+    public static void SV_ClientThink(client_t cl, usercmd_t cmd) {
+        cl.commandMsec -= cmd.msec & 0xFF;
+
+        if (cl.commandMsec < 0 && Server.sv_enforcetime.value != 0) {
+            Com.DPrintf("commandMsec underflow from " + cl.name + "\n");
+            return;
+        }
+
+        PlayerClient.ClientThink(cl.edict, cmd);
+    }
+
+    /*
+     * ===========================================================================
+     * 
+     * USER CMD EXECUTION
+     * 
+     * ===========================================================================
+     */
+
+    /*
+     * =================== SV_ExecuteClientMessage
+     *
+     * The current net_message is parsed for the given client
+     * ===================
+     */
+    public static void SV_ExecuteClientMessage(client_t cl) {
+        int c;
+        String s;
+
+        usercmd_t nullcmd = new usercmd_t();
+        usercmd_t oldest = new usercmd_t(), oldcmd = new usercmd_t(), newcmd = new usercmd_t();
+        int net_drop;
+        int stringCmdCount;
+        int checksum, calculatedChecksum;
+        int checksumIndex;
+        boolean move_issued;
+        int lastframe;
+
+        Server.sv_client = cl;
+        SV_USER.sv_player = Server.sv_client.edict;
+
+        // only allow one move command
+        move_issued = false;
+        stringCmdCount = 0;
+
+        while (true) {
+            if (Globals.net_message.readcount > Globals.net_message.cursize) {
+                Com.Printf("SV_ReadClientMessage: bad read:\n");
+                Com.Printf(Lib.hexDump(Globals.net_message.data, 32, false));
+                Server.SV_DropClient(cl);
+                return;
+            }
+
+            c = MSG.ReadByte(Globals.net_message);
+            if (c == -1)
+                break;
+
+            switch (c) {
+                default:
+                    Com.Printf("SV_ReadClientMessage: unknown command char\n");
+                    Server.SV_DropClient(cl);
+                    return;
+
+                case Defines.clc_nop:
+                    break;
+
+                case Defines.clc_userinfo:
+                    cl.userinfo = MSG.ReadString(Globals.net_message);
+                    Server.SV_UserinfoChanged(cl);
+                    break;
+
+                case Defines.clc_move:
+                    if (move_issued)
+                        return; // someone is trying to cheat...
+
+                    move_issued = true;
+                    checksumIndex = Globals.net_message.readcount;
+                    checksum = MSG.ReadByte(Globals.net_message);
+                    lastframe = MSG.ReadLong(Globals.net_message);
+
+                    if (lastframe != cl.lastframe) {
+                        cl.lastframe = lastframe;
+                        if (cl.lastframe > 0) {
+                            cl.frame_latency[cl.lastframe
+                                    & (Defines.LATENCY_COUNTS - 1)] = SV_INIT.svs.realtime
+                                    - cl.frames[cl.lastframe & Defines.UPDATE_MASK].senttime;
+                        }
+                    }
+
+                    //memset (nullcmd, 0, sizeof(nullcmd));
+                    nullcmd = new usercmd_t();
+                    MSG.ReadDeltaUsercmd(Globals.net_message, nullcmd, oldest);
+                    MSG.ReadDeltaUsercmd(Globals.net_message, oldest, oldcmd);
+                    MSG.ReadDeltaUsercmd(Globals.net_message, oldcmd, newcmd);
+
+                    if (cl.state != Defines.cs_spawned) {
+                        cl.lastframe = -1;
+                        break;
+                    }
+
+                    // if the checksum fails, ignore the rest of the packet
+
+                    calculatedChecksum = Com.BlockSequenceCRCByte(
+                            Globals.net_message.data, checksumIndex + 1,
+                            Globals.net_message.readcount - checksumIndex - 1,
+                            cl.netchan.incoming_sequence);
+
+                    if ((calculatedChecksum & 0xff) != checksum) {
+                        Com.DPrintf("Failed command checksum for " + cl.name + " ("
+                                + calculatedChecksum + " != " + checksum + ")/"
+                                + cl.netchan.incoming_sequence + "\n");
+                        return;
+                    }
+
+                    if (0 == Server.sv_paused.value) {
+                        net_drop = cl.netchan.dropped;
+                        if (net_drop < 20) {
+
+                            //if (net_drop > 2)
+
+                            // Com.Printf ("drop %i\n", net_drop);
+                            while (net_drop > 2) {
+                                SV_ClientThink(cl, cl.lastcmd);
+
+                                net_drop--;
+                            }
+                            if (net_drop > 1)
+                                SV_ClientThink(cl, oldest);
+
+                            if (net_drop > 0)
+                                SV_ClientThink(cl, oldcmd);
+
+                        }
+                        SV_ClientThink(cl, newcmd);
+                    }
+
+                    // copy.
+                    cl.lastcmd.set(newcmd);
+                    break;
+
+                case Defines.clc_stringcmd:
+                    s = MSG.ReadString(Globals.net_message);
+
+                    // malicious users may try using too many string commands
+                    if (++stringCmdCount < SV_USER.MAX_STRINGCMDS)
+                        SV_ExecuteUserCommand(s);
+
+                    if (cl.state == Defines.cs_zombie)
+                        return; // disconnect command
+                    break;
+            }
+        }
+    }
+
+    public static class ucmd_t {
+        final String name;
+        final Runnable r;
+
+        public ucmd_t(String n, Runnable r) {
+            name = n;
+            this.r = r;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/server/SV_WORLD.java b/src/main/java/lwjake2/server/SV_WORLD.java
new file mode 100644 (file)
index 0000000..aa0256a
--- /dev/null
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.server;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.*;
+import lwjake2.qcommon.CM;
+import lwjake2.qcommon.Com;
+import lwjake2.util.Math3D;
+
+public class SV_WORLD {
+    private static final int MAX_TOTAL_ENT_LEAFS = 128;
+    // world.c -- world query functions
+    //
+    //
+    //===============================================================================
+    //
+    //ENTITY AREA CHECKING
+    //
+    //FIXME: this use of "area" is different from the bsp file use
+    //===============================================================================
+    private static final areanode_t[] sv_areanodes = new areanode_t[Defines.AREA_NODES];
+    private static final int[] leafs = new int[MAX_TOTAL_ENT_LEAFS];
+    private static final int[] clusters = new int[MAX_TOTAL_ENT_LEAFS];
+    //===========================================================================
+    private static final EDict[] touch = new EDict[Defines.MAX_EDICTS];
+    //===========================================================================
+    private static final EDict[] touchlist = new EDict[Defines.MAX_EDICTS];
+    private static int sv_numareanodes;
+    private static float[] area_mins;
+    private static float[] area_maxs;
+    private static EDict[] area_list;
+    private static int area_count;
+    private static int area_maxcount;
+    private static int area_type;
+
+    static {
+        SV_WORLD.initNodes();
+    }
+
+    private static void initNodes() {
+        for (int n = 0; n < Defines.AREA_NODES; n++)
+            SV_WORLD.sv_areanodes[n] = new areanode_t();
+    }
+
+    // ClearLink is used for new headnodes
+    private static void ClearLink(link_t l) {
+        l.prev = l.next = l;
+    }
+
+    private static void RemoveLink(link_t l) {
+        l.next.prev = l.prev;
+        l.prev.next = l.next;
+    }
+
+    private static void InsertLinkBefore(link_t l, link_t before) {
+        l.next = before;
+        l.prev = before.prev;
+        l.prev.next = l;
+        l.next.prev = l;
+    }
+
+    /*
+     * =============== SV_CreateAreaNode
+     * 
+     * Builds a uniformly subdivided tree for the given world size
+     * ===============
+     */
+    private static areanode_t SV_CreateAreaNode(int depth, float[] mins,
+                                                float[] maxs) {
+        areanode_t anode;
+        float[] size = {0, 0, 0};
+        float[] mins1 = {0, 0, 0}, maxs1 = {0, 0, 0}, mins2 = {0, 0, 0}, maxs2 = {
+                0, 0, 0};
+        anode = SV_WORLD.sv_areanodes[SV_WORLD.sv_numareanodes];
+        // just for debugging (rst)
+        Math3D.vectorCopy(mins, anode.mins_rst);
+        Math3D.vectorCopy(maxs, anode.maxs_rst);
+        SV_WORLD.sv_numareanodes++;
+        ClearLink(anode.trigger_edicts);
+        ClearLink(anode.solid_edicts);
+        if (depth == Defines.AREA_DEPTH) {
+            anode.axis = -1;
+            anode.children[0] = anode.children[1] = null;
+            return anode;
+        }
+        Math3D.vectorSubtract(maxs, mins, size);
+        if (size[0] > size[1])
+            anode.axis = 0;
+        else
+            anode.axis = 1;
+        anode.dist = 0.5f * (maxs[anode.axis] + mins[anode.axis]);
+        Math3D.vectorCopy(mins, mins1);
+        Math3D.vectorCopy(mins, mins2);
+        Math3D.vectorCopy(maxs, maxs1);
+        Math3D.vectorCopy(maxs, maxs2);
+        maxs1[anode.axis] = mins2[anode.axis] = anode.dist;
+        anode.children[0] = SV_CreateAreaNode(depth + 1, mins2, maxs2);
+        anode.children[1] = SV_CreateAreaNode(depth + 1, mins1, maxs1);
+        return anode;
+    }
+
+    /*
+     * =============== SV_ClearWorld
+     * 
+     * ===============
+     */
+    public static void SV_ClearWorld() {
+        initNodes();
+        SV_WORLD.sv_numareanodes = 0;
+        SV_CreateAreaNode(0, SV_INIT.sv.models[1].mins,
+                SV_INIT.sv.models[1].maxs);
+        /*
+         * Com.p("areanodes:" + sv_numareanodes + " (sollten 32 sein)."); for
+         * (int n = 0; n < sv_numareanodes; n++) { Com.Printf( "|%3i|%2i|%8.2f
+         * |%8.2f|%8.2f|%8.2f| %8.2f|%8.2f|%8.2f|\n", new Vargs() .add(n)
+         * .add(sv_areanodes[n].axis) .add(sv_areanodes[n].dist)
+         * .add(sv_areanodes[n].mins_rst[0]) .add(sv_areanodes[n].mins_rst[1])
+         * .add(sv_areanodes[n].mins_rst[2]) .add(sv_areanodes[n].maxs_rst[0])
+         * .add(sv_areanodes[n].maxs_rst[1]) .add(sv_areanodes[n].maxs_rst[2])); }
+         */
+    }
+
+    /*
+     * =============== SV_UnlinkEdict ===============
+     */
+    public static void SV_UnlinkEdict(EDict ent) {
+        if (null == ent.area.prev)
+            return; // not linked in anywhere
+        RemoveLink(ent.area);
+        ent.area.prev = ent.area.next = null;
+    }
+
+    public static void SV_LinkEdict(EDict ent) {
+        areanode_t node;
+        int num_leafs;
+        int j, k;
+        int area;
+        int topnode = 0;
+        if (ent.area.prev != null)
+            SV_UnlinkEdict(ent); // unlink from old position
+        if (ent == GameBase.g_edicts[0])
+            return; // don't add the world
+        if (!ent.inuse)
+            return;
+        // set the size
+        Math3D.vectorSubtract(ent.maxs, ent.mins, ent.size);
+        // encode the size into the entity_state for client prediction
+        if (ent.solid == Defines.SOLID_BBOX
+                && 0 == (ent.svflags & Defines.SVF_DEADMONSTER)) {
+            // assume that x/y are equal and symetric
+            int i = (int) (ent.maxs[0] / 8);
+            if (i < 1)
+                i = 1;
+            if (i > 31)
+                i = 31;
+            // z is not symetric
+            j = (int) ((-ent.mins[2]) / 8);
+            if (j < 1)
+                j = 1;
+            if (j > 31)
+                j = 31;
+            // and z maxs can be negative...
+            k = (int) ((ent.maxs[2] + 32) / 8);
+            if (k < 1)
+                k = 1;
+            if (k > 63)
+                k = 63;
+            ent.s.solid = (k << 10) | (j << 5) | i;
+        } else if (ent.solid == Defines.SOLID_BSP) {
+            ent.s.solid = 31; // a solid_bbox will never create this value
+        } else
+            ent.s.solid = 0;
+        // set the abs box
+        if (ent.solid == Defines.SOLID_BSP
+                && (ent.s.angles[0] != 0 || ent.s.angles[1] != 0 || ent.s.angles[2] != 0)) {
+            // expand for rotation
+            float max, v;
+            max = 0;
+            for (int i = 0; i < 3; i++) {
+                v = Math.abs(ent.mins[i]);
+                if (v > max)
+                    max = v;
+                v = Math.abs(ent.maxs[i]);
+                if (v > max)
+                    max = v;
+            }
+            for (int i = 0; i < 3; i++) {
+                ent.absmin[i] = ent.s.origin[i] - max;
+                ent.absmax[i] = ent.s.origin[i] + max;
+            }
+        } else {
+            // normal
+            Math3D.vectorAdd(ent.s.origin, ent.mins, ent.absmin);
+            Math3D.vectorAdd(ent.s.origin, ent.maxs, ent.absmax);
+        }
+        // because movement is clipped an epsilon away from an actual edge,
+        // we must fully check even when bounding boxes don't quite touch
+        ent.absmin[0]--;
+        ent.absmin[1]--;
+        ent.absmin[2]--;
+        ent.absmax[0]++;
+        ent.absmax[1]++;
+        ent.absmax[2]++;
+        // link to PVS leafs
+        ent.num_clusters = 0;
+        ent.areanum = 0;
+        ent.areanum2 = 0;
+        // get all leafs, including solids
+        int iw[] = {topnode};
+        num_leafs = CM.CM_BoxLeafnums(ent.absmin, ent.absmax, SV_WORLD.leafs,
+                SV_WORLD.MAX_TOTAL_ENT_LEAFS, iw);
+        topnode = iw[0];
+        // set areas
+        for (int i = 0; i < num_leafs; i++) {
+            SV_WORLD.clusters[i] = CM.CM_LeafCluster(SV_WORLD.leafs[i]);
+            area = CM.CM_LeafArea(SV_WORLD.leafs[i]);
+            if (area != 0) {
+                // doors may legally straggle two areas,
+                // but nothing should evern need more than that
+                if (ent.areanum != 0 && ent.areanum != area) {
+                    if (ent.areanum2 != 0 && ent.areanum2 != area
+                            && SV_INIT.sv.state == Defines.ss_loading)
+                        Com.DPrintf("Object touching 3 areas at "
+                                + ent.absmin[0] + " " + ent.absmin[1] + " "
+                                + ent.absmin[2] + "\n");
+                    ent.areanum2 = area;
+                } else
+                    ent.areanum = area;
+            }
+        }
+        if (num_leafs >= SV_WORLD.MAX_TOTAL_ENT_LEAFS) {
+            // assume we missed some leafs, and mark by headnode
+            ent.num_clusters = -1;
+            ent.headnode = topnode;
+        } else {
+            ent.num_clusters = 0;
+            for (int i = 0; i < num_leafs; i++) {
+                if (SV_WORLD.clusters[i] == -1)
+                    continue; // not a visible leaf
+                for (j = 0; j < i; j++)
+                    if (SV_WORLD.clusters[j] == SV_WORLD.clusters[i])
+                        break;
+                if (j == i) {
+                    if (ent.num_clusters == Defines.MAX_ENT_CLUSTERS) {
+                        // assume we missed some leafs, and mark by headnode
+                        ent.num_clusters = -1;
+                        ent.headnode = topnode;
+                        break;
+                    }
+                    ent.clusternums[ent.num_clusters++] = SV_WORLD.clusters[i];
+                }
+            }
+        }
+        // if first time, make sure old_origin is valid
+        if (0 == ent.linkcount) {
+            Math3D.vectorCopy(ent.s.origin, ent.s.old_origin);
+        }
+        ent.linkcount++;
+        if (ent.solid == Defines.SOLID_NOT)
+            return;
+        // find the first node that the ent's box crosses
+        node = SV_WORLD.sv_areanodes[0];
+        while (true) {
+            if (node.axis == -1)
+                break;
+            if (ent.absmin[node.axis] > node.dist)
+                node = node.children[0];
+            else if (ent.absmax[node.axis] < node.dist)
+                node = node.children[1];
+            else
+                break; // crosses the node
+        }
+        // link it in
+        if (ent.solid == Defines.SOLID_TRIGGER)
+            InsertLinkBefore(ent.area, node.trigger_edicts);
+        else
+            InsertLinkBefore(ent.area, node.solid_edicts);
+    }
+
+    /*
+     * ==================== SV_AreaEdicts_r
+     * 
+     * ====================
+     */
+    private static void SV_AreaEdicts_r(areanode_t node) {
+        link_t l, next, start;
+        EDict check;
+        // touch linked edicts
+        if (SV_WORLD.area_type == Defines.AREA_SOLID)
+            start = node.solid_edicts;
+        else
+            start = node.trigger_edicts;
+        for (l = start.next; l != start; l = next) {
+            next = l.next;
+            check = (EDict) l.o;
+            if (check.solid == Defines.SOLID_NOT)
+                continue; // deactivated
+            if (check.absmin[0] > SV_WORLD.area_maxs[0]
+                    || check.absmin[1] > SV_WORLD.area_maxs[1]
+                    || check.absmin[2] > SV_WORLD.area_maxs[2]
+                    || check.absmax[0] < SV_WORLD.area_mins[0]
+                    || check.absmax[1] < SV_WORLD.area_mins[1]
+                    || check.absmax[2] < SV_WORLD.area_mins[2])
+                continue; // not touching
+            if (SV_WORLD.area_count == SV_WORLD.area_maxcount) {
+                Com.Printf("SV_AreaEdicts: MAXCOUNT\n");
+                return;
+            }
+            SV_WORLD.area_list[SV_WORLD.area_count] = check;
+            SV_WORLD.area_count++;
+        }
+        if (node.axis == -1)
+            return; // terminal node
+        // recurse down both sides
+        if (SV_WORLD.area_maxs[node.axis] > node.dist)
+            SV_AreaEdicts_r(node.children[0]);
+        if (SV_WORLD.area_mins[node.axis] < node.dist)
+            SV_AreaEdicts_r(node.children[1]);
+    }
+
+    /*
+     * ================ SV_AreaEdicts ================
+     */
+    public static int SV_AreaEdicts(float[] mins, float[] maxs, EDict list[],
+                                    int maxcount, int areatype) {
+        SV_WORLD.area_mins = mins;
+        SV_WORLD.area_maxs = maxs;
+        SV_WORLD.area_list = list;
+        SV_WORLD.area_count = 0;
+        SV_WORLD.area_maxcount = maxcount;
+        SV_WORLD.area_type = areatype;
+        SV_AreaEdicts_r(SV_WORLD.sv_areanodes[0]);
+        return SV_WORLD.area_count;
+    }
+
+    /*
+     * ============= SV_PointContents =============
+     */
+    public static int SV_PointContents(float[] p) {
+        EDict hit;
+        int i, num;
+        int contents, c2;
+        int headnode;
+        // get base contents from world
+        contents = CM.PointContents(p, SV_INIT.sv.models[1].headnode);
+        // or in contents from all the other entities
+        num = SV_AreaEdicts(p, p, SV_WORLD.touch, Defines.MAX_EDICTS,
+                Defines.AREA_SOLID);
+        for (i = 0; i < num; i++) {
+            hit = SV_WORLD.touch[i];
+            // might intersect, so do an exact clip
+            headnode = SV_HullForEntity(hit);
+            c2 = CM.TransformedPointContents(p, headnode, hit.s.origin,
+                    hit.s.angles);
+            contents |= c2;
+        }
+        return contents;
+    }
+
+    /*
+     * ================ SV_HullForEntity
+     * 
+     * Returns a headnode that can be used for testing or clipping an object of
+     * mins/maxs size. Offset is filled in to contain the adjustment that must
+     * be added to the testing object's origin to get a point to use with the
+     * returned hull. ================
+     */
+    private static int SV_HullForEntity(EDict ent) {
+        cmodel_t model;
+        // decide which clipping hull to use, based on the size
+        if (ent.solid == Defines.SOLID_BSP) {
+            // explicit hulls in the BSP model
+            model = SV_INIT.sv.models[ent.s.modelindex];
+            if (null == model)
+                Com.Error(Defines.ERR_FATAL,
+                        "MOVETYPE_PUSH with a non bsp model");
+            return model.headnode;
+        }
+        // create a temp hull from bounding box sizes
+        return CM.HeadnodeForBox(ent.mins, ent.maxs);
+    }
+
+    private static void SV_ClipMoveToEntities(moveclip_t clip) {
+        int i, num;
+        EDict touch;
+        trace_t trace;
+        int headnode;
+        float angles[];
+        num = SV_AreaEdicts(clip.boxmins, clip.boxmaxs, SV_WORLD.touchlist,
+                Defines.MAX_EDICTS, Defines.AREA_SOLID);
+        // be careful, it is possible to have an entity in this
+        // list removed before we get to it (killtriggered)
+        for (i = 0; i < num; i++) {
+            touch = SV_WORLD.touchlist[i];
+            if (touch.solid == Defines.SOLID_NOT)
+                continue;
+            if (touch == clip.passedict)
+                continue;
+            if (clip.trace.allsolid)
+                return;
+            if (clip.passedict != null) {
+                if (touch.owner == clip.passedict)
+                    continue; // don't clip against own missiles
+                if (clip.passedict.owner == touch)
+                    continue; // don't clip against owner
+            }
+            if (0 == (clip.contentmask & Defines.CONTENTS_DEADMONSTER)
+                    && 0 != (touch.svflags & Defines.SVF_DEADMONSTER))
+                continue;
+            // might intersect, so do an exact clip
+            headnode = SV_HullForEntity(touch);
+            angles = touch.s.angles;
+            if (touch.solid != Defines.SOLID_BSP)
+                angles = Globals.vec3_origin; // boxes don't rotate
+            if ((touch.svflags & Defines.SVF_MONSTER) != 0)
+                trace = CM.TransformedBoxTrace(clip.start, clip.end,
+                        clip.mins2, clip.maxs2, headnode, clip.contentmask,
+                        touch.s.origin, angles);
+            else
+                trace = CM.TransformedBoxTrace(clip.start, clip.end, clip.mins,
+                        clip.maxs, headnode, clip.contentmask, touch.s.origin,
+                        angles);
+            if (trace.allsolid || trace.startsolid
+                    || trace.fraction < clip.trace.fraction) {
+                trace.ent = touch;
+                if (clip.trace.startsolid) {
+                    clip.trace = trace;
+                    clip.trace.startsolid = true;
+                } else
+                    clip.trace.set(trace);
+            } else if (trace.startsolid)
+                clip.trace.startsolid = true;
+        }
+    }
+
+    /*
+     * ================== SV_TraceBounds ==================
+     */
+    private static void SV_TraceBounds(float[] start, float[] mins,
+                                       float[] maxs, float[] end, float[] boxmins, float[] boxmaxs) {
+        int i;
+        for (i = 0; i < 3; i++) {
+            if (end[i] > start[i]) {
+                boxmins[i] = start[i] + mins[i] - 1;
+                boxmaxs[i] = end[i] + maxs[i] + 1;
+            } else {
+                boxmins[i] = end[i] + mins[i] - 1;
+                boxmaxs[i] = start[i] + maxs[i] + 1;
+            }
+        }
+    }
+
+    /*
+     * ================== SV_Trace
+     * 
+     * Moves the given mins/maxs volume through the world from start to end.
+     * 
+     * Passedict and edicts owned by passedict are explicitly not checked.
+     * 
+     * ==================
+     */
+    public static trace_t SV_Trace(float[] start, float[] mins, float[] maxs,
+                                   float[] end, EDict passedict, int contentmask) {
+        moveclip_t clip = new moveclip_t();
+        if (mins == null)
+            mins = Globals.vec3_origin;
+        if (maxs == null)
+            maxs = Globals.vec3_origin;
+
+        // clip to world
+        clip.trace = CM.boxTrace(start, end, mins, maxs, 0, contentmask);
+        clip.trace.ent = GameBase.g_edicts[0];
+        if (clip.trace.fraction == 0)
+            return clip.trace; // blocked by the world
+        clip.contentmask = contentmask;
+        clip.start = start;
+        clip.end = end;
+        clip.mins = mins;
+        clip.maxs = maxs;
+        clip.passedict = passedict;
+        Math3D.vectorCopy(mins, clip.mins2);
+        Math3D.vectorCopy(maxs, clip.maxs2);
+        // create the bounding box of the entire move
+        SV_TraceBounds(start, clip.mins2, clip.maxs2, end, clip.boxmins,
+                clip.boxmaxs);
+        // clip to other solid entities
+        SV_ClipMoveToEntities(clip);
+        return clip.trace;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/server/Server.java b/src/main/java/lwjake2/server/Server.java
new file mode 100644 (file)
index 0000000..c2b5a2d
--- /dev/null
@@ -0,0 +1,955 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.server;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.*;
+import lwjake2.qcommon.*;
+import lwjake2.sys.NET;
+import lwjake2.util.Lib;
+
+import java.io.IOException;
+
+public class Server {
+
+    /**
+     * Address of group servers.
+     */
+    public static final NetadrT[] master_adr = new NetadrT[Defines.MAX_MASTERS];
+    /**
+     * Send a message to the master every few minutes to let it know we are
+     * alive, and log information.
+     */
+    private static final int HEARTBEAT_SECONDS = 300;
+    public static client_t sv_client; // current client
+
+    public static CvarT sv_paused;
+    public static CvarT sv_enforcetime;
+    public static CvarT allow_download;
+    public static CvarT allow_download_players;
+    public static CvarT allow_download_models;
+    // disconnect
+    public static CvarT allow_download_sounds;
+    public static CvarT allow_download_maps;
+    public static CvarT sv_airaccelerate;
+    public static CvarT sv_noreload; // don't reload level state when
+    public static CvarT maxclients; // FIXME: rename sv_maxclients
+    private static CvarT sv_timedemo;
+    private static CvarT timeout; // seconds without any message
+    private static CvarT zombietime; // seconds to sink messages after
+    // reentering
+    private static CvarT rcon_password; // password for remote server commands
+    private static CvarT sv_showclamp;
+
+    private static CvarT hostname;
+
+    private static CvarT public_server; // should heartbeats be sent
+
+    private static CvarT sv_reconnect_limit; // minimum seconds between connect
+    // messages
+
+    static {
+        for (int i = 0; i < Defines.MAX_MASTERS; i++) {
+            master_adr[i] = new NetadrT();
+        }
+    }
+
+    /**
+     * Called when the player is totally leaving the server, either willingly or
+     * unwillingly. This is NOT called if the entire server is quiting or
+     * crashing.
+     */
+    public static void SV_DropClient(client_t drop) {
+        // add the disconnect
+        MSG.WriteByte(drop.netchan.message, Defines.svc_disconnect);
+
+        if (drop.state == Defines.cs_spawned) {
+            // call the prog function for removing a client
+            // this will remove the body, among other things
+            PlayerClient.ClientDisconnect(drop.edict);
+        }
+
+        if (drop.download != null) {
+            FS.FreeFile();
+            drop.download = null;
+        }
+
+        drop.state = Defines.cs_zombie; // become free in a few seconds
+        drop.name = "";
+    }
+
+    
+    /* ==============================================================================
+     * 
+     * CONNECTIONLESS COMMANDS
+     * 
+     * ==============================================================================*/
+
+    /**
+     * Builds the string that is sent as heartbeats and status replies.
+     */
+    private static String SV_StatusString() {
+        String player;
+        StringBuilder status = new StringBuilder();
+        int i;
+        client_t cl;
+        int statusLength;
+        int playerLength;
+
+        status = new StringBuilder(Cvar.serverinfo() + "\n");
+
+        for (i = 0; i < Server.maxclients.value; i++) {
+            cl = SV_INIT.svs.clients[i];
+            if (cl.state == Defines.cs_connected
+                    || cl.state == Defines.cs_spawned) {
+                player = "" + cl.edict.client.ps.stats[Defines.STAT_FRAGS]
+                        + " " + cl.ping + "\"" + cl.name + "\"\n";
+
+                playerLength = player.length();
+                statusLength = status.length();
+
+                if (statusLength + playerLength >= 1024)
+                    break; // can't hold any more
+
+                status.append(player);
+            }
+        }
+
+        return status.toString();
+    }
+
+    /**
+     * Responds with all the info that qplug or qspy can see
+     */
+    private static void SVC_Status() {
+        Netchan.OutOfBandPrint(Defines.NS_SERVER, Globals.net_from, "print\n"
+                + SV_StatusString());
+    }
+
+    /**
+     * SVC_Ack
+     */
+    private static void SVC_Ack() {
+        Com.Printf("Ping acknowledge from " + NET.AdrToString(Globals.net_from)
+                + "\n");
+    }
+
+    /**
+     * SVC_Info, responds with short info for broadcast scans The second parameter should
+     * be the current protocol version number.
+     */
+    private static void SVC_Info() {
+        String string;
+        int i, count;
+        int version;
+
+        if (Server.maxclients.value == 1)
+            return; // ignore in single player
+
+        version = Lib.atoi(Cmd.Argv(1));
+
+        if (version != Defines.PROTOCOL_VERSION)
+            string = Server.hostname.string + ": wrong version\n";
+        else {
+            count = 0;
+            for (i = 0; i < Server.maxclients.value; i++)
+                if (SV_INIT.svs.clients[i].state >= Defines.cs_connected)
+                    count++;
+
+            string = Server.hostname.string + " " + SV_INIT.sv.name + " "
+                    + count + "/" + (int) Server.maxclients.value + "\n";
+        }
+
+        Netchan.OutOfBandPrint(Defines.NS_SERVER, Globals.net_from, "info\n"
+                + string);
+    }
+
+    /**
+     * SVC_Ping, Just responds with an acknowledgement.
+     */
+    private static void SVC_Ping() {
+        Netchan.OutOfBandPrint(Defines.NS_SERVER, Globals.net_from, "ack");
+    }
+
+    /**
+     * Returns a challenge number that can be used in a subsequent
+     * client_connect command. We do this to prevent denial of service attacks
+     * that flood the server with invalid connection IPs. With a challenge, they
+     * must give a valid IP address.
+     */
+    private static void SVC_GetChallenge() {
+        int i;
+        int oldest;
+        int oldestTime;
+
+        oldest = 0;
+        oldestTime = 0x7fffffff;
+
+        // see if we already have a challenge for this ip
+        for (i = 0; i < Defines.MAX_CHALLENGES; i++) {
+            if (NET.CompareBaseAdr(Globals.net_from,
+                    SV_INIT.svs.challenges[i].adr))
+                break;
+            if (SV_INIT.svs.challenges[i].time < oldestTime) {
+                oldestTime = SV_INIT.svs.challenges[i].time;
+                oldest = i;
+            }
+        }
+
+        if (i == Defines.MAX_CHALLENGES) {
+            // overwrite the oldest
+            SV_INIT.svs.challenges[oldest].challenge = Lib.rand() & 0x7fff;
+            SV_INIT.svs.challenges[oldest].adr = Globals.net_from;
+            SV_INIT.svs.challenges[oldest].time = Globals.curtime;
+            i = oldest;
+        }
+
+        // send it back
+        Netchan.OutOfBandPrint(Defines.NS_SERVER, Globals.net_from,
+                "challenge " + SV_INIT.svs.challenges[i].challenge);
+    }
+
+    /**
+     * A connection request that did not come from the master.
+     */
+    private static void SVC_DirectConnect() {
+        String userinfo;
+        NetadrT adr;
+        int i;
+        client_t cl;
+
+        int version;
+        int qport;
+
+        adr = Globals.net_from;
+
+        Com.DPrintf("SVC_DirectConnect ()\n");
+
+        version = Lib.atoi(Cmd.Argv(1));
+        if (version != Defines.PROTOCOL_VERSION) {
+            Netchan.OutOfBandPrint(Defines.NS_SERVER, adr,
+                    "print\nServer is version " + Globals.VERSION + "\n");
+            Com.DPrintf("    rejected connect from version " + version + "\n");
+            return;
+        }
+
+        qport = Lib.atoi(Cmd.Argv(2));
+        int challenge = Lib.atoi(Cmd.Argv(3));
+        userinfo = Cmd.Argv(4);
+
+        // force the IP key/value pair so the game can filter based on ip
+        userinfo = Info.Info_SetValueForKey(userinfo, "ip", NET.AdrToString(Globals.net_from));
+
+        // attractloop servers are ONLY for local clients
+        if (SV_INIT.sv.attractloop) {
+            if (!NET.IsLocalAddress(adr)) {
+                Com.Printf("Remote connect in attract loop.  Ignored.\n");
+                Netchan.OutOfBandPrint(Defines.NS_SERVER, adr,
+                        "print\nConnection refused.\n");
+                return;
+            }
+        }
+
+        // see if the challenge is valid
+        if (!NET.IsLocalAddress(adr)) {
+            for (i = 0; i < Defines.MAX_CHALLENGES; i++) {
+                if (NET.CompareBaseAdr(Globals.net_from,
+                        SV_INIT.svs.challenges[i].adr)) {
+                    if (challenge == SV_INIT.svs.challenges[i].challenge)
+                        break; // good
+                    Netchan.OutOfBandPrint(Defines.NS_SERVER, adr,
+                            "print\nBad challenge.\n");
+                    return;
+                }
+            }
+            if (i == Defines.MAX_CHALLENGES) {
+                Netchan.OutOfBandPrint(Defines.NS_SERVER, adr,
+                        "print\nNo challenge for address.\n");
+                return;
+            }
+        }
+
+        // if there is already a slot for this ip, reuse it
+        for (i = 0; i < Server.maxclients.value; i++) {
+            cl = SV_INIT.svs.clients[i];
+
+            if (cl.state == Defines.cs_free)
+                continue;
+            if (NET.CompareBaseAdr(adr, cl.netchan.remote_address)
+                    && (cl.netchan.qport == qport || adr.port == cl.netchan.remote_address.port)) {
+                if (!NET.IsLocalAddress(adr)
+                        && (SV_INIT.svs.realtime - cl.lastconnect) < ((int) Server.sv_reconnect_limit.value * 1000)) {
+                    Com.DPrintf(NET.AdrToString(adr)
+                            + ":reconnect rejected : too soon\n");
+                    return;
+                }
+                Com.Printf(NET.AdrToString(adr) + ":reconnect\n");
+
+                gotnewcl(i, challenge, userinfo, adr, qport);
+                return;
+            }
+        }
+
+        // find a client slot
+        //newcl = null;
+        int index = -1;
+        for (i = 0; i < Server.maxclients.value; i++) {
+            cl = SV_INIT.svs.clients[i];
+            if (cl.state == Defines.cs_free) {
+                index = i;
+                break;
+            }
+        }
+        if (index == -1) {
+            Netchan.OutOfBandPrint(Defines.NS_SERVER, adr,
+                    "print\nServer is full.\n");
+            Com.DPrintf("Rejected a connection.\n");
+            return;
+        }
+        gotnewcl(index, challenge, userinfo, adr, qport);
+    }
+
+    /**
+     * Initializes player structures after successfull connection.
+     */
+    private static void gotnewcl(int i, int challenge, String userinfo,
+                                 NetadrT adr, int qport) {
+        // build a new connection
+        // accept the new client
+        // this is the only place a client_t is ever initialized
+
+        Server.sv_client = SV_INIT.svs.clients[i];
+
+        int edictnum = i + 1;
+
+        EDict ent = GameBase.g_edicts[edictnum];
+        SV_INIT.svs.clients[i].edict = ent;
+
+        // save challenge for checksumming
+        SV_INIT.svs.clients[i].challenge = challenge;
+
+
+        // get the game a chance to reject this connection or modify the
+        // userinfo
+        if (!(PlayerClient.ClientConnect(ent, userinfo))) {
+            if (Info.Info_ValueForKey(userinfo, "rejmsg") != null)
+                Netchan.OutOfBandPrint(Defines.NS_SERVER, adr, "print\n"
+                        + Info.Info_ValueForKey(userinfo, "rejmsg")
+                        + "\nConnection refused.\n");
+            else
+                Netchan.OutOfBandPrint(Defines.NS_SERVER, adr,
+                        "print\nConnection refused.\n");
+            Com.DPrintf("Game rejected a connection.\n");
+            return;
+        }
+
+        // parse some info from the info strings
+        SV_INIT.svs.clients[i].userinfo = userinfo;
+        SV_UserinfoChanged(SV_INIT.svs.clients[i]);
+
+        // send the connect packet to the client
+        Netchan.OutOfBandPrint(Defines.NS_SERVER, adr, "client_connect");
+
+        Netchan.Setup(Defines.NS_SERVER, SV_INIT.svs.clients[i].netchan, adr, qport);
+
+        SV_INIT.svs.clients[i].state = Defines.cs_connected;
+
+        SZ.Init(SV_INIT.svs.clients[i].datagram,
+                SV_INIT.svs.clients[i].datagram_buf,
+                SV_INIT.svs.clients[i].datagram_buf.length);
+
+        SV_INIT.svs.clients[i].datagram.allowoverflow = true;
+        SV_INIT.svs.clients[i].lastmessage = SV_INIT.svs.realtime; // don't timeout
+        SV_INIT.svs.clients[i].lastconnect = SV_INIT.svs.realtime;
+        Com.DPrintf("new client added.\n");
+    }
+
+
+    /**
+     * Checks if the rcon password is corect.
+     */
+    private static int Rcon_Validate() {
+        if (0 == Server.rcon_password.string.length())
+            return 0;
+
+        if (0 != Lib.strcmp(Cmd.Argv(1), Server.rcon_password.string))
+            return 0;
+
+        return 1;
+    }
+
+    /**
+     * A client issued an rcon command. Shift down the remaining args Redirect
+     * all printfs fromt hte server to the client.
+     */
+    private static void SVC_RemoteCommand() {
+        int i;
+        StringBuilder remaining;
+
+        i = Rcon_Validate();
+
+        String msg = Lib.CtoJava(Globals.net_message.data, 4, 1024);
+
+        if (i == 0)
+            Com.Printf("Bad rcon from " + NET.AdrToString(Globals.net_from)
+                    + ":\n" + msg + "\n");
+        else
+            Com.Printf("Rcon from " + NET.AdrToString(Globals.net_from) + ":\n"
+                    + msg + "\n");
+
+        Com.BeginRedirect(Defines.RD_PACKET, SV_SEND.sv_outputbuf,
+                Defines.SV_OUTPUTBUF_LENGTH, new Com.RD_Flusher() {
+                    public void rd_flush(int target, StringBuffer buffer) {
+                        SV_SEND.SV_FlushRedirect(target, Lib.stringToBytes(buffer.toString()));
+                    }
+                });
+
+        if (0 == Rcon_Validate()) {
+            Com.Printf("Bad rcon_password.\n");
+        } else {
+            remaining = new StringBuilder();
+
+            for (i = 2; i < Cmd.Argc(); i++) {
+                remaining.append(Cmd.Argv(i));
+                remaining.append(" ");
+            }
+
+            Cmd.ExecuteString(remaining.toString());
+        }
+
+        Com.EndRedirect();
+    }
+
+    /**
+     * A connectionless packet has four leading 0xff characters to distinguish
+     * it from a game channel. Clients that are in the game can still send
+     * connectionless packets. It is used also by rcon commands.
+     */
+    private static void SV_ConnectionlessPacket() {
+        String s;
+        String c;
+
+        MSG.BeginReading(Globals.net_message);
+        MSG.ReadLong(Globals.net_message); // skip the -1 marker
+
+        s = MSG.ReadStringLine(Globals.net_message);
+
+        Cmd.TokenizeString(s.toCharArray(), false);
+
+        c = Cmd.Argv(0);
+
+        //for debugging purposes 
+        //Com.Printf("Packet " + NET.AdrToString(Netchan.net_from) + " : " + c + "\n");
+        //Com.Printf(Lib.hexDump(net_message.data, 64, false) + "\n");
+
+        if (0 == Lib.strcmp(c, "ping"))
+            SVC_Ping();
+        else if (0 == Lib.strcmp(c, "ack"))
+            SVC_Ack();
+        else if (0 == Lib.strcmp(c, "status"))
+            SVC_Status();
+        else if (0 == Lib.strcmp(c, "info"))
+            SVC_Info();
+        else if (0 == Lib.strcmp(c, "getchallenge"))
+            SVC_GetChallenge();
+        else if (0 == Lib.strcmp(c, "connect"))
+            SVC_DirectConnect();
+        else if (0 == Lib.strcmp(c, "rcon"))
+            SVC_RemoteCommand();
+        else {
+            Com.Printf("bad connectionless packet from "
+                    + NET.AdrToString(Globals.net_from) + "\n");
+            Com.Printf("[" + s + "]\n");
+            Com.Printf("" + Lib.hexDump(Globals.net_message.data, 128, false));
+        }
+    }
+
+    /**
+     * Updates the cl.ping variables.
+     */
+    private static void SV_CalcPings() {
+        int i, j;
+        client_t cl;
+        int total, count;
+
+        for (i = 0; i < Server.maxclients.value; i++) {
+            cl = SV_INIT.svs.clients[i];
+            if (cl.state != Defines.cs_spawned)
+                continue;
+
+            total = 0;
+            count = 0;
+            for (j = 0; j < Defines.LATENCY_COUNTS; j++) {
+                if (cl.frame_latency[j] > 0) {
+                    count++;
+                    total += cl.frame_latency[j];
+                }
+            }
+            if (0 == count)
+                cl.ping = 0;
+            else
+                cl.ping = total / count;
+
+            // let the game dll know about the ping
+            cl.edict.client.ping = cl.ping;
+        }
+    }
+
+    /**
+     * Every few frames, gives all clients an allotment of milliseconds for
+     * their command moves. If they exceed it, assume cheating.
+     */
+    private static void SV_GiveMsec() {
+        int i;
+        client_t cl;
+
+        if ((SV_INIT.sv.framenum & 15) != 0)
+            return;
+
+        for (i = 0; i < Server.maxclients.value; i++) {
+            cl = SV_INIT.svs.clients[i];
+            if (cl.state == Defines.cs_free)
+                continue;
+
+            cl.commandMsec = 1800; // 1600 + some slop
+        }
+    }
+
+    /**
+     * Reads packets from the network or loopback.
+     */
+    private static void SV_ReadPackets() {
+        int i;
+        client_t cl;
+        int qport = 0;
+
+        while (NET.GetPacket(Defines.NS_SERVER, Globals.net_from,
+                Globals.net_message)) {
+
+            // check for connectionless packet (0xffffffff) first
+            if ((Globals.net_message.data[0] == -1)
+                    && (Globals.net_message.data[1] == -1)
+                    && (Globals.net_message.data[2] == -1)
+                    && (Globals.net_message.data[3] == -1)) {
+                SV_ConnectionlessPacket();
+                continue;
+            }
+
+            // read the qport out of the message so we can fix up
+            // stupid address translating routers
+            MSG.BeginReading(Globals.net_message);
+            MSG.ReadLong(Globals.net_message); // sequence number
+            MSG.ReadLong(Globals.net_message); // sequence number
+            qport = MSG.ReadShort(Globals.net_message) & 0xffff;
+
+            // check for packets from connected clients
+            for (i = 0; i < Server.maxclients.value; i++) {
+                cl = SV_INIT.svs.clients[i];
+                if (cl.state == Defines.cs_free)
+                    continue;
+                if (!NET.CompareBaseAdr(Globals.net_from,
+                        cl.netchan.remote_address))
+                    continue;
+                if (cl.netchan.qport != qport)
+                    continue;
+                if (cl.netchan.remote_address.port != Globals.net_from.port) {
+                    Com.Printf("SV_ReadPackets: fixing up a translated port\n");
+                    cl.netchan.remote_address.port = Globals.net_from.port;
+                }
+
+                if (Netchan.Process(cl.netchan, Globals.net_message)) {
+                    // this is a valid, sequenced packet, so process it
+                    if (cl.state != Defines.cs_zombie) {
+                        cl.lastmessage = SV_INIT.svs.realtime; // don't timeout
+                        SV_USER.SV_ExecuteClientMessage(cl);
+                    }
+                }
+                break;
+            }
+
+            if (i != Server.maxclients.value) {
+            }
+        }
+    }
+
+    /**
+     * If a packet has not been received from a client for timeout.value
+     * seconds, drop the conneciton. Server frames are used instead of realtime
+     * to avoid dropping the local client while debugging.
+     * <p>
+     * When a client is normally dropped, the client_t goes into a zombie state
+     * for a few seconds to make sure any final reliable message gets resent if
+     * necessary.
+     */
+    private static void SV_CheckTimeouts() {
+        int i;
+        client_t cl;
+        int droppoint;
+        int zombiepoint;
+
+        droppoint = (int) (SV_INIT.svs.realtime - 1000 * Server.timeout.value);
+        zombiepoint = (int) (SV_INIT.svs.realtime - 1000 * Server.zombietime.value);
+
+        for (i = 0; i < Server.maxclients.value; i++) {
+            cl = SV_INIT.svs.clients[i];
+            // message times may be wrong across a changelevel
+            if (cl.lastmessage > SV_INIT.svs.realtime)
+                cl.lastmessage = SV_INIT.svs.realtime;
+
+            if (cl.state == Defines.cs_zombie && cl.lastmessage < zombiepoint) {
+                cl.state = Defines.cs_free; // can now be reused
+                continue;
+            }
+            if ((cl.state == Defines.cs_connected || cl.state == Defines.cs_spawned)
+                    && cl.lastmessage < droppoint) {
+                SV_SEND.SV_BroadcastPrintf(Defines.PRINT_HIGH, cl.name
+                        + " timed out\n");
+                SV_DropClient(cl);
+                cl.state = Defines.cs_free; // don't bother with zombie state
+            }
+        }
+    }
+
+    /**
+     * SV_PrepWorldFrame
+     * <p>
+     * This has to be done before the world logic, because player processing
+     * happens outside RunWorldFrame.
+     */
+    private static void SV_PrepWorldFrame() {
+        EDict ent;
+        int i;
+
+        for (i = 0; i < GameBase.num_edicts; i++) {
+            ent = GameBase.g_edicts[i];
+            // events only last for a single message
+            ent.s.event = 0;
+        }
+
+    }
+
+    /**
+     * SV_RunGameFrame.
+     */
+    private static void SV_RunGameFrame() {
+
+        // we always need to bump framenum, even if we
+        // don't run the world, otherwise the delta
+        // compression can get confused when a client
+        // has the "current" frame
+        SV_INIT.sv.framenum++;
+        SV_INIT.sv.time = SV_INIT.sv.framenum * 100;
+
+        // don't run if paused
+        if (0 == Server.sv_paused.value || Server.maxclients.value > 1) {
+            GameBase.G_RunFrame();
+
+            // never get more than one tic behind
+            if (SV_INIT.sv.time < SV_INIT.svs.realtime) {
+                if (Server.sv_showclamp.value != 0)
+                    Com.Printf("sv highclamp\n");
+                SV_INIT.svs.realtime = SV_INIT.sv.time;
+            }
+        }
+
+
+    }
+
+    /**
+     * doFrame.
+     */
+    public static void doFrame(long msec) {
+        Globals.time_before_game = Globals.time_after_game = 0;
+
+        // if server is not active, do nothing
+        if (!SV_INIT.svs.initialized)
+            return;
+
+        SV_INIT.svs.realtime += msec;
+
+        // keep the random time dependent
+        Lib.rand();
+
+        // check timeouts
+        SV_CheckTimeouts();
+
+        // get packets from clients
+        SV_ReadPackets();
+
+        //if (Game.g_edicts[1] !=null)
+        //     Com.p("player at:" + Lib.vtofsbeaty(Game.g_edicts[1].s.origin ));
+
+        // move autonomous things around if enough time has passed
+        if (0 == Server.sv_timedemo.value
+                && SV_INIT.svs.realtime < SV_INIT.sv.time) {
+            // never let the time get too far off
+            if (SV_INIT.sv.time - SV_INIT.svs.realtime > 100) {
+                if (Server.sv_showclamp.value != 0)
+                    Com.Printf("sv lowclamp\n");
+                SV_INIT.svs.realtime = SV_INIT.sv.time - 100;
+            }
+            NET.Sleep(SV_INIT.sv.time - SV_INIT.svs.realtime);
+            return;
+        }
+
+        // update ping based on the last known frame from all clients
+        SV_CalcPings();
+
+        // give the clients some timeslices
+        SV_GiveMsec();
+
+        // let everything in the world think and move
+        SV_RunGameFrame();
+
+        // send messages back to the clients that had packets read this frame
+        SV_SEND.SV_SendClientMessages();
+
+        // save the entire world state if recording a serverdemo
+        SV_ENTS.SV_RecordDemoMessage();
+
+        // send a heartbeat to the master if needed
+        Master_Heartbeat();
+
+        // clear teleport flags, etc for next frame
+        SV_PrepWorldFrame();
+
+    }
+
+    private static void Master_Heartbeat() {
+        String string;
+        int i;
+
+        // pgm post3.19 change, cvar pointer not validated before dereferencing
+        if (Globals.dedicated == null || 0 == Globals.dedicated.value)
+            return; // only dedicated servers send heartbeats
+
+        // pgm post3.19 change, cvar pointer not validated before dereferencing
+        if (null == Server.public_server || 0 == Server.public_server.value)
+            return; // a private dedicated game
+
+        // check for time wraparound
+        if (SV_INIT.svs.last_heartbeat > SV_INIT.svs.realtime)
+            SV_INIT.svs.last_heartbeat = SV_INIT.svs.realtime;
+
+        if (SV_INIT.svs.realtime - SV_INIT.svs.last_heartbeat < Server.HEARTBEAT_SECONDS * 1000)
+            return; // not time to send yet
+
+        SV_INIT.svs.last_heartbeat = SV_INIT.svs.realtime;
+
+        // send the same string that we would give for a status OOB command
+        string = SV_StatusString();
+
+        // send to group master
+        for (i = 0; i < Defines.MAX_MASTERS; i++)
+            if (Server.master_adr[i].port != 0) {
+                Com.Printf("Sending heartbeat to "
+                        + NET.AdrToString(Server.master_adr[i]) + "\n");
+                Netchan.OutOfBandPrint(Defines.NS_SERVER,
+                        Server.master_adr[i], "heartbeat\n" + string);
+            }
+    }
+
+
+    /**
+     * Master_Shutdown, Informs all masters that this server is going down.
+     */
+    private static void Master_Shutdown() {
+        int i;
+
+        // pgm post3.19 change, cvar pointer not validated before dereferencing
+        if (null == Globals.dedicated || 0 == Globals.dedicated.value)
+            return; // only dedicated servers send heartbeats
+
+        // pgm post3.19 change, cvar pointer not validated before dereferencing
+        if (null == Server.public_server || 0 == Server.public_server.value)
+            return; // a private dedicated game
+
+        // send to group master
+        for (i = 0; i < Defines.MAX_MASTERS; i++)
+            if (Server.master_adr[i].port != 0) {
+                if (i > 0)
+                    Com.Printf("Sending heartbeat to "
+                            + NET.AdrToString(Server.master_adr[i]) + "\n");
+                Netchan.OutOfBandPrint(Defines.NS_SERVER,
+                        Server.master_adr[i], "shutdown");
+            }
+    }
+
+
+    /**
+     * Pull specific info from a newly changed userinfo string into a more C
+     * freindly form.
+     */
+    public static void SV_UserinfoChanged(client_t cl) {
+        String val;
+        int i;
+
+        // call prog code to allow overrides
+        PlayerClient.ClientUserinfoChanged(cl.edict, cl.userinfo);
+
+        // name for C code
+        cl.name = Info.Info_ValueForKey(cl.userinfo, "name");
+
+        // mask off high bit
+        //TODO: masking for german umlaute
+        //for (i=0 ; i<sizeof(cl.name) ; i++)
+        //     cl.name[i] &= 127;
+
+        // rate command
+        val = Info.Info_ValueForKey(cl.userinfo, "rate");
+        if (val.length() > 0) {
+            i = Lib.atoi(val);
+            cl.rate = i;
+            if (cl.rate < 100)
+                cl.rate = 100;
+            if (cl.rate > 15000)
+                cl.rate = 15000;
+        } else
+            cl.rate = 5000;
+
+        // msg command
+        val = Info.Info_ValueForKey(cl.userinfo, "msg");
+        if (val.length() > 0) {
+            cl.messagelevel = Lib.atoi(val);
+        }
+
+    }
+
+    /**
+     * Only called at quake2.exe startup, not for each game
+     */
+    public static void SV_Init() {
+        SV_CCMDS.SV_InitOperatorCommands(); //ok.
+
+        Server.rcon_password = Cvar.get("rcon_password", "", 0);
+        Cvar.get("skill", "1", 0);
+        Cvar.get("deathmatch", "0", Defines.CVAR_LATCH);
+        Cvar.get("coop", "0", Defines.CVAR_LATCH);
+        Cvar.get("dmflags", "" + Defines.DF_INSTANT_ITEMS,
+                Defines.CVAR_SERVERINFO);
+        Cvar.get("fraglimit", "0", Defines.CVAR_SERVERINFO);
+        Cvar.get("timelimit", "0", Defines.CVAR_SERVERINFO);
+        Cvar.get("cheats", "0", Defines.CVAR_SERVERINFO | Defines.CVAR_LATCH);
+        Cvar.get("protocol", "" + Defines.PROTOCOL_VERSION,
+                Defines.CVAR_SERVERINFO | Defines.CVAR_NOSET);
+
+        Server.maxclients = Cvar.get("maxclients", "1",
+                Defines.CVAR_SERVERINFO | Defines.CVAR_LATCH);
+        Server.hostname = Cvar.get("hostname", "noname",
+                Defines.CVAR_SERVERINFO | Defines.CVAR_ARCHIVE);
+        Server.timeout = Cvar.get("timeout", "125", 0);
+        Server.zombietime = Cvar.get("zombietime", "2", 0);
+        Server.sv_showclamp = Cvar.get("showclamp", "0", 0);
+        Server.sv_paused = Cvar.get("paused", "0", 0);
+        Server.sv_timedemo = Cvar.get("timedemo", "0", 0);
+        Server.sv_enforcetime = Cvar.get("sv_enforcetime", "0", 0);
+
+        Server.allow_download = Cvar.get("allow_download", "1",
+                Defines.CVAR_ARCHIVE);
+        Server.allow_download_players = Cvar.get("allow_download_players",
+                "0", Defines.CVAR_ARCHIVE);
+        Server.allow_download_models = Cvar.get("allow_download_models", "1",
+                Defines.CVAR_ARCHIVE);
+        Server.allow_download_sounds = Cvar.get("allow_download_sounds", "1",
+                Defines.CVAR_ARCHIVE);
+        Server.allow_download_maps = Cvar.get("allow_download_maps", "1",
+                Defines.CVAR_ARCHIVE);
+
+        Server.sv_noreload = Cvar.get("sv_noreload", "0", 0);
+        Server.sv_airaccelerate = Cvar.get("sv_airaccelerate", "0",
+                Defines.CVAR_LATCH);
+        Server.public_server = Cvar.get("public", "0", 0);
+        Server.sv_reconnect_limit = Cvar.get("sv_reconnect_limit", "3",
+                Defines.CVAR_ARCHIVE);
+
+        SZ.Init(Globals.net_message, Globals.net_message_buffer,
+                Globals.net_message_buffer.length);
+    }
+
+    /**
+     * Used by SV_Shutdown to send a final message to all connected clients
+     * before the server goes down. The messages are sent immediately, not just
+     * stuck on the outgoing message list, because the server is going to
+     * totally exit after returning from this function.
+     */
+    private static void SV_FinalMessage(String message, boolean reconnect) {
+        int i;
+        client_t cl;
+
+        SZ.Clear(Globals.net_message);
+        MSG.WriteByte(Globals.net_message, Defines.svc_print);
+        MSG.WriteByte(Globals.net_message, Defines.PRINT_HIGH);
+        MSG.WriteString(Globals.net_message, message);
+
+        if (reconnect)
+            MSG.WriteByte(Globals.net_message, Defines.svc_reconnect);
+        else
+            MSG.WriteByte(Globals.net_message, Defines.svc_disconnect);
+
+        // send it twice
+        // stagger the packets to crutch operating system limited buffers
+
+        for (i = 0; i < SV_INIT.svs.clients.length; i++) {
+            cl = SV_INIT.svs.clients[i];
+            if (cl.state >= Defines.cs_connected)
+                Netchan.Transmit(cl.netchan, Globals.net_message.cursize,
+                        Globals.net_message.data);
+        }
+        for (i = 0; i < SV_INIT.svs.clients.length; i++) {
+            cl = SV_INIT.svs.clients[i];
+            if (cl.state >= Defines.cs_connected)
+                Netchan.Transmit(cl.netchan, Globals.net_message.cursize,
+                        Globals.net_message.data);
+        }
+    }
+
+    /**
+     * Called when each game quits, before Sys_Quit or Sys_Error.
+     */
+    public static void SV_Shutdown(String finalmsg, boolean reconnect) {
+        if (SV_INIT.svs.clients != null)
+            SV_FinalMessage(finalmsg, reconnect);
+
+        Master_Shutdown();
+
+        SV_GAME.SV_ShutdownGameProgs();
+
+        // free current level
+        if (SV_INIT.sv.demofile != null)
+            try {
+                SV_INIT.sv.demofile.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+
+        SV_INIT.sv = new server_t();
+
+        Globals.server_state = SV_INIT.sv.state;
+
+        if (SV_INIT.svs.demofile != null)
+            try {
+                SV_INIT.svs.demofile.close();
+            } catch (IOException e1) {
+                e1.printStackTrace();
+            }
+
+        SV_INIT.svs = new server_static_t();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/server/areanode_t.java b/src/main/java/lwjake2/server/areanode_t.java
new file mode 100644 (file)
index 0000000..12f9218
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.server;
+
+import lwjake2.game.link_t;
+
+public class areanode_t {
+    final areanode_t[] children = new areanode_t[2];
+    final link_t trigger_edicts = new link_t(this);
+    final link_t solid_edicts = new link_t(this);
+    // used for debugging
+    final float[] mins_rst = {0, 0, 0};
+    final float[] maxs_rst = {0, 0, 0};
+    int axis; // -1 = leaf node
+    float dist;
+}
diff --git a/src/main/java/lwjake2/server/challenge_t.java b/src/main/java/lwjake2/server/challenge_t.java
new file mode 100644 (file)
index 0000000..bc9b56a
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.server;
+
+import lwjake2.qcommon.NetadrT;
+
+class challenge_t {
+    //mem
+    NetadrT adr = new NetadrT();
+    int challenge;
+    int time;
+}
diff --git a/src/main/java/lwjake2/server/client_frame_t.java b/src/main/java/lwjake2/server/client_frame_t.java
new file mode 100644 (file)
index 0000000..ea3635a
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.server;
+
+import lwjake2.Defines;
+import lwjake2.game.player_state_t;
+
+class client_frame_t {
+
+    final byte[] areabits = new byte[Defines.MAX_MAP_AREAS / 8];        // portalarea visibility bits
+    final player_state_t ps = new player_state_t();
+    int areabytes;
+    int num_entities;
+    int first_entity;        // into the circular sv_packet_entities[]
+    int senttime;            // for ping calculations
+}
diff --git a/src/main/java/lwjake2/server/client_t.java b/src/main/java/lwjake2/server/client_t.java
new file mode 100644 (file)
index 0000000..f3344dd
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.server;
+
+import lwjake2.Defines;
+import lwjake2.game.EDict;
+import lwjake2.game.usercmd_t;
+import lwjake2.qcommon.netchan_t;
+import lwjake2.qcommon.sizebuf_t;
+
+public class client_t {
+
+    public static final int LATENCY_COUNTS = 16;
+    public static final int RATE_MESSAGES = 10;
+    final int[] frame_latency = new int[LATENCY_COUNTS];
+    final int[] message_size = new int[RATE_MESSAGES]; // used to rate drop packets
+    // The datagram is written to by sound calls, prints, temp ents, etc.
+    // It can be harmlessly overflowed.
+    final sizebuf_t datagram = new sizebuf_t();
+    final byte[] datagram_buf = new byte[Defines.MAX_MSGLEN];
+    final client_frame_t[] frames = new client_frame_t[Defines.UPDATE_BACKUP]; // updates can be delta'd from here
+    final netchan_t netchan = new netchan_t();
+    int state;
+    String userinfo = "";
+    int lastframe; // for delta compression
+    usercmd_t lastcmd = new usercmd_t(); // for filling in big drops
+    int commandMsec; // every seconds this is reset, if user
+    // commands exhaust it, assume time cheating
+    int ping;
+    int rate;
+    int surpressCount; // number of messages rate supressed
+    // pointer
+    EDict edict; // EDICT_NUM(clientnum+1)
+    //char                             name[32];                       // extracted from userinfo, high bits masked
+    String name = ""; // extracted from userinfo, high bits masked
+    int messagelevel; // for filtering printed messages
+    byte download[]; // file being downloaded
+    int downloadsize; // total bytes (can't use EOF because of paks)
+    int downloadcount; // bytes sent
+    int lastmessage; // sv.framenum when packet was last received
+    int lastconnect;
+    int challenge; // challenge of this user, randomly generated
+    //this was introduced by rst, since java can't calculate the index out of the address.
+    int serverindex;
+
+    public client_t() {
+        for (int n = 0; n < Defines.UPDATE_BACKUP; n++) {
+            frames[n] = new client_frame_t();
+        }
+    }
+}
diff --git a/src/main/java/lwjake2/server/moveclip_t.java b/src/main/java/lwjake2/server/moveclip_t.java
new file mode 100644 (file)
index 0000000..89e7540
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.server;
+
+import lwjake2.game.EDict;
+import lwjake2.game.trace_t;
+
+public class moveclip_t {
+    final float[] boxmins = {0, 0, 0};
+    final float[] boxmaxs = {0, 0, 0};// enclose the test object along entire move
+    final float[] mins2 = {0, 0, 0};
+    final float[] maxs2 = {0, 0, 0};    // size when clipping against mosnters
+    float[] mins, maxs;    // size of the moving object
+    float[] start, end;
+    // mem
+    trace_t trace = new trace_t();
+    EDict passedict;
+    int contentmask;
+
+}
diff --git a/src/main/java/lwjake2/server/server_static_t.java b/src/main/java/lwjake2/server/server_static_t.java
new file mode 100644 (file)
index 0000000..ae27580
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.server;
+
+import lwjake2.Defines;
+import lwjake2.game.entity_state_t;
+import lwjake2.qcommon.sizebuf_t;
+
+import java.io.RandomAccessFile;
+
+public class server_static_t {
+    final challenge_t[] challenges = new challenge_t[Defines.MAX_CHALLENGES]; // to
+    // prevent
+    // invalid
+    // IPs
+    // from
+    // connecting
+    final sizebuf_t demo_multicast = new sizebuf_t();
+    final byte[] demo_multicast_buf = new byte[Defines.MAX_MSGLEN];
+    boolean initialized; // sv_init has completed
+    int realtime; // always increasing, no clamping, etc
+    String mapcmd = ""; // ie: *intro.cin+base
+    int spawncount; // incremented each server start
+    client_t clients[]; // [maxclients->value];
+    // used to check late spawns
+    int num_client_entities; // maxclients->value*UPDATE_BACKUP*MAX_PACKET_ENTITIES
+    int next_client_entities; // next client_entity to use
+    entity_state_t client_entities[]; // [num_client_entities]
+    int last_heartbeat;
+    // serverrecord values
+    RandomAccessFile demofile;
+
+    public server_static_t() {
+        for (int n = 0; n < Defines.MAX_CHALLENGES; n++) {
+            challenges[n] = new challenge_t();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/server/server_t.java b/src/main/java/lwjake2/server/server_t.java
new file mode 100644 (file)
index 0000000..3a34a08
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.server;
+
+import lwjake2.Defines;
+import lwjake2.game.cmodel_t;
+import lwjake2.game.entity_state_t;
+import lwjake2.qcommon.sizebuf_t;
+
+import java.io.RandomAccessFile;
+
+public class server_t {
+
+    final cmodel_t[] models;
+    final String[] configstrings = new String[Defines.MAX_CONFIGSTRINGS];
+    final entity_state_t[] baselines = new entity_state_t[Defines.MAX_EDICTS];
+    // the multicast buffer is used to send a message to a set of clients
+    // it is only used to marshall data until SV_Multicast is called
+    final sizebuf_t multicast = new sizebuf_t();
+    final byte[] multicast_buf = new byte[Defines.MAX_MSGLEN];
+    int state; // precache commands are only valid during load
+    boolean attractloop; // running cinematics and demos for the local system
+    boolean loadgame; // client begins should reuse existing entity
+    // only
+    int time; // always sv.framenum * 100 msec
+    int framenum;
+    String name = ""; // map name, or cinematic name
+    // demo server information
+    RandomAccessFile demofile;
+
+    public server_t() {
+        models = new cmodel_t[Defines.MAX_MODELS];
+        for (int n = 0; n < Defines.MAX_MODELS; n++)
+            models[n] = new cmodel_t();
+
+        for (int n = 0; n < Defines.MAX_EDICTS; n++)
+            baselines[n] = new entity_state_t(null);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/sound/DummyDriver.java b/src/main/java/lwjake2/sound/DummyDriver.java
new file mode 100644 (file)
index 0000000..7fd8ef3
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.sound;
+
+import java.nio.ByteBuffer;
+
+/**
+ * DummyDriver
+ *
+ * @author cwei
+ */
+public final class DummyDriver implements Sound {
+
+    static {
+        S.register(new DummyDriver());
+    }
+
+    private DummyDriver() {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.Sound#Init()
+     */
+    public boolean Init() {
+        return true;
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.Sound#Shutdown()
+     */
+    public void Shutdown() {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.Sound#BeginRegistration()
+     */
+    public void BeginRegistration() {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.Sound#RegisterSound(java.lang.String)
+     */
+    public sfx_t RegisterSound(String sample) {
+        return null;
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.Sound#EndRegistration()
+     */
+    public void EndRegistration() {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.Sound#StartLocalSound(java.lang.String)
+     */
+    public void StartLocalSound(String sound) {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.Sound#StartSound(float[], int, int, jake2.sound.sfx_t, float, float, float)
+     */
+    public void StartSound(float[] origin, int entnum, int entchannel, sfx_t sfx, float fvol, float attenuation, float timeofs) {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.Sound#Update(float[], float[], float[], float[])
+     */
+    public void Update(float[] origin, float[] forward, float[] right, float[] up) {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.Sound#RawSamples(int, int, int, int, byte[])
+     */
+    public void RawSamples(int samples, int rate, int width, int channels, ByteBuffer data) {
+    }
+
+    public void disableStreaming() {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.Sound#StopAllSounds()
+        */
+    public void StopAllSounds() {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.Sound#getName()
+     */
+    public String getName() {
+        return "dummy";
+    }
+}
diff --git a/src/main/java/lwjake2/sound/S.java b/src/main/java/lwjake2/sound/S.java
new file mode 100644 (file)
index 0000000..49a7197
--- /dev/null
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.sound;
+
+import lwjake2.Defines;
+import lwjake2.game.CvarT;
+import lwjake2.qcommon.Com;
+import lwjake2.qcommon.Cvar;
+
+import java.nio.ByteBuffer;
+import java.util.Vector;
+
+public class S {
+
+    static final Vector<Sound> drivers = new Vector<>(1);
+    static Sound impl;
+    static CvarT s_impl;
+
+    /**
+     * Searches for and initializes all known sound drivers.
+     */
+    static {
+        // dummy driver (no sound)
+        try {
+            Class.forName("lwjake2.sound.DummyDriver");
+            // initialize impl with the default value
+            // this is  necessary for dedicated mode
+            useDriver("dummy");
+        } catch (Throwable e) {
+            Com.DPrintf("could not init dummy sound driver class.");
+        }
+
+        try {
+            Class.forName("org.lwjgl.openal.AL");
+            Class.forName("lwjake2.sound.lwjgl.LWJGLSoundImpl");
+        } catch (Throwable e) {
+            // ignore the lwjgl driver if runtime not in classpath
+            Com.DPrintf("could not init lwjgl sound driver class.");
+        }
+    }
+
+    /**
+     * Registers a new Sound Implementor.
+     */
+    public static void register(Sound driver) {
+        if (driver == null) {
+            throw new IllegalArgumentException("Sound implementation can't be null");
+        }
+        if (!drivers.contains(driver)) {
+            drivers.add(driver);
+        }
+    }
+
+    /**
+     * Switches to the specific sound driver.
+     */
+    public static void useDriver(String driverName) {
+        Sound driver = null;
+        int count = drivers.size();
+        for (Sound driver1 : drivers) {
+            driver = driver1;
+            if (driver.getName().equals(driverName)) {
+                impl = driver;
+                return;
+            }
+        }
+        // if driver not found use dummy
+        impl = drivers.lastElement();
+    }
+
+    /**
+     * Initializes the sound module.
+     */
+    public static void Init() {
+
+        Com.Printf("\n------- sound initialization -------\n");
+
+        CvarT cv = Cvar.get("s_initsound", "1", 0);
+        if (cv.value == 0.0f) {
+            Com.Printf("not initializing.\n");
+            useDriver("dummy");
+            return;
+        }
+
+        // set the last registered driver as default
+        String defaultDriver = "dummy";
+        if (drivers.size() > 1) {
+            defaultDriver = (drivers.lastElement()).getName();
+        }
+
+        s_impl = Cvar.get("s_impl", defaultDriver, Defines.CVAR_ARCHIVE);
+        useDriver(s_impl.string);
+
+        if (impl.Init()) {
+            // driver ok
+            Cvar.set("s_impl", impl.getName());
+        } else {
+            // fallback
+            useDriver("dummy");
+        }
+
+        Com.Printf("\n------- use sound driver \"" + impl.getName() + "\" -------\n");
+        StopAllSounds();
+    }
+
+    public static void Shutdown() {
+        impl.Shutdown();
+    }
+
+    /**
+     * Called before the sounds are to be loaded and registered.
+     */
+    public static void BeginRegistration() {
+        impl.BeginRegistration();
+    }
+
+    /**
+     * Registers and loads a sound.
+     */
+    public static sfx_t RegisterSound(String sample) {
+        return impl.RegisterSound(sample);
+    }
+
+    /**
+     * Called after all sounds are registered and loaded.
+     */
+    public static void EndRegistration() {
+        impl.EndRegistration();
+    }
+
+    /**
+     * Starts a local sound.
+     */
+    public static void StartLocalSound(String sound) {
+        impl.StartLocalSound(sound);
+    }
+
+    /**
+     * StartSound - Validates the parms and ques the sound up
+     * if pos is NULL, the sound will be dynamically sourced from the entity
+     * Entchannel 0 will never override a playing sound
+     */
+    public static void StartSound(float[] origin, int entnum, int entchannel, sfx_t sfx, float fvol, float attenuation, float timeofs) {
+        impl.StartSound(origin, entnum, entchannel, sfx, fvol, attenuation, timeofs);
+    }
+
+    /**
+     * Updates the sound renderer according to the changes in the environment,
+     * called once each time through the main loop.
+     */
+    public static void Update(float[] origin, float[] forward, float[] right, float[] up) {
+        impl.Update(origin, forward, right, up);
+    }
+
+    /**
+     * Cinematic streaming and voice over network.
+     */
+    public static void RawSamples(int samples, int rate, int width, int channels, ByteBuffer data) {
+        impl.RawSamples(samples, rate, width, channels, data);
+    }
+
+    /**
+     * Switches off the sound streaming.
+     */
+    public static void disableStreaming() {
+        impl.disableStreaming();
+    }
+
+    /**
+     * Stops all sounds.
+     */
+    public static void StopAllSounds() {
+        impl.StopAllSounds();
+    }
+
+    public static String getDriverName() {
+        return impl.getName();
+    }
+
+    /**
+     * Returns a string array containing all sound driver names.
+     */
+    public static String[] getDriverNames() {
+        String[] names = new String[drivers.size()];
+        for (int i = 0; i < names.length; i++) {
+            names[i] = (drivers.get(i)).getName();
+        }
+        return names;
+    }
+
+    /**
+     * This is used, when resampling to this default sampling rate is activated
+     * in the wavloader. It is placed here that sound implementors can override
+     * this one day.
+     */
+    public static int getDefaultSampleRate() {
+        return 44100;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/sound/Sound.java b/src/main/java/lwjake2/sound/Sound.java
new file mode 100644 (file)
index 0000000..2c9cd67
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.sound;
+
+import lwjake2.Defines;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Sound
+ *
+ * @author cwei
+ */
+public interface Sound {
+
+    int MAX_SFX = Defines.MAX_SOUNDS * 2;
+    int STREAM_QUEUE = 8;
+
+    String getName();
+
+    boolean Init();
+
+    void Shutdown();
+
+    /*
+    =====================
+    S_BeginRegistration
+    =====================
+    */
+    void BeginRegistration();
+
+    /*
+    =====================
+    S_RegisterSound
+    =====================
+    */
+    sfx_t RegisterSound(String sample);
+
+    /*
+    =====================
+    S_EndRegistration
+    =====================
+    */
+    void EndRegistration();
+
+    /*
+    ==================
+    S_StartLocalSound
+    ==================
+    */
+    void StartLocalSound(String sound);
+
+    /*
+    ====================
+    S_StartSound
+
+    Validates the parms and ques the sound up
+    if pos is NULL, the sound will be dynamically sourced from the entity
+    Entchannel 0 will never override a playing sound
+    ====================
+    */
+    void StartSound(float[] origin, int entnum, int entchannel, sfx_t sfx, float fvol, float attenuation, float timeofs);
+
+    /*
+    ============
+    S_Update
+
+    Called once each time through the main loop
+    ============
+    */
+    void Update(float[] origin, float[] forward, float[] right, float[] up);
+
+    /*
+    ============
+    S_RawSamples
+
+    Cinematic streaming and voice over network
+    ============
+    */
+    void RawSamples(int samples, int rate, int width, int channels, ByteBuffer data);
+
+    void disableStreaming();
+
+    /*
+    ==================
+    S_StopAllSounds
+    ==================
+    */
+    void StopAllSounds();
+
+}
diff --git a/src/main/java/lwjake2/sound/WaveLoader.java b/src/main/java/lwjake2/sound/WaveLoader.java
new file mode 100644 (file)
index 0000000..76d4186
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.sound;
+
+import lwjake2.Defines;
+import lwjake2.qcommon.Com;
+import lwjake2.qcommon.FS;
+
+/**
+ * SND_MEM
+ */
+public class WaveLoader {
+
+    /**
+     * The ResampleSfx can squeeze and stretch samples to a default sample rate.
+     * Since Joal and lwjgl sound drivers support this, we don't need it and the samples
+     * can keep their original sample rate. Use this switch for reactivating resampling.
+     */
+    private static final boolean DONT_DO_A_RESAMPLING_FOR_JOAL_AND_LWJGL = true;
+    static byte[] data_b;
+    static int data_p;
+    static int iff_end;
+    static int last_chunk;
+    static int iff_data;
+    static int iff_chunk_len;
+
+    /**
+     * Loads a sound from a wav file.
+     */
+    public static sfxcache_t LoadSound(sfx_t s) {
+        if (s.name.charAt(0) == '*')
+            return null;
+
+        // see if still in memory
+        sfxcache_t sc = s.cache;
+        if (sc != null)
+            return sc;
+
+        String name;
+        // load it in
+        if (s.truename != null)
+            name = s.truename;
+        else
+            name = s.name;
+
+        String namebuffer;
+        if (name.charAt(0) == '#')
+            namebuffer = name.substring(1);
+        else
+            namebuffer = "sound/" + name;
+
+        byte[] data = FS.LoadFile(namebuffer);
+
+        if (data == null) {
+            Com.DPrintf("Couldn't load " + namebuffer + "\n");
+            return null;
+        }
+
+        int size = data.length;
+
+        wavinfo_t info = GetWavinfo(s.name, data, size);
+
+        if (info.channels != 1) {
+            Com.Printf(s.name + " is a stereo sample - ignoring\n");
+            return null;
+        }
+
+        float stepscale;
+        if (DONT_DO_A_RESAMPLING_FOR_JOAL_AND_LWJGL)
+            stepscale = 1;
+        else
+            stepscale = (float) info.rate / S.getDefaultSampleRate();
+
+        int len = (int) (info.samples / stepscale);
+        len = len * info.width * info.channels;
+
+        // TODO: handle max sample bytes with a cvar
+        /*
+      This is the maximum sample length in bytes which has to be replaced by
+      a configurable variable.
+     */
+        int maxsamplebytes = 2048 * 1024;
+        if (len >= maxsamplebytes) {
+            Com.Printf(s.name + " is too long: " + len + " bytes?! ignoring.\n");
+            return null;
+        }
+
+        sc = s.cache = new sfxcache_t(len);
+
+        sc.length = info.samples;
+        sc.loopstart = info.loopstart;
+        sc.speed = info.rate;
+        sc.width = info.width;
+        sc.stereo = info.channels;
+
+        ResampleSfx(s, sc.speed, sc.width, data, info.dataofs);
+        data = null;
+
+        return sc;
+    }
+
+    /**
+     * Converts sample data with respect to the endianess and adjusts
+     * the sample rate of a loaded sample, see flag DONT_DO_A_RESAMPLING_FOR_JOAL_AND_LWJGL.
+     */
+    public static void ResampleSfx(sfx_t sfx, int inrate, int inwidth, byte data[], int offset) {
+        int outcount;
+        int srcsample;
+        int i;
+        int sample, samplefrac, fracstep;
+        sfxcache_t sc;
+
+        sc = sfx.cache;
+
+        if (sc == null)
+            return;
+
+        // again calculate the stretching factor.
+        // this is usually 0.5, 1, or 2
+
+        float stepscale;
+        if (DONT_DO_A_RESAMPLING_FOR_JOAL_AND_LWJGL)
+            stepscale = 1;
+        else
+            stepscale = (float) inrate / S.getDefaultSampleRate();
+        outcount = (int) (sc.length / stepscale);
+        sc.length = outcount;
+
+        if (sc.loopstart != -1)
+            sc.loopstart = (int) (sc.loopstart / stepscale);
+
+        // if resampled, sample has now the default sample rate
+        if (!DONT_DO_A_RESAMPLING_FOR_JOAL_AND_LWJGL)
+            sc.speed = S.getDefaultSampleRate();
+
+        sc.width = inwidth;
+        sc.stereo = 0;
+        samplefrac = 0;
+        fracstep = (int) (stepscale * 256);
+
+        for (i = 0; i < outcount; i++) {
+            srcsample = samplefrac >> 8;
+            samplefrac += fracstep;
+
+            if (inwidth == 2) {
+                sample = (data[offset + srcsample * 2] & 0xff)
+                        + (data[offset + srcsample * 2 + 1] << 8);
+            } else {
+                sample = ((data[offset + srcsample] & 0xff) - 128) << 8;
+            }
+
+            if (sc.width == 2) {
+                if (Defines.LITTLE_ENDIAN) {
+                    sc.data[i * 2] = (byte) (sample & 0xff);
+                    sc.data[i * 2 + 1] = (byte) ((sample >>> 8) & 0xff);
+                } else {
+                    sc.data[i * 2] = (byte) ((sample >>> 8) & 0xff);
+                    sc.data[i * 2 + 1] = (byte) (sample & 0xff);
+                }
+            } else {
+                sc.data[i] = (byte) (sample >> 8);
+            }
+        }
+    }
+
+    static short GetLittleShort() {
+        int val = 0;
+        val = data_b[data_p] & 0xFF;
+        data_p++;
+        val |= ((data_b[data_p] & 0xFF) << 8);
+        data_p++;
+        return (short) val;
+    }
+
+    static int GetLittleLong() {
+        int val = 0;
+        val = data_b[data_p] & 0xFF;
+        data_p++;
+        val |= ((data_b[data_p] & 0xFF) << 8);
+        data_p++;
+        val |= ((data_b[data_p] & 0xFF) << 16);
+        data_p++;
+        val |= ((data_b[data_p] & 0xFF) << 24);
+        data_p++;
+        return val;
+    }
+
+    static void FindNextChunk(String name) {
+        while (true) {
+            data_p = last_chunk;
+
+            if (data_p >= iff_end) { // didn't find the chunk
+                data_p = 0;
+                return;
+            }
+
+            data_p += 4;
+
+            iff_chunk_len = GetLittleLong();
+
+            if (iff_chunk_len < 0) {
+                data_p = 0;
+                return;
+            }
+            if (iff_chunk_len > 1024 * 1024) {
+                Com.Println(" Warning: FindNextChunk: length is past the 1 meg sanity limit");
+            }
+            data_p -= 8;
+            last_chunk = data_p + 8 + ((iff_chunk_len + 1) & ~1);
+            String s = new String(data_b, data_p, 4);
+            if (s.equals(name))
+                return;
+        }
+    }
+
+    static void FindChunk(String name) {
+        last_chunk = iff_data;
+        FindNextChunk(name);
+    }
+
+    /*
+    ============
+    GetWavinfo
+    ============
+    */
+    static wavinfo_t GetWavinfo(String name, byte[] wav, int wavlength) {
+        wavinfo_t info = new wavinfo_t();
+        int i;
+        int format;
+        int samples;
+
+        if (wav == null)
+            return info;
+
+        iff_data = 0;
+        iff_end = wavlength;
+        data_b = wav;
+
+        // find "RIFF" chunk
+        FindChunk("RIFF");
+        String s = new String(data_b, data_p + 8, 4);
+        if (!s.equals("WAVE")) {
+            Com.Printf("Missing RIFF/WAVE chunks\n");
+            return info;
+        }
+
+        //        get "fmt " chunk
+        iff_data = data_p + 12;
+        //        DumpChunks ();
+
+        FindChunk("fmt ");
+        if (data_p == 0) {
+            Com.Printf("Missing fmt chunk\n");
+            return info;
+        }
+        data_p += 8;
+        format = GetLittleShort();
+        if (format != 1) {
+            Com.Printf("Microsoft PCM format only\n");
+            return info;
+        }
+
+        info.channels = GetLittleShort();
+        info.rate = GetLittleLong();
+        data_p += 4 + 2;
+        info.width = GetLittleShort() / 8;
+
+        //        get cue chunk
+        FindChunk("cue ");
+        if (data_p != 0) {
+            data_p += 32;
+            info.loopstart = GetLittleLong();
+            //                 Com_Printf("loopstart=%d\n", sfx->loopstart);
+
+            // if the next chunk is a LIST chunk, look for a cue length marker
+            FindNextChunk("LIST");
+            if (data_p != 0) {
+                if (data_b.length >= data_p + 32) {
+                    s = new String(data_b, data_p + 28, 4);
+                    if (s.equals("MARK")) { // this is not a proper parse, but
+                        // it works with cooledit...
+                        data_p += 24;
+                        i = GetLittleLong(); // samples in loop
+                        info.samples = info.loopstart + i;
+                        //                                     Com_Printf("looped length: %i\n", i);
+                    }
+                }
+            }
+        } else
+            info.loopstart = -1;
+
+        //        find data chunk
+        FindChunk("data");
+        if (data_p == 0) {
+            Com.Printf("Missing data chunk\n");
+            return info;
+        }
+
+        data_p += 4;
+        samples = GetLittleLong() / info.width;
+
+        if (info.samples != 0) {
+            if (samples < info.samples)
+                Com.Error(Defines.ERR_DROP, "Sound " + name + " has a bad loop length");
+        } else {
+            info.samples = samples;
+            if (info.loopstart > 0) info.samples -= info.loopstart;
+        }
+
+        info.dataofs = data_p;
+
+        return info;
+    }
+
+    static class wavinfo_t {
+        int rate;
+        int width;
+        int channels;
+        int loopstart;
+        int samples;
+        int dataofs; // chunk starts this many bytes from file start
+    }
+}
diff --git a/src/main/java/lwjake2/sound/lwjgl/Channel.java b/src/main/java/lwjake2/sound/lwjgl/Channel.java
new file mode 100644 (file)
index 0000000..8e2299a
--- /dev/null
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.sound.lwjgl;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.client.CL_ents;
+import lwjake2.game.entity_state_t;
+import lwjake2.qcommon.Com;
+import lwjake2.sound.Sound;
+import lwjake2.sound.sfx_t;
+import lwjake2.sound.sfxcache_t;
+import lwjake2.util.Lib;
+import lwjake2.util.Math3D;
+import org.lwjgl.openal.AL10;
+import org.lwjgl.openal.AL11;
+import org.lwjgl.openal.EFX10;
+
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Channel
+ *
+ * @author dsanders/cwei
+ */
+public class Channel {
+
+    final static int LISTENER = 0;
+    final static int FIXED = 1;
+    final static int DYNAMIC = 2;
+    final static int MAX_CHANNELS = 128;
+
+    private static final Channel[] channels = new Channel[MAX_CHANNELS];
+    private static final IntBuffer sources = Lib.newIntBuffer(MAX_CHANNELS);
+    private static final Map<Integer, Channel> looptable = new Hashtable<>(MAX_CHANNELS);
+    private static final IntBuffer tmp = Lib.newIntBuffer(1);
+    private static final FloatBuffer sourceOriginBuffer = Lib.newFloatBuffer(3);
+    //stack variable
+    private static final float[] entityOrigin = {0, 0, 0};
+    // a reference of LWJGLSoundImpl.buffers
+    private static IntBuffer buffers;
+    private static int numChannels;
+    // stream handling
+    private static boolean streamingEnabled = false;
+    private static int streamQueue = 0;
+    private final int sourceId;
+    private final float[] origin = {0, 0, 0};
+    // sound attributes
+    private int type;
+    private int entnum;
+    private int entchannel;
+    private int bufferId;
+    private float volume;
+    private float rolloff;
+    // update flags
+    private boolean autosound;
+    private boolean active;
+    private boolean modified;
+    private boolean bufferChanged;
+    private boolean volumeChanged;
+
+    private Channel(int sourceId) {
+        this.sourceId = sourceId;
+        clear();
+        volumeChanged = false;
+        volume = 1.0f;
+    }
+
+    static int init(IntBuffer buffers) {
+        Channel.buffers = buffers;
+        // create channels
+        int sourceId;
+        for (int i = 0; i < MAX_CHANNELS; i++) {
+
+            AL10.alGenSources(tmp);
+            sourceId = tmp.get(0);
+
+            // can't generate more sources
+            if (sourceId <= 0) break;
+
+            sources.put(i, sourceId);
+
+            channels[i] = new Channel(sourceId);
+            numChannels++;
+
+            // set default values for AL sources
+            AL10.alSourcef(sourceId, AL10.AL_GAIN, 1.0f);
+            AL10.alSourcef(sourceId, AL10.AL_PITCH, 1.0f);
+            AL10.alSourcei(sourceId, AL10.AL_SOURCE_ABSOLUTE, AL10.AL_TRUE);
+            AL10.alSource3f(sourceId, AL10.AL_VELOCITY, 0, 0, 0);
+            AL10.alSourcei(sourceId, AL10.AL_LOOPING, AL10.AL_FALSE);
+            AL10.alSourcef(sourceId, AL10.AL_REFERENCE_DISTANCE, 200.0f);
+            AL10.alSourcef(sourceId, AL10.AL_MIN_GAIN, 0.0005f);
+            AL10.alSourcef(sourceId, AL10.AL_MAX_GAIN, 1.0f);
+        }
+        return numChannels;
+    }
+
+    static void reset() {
+        for (int i = 0; i < numChannels; i++) {
+            AL10.alSourceStop(sources.get(i));
+            AL10.alSourcei(sources.get(i), AL10.AL_BUFFER, 0);
+            channels[i].clear();
+        }
+    }
+
+    static void shutdown() {
+        AL10.alDeleteSources(sources);
+        numChannels = 0;
+    }
+
+    static void enableStreaming() {
+        if (streamingEnabled) return;
+
+        // use the last source
+        numChannels--;
+        streamingEnabled = true;
+        streamQueue = 0;
+
+        int source = channels[numChannels].sourceId;
+        AL10.alSourcei(source, AL10.AL_SOURCE_RELATIVE, AL10.AL_TRUE);
+        AL10.alSourcef(source, AL10.AL_GAIN, 1.0f);
+        channels[numChannels].volumeChanged = true;
+
+        Com.DPrintf("streaming enabled\n");
+    }
+
+    static void disableStreaming() {
+        if (!streamingEnabled) return;
+        unqueueStreams();
+        int source = channels[numChannels].sourceId;
+        AL10.alSourcei(source, AL10.AL_SOURCE_ABSOLUTE, AL10.AL_TRUE);
+
+        // free the last source
+        //numChannels++;
+        streamingEnabled = false;
+        Com.DPrintf("streaming disabled\n");
+    }
+
+    static void unqueueStreams() {
+        if (!streamingEnabled) return;
+        int source = channels[numChannels].sourceId;
+
+        // stop streaming
+        AL10.alSourceStop(source);
+        int count = AL10.alGetSourcei(source, AL10.AL_BUFFERS_QUEUED);
+        Com.DPrintf("unqueue " + count + " buffers\n");
+        while (count-- > 0) {
+            AL10.alSourceUnqueueBuffers(source, tmp);
+        }
+        streamQueue = 0;
+    }
+
+    static void updateStream(ByteBuffer samples, int count, int format, int rate) {
+        enableStreaming();
+        int source = channels[numChannels].sourceId;
+        int processed = AL10.alGetSourcei(source, AL10.AL_BUFFERS_PROCESSED);
+
+        boolean playing = (AL10.alGetSourcei(source, AL10.AL_SOURCE_STATE) == AL10.AL_PLAYING);
+        boolean interupted = !playing && streamQueue > 2;
+
+        IntBuffer buffer = tmp;
+        if (interupted) {
+            unqueueStreams();
+            buffer.put(0, buffers.get(Sound.MAX_SFX + streamQueue++));
+            Com.DPrintf("queue " + (streamQueue - 1) + '\n');
+        } else if (processed < 2) {
+            // check queue overrun
+            if (streamQueue >= Sound.STREAM_QUEUE) return;
+            buffer.put(0, buffers.get(Sound.MAX_SFX + streamQueue++));
+            Com.DPrintf("queue " + (streamQueue - 1) + '\n');
+        } else {
+            // reuse the buffer
+            AL10.alSourceUnqueueBuffers(source, buffer);
+        }
+
+        samples.position(0);
+        samples.limit(count);
+        AL10.alBufferData(buffer.get(0), format, samples, rate);
+        AL10.alSourceQueueBuffers(source, buffer);
+
+        if (streamQueue > 1 && !playing) {
+            Com.DPrintf("start sound\n");
+            AL10.alSourcePlay(source);
+        }
+    }
+
+    static void addPlaySounds() {
+        while (Channel.assign(PlaySound.nextPlayableSound())) ;
+    }
+
+    private static boolean assign(PlaySound ps) {
+        if (ps == null) return false;
+        Channel ch = null;
+        int i;
+        for (i = 0; i < numChannels; i++) {
+            ch = channels[i];
+
+            if (ps.entchannel != 0 && ch.entnum == ps.entnum && ch.entchannel == ps.entchannel) {
+                // always override sound from same entity
+                if (ch.bufferId != ps.bufferId) {
+                    AL10.alSourceStop(ch.sourceId);
+                }
+                break;
+            }
+
+            // don't let monster sounds override player sounds
+            if ((ch.entnum == Globals.clientStateT.playernum + 1) && (ps.entnum != Globals.clientStateT.playernum + 1) && ch.bufferId != -1)
+                continue;
+
+            // looking for a free AL source
+            if (!ch.active) {
+                break;
+            }
+        }
+
+        if (i == numChannels)
+            return false;
+
+        ch.type = ps.type;
+        if (ps.type == Channel.FIXED)
+            Math3D.vectorCopy(ps.origin, ch.origin);
+        ch.entnum = ps.entnum;
+        ch.entchannel = ps.entchannel;
+        ch.bufferChanged = (ch.bufferId != ps.bufferId);
+        ch.bufferId = ps.bufferId;
+        ch.rolloff = ps.attenuation * 2;
+        ch.volumeChanged = (ch.volume != ps.volume);
+        ch.volume = ps.volume;
+        ch.active = true;
+        ch.modified = true;
+        return true;
+    }
+
+    private static Channel pickForLoop(int bufferId) {
+        Channel ch;
+        for (int i = 0; i < numChannels; i++) {
+            ch = channels[i];
+            // looking for a free AL source
+            if (!ch.active) {
+                ch.entnum = 0;
+                ch.entchannel = 0;
+                ch.bufferChanged = (ch.bufferId != bufferId);
+                ch.bufferId = bufferId;
+                ch.volumeChanged = (ch.volume != 1.0f);
+                ch.volume = 1.0f;
+                ch.rolloff = (float) 6 * 2;
+                ch.active = true;
+                ch.modified = true;
+                return ch;
+            }
+        }
+        return null;
+    }
+
+    static void playAllSounds(FloatBuffer listenerOrigin, int currentEffectIndex, int currentFilterIndex) {
+        FloatBuffer sourceOrigin = sourceOriginBuffer;
+        Channel ch;
+        int sourceId;
+        int state;
+
+        for (int i = 0; i < numChannels; i++) {
+            ch = channels[i];
+            if (ch.active) {
+                sourceId = ch.sourceId;
+                switch (ch.type) {
+                    case Channel.LISTENER:
+                        sourceOrigin.put(0, listenerOrigin.get(0));
+                        sourceOrigin.put(1, listenerOrigin.get(1));
+                        sourceOrigin.put(2, listenerOrigin.get(2));
+                        break;
+                    case Channel.DYNAMIC:
+                        CL_ents.GetEntitySoundOrigin(ch.entnum, entityOrigin);
+                        convertVector(entityOrigin, sourceOrigin);
+                        break;
+                    case Channel.FIXED:
+                        convertVector(ch.origin, sourceOrigin);
+                        break;
+                }
+
+                if (ch.modified) {
+                    if (ch.bufferChanged) {
+                        AL10.alSourcei(sourceId, AL10.AL_BUFFER, ch.bufferId);
+                    }
+                    if (ch.volumeChanged) {
+                        AL10.alSourcef(sourceId, AL10.AL_GAIN, ch.volume);
+                    }
+                    AL10.alSourcef(sourceId, AL10.AL_ROLLOFF_FACTOR, ch.rolloff);
+                    AL10.alSource3f(sourceId, AL10.AL_POSITION, sourceOrigin.get(), sourceOrigin.get(), sourceOrigin.get());
+                    AL11.alSource3i(sourceId, EFX10.AL_AUXILIARY_SEND_FILTER, currentEffectIndex, 0, EFX10.AL_FILTER_NULL);
+                    AL10.alSourcei(sourceId, EFX10.AL_DIRECT_FILTER, currentFilterIndex);
+                    AL10.alSourcePlay(sourceId);
+                    sourceOrigin.rewind();
+                    ch.modified = false;
+                } else {
+                    state = AL10.alGetSourcei(sourceId, AL10.AL_SOURCE_STATE);
+                    if (state == AL10.AL_PLAYING) {
+                        AL10.alSource3f(sourceId, AL10.AL_POSITION, sourceOrigin.get(), sourceOrigin.get(), sourceOrigin.get());
+                        sourceOrigin.rewind();
+                    } else {
+                        ch.clear();
+                    }
+                }
+                ch.autosound = false;
+            }
+        }
+    }
+
+    /*
+     *         adddLoopSounds
+     *         Entities with a ->sound field will generated looped sounds
+     *         that are automatically started, stopped, and merged together
+     *         as the entities are sent to the client
+     */
+    static void addLoopSounds() {
+
+        if ((Globals.cl_paused.value != 0.0f) || (Globals.clientStaticT.state != Globals.ca_active) || !Globals.clientStateT.sound_prepped) {
+            removeUnusedLoopSounds();
+            return;
+        }
+
+        Channel ch;
+        sfx_t sfx;
+        sfxcache_t sc;
+        int num;
+        entity_state_t ent;
+        int key;
+        int sound = 0;
+
+        for (int i = 0; i < Globals.clientStateT.frame.num_entities; i++) {
+            num = (Globals.clientStateT.frame.parse_entities + i) & (Defines.MAX_PARSE_ENTITIES - 1);
+            ent = Globals.cl_parse_entities[num];
+            sound = ent.sound;
+
+            if (sound == 0) continue;
+
+            key = ent.number;
+            ch = looptable.get(key);
+
+            if (ch != null) {
+                // keep on looping
+                ch.autosound = true;
+                Math3D.vectorCopy(ent.origin, ch.origin);
+                continue;
+            }
+
+            sfx = Globals.clientStateT.sound_precache[sound];
+            if (sfx == null)
+                continue;        // bad sound effect
+
+            sc = sfx.cache;
+            if (sc == null)
+                continue;
+
+            // allocate a channel
+            ch = Channel.pickForLoop(buffers.get(sfx.bufferId));
+            if (ch == null)
+                break;
+
+            ch.type = FIXED;
+            Math3D.vectorCopy(ent.origin, ch.origin);
+            ch.autosound = true;
+
+            looptable.put(key, ch);
+            AL10.alSourcei(ch.sourceId, AL10.AL_LOOPING, AL10.AL_TRUE);
+        }
+
+        removeUnusedLoopSounds();
+
+    }
+
+    private static void removeUnusedLoopSounds() {
+        Channel ch;
+        // stop unused loopsounds
+        for (Iterator<Channel> iter = looptable.values().iterator(); iter.hasNext(); ) {
+            ch = iter.next();
+            if (!ch.autosound) {
+                AL10.alSourceStop(ch.sourceId);
+                AL10.alSourcei(ch.sourceId, AL10.AL_LOOPING, AL10.AL_FALSE);
+                iter.remove();
+                ch.clear();
+            }
+        }
+    }
+
+    static void convertVector(float[] from, FloatBuffer to) {
+        to.put(0, from[0]);
+        to.put(1, from[2]);
+        to.put(2, -from[1]);
+    }
+
+    static void convertOrientation(float[] forward, float[] up, FloatBuffer orientation) {
+        orientation.put(0, forward[0]);
+        orientation.put(1, forward[2]);
+        orientation.put(2, -forward[1]);
+        orientation.put(3, up[0]);
+        orientation.put(4, up[2]);
+        orientation.put(5, -up[1]);
+    }
+
+    private void clear() {
+        entnum = entchannel = bufferId = -1;
+        bufferChanged = false;
+        rolloff = 0;
+        autosound = false;
+        active = false;
+        modified = false;
+    }
+
+}
diff --git a/src/main/java/lwjake2/sound/lwjgl/LWJGLSoundImpl.java b/src/main/java/lwjake2/sound/lwjgl/LWJGLSoundImpl.java
new file mode 100644 (file)
index 0000000..e70f99a
--- /dev/null
@@ -0,0 +1,600 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.sound.lwjgl;
+
+import lwjake2.Defines;
+import lwjake2.EFX.EFXFilterLowPass;
+import lwjake2.Globals;
+import lwjake2.game.Cmd;
+import lwjake2.game.CvarT;
+import lwjake2.game.GameBase;
+import lwjake2.game.entity_state_t;
+import lwjake2.qcommon.Com;
+import lwjake2.qcommon.Cvar;
+import lwjake2.qcommon.FS;
+import lwjake2.qcommon.xcommand_t;
+import lwjake2.sound.*;
+import lwjake2.util.Lib;
+import lwjake2.util.Vargs;
+import org.lwjgl.LWJGLException;
+import org.lwjgl.openal.*;
+
+import java.nio.*;
+
+/**
+ * LWJGLSoundImpl
+ *
+ * @author dsanders/cwei
+ */
+public final class LWJGLSoundImpl implements Sound {
+
+    static final sfx_t[] known_sfx = new sfx_t[MAX_SFX];
+    static int num_sfx;
+
+    static {
+        S.register(new LWJGLSoundImpl());
+    }
+
+    static {
+        for (int i = 0; i < known_sfx.length; i++)
+            known_sfx[i] = new sfx_t();
+    }
+
+    // the last 4 buffers are used for cinematics streaming
+    private final IntBuffer buffers = Lib.newIntBuffer(MAX_SFX + STREAM_QUEUE);
+    // TODO check the sfx direct buffer size
+    // 2MB sfx buffer
+    private final ByteBuffer sfxDataBuffer = Lib.newByteBuffer(2 * 1024 * 1024);
+    private final FloatBuffer listenerOrigin = Lib.newFloatBuffer(3);
+    private final FloatBuffer listenerOrientation = Lib.newFloatBuffer(6);
+    private final ShortBuffer streamBuffer = sfxDataBuffer.slice().order(ByteOrder.BIG_ENDIAN).asShortBuffer();
+    int s_registration_sequence;
+    boolean s_registering;
+    private CvarT s_volume;
+    /**
+     * EFX Variables
+     */
+    private int currentEffectIndex;
+    private EFXFilterLowPass underwaterFilter;
+
+    // singleton
+    private LWJGLSoundImpl() {
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.SoundImpl#Init()
+     */
+    public boolean Init() {
+
+        try {
+            initOpenAL();
+            checkError();
+            initOpenALExtensions();
+        } catch (OpenALException e) {
+            Com.Printf(e.getMessage() + '\n');
+            return false;
+        } catch (Exception e) {
+            Com.DPrintf(e.getMessage() + '\n');
+            return false;
+        }
+
+        // set the listerner (master) volume
+        s_volume = Cvar.get("s_volume", "0.7", Defines.CVAR_ARCHIVE);
+        AL10.alGenBuffers(buffers);
+        int count = Channel.init(buffers);
+        Com.Printf("... using " + count + " channels\n");
+        AL10.alDistanceModel(AL10.AL_INVERSE_DISTANCE_CLAMPED);
+        Cmd.AddCommand("play", new xcommand_t() {
+            public void execute() {
+                Play();
+            }
+        });
+        Cmd.AddCommand("stopsound", new xcommand_t() {
+            public void execute() {
+                StopAllSounds();
+            }
+        });
+        Cmd.AddCommand("soundlist", new xcommand_t() {
+            public void execute() {
+                SoundList();
+            }
+        });
+        Cmd.AddCommand("soundinfo", new xcommand_t() {
+            public void execute() {
+                SoundInfo_f();
+            }
+        });
+
+        num_sfx = 0;
+
+        Com.Printf("sound sampling rate: 44100Hz\n");
+
+        StopAllSounds();
+        Com.Printf("------------------------------------\n");
+        return true;
+    }
+
+    private void initOpenAL() throws OpenALException {
+        try {
+            AL.create();
+        } catch (LWJGLException e) {
+            throw new OpenALException(e);
+        }
+        String deviceName = null;
+
+        String os = System.getProperty("os.name");
+        if (os.startsWith("Windows")) {
+            deviceName = "DirectSound3D";
+        }
+
+        String defaultSpecifier = ALC10.alcGetString(AL.getDevice(), ALC10.ALC_DEFAULT_DEVICE_SPECIFIER);
+
+        Com.Printf(os + " using " + ((deviceName == null) ? defaultSpecifier : deviceName) + '\n');
+
+        // Check for an error.
+        if (ALC10.alcGetError(AL.getDevice()) != ALC10.ALC_NO_ERROR) {
+            Com.DPrintf("Error with SoundDevice");
+        }
+    }
+
+    /**
+     * Initializes OpenAL EFX effects.
+     */
+    private void initOpenALExtensions() {
+        Com.Printf("... using EFX effects:\n");
+        underwaterFilter = new EFXFilterLowPass();
+        underwaterFilter.setGain(1.0f);
+        underwaterFilter.setGainHF(0.0f);
+    }
+
+    void exitOpenAL() {
+        // Unload EFX Effects
+        underwaterFilter.killFilter();
+
+        // Release the context and the device.
+        AL.destroy();
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.SoundImpl#RegisterSound(jake2.sound.sfx_t)
+     */
+    private void initBuffer(byte[] samples, int bufferId, int freq) {
+        ByteBuffer data = sfxDataBuffer.slice();
+        data.put(samples).flip();
+        AL10.alBufferData(buffers.get(bufferId), AL10.AL_FORMAT_MONO16,
+                data, freq);
+    }
+
+    private void checkError() {
+        Com.DPrintf("AL Error: " + alErrorString() + '\n');
+    }
+
+    private String alErrorString() {
+        int error;
+        String message = "";
+        if ((error = AL10.alGetError()) != AL10.AL_NO_ERROR) {
+            switch (error) {
+                case AL10.AL_INVALID_OPERATION:
+                    message = "invalid operation";
+                    break;
+                case AL10.AL_INVALID_VALUE:
+                    message = "invalid value";
+                    break;
+                case AL10.AL_INVALID_ENUM:
+                    message = "invalid enum";
+                    break;
+                case AL10.AL_INVALID_NAME:
+                    message = "invalid name";
+                    break;
+                default:
+                    message = "" + error;
+            }
+        }
+        return message;
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.SoundImpl#Shutdown()
+     */
+    public void Shutdown() {
+        StopAllSounds();
+        Channel.shutdown();
+        AL10.alDeleteBuffers(buffers);
+        exitOpenAL();
+
+        Cmd.RemoveCommand("play");
+        Cmd.RemoveCommand("stopsound");
+        Cmd.RemoveCommand("soundlist");
+        Cmd.RemoveCommand("soundinfo");
+
+        // free all sounds
+        for (int i = 0; i < num_sfx; i++) {
+            if (known_sfx[i].name == null)
+                continue;
+            known_sfx[i].clear();
+        }
+        num_sfx = 0;
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.SoundImpl#StartSound(float[], int, int, jake2.sound.sfx_t, float, float, float)
+     */
+    public void StartSound(float[] origin, int entnum, int entchannel, sfx_t sfx, float fvol, float attenuation, float timeofs) {
+
+        if (sfx == null)
+            return;
+
+        if (sfx.name.charAt(0) == '*')
+            sfx = RegisterSexedSound(Globals.cl_entities[entnum].current, sfx.name);
+
+        if (LoadSound(sfx) == null)
+            return; // can't load sound
+
+        if (attenuation != Defines.ATTN_STATIC)
+            attenuation *= 0.5f;
+
+        PlaySound.allocate(origin, entnum, entchannel, buffers.get(sfx.bufferId), fvol, attenuation, timeofs);
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.SoundImpl#Update(float[], float[], float[], float[])
+     */
+    public void Update(float[] origin, float[] forward, float[] right, float[] up) {
+
+        Channel.convertVector(origin, listenerOrigin);
+        AL10.alListener(AL10.AL_POSITION, listenerOrigin);
+
+        Channel.convertOrientation(forward, up, listenerOrientation);
+        AL10.alListener(AL10.AL_ORIENTATION, listenerOrientation);
+
+        // set the master volume
+        AL10.alListenerf(AL10.AL_GAIN, s_volume.value);
+
+        // Detect EFX Conditions
+        int currentFilterIndex;
+        if ((GameBase.gi.pointcontents.pointcontents(origin) & Defines.MASK_WATER) != 0) {
+            currentFilterIndex = underwaterFilter.getIndex();
+        } else {
+            currentEffectIndex = EFX10.AL_EFFECTSLOT_NULL;
+            currentFilterIndex = EFX10.AL_FILTER_NULL;
+        }
+
+        Channel.addLoopSounds();
+        Channel.addPlaySounds();
+        Channel.playAllSounds(listenerOrigin, currentEffectIndex, currentFilterIndex);
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.SoundImpl#StopAllSounds()
+     */
+    public void StopAllSounds() {
+        // mute the listener (master)
+        AL10.alListenerf(AL10.AL_GAIN, 0);
+        PlaySound.reset();
+        Channel.reset();
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.Sound#getName()
+     */
+    public String getName() {
+        return "lwjgl";
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.Sound#BeginRegistration()
+     */
+    public void BeginRegistration() {
+        s_registration_sequence++;
+        s_registering = true;
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.Sound#RegisterSound(java.lang.String)
+     */
+    public sfx_t RegisterSound(String name) {
+        sfx_t sfx = FindName(name, true);
+        sfx.registration_sequence = s_registration_sequence;
+
+        if (!s_registering)
+            LoadSound(sfx);
+
+        return sfx;
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.Sound#EndRegistration()
+     */
+    public void EndRegistration() {
+        int i;
+        sfx_t sfx;
+
+        // free any sounds not from this registration sequence
+        for (i = 0; i < num_sfx; i++) {
+            sfx = known_sfx[i];
+            if (sfx.name == null)
+                continue;
+            if (sfx.registration_sequence != s_registration_sequence) {
+                // don't need this sound
+                sfx.clear();
+            }
+        }
+
+        // load everything in
+        for (i = 0; i < num_sfx; i++) {
+            sfx = known_sfx[i];
+            if (sfx.name == null)
+                continue;
+            LoadSound(sfx);
+        }
+
+        s_registering = false;
+    }
+
+    sfx_t RegisterSexedSound(entity_state_t ent, String base) {
+
+        sfx_t sfx = null;
+
+        // determine what model the client is using
+        String model = null;
+        int n = Globals.CS_PLAYERSKINS + ent.number - 1;
+        if (Globals.clientStateT.configstrings[n] != null) {
+            int p = Globals.clientStateT.configstrings[n].indexOf('\\');
+            if (p >= 0) {
+                p++;
+                model = Globals.clientStateT.configstrings[n].substring(p);
+                //strcpy(model, p);
+                p = model.indexOf('/');
+                if (p > 0)
+                    model = model.substring(0, p);
+            }
+        }
+        // if we can't figure it out, they're male
+        if (model == null || model.length() == 0)
+            model = "male";
+
+        // see if we already know of the model specific sound
+        String sexedFilename = "#players/" + model + "/" + base.substring(1);
+        //Com_sprintf (sexedFilename, sizeof(sexedFilename), "#players/%s/%s", model, base+1);
+        sfx = FindName(sexedFilename, false);
+
+        if (sfx != null) return sfx;
+
+        //
+        // fall back strategies
+        //
+        // not found , so see if it exists
+        if (FS.FileLength(sexedFilename.substring(1)) > 0) {
+            // yes, register it
+            return RegisterSound(sexedFilename);
+        }
+        // try it with the female sound in the pak0.pak
+        if (model.equalsIgnoreCase("female")) {
+            String femaleFilename = "player/female/" + base.substring(1);
+            if (FS.FileLength("sound/" + femaleFilename) > 0)
+                return AliasName(sexedFilename, femaleFilename);
+        }
+        // no chance, revert to the male sound in the pak0.pak
+        String maleFilename = "player/male/" + base.substring(1);
+        return AliasName(sexedFilename, maleFilename);
+    }
+
+    sfx_t FindName(String name, boolean create) {
+        int i;
+        sfx_t sfx = null;
+
+        if (name == null)
+            Com.Error(Defines.ERR_FATAL, "S_FindName: NULL\n");
+        if (name.length() == 0)
+            Com.Error(Defines.ERR_FATAL, "S_FindName: empty name\n");
+
+        if (name.length() >= Defines.MAX_QPATH)
+            Com.Error(Defines.ERR_FATAL, "Sound name too long: " + name);
+
+        // see if already loaded
+        for (i = 0; i < num_sfx; i++)
+            if (name.equals(known_sfx[i].name)) {
+                return known_sfx[i];
+            }
+
+        if (!create)
+            return null;
+
+        // find a free sfx
+        for (i = 0; i < num_sfx; i++)
+            if (known_sfx[i].name == null)
+                // registration_sequence < s_registration_sequence)
+                break;
+
+        if (i == num_sfx) {
+            if (num_sfx == MAX_SFX)
+                Com.Error(Defines.ERR_FATAL, "S_FindName: out of sfx_t");
+            num_sfx++;
+        }
+
+        sfx = known_sfx[i];
+        sfx.clear();
+        sfx.name = name;
+        sfx.registration_sequence = s_registration_sequence;
+        sfx.bufferId = i;
+
+        return sfx;
+    }
+
+    /*
+    ==================
+    S_AliasName
+
+    ==================
+    */
+    sfx_t AliasName(String aliasname, String truename) {
+        sfx_t sfx = null;
+        String s;
+        int i;
+
+        s = truename;
+
+        // find a free sfx
+        for (i = 0; i < num_sfx; i++)
+            if (known_sfx[i].name == null)
+                break;
+
+        if (i == num_sfx) {
+            if (num_sfx == MAX_SFX)
+                Com.Error(Defines.ERR_FATAL, "S_FindName: out of sfx_t");
+            num_sfx++;
+        }
+
+        sfx = known_sfx[i];
+        sfx.clear();
+        sfx.name = aliasname;
+        sfx.registration_sequence = s_registration_sequence;
+        sfx.truename = s;
+        // set the AL bufferId
+        sfx.bufferId = i;
+
+        return sfx;
+    }
+
+    /*
+    ==============
+    S_LoadSound
+    ==============
+    */
+    public sfxcache_t LoadSound(sfx_t s) {
+        if (s.isCached) return s.cache;
+        sfxcache_t sc = WaveLoader.LoadSound(s);
+        if (sc != null) {
+            initBuffer(sc.data, s.bufferId, sc.speed);
+            s.isCached = true;
+            // free samples for GC
+            s.cache.data = null;
+        }
+        return sc;
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.Sound#StartLocalSound(java.lang.String)
+     */
+    public void StartLocalSound(String sound) {
+        sfx_t sfx;
+
+        sfx = RegisterSound(sound);
+        if (sfx == null) {
+            Com.Printf("S_StartLocalSound: can't cache " + sound + "\n");
+            return;
+        }
+        StartSound(null, Globals.clientStateT.playernum + 1, 0, sfx, 1, 1, 0);
+    }
+
+    /* (non-Javadoc)
+     * @see jake2.sound.Sound#RawSamples(int, int, int, int, byte[])
+        */
+    public void RawSamples(int samples, int rate, int width, int channels, ByteBuffer data) {
+        int format;
+        if (channels == 2) {
+            format = (width == 2) ? AL10.AL_FORMAT_STEREO16
+                    : AL10.AL_FORMAT_STEREO8;
+        } else {
+            format = (width == 2) ? AL10.AL_FORMAT_MONO16
+                    : AL10.AL_FORMAT_MONO8;
+        }
+
+        // convert to signed 16 bit samples
+        if (format == AL10.AL_FORMAT_MONO8) {
+            int value;
+            for (int i = 0; i < samples; i++) {
+                value = (data.get(i) & 0xFF) - 128;
+                streamBuffer.put(i, (short) value);
+            }
+            format = AL10.AL_FORMAT_MONO16;
+            width = 2;
+            data = sfxDataBuffer.slice();
+        }
+
+        Channel.updateStream(data, samples * channels * width, format, rate);
+    }
+
+    public void disableStreaming() {
+        Channel.disableStreaming();
+    }
+    /*
+    ===============================================================================
+
+       console functions
+
+       ===============================================================================
+       */
+
+    void Play() {
+        int i;
+        String name;
+        sfx_t sfx;
+
+        i = 1;
+        while (i < Cmd.Argc()) {
+            name = Cmd.Argv(i);
+            if (name.indexOf('.') == -1)
+                name += ".wav";
+
+            sfx = RegisterSound(name);
+            StartSound(null, Globals.clientStateT.playernum + 1, 0, sfx, 1.0f, 1.0f, 0.0f);
+            i++;
+        }
+    }
+
+    void SoundList() {
+        int i;
+        sfx_t sfx;
+        sfxcache_t sc;
+        int size, total;
+
+        total = 0;
+        for (i = 0; i < num_sfx; i++) {
+            sfx = known_sfx[i];
+            if (sfx.registration_sequence == 0)
+                continue;
+            sc = sfx.cache;
+            if (sc != null) {
+                size = sc.length * sc.width * (sc.stereo + 1);
+                total += size;
+                if (sc.loopstart >= 0)
+                    Com.Printf("L");
+                else
+                    Com.Printf(" ");
+                Com.Printf("(%2db) %6i : %s\n", new Vargs(3).add(sc.width * 8).add(size).add(sfx.name));
+            } else {
+                if (sfx.name.charAt(0) == '*')
+                    Com.Printf("  placeholder : " + sfx.name + "\n");
+                else
+                    Com.Printf("  not loaded  : " + sfx.name + "\n");
+            }
+        }
+        Com.Printf("Total resident: " + total + "\n");
+    }
+
+    void SoundInfo_f() {
+
+        Com.Printf("%5d stereo\n", new Vargs(1).add(1));
+        Com.Printf("%5d samples\n", new Vargs(1).add(22050));
+        Com.Printf("%5d samplebits\n", new Vargs(1).add(16));
+        Com.Printf("%5d speed\n", new Vargs(1).add(44100));
+    }
+
+}
diff --git a/src/main/java/lwjake2/sound/lwjgl/PlaySound.java b/src/main/java/lwjake2/sound/lwjgl/PlaySound.java
new file mode 100644 (file)
index 0000000..02e7c6d
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.sound.lwjgl;
+
+import lwjake2.Globals;
+import lwjake2.util.Math3D;
+
+/**
+ * PlaySound
+ *
+ * @author cwei
+ */
+public class PlaySound {
+
+    final static int MAX_PLAYSOUNDS = 128;
+
+    // list with sentinel
+    private static final PlaySound freeList;
+    private static final PlaySound playableList;
+
+    private static final PlaySound[] backbuffer = new PlaySound[MAX_PLAYSOUNDS];
+
+    static {
+        for (int i = 0; i < backbuffer.length; i++) {
+            backbuffer[i] = new PlaySound();
+        }
+        // init the sentinels
+        freeList = new PlaySound();
+        playableList = new PlaySound();
+        // reset the lists
+        reset();
+    }
+
+    final float[] origin = {0, 0, 0};
+    // sound attributes
+    int type;
+    int entnum;
+    int entchannel;
+    int bufferId;
+    float volume;
+    float attenuation;
+    // begin time in ms
+    private long beginTime;
+
+    // for linked list
+    private PlaySound prev, next;
+
+    private PlaySound() {
+        prev = next = null;
+        this.clear();
+    }
+
+    static void reset() {
+        // init the sentinels
+        freeList.next = freeList.prev = freeList;
+        playableList.next = playableList.prev = playableList;
+
+        // concat the the freeList
+        PlaySound ps;
+        for (PlaySound aBackbuffer : backbuffer) {
+            ps = aBackbuffer;
+            ps.clear();
+            ps.prev = freeList;
+            ps.next = freeList.next;
+            ps.prev.next = ps;
+            ps.next.prev = ps;
+        }
+    }
+
+    static PlaySound nextPlayableSound() {
+        PlaySound ps = null;
+        ps = playableList.next;
+        if (ps == playableList || ps.beginTime > Globals.clientStateT.time)
+            return null;
+        PlaySound.release(ps);
+        return ps;
+    }
+
+    private static PlaySound get() {
+        PlaySound ps = freeList.next;
+        if (ps == freeList)
+            return null;
+
+        ps.prev.next = ps.next;
+        ps.next.prev = ps.prev;
+        return ps;
+    }
+
+    private static void add(PlaySound ps) {
+
+        PlaySound sort = playableList.next;
+
+        for (; sort != playableList && sort.beginTime < ps.beginTime; sort = sort.next) ;
+        ps.next = sort;
+        ps.prev = sort.prev;
+        ps.next.prev = ps;
+        ps.prev.next = ps;
+    }
+
+    private static void release(PlaySound ps) {
+        ps.prev.next = ps.next;
+        ps.next.prev = ps.prev;
+        // add to free list
+        ps.next = freeList.next;
+        freeList.next.prev = ps;
+        ps.prev = freeList;
+        freeList.next = ps;
+    }
+
+    static void allocate(float[] origin, int entnum, int entchannel,
+                         int bufferId, float volume, float attenuation, float timeoffset) {
+
+        PlaySound ps = PlaySound.get();
+
+        if (ps != null) {
+            // find the right sound type
+            if (entnum == Globals.clientStateT.playernum + 1) {
+                ps.type = Channel.LISTENER;
+            } else if (origin != null) {
+                ps.type = Channel.FIXED;
+                Math3D.vectorCopy(origin, ps.origin);
+            } else {
+                ps.type = Channel.DYNAMIC;
+            }
+            ps.entnum = entnum;
+            ps.entchannel = entchannel;
+            ps.bufferId = bufferId;
+            ps.volume = volume;
+            ps.attenuation = attenuation;
+            ps.beginTime = Globals.clientStateT.time + (long) (timeoffset * 1000);
+            PlaySound.add(ps);
+        } else {
+            System.err.println("PlaySounds out of Limit");
+        }
+    }
+
+    private void clear() {
+        type = bufferId = entnum = entchannel = -1;
+        // volume = attenuation = beginTime = 0;
+        attenuation = beginTime = 0;
+        // Math3D.VectorClear(origin);
+    }
+}
diff --git a/src/main/java/lwjake2/sound/sfx_t.java b/src/main/java/lwjake2/sound/sfx_t.java
new file mode 100644 (file)
index 0000000..22dbcdd
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.sound;
+
+public class sfx_t {
+    public String name;
+    public int registration_sequence;
+    public sfxcache_t cache;
+    public String truename;
+
+    // is used for AL buffers
+    public int bufferId = -1;
+    public boolean isCached = false;
+
+    public void clear() {
+        name = truename = null;
+        cache = null;
+        registration_sequence = 0;
+        bufferId = -1;
+        isCached = false;
+    }
+}
diff --git a/src/main/java/lwjake2/sound/sfxcache_t.java b/src/main/java/lwjake2/sound/sfxcache_t.java
new file mode 100644 (file)
index 0000000..b780f05
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.sound;
+
+public class sfxcache_t {
+    public int length;
+    public int loopstart;
+    public int speed;            // not needed, because converted on load?
+    public int width;
+    public int stereo;
+    public byte data[];        // variable sized
+
+    public sfxcache_t(int size) {
+        data = new byte[size];
+    }
+}
diff --git a/src/main/java/lwjake2/sound/wavinfo_t.java b/src/main/java/lwjake2/sound/wavinfo_t.java
new file mode 100644 (file)
index 0000000..77b938f
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.sound;
+
+/**
+ * wavinfo_t
+ */
+public class wavinfo_t {
+}
diff --git a/src/main/java/lwjake2/sys/InputListener.java b/src/main/java/lwjake2/sys/InputListener.java
new file mode 100644 (file)
index 0000000..3b9abfb
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.sys;
+
+import java.awt.event.*;
+import java.util.LinkedList;
+
+/**
+ * InputListener
+ */
+public final class InputListener implements KeyListener, MouseListener,
+        MouseMotionListener, ComponentListener, MouseWheelListener {
+
+    // modifications of eventQueue must be thread safe!
+    private static final LinkedList<LWJake2InputEvent> eventQueue = new LinkedList<>();
+
+    private static void addEvent(LWJake2InputEvent ev) {
+        synchronized (eventQueue) {
+            eventQueue.addLast(ev);
+        }
+    }
+
+    static LWJake2InputEvent nextEvent() {
+        LWJake2InputEvent ev;
+        synchronized (eventQueue) {
+            ev = (!eventQueue.isEmpty()) ? eventQueue.removeFirst() : null;
+        }
+        return ev;
+    }
+
+    public void keyPressed(KeyEvent e) {
+        if (!((e.getModifiersEx() & InputEvent.ALT_GRAPH_DOWN_MASK) != 0)) {
+            addEvent(new LWJake2InputEvent(LWJake2InputEvent.KeyPress, e));
+        }
+    }
+
+    public void keyReleased(KeyEvent e) {
+        addEvent(new LWJake2InputEvent(LWJake2InputEvent.KeyRelease, e));
+    }
+
+    public void keyTyped(KeyEvent e) {
+        if ((e.getModifiersEx() & InputEvent.ALT_GRAPH_DOWN_MASK) != 0) {
+            addEvent(new LWJake2InputEvent(LWJake2InputEvent.KeyPress, e));
+            addEvent(new LWJake2InputEvent(LWJake2InputEvent.KeyRelease, e));
+        }
+    }
+
+    public void mouseClicked(MouseEvent e) {
+    }
+
+    public void mouseEntered(MouseEvent e) {
+    }
+
+    public void mouseExited(MouseEvent e) {
+    }
+
+    public void mousePressed(MouseEvent e) {
+        addEvent(new LWJake2InputEvent(LWJake2InputEvent.ButtonPress, e));
+    }
+
+    public void mouseReleased(MouseEvent e) {
+        addEvent(new LWJake2InputEvent(LWJake2InputEvent.ButtonRelease, e));
+    }
+
+    public void mouseDragged(MouseEvent e) {
+        addEvent(new LWJake2InputEvent(LWJake2InputEvent.MotionNotify, e));
+    }
+
+    public void mouseMoved(MouseEvent e) {
+        addEvent(new LWJake2InputEvent(LWJake2InputEvent.MotionNotify, e));
+    }
+
+    public void componentHidden(ComponentEvent e) {
+    }
+
+    public void componentMoved(ComponentEvent e) {
+        addEvent(new LWJake2InputEvent(LWJake2InputEvent.ConfigureNotify, e));
+    }
+
+    public void componentResized(ComponentEvent e) {
+        addEvent(new LWJake2InputEvent(LWJake2InputEvent.ConfigureNotify, e));
+    }
+
+    public void componentShown(ComponentEvent e) {
+        JOGLKBD.c = e.getComponent();
+        addEvent(new LWJake2InputEvent(LWJake2InputEvent.CreateNotify, e));
+    }
+
+    public void mouseWheelMoved(MouseWheelEvent e) {
+        addEvent(new LWJake2InputEvent(LWJake2InputEvent.WheelMoved, e));
+    }
+
+}
+
diff --git a/src/main/java/lwjake2/sys/JOGLKBD.java b/src/main/java/lwjake2/sys/JOGLKBD.java
new file mode 100644 (file)
index 0000000..e64eb84
--- /dev/null
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.sys;
+
+import lwjake2.client.Key;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ComponentEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseWheelEvent;
+
+final public class JOGLKBD extends KBD {
+    static Component c = null;
+    private static Robot robot;
+    private static Cursor emptyCursor = null;
+    private static int win_w2 = 0;
+    private static int win_h2 = 0;
+
+    static {
+        try {
+            robot = new Robot();
+        } catch (AWTException e) {
+            System.exit(1);
+        }
+    }
+
+    private static int XLateKey(KeyEvent ev) {
+
+        int key = 0;
+        int code = ev.getKeyCode();
+
+        switch (code) {
+//     00626                 case XK_KP_Page_Up:      key = K_KP_PGUP; break;
+            case KeyEvent.VK_PAGE_UP:
+                key = Key.K_PGUP;
+                break;
+
+//     00629                 case XK_KP_Page_Down: key = K_KP_PGDN; break;
+            case KeyEvent.VK_PAGE_DOWN:
+                key = Key.K_PGDN;
+                break;
+
+//     00632                 case XK_KP_Home: key = K_KP_HOME; break;
+            case KeyEvent.VK_HOME:
+                key = Key.K_HOME;
+                break;
+
+//     00635                 case XK_KP_End:  key = K_KP_END; break;
+            case KeyEvent.VK_END:
+                key = Key.K_END;
+                break;
+
+            case KeyEvent.VK_KP_LEFT:
+                key = Key.K_KP_LEFTARROW;
+                break;
+            case KeyEvent.VK_LEFT:
+                key = Key.K_LEFTARROW;
+                break;
+
+            case KeyEvent.VK_KP_RIGHT:
+                key = Key.K_KP_RIGHTARROW;
+                break;
+            case KeyEvent.VK_RIGHT:
+                key = Key.K_RIGHTARROW;
+                break;
+
+            case KeyEvent.VK_KP_DOWN:
+                key = Key.K_KP_DOWNARROW;
+                break;
+            case KeyEvent.VK_DOWN:
+                key = Key.K_DOWNARROW;
+                break;
+
+            case KeyEvent.VK_KP_UP:
+                key = Key.K_KP_UPARROW;
+                break;
+            case KeyEvent.VK_UP:
+                key = Key.K_UPARROW;
+                break;
+
+            case KeyEvent.VK_ESCAPE:
+                key = Key.K_ESCAPE;
+                break;
+
+
+            case KeyEvent.VK_ENTER:
+                key = Key.K_ENTER;
+                break;
+//     00652                 case XK_KP_Enter: key = K_KP_ENTER;     break;
+
+            case KeyEvent.VK_TAB:
+                key = Key.K_TAB;
+                break;
+
+            case KeyEvent.VK_F1:
+                key = Key.K_F1;
+                break;
+            case KeyEvent.VK_F2:
+                key = Key.K_F2;
+                break;
+            case KeyEvent.VK_F3:
+                key = Key.K_F3;
+                break;
+            case KeyEvent.VK_F4:
+                key = Key.K_F4;
+                break;
+            case KeyEvent.VK_F5:
+                key = Key.K_F5;
+                break;
+            case KeyEvent.VK_F6:
+                key = Key.K_F6;
+                break;
+            case KeyEvent.VK_F7:
+                key = Key.K_F7;
+                break;
+            case KeyEvent.VK_F8:
+                key = Key.K_F8;
+                break;
+            case KeyEvent.VK_F9:
+                key = Key.K_F9;
+                break;
+            case KeyEvent.VK_F10:
+                key = Key.K_F10;
+                break;
+            case KeyEvent.VK_F11:
+                key = Key.K_F11;
+                break;
+            case KeyEvent.VK_F12:
+                key = Key.K_F12;
+                break;
+
+            case KeyEvent.VK_BACK_SPACE:
+                key = Key.K_BACKSPACE;
+                break;
+
+            case KeyEvent.VK_DELETE:
+                key = Key.K_DEL;
+                break;
+//     00683                 case XK_KP_Delete: key = K_KP_DEL; break;
+
+            case KeyEvent.VK_PAUSE:
+                key = Key.K_PAUSE;
+                break;
+
+            case KeyEvent.VK_SHIFT:
+                key = Key.K_SHIFT;
+                break;
+            case KeyEvent.VK_CONTROL:
+                key = Key.K_CTRL;
+                break;
+
+            case KeyEvent.VK_ALT:
+            case KeyEvent.VK_ALT_GRAPH:
+                key = Key.K_ALT;
+                break;
+
+//     00700                 case XK_KP_Begin: key = K_KP_5; break;
+//     00701
+            case KeyEvent.VK_INSERT:
+                key = Key.K_INS;
+                break;
+            // toggle console for DE and US keyboards
+            case KeyEvent.VK_DEAD_ACUTE:
+            case KeyEvent.VK_CIRCUMFLEX:
+            case KeyEvent.VK_DEAD_CIRCUMFLEX:
+                key = '`';
+                break;
+
+            default:
+                key = ev.getKeyChar();
+
+                if (key >= 'A' && key <= 'Z')
+                    key = key - 'A' + 'a';
+                break;
+        }
+        if (key > 255) key = 0;
+
+        return key;
+    }
+
+    public void Init() {
+    }
+
+    public void Update() {
+        // get events
+        HandleEvents();
+    }
+
+    public void Close() {
+    }
+
+    private void HandleEvents() {
+        int key;
+
+        LWJake2InputEvent event;
+        while ((event = InputListener.nextEvent()) != null) {
+            switch (event.type) {
+                case LWJake2InputEvent.KeyPress:
+                case LWJake2InputEvent.KeyRelease:
+                    Do_Key_Event(XLateKey((KeyEvent) event.ev), event.type == LWJake2InputEvent.KeyPress);
+                    break;
+
+                case LWJake2InputEvent.MotionNotify:
+//                                     if (IN.ignorefirst) {
+//                                             IN.ignorefirst = false;
+//                                             break;
+//                                     }
+                    if (UserInputHandler.mouse_active) {
+                        mx = (((MouseEvent) event.ev).getX() - win_w2) * 2;
+                        my = (((MouseEvent) event.ev).getY() - win_h2) * 2;
+                    } else {
+                        mx = 0;
+                        my = 0;
+                    }
+                    break;
+                // see java.awt.MouseEvent
+                case LWJake2InputEvent.ButtonPress:
+                    key = mouseEventToKey((MouseEvent) event.ev);
+                    Do_Key_Event(key, true);
+                    break;
+
+                case LWJake2InputEvent.ButtonRelease:
+                    key = mouseEventToKey((MouseEvent) event.ev);
+                    Do_Key_Event(key, false);
+                    break;
+
+                case LWJake2InputEvent.WheelMoved:
+                    int dir = ((MouseWheelEvent) event.ev).getWheelRotation();
+                    if (dir > 0) {
+                        Do_Key_Event(Key.K_MWHEELDOWN, true);
+                        Do_Key_Event(Key.K_MWHEELDOWN, false);
+                    } else {
+                        Do_Key_Event(Key.K_MWHEELUP, true);
+                        Do_Key_Event(Key.K_MWHEELUP, false);
+                    }
+                    break;
+
+                case LWJake2InputEvent.CreateNotify:
+                case LWJake2InputEvent.ConfigureNotify:
+                    Component c = ((ComponentEvent) event.ev).getComponent();
+                    win_x = 0;
+                    win_y = 0;
+                    win_w2 = c.getWidth() / 2;
+                    win_h2 = c.getHeight() / 2;
+                    while (c != null) {
+                        if (c instanceof Container) {
+                            Insets insets = ((Container) c).getInsets();
+                            win_x += insets.left;
+                            win_y += insets.top;
+                        }
+                        win_x += c.getX();
+                        win_y += c.getY();
+                        c = c.getParent();
+                    }
+                    break;
+            }
+        }
+
+        if (mx != 0 || my != 0) {
+            // move the mouse to the window center again
+            robot.mouseMove(win_x + win_w2, win_y + win_h2);
+        }
+    }
+
+    // strange button numbering in java.awt.MouseEvent
+    // BUTTON1(left) BUTTON2(center) BUTTON3(right)
+    // K_MOUSE1      K_MOUSE3        K_MOUSE2
+    private int mouseEventToKey(MouseEvent ev) {
+        switch (ev.getButton()) {
+            case MouseEvent.BUTTON3:
+                return Key.K_MOUSE2;
+            case MouseEvent.BUTTON2:
+                return Key.K_MOUSE3;
+            default:
+                return Key.K_MOUSE1;
+        }
+    }
+
+    public void Do_Key_Event(int key, boolean down) {
+        Key.Event(key, down, Timer.getCurrentTimeMillis());
+    }
+
+    private void centerMouse() {
+        robot.mouseMove(win_x + win_w2, win_y + win_h2);
+    }
+
+    public void installGrabs() {
+        if (emptyCursor == null) {
+            ImageIcon emptyIcon = new ImageIcon(new byte[0]);
+            emptyCursor = c.getToolkit().createCustomCursor(emptyIcon.getImage(), new Point(0, 0), "emptyCursor");
+        }
+        c.setCursor(emptyCursor);
+        centerMouse();
+    }
+
+    public void uninstallGrabs() {
+        c.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+    }
+}
diff --git a/src/main/java/lwjake2/sys/KBD.java b/src/main/java/lwjake2/sys/KBD.java
new file mode 100644 (file)
index 0000000..4d89405
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.sys;
+
+/**
+ * KBD
+ */
+abstract public class KBD {
+
+    // motion values
+    public static int mx = 0;
+    public static int my = 0;
+    static int win_x = 0;
+    static int win_y = 0;
+
+    abstract public void Init();
+
+    abstract public void Update();
+
+    abstract public void Close();
+
+    abstract public void Do_Key_Event(int key, boolean down);
+
+    abstract public void installGrabs();
+
+    abstract public void uninstallGrabs();
+    //abstract public void centerMouse();
+}
+
diff --git a/src/main/java/lwjake2/sys/LWJGLKBD.java b/src/main/java/lwjake2/sys/LWJGLKBD.java
new file mode 100644 (file)
index 0000000..1cd752e
--- /dev/null
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.sys;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.client.Key;
+import lwjake2.qcommon.CommandBuffer;
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.input.Mouse;
+import org.lwjgl.opengl.Display;
+
+/**
+ * @author dsanders
+ */
+public class LWJGLKBD extends KBD {
+
+    private static int lastRepeat;
+    private char[] lwjglKeycodeMap = null;
+    private int pressed[] = null;
+
+    public void Init() {
+        try {
+            if (!Keyboard.isCreated()) Keyboard.create();
+            if (!Mouse.isCreated()) Mouse.create();
+
+            // Old code from old LWJGL, not sure if it's needed - flibit
+
+            // if (!Keyboard.isBuffered()) Keyboard.enableBuffer();
+            // if (!Keyboard.isTranslationEnabled()) Keyboard.enableTranslation();
+            // if (!Mouse.isBuffered()) Mouse.enableBuffer();
+
+            if (lwjglKeycodeMap == null) lwjglKeycodeMap = new char[256];
+            if (pressed == null) pressed = new int[256];
+
+            lastRepeat = Timer.getCurrentTimeMillis();
+        } catch (Exception ignored) {
+        }
+    }
+
+    public void Update() {
+        // get events
+        HandleEvents();
+    }
+
+    public void Close() {
+        Keyboard.destroy();
+        Mouse.destroy();
+        // free the memory for GC
+        lwjglKeycodeMap = null;
+        pressed = null;
+    }
+
+    private void HandleEvents() {
+        Keyboard.poll();
+
+        if (Display.isCloseRequested()) {
+            CommandBuffer.ExecuteText(Defines.EXEC_APPEND, "quit");
+        }
+
+        while (Keyboard.next()) {
+            int key = Keyboard.getEventKey();
+            char ch = Keyboard.getEventCharacter();
+            boolean down = Keyboard.getEventKeyState();
+
+            // fill the character translation table
+            // this is needed because the getEventCharacter() returns \0 if a key is released
+            // keycode is correct but the charachter value is not
+            if (down) {
+                lwjglKeycodeMap[key] = ch;
+                pressed[key] = Globals.sys_frame_time;
+            } else {
+                pressed[key] = 0;
+            }
+
+            Do_Key_Event(XLateKey(key, ch), down);
+        }
+
+        generateRepeats();
+
+        if (UserInputHandler.mouse_active) {
+            mx = Mouse.getDX() << 1;
+            my = -Mouse.getDY() << 1;
+        } else {
+            mx = 0;
+            my = 0;
+        }
+
+        while (Mouse.next()) {
+            int button = Mouse.getEventButton();
+            if (button >= 0) {
+                Do_Key_Event(Key.K_MOUSE1 + button, Mouse.getEventButtonState());
+            } else {
+                button = Mouse.getEventDWheel();
+                if (button > 0) {
+                    Do_Key_Event(Key.K_MWHEELUP, true);
+                    Do_Key_Event(Key.K_MWHEELUP, false);
+                } else if (button < 0) {
+                    Do_Key_Event(Key.K_MWHEELDOWN, true);
+                    Do_Key_Event(Key.K_MWHEELDOWN, false);
+                }
+            }
+        }
+    }
+
+    private void generateRepeats() {
+        int time = Globals.sys_frame_time;
+        if (time - lastRepeat > 50) {
+            for (int i = 0; i < pressed.length; i++) {
+                if (pressed[i] > 0 && time - pressed[i] > 500)
+                    Do_Key_Event(XLateKey(i, lwjglKeycodeMap[i]), true);
+            }
+            lastRepeat = time;
+        }
+    }
+
+    private int XLateKey(int code, int ch) {
+        int key = 0;
+
+        switch (code) {
+//     00626                 case XK_KP_Page_Up:      key = K_KP_PGUP; break;
+            case Keyboard.KEY_PRIOR:
+                key = Key.K_PGUP;
+                break;
+
+//     00629                 case XK_KP_Page_Down: key = K_KP_PGDN; break;
+            case Keyboard.KEY_NEXT:
+                key = Key.K_PGDN;
+                break;
+
+//     00632                 case XK_KP_Home: key = K_KP_HOME; break;
+            case Keyboard.KEY_HOME:
+                key = Key.K_HOME;
+                break;
+
+//     00635                 case XK_KP_End:  key = K_KP_END; break;
+            case Keyboard.KEY_END:
+                key = Key.K_END;
+                break;
+
+            // case Keyboard.KEY_LEFT: key = Key.K_KP_LEFTARROW; break;
+            case Keyboard.KEY_LEFT:
+                key = Key.K_LEFTARROW;
+                break;
+
+            // case Keyboard.KEY_RIGHT: key = Key.K_KP_RIGHTARROW; break;
+            case Keyboard.KEY_RIGHT:
+                key = Key.K_RIGHTARROW;
+                break;
+
+            // case Keyboard.KEY_DOWN: key = Key.K_KP_DOWNARROW; break;
+            case Keyboard.KEY_DOWN:
+                key = Key.K_DOWNARROW;
+                break;
+
+            // case Keyboard.KEY_UP: key = Key.K_KP_UPARROW; break;
+            case Keyboard.KEY_UP:
+                key = Key.K_UPARROW;
+                break;
+
+            case Keyboard.KEY_ESCAPE:
+                key = Key.K_ESCAPE;
+                break;
+
+
+            case Keyboard.KEY_RETURN:
+                key = Key.K_ENTER;
+                break;
+//     00652                 case XK_KP_Enter: key = K_KP_ENTER;     break;
+
+            case Keyboard.KEY_TAB:
+                key = Key.K_TAB;
+                break;
+
+            case Keyboard.KEY_F1:
+                key = Key.K_F1;
+                break;
+            case Keyboard.KEY_F2:
+                key = Key.K_F2;
+                break;
+            case Keyboard.KEY_F3:
+                key = Key.K_F3;
+                break;
+            case Keyboard.KEY_F4:
+                key = Key.K_F4;
+                break;
+            case Keyboard.KEY_F5:
+                key = Key.K_F5;
+                break;
+            case Keyboard.KEY_F6:
+                key = Key.K_F6;
+                break;
+            case Keyboard.KEY_F7:
+                key = Key.K_F7;
+                break;
+            case Keyboard.KEY_F8:
+                key = Key.K_F8;
+                break;
+            case Keyboard.KEY_F9:
+                key = Key.K_F9;
+                break;
+            case Keyboard.KEY_F10:
+                key = Key.K_F10;
+                break;
+            case Keyboard.KEY_F11:
+                key = Key.K_F11;
+                break;
+            case Keyboard.KEY_F12:
+                key = Key.K_F12;
+                break;
+
+            case Keyboard.KEY_BACK:
+                key = Key.K_BACKSPACE;
+                break;
+
+            case Keyboard.KEY_DELETE:
+                key = Key.K_DEL;
+                break;
+//     00683                 case XK_KP_Delete: key = K_KP_DEL; break;
+
+            case Keyboard.KEY_PAUSE:
+                key = Key.K_PAUSE;
+                break;
+
+            case Keyboard.KEY_RSHIFT:
+            case Keyboard.KEY_LSHIFT:
+                key = Key.K_SHIFT;
+                break;
+
+            case Keyboard.KEY_RCONTROL:
+            case Keyboard.KEY_LCONTROL:
+                key = Key.K_CTRL;
+                break;
+
+            case Keyboard.KEY_LMENU:
+            case Keyboard.KEY_RMENU:
+                key = Key.K_ALT;
+                break;
+
+//     00700                 case XK_KP_Begin: key = K_KP_5; break;
+//     00701
+            case Keyboard.KEY_INSERT:
+                key = Key.K_INS;
+                break;
+            // toggle console for DE and US keyboards
+            case Keyboard.KEY_GRAVE:
+            case Keyboard.KEY_CIRCUMFLEX:
+                key = '`';
+                break;
+
+            default:
+                key = lwjglKeycodeMap[code];
+                if (key >= 'A' && key <= 'Z')
+                    key = key - 'A' + 'a';
+                break;
+        }
+        if (key > 255) key = 0;
+        return key;
+    }
+
+    public void Do_Key_Event(int key, boolean down) {
+        Key.Event(key, down, Timer.getCurrentTimeMillis());
+    }
+
+    public void installGrabs() {
+        Mouse.setGrabbed(true);
+    }
+
+    public void uninstallGrabs() {
+        Mouse.setGrabbed(false);
+    }
+}
diff --git a/src/main/java/lwjake2/sys/LWJake2InputEvent.java b/src/main/java/lwjake2/sys/LWJake2InputEvent.java
new file mode 100644 (file)
index 0000000..47fa016
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.sys;
+
+import java.awt.*;
+
+/**
+ * LWJake2InputEvent
+ */
+class LWJake2InputEvent {
+    static final int KeyPress = 0;
+    static final int KeyRelease = 1;
+    static final int MotionNotify = 2;
+    static final int ButtonPress = 3;
+    static final int ButtonRelease = 4;
+    static final int CreateNotify = 5;
+    static final int ConfigureNotify = 6;
+    static final int WheelMoved = 7;
+    final int type;
+    final AWTEvent ev;
+
+    LWJake2InputEvent(int type, AWTEvent ev) {
+        this.type = type;
+        this.ev = ev;
+    }
+}
diff --git a/src/main/java/lwjake2/sys/NET.java b/src/main/java/lwjake2/sys/NET.java
new file mode 100644 (file)
index 0000000..0e704ef
--- /dev/null
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.sys;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.game.CvarT;
+import lwjake2.qcommon.Com;
+import lwjake2.qcommon.Cvar;
+import lwjake2.qcommon.NetadrT;
+import lwjake2.qcommon.sizebuf_t;
+import lwjake2.util.Lib;
+
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.DatagramChannel;
+
+public final class NET {
+
+    private static final loopback_t[] loopbacks = new loopback_t[2];
+    private final static int MAX_LOOPBACK = 4;
+    /**
+     * Local loopback adress.
+     */
+    private static final NetadrT net_local_adr = new NetadrT();
+    private static final DatagramChannel[] ip_channels = {null, null};
+    private static final DatagramSocket[] ip_sockets = {null, null};
+
+    static {
+        loopbacks[0] = new loopback_t();
+        loopbacks[1] = new loopback_t();
+    }
+
+    /**
+     * Compares ip address and port.
+     */
+    public static boolean CompareAdr(NetadrT a, NetadrT b) {
+        return (a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2]
+                && a.ip[3] == b.ip[3] && a.port == b.port);
+    }
+
+    /**
+     * Compares ip address without the port.
+     */
+    public static boolean CompareBaseAdr(NetadrT a, NetadrT b) {
+        if (a.type != b.type)
+            return false;
+
+        if (a.type == Defines.NA_LOOPBACK)
+            return true;
+
+        if (a.type == Defines.NA_IP) {
+            return (a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1]
+                    && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3]);
+        }
+        return false;
+    }
+
+    /**
+     * Returns a string holding ip address and port like "ip0.ip1.ip2.ip3:port".
+     */
+    public static String AdrToString(NetadrT a) {
+        return String.valueOf(a.ip[0] & 0xFF) + '.' + (a.ip[1] & 0xFF) +
+                '.' +
+                (a.ip[2] & 0xFF) + '.' + (a.ip[3] & 0xFF) +
+                ':' + a.port;
+    }
+
+    /**
+     * Returns IP address without the port as string.
+     */
+    public static String BaseAdrToString(NetadrT a) {
+        return String.valueOf(a.ip[0] & 0xFF) + '.' + (a.ip[1] & 0xFF) +
+                '.' +
+                (a.ip[2] & 0xFF) + '.' + (a.ip[3] & 0xFF);
+    }
+
+    /**
+     * Creates an NetadrT from an string.
+     */
+    public static boolean StringToAdr(String s, NetadrT a) {
+        if (s.equalsIgnoreCase("localhost") || s.equalsIgnoreCase("loopback")) {
+            a.set(net_local_adr);
+            return true;
+        }
+        try {
+            String[] address = s.split(":");
+            InetAddress ia = InetAddress.getByName(address[0]);
+            a.ip = ia.getAddress();
+            a.type = Defines.NA_IP;
+            if (address.length == 2)
+                a.port = Lib.atoi(address[1]);
+            return true;
+        } catch (Exception e) {
+            Com.Println(e.getMessage());
+            return false;
+        }
+    }
+
+    /**
+     * Seems to return true, if the address is is on 127.0.0.1.
+     */
+    public static boolean IsLocalAddress(NetadrT adr) {
+        return CompareAdr(adr, net_local_adr);
+    }
+
+    /**
+     * Gets a packet from internal loopback.
+     */
+    private static boolean GetLoopPacket(int sock, NetadrT net_from,
+                                         sizebuf_t net_message) {
+        loopback_t loop;
+        loop = loopbacks[sock];
+
+        if (loop.send - loop.get > MAX_LOOPBACK)
+            loop.get = loop.send - MAX_LOOPBACK;
+
+        if (loop.get >= loop.send)
+            return false;
+
+        int i = loop.get & (MAX_LOOPBACK - 1);
+        loop.get++;
+
+        System.arraycopy(loop.msgs[i].data, 0, net_message.data, 0,
+                loop.msgs[i].datalen);
+        net_message.cursize = loop.msgs[i].datalen;
+
+        net_from.set(net_local_adr);
+        return true;
+    }
+
+    /**
+     * Sends a packet via internal loopback.
+     */
+    private static void SendLoopPacket(int sock, int length, byte[] data,
+                                       NetadrT to) {
+        int i;
+        loopback_t loop;
+
+        loop = loopbacks[sock ^ 1];
+
+        // modulo 4
+        i = loop.send & (MAX_LOOPBACK - 1);
+        loop.send++;
+
+        System.arraycopy(data, 0, loop.msgs[i].data, 0, length);
+        loop.msgs[i].datalen = length;
+    }
+
+    /*
+     * ==================================================
+     * 
+     * LOOPBACK BUFFERS FOR LOCAL PLAYER
+     * 
+     * ==================================================
+     */
+
+    /**
+     * Gets a packet from a network channel
+     */
+    public static boolean GetPacket(int sock, NetadrT net_from,
+                                    sizebuf_t net_message) {
+
+        if (GetLoopPacket(sock, net_from, net_message)) {
+            return true;
+        }
+
+        if (ip_sockets[sock] == null)
+            return false;
+
+        try {
+            ByteBuffer receiveBuffer = ByteBuffer.wrap(net_message.data);
+
+            InetSocketAddress srcSocket = (InetSocketAddress) ip_channels[sock]
+                    .receive(receiveBuffer);
+            if (srcSocket == null)
+                return false;
+
+            net_from.ip = srcSocket.getAddress().getAddress();
+            net_from.port = srcSocket.getPort();
+            net_from.type = Defines.NA_IP;
+
+            int packetLength = receiveBuffer.position();
+
+            if (packetLength > net_message.maxsize) {
+                Com.Println("Oversize packet from " + AdrToString(net_from));
+                return false;
+            }
+
+            // set the size
+            net_message.cursize = packetLength;
+            // set the sentinel
+            net_message.data[packetLength] = 0;
+            return true;
+
+        } catch (IOException e) {
+            Com.DPrintf("NET_GetPacket: " + e + " from "
+                    + AdrToString(net_from) + "\n");
+            return false;
+        }
+    }
+
+    /**
+     * Sends a Packet.
+     */
+    public static void SendPacket(int sock, int length, byte[] data, NetadrT to) {
+        if (to.type == Defines.NA_LOOPBACK) {
+            SendLoopPacket(sock, length, data, to);
+            return;
+        }
+
+        if (ip_sockets[sock] == null)
+            return;
+
+        if (to.type != Defines.NA_BROADCAST && to.type != Defines.NA_IP) {
+            Com.Error(Defines.ERR_FATAL, "NET_SendPacket: bad address type");
+            return;
+        }
+
+        try {
+            SocketAddress dstSocket = new InetSocketAddress(to.getInetAddress(), to.port);
+            ip_channels[sock].send(ByteBuffer.wrap(data, 0, length), dstSocket);
+        } catch (Exception e) {
+            Com.Println("NET_SendPacket ERROR: " + e + " to " + AdrToString(to));
+        }
+    }
+
+    /**
+     * OpenIP, creates the network sockets.
+     */
+    private static void OpenIP() {
+        CvarT port, ip, clientport;
+
+        port = Cvar.get("port", "" + Defines.PORT_SERVER, Defines.CVAR_NOSET);
+        ip = Cvar.get("ip", "localhost", Defines.CVAR_NOSET);
+        clientport = Cvar.get("clientport", "" + Defines.PORT_CLIENT, Defines.CVAR_NOSET);
+
+        if (ip_sockets[Defines.NS_SERVER] == null)
+            ip_sockets[Defines.NS_SERVER] = Socket(Defines.NS_SERVER,
+                    ip.string, (int) port.value);
+
+        if (ip_sockets[Defines.NS_CLIENT] == null)
+            ip_sockets[Defines.NS_CLIENT] = Socket(Defines.NS_CLIENT,
+                    ip.string, (int) clientport.value);
+        if (ip_sockets[Defines.NS_CLIENT] == null)
+            ip_sockets[Defines.NS_CLIENT] = Socket(Defines.NS_CLIENT,
+                    ip.string, Defines.PORT_ANY);
+    }
+
+    /**
+     * Config multi or singlepalyer - A single player game will only use the loopback code.
+     */
+    public static void Config(boolean multiplayer) {
+        if (!multiplayer) {
+            // shut down any existing sockets
+            for (int i = 0; i < 2; i++) {
+                if (ip_sockets[i] != null) {
+                    ip_sockets[i].close();
+                    ip_sockets[i] = null;
+                }
+            }
+        } else {
+            // open sockets
+            OpenIP();
+        }
+    }
+
+    /**
+     * Init
+     */
+    public static void Init() {
+        // nothing to do
+    }
+
+    /*
+     * Socket
+     */
+    private static DatagramSocket Socket(int sock, String ip, int port) {
+
+        DatagramSocket newsocket = null;
+        try {
+            if (ip_channels[sock] == null || !ip_channels[sock].isOpen())
+                ip_channels[sock] = DatagramChannel.open();
+
+            if (ip == null || ip.length() == 0 || ip.equals("localhost")) {
+                if (port == Defines.PORT_ANY) {
+                    newsocket = ip_channels[sock].socket();
+                    newsocket.bind(new InetSocketAddress(0));
+                } else {
+                    newsocket = ip_channels[sock].socket();
+                    newsocket.bind(new InetSocketAddress(port));
+                }
+            } else {
+                InetAddress ia = InetAddress.getByName(ip);
+                newsocket = ip_channels[sock].socket();
+                newsocket.bind(new InetSocketAddress(ia, port));
+            }
+
+            // nonblocking channel
+            ip_channels[sock].configureBlocking(false);
+            // the socket have to be broadcastable
+            newsocket.setBroadcast(true);
+        } catch (Exception e) {
+            Com.Println("Error: " + e.toString());
+            newsocket = null;
+        }
+        return newsocket;
+    }
+
+    /**
+     * Shutdown - closes the sockets
+     */
+    public static void Shutdown() {
+        // close sockets
+        Config(false);
+    }
+
+    /**
+     * Sleeps msec or until net socket is ready.
+     */
+    public static void Sleep(int msec) {
+        if (ip_sockets[Defines.NS_SERVER] == null
+                || (Globals.dedicated != null && Globals.dedicated.value == 0))
+            return; // we're not a server, just run full speed
+
+        try {
+            //TODO: check for timeout
+            Thread.sleep(msec);
+        } catch (InterruptedException ignored) {
+        }
+        //ip_sockets[NS_SERVER].
+
+        // this should wait up to 100ms until a packet
+        /*
+         * struct timeval timeout;
+         * fd_set fdset;
+         * extern CvarT *dedicated;
+         * extern qboolean stdin_active;
+         *
+         * if (!ip_sockets[NS_SERVER] || (dedicated && !dedicated.value))
+         *             return; // we're not a server, just run full speed
+         *
+         * FD_ZERO(&fdset);
+         *
+         * if (stdin_active)
+         *             FD_SET(0, &fdset); // stdin is processed too
+         *
+         * FD_SET(ip_sockets[NS_SERVER], &fdset); // network socket
+         *
+         * timeout.tv_sec = msec/1000;
+         * timeout.tv_usec = (msec%1000)*1000;
+         *
+         * select(ip_sockets[NS_SERVER]+1, &fdset, NULL, NULL, &timeout);
+         */
+    }
+
+    static class loopmsg_t {
+        final byte[] data = new byte[Defines.MAX_MSGLEN];
+
+        int datalen;
+    }
+
+    public static class loopback_t {
+        final loopmsg_t[] msgs;
+        int get, send;
+
+        loopback_t() {
+            msgs = new loopmsg_t[MAX_LOOPBACK];
+            for (int n = 0; n < MAX_LOOPBACK; n++) {
+                msgs[n] = new loopmsg_t();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/sys/NanoTimer.java b/src/main/java/lwjake2/sys/NanoTimer.java
new file mode 100644 (file)
index 0000000..5f9e923
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.sys;
+
+public class NanoTimer extends Timer {
+
+    private final long base;
+
+    NanoTimer() {
+        base = System.nanoTime();
+    }
+
+    public long currentTimeMillis() {
+        long time = System.nanoTime();
+        long delta = time - base;
+        if (delta < 0) {
+            delta += Long.MAX_VALUE + 1;
+        }
+        return (long) (delta * 0.000001);
+    }
+
+}
diff --git a/src/main/java/lwjake2/sys/StandardTimer.java b/src/main/java/lwjake2/sys/StandardTimer.java
new file mode 100644 (file)
index 0000000..1ca47e8
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.sys;
+
+class StandardTimer extends Timer {
+
+    private final long base;
+
+    StandardTimer() {
+        base = System.currentTimeMillis();
+    }
+
+    public long currentTimeMillis() {
+        long time = System.currentTimeMillis();
+        long delta = time - base;
+        if (delta < 0) {
+            delta += Long.MAX_VALUE + 1;
+        }
+        return delta;
+    }
+
+}
diff --git a/src/main/java/lwjake2/sys/Sys.java b/src/main/java/lwjake2/sys/Sys.java
new file mode 100644 (file)
index 0000000..eea35d8
--- /dev/null
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.sys;
+
+import lwjake2.Defines;
+import lwjake2.Globals;
+import lwjake2.client.Client;
+import lwjake2.qcommon.Com;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * Sys
+ */
+public final class Sys extends Defines {
+
+    private static File[] fdir;
+    private static int fileindex;
+
+
+    //============================================
+
+    public static void Error(String error) {
+
+        Client.Shutdown();
+        //StackTrace();
+        new Exception(error).printStackTrace();
+        System.exit(1);
+    }
+
+    public static void Quit() {
+        Client.Shutdown();
+
+        System.exit(0);
+    }
+
+    //ok!
+    public static File[] FindAll(String path, int musthave, int canthave) {
+
+        int index = path.lastIndexOf('/');
+
+        String findpattern;
+        String findbase;
+        if (index != -1) {
+            findbase = path.substring(0, index);
+            findpattern = path.substring(index + 1, path.length());
+        } else {
+            findbase = path;
+            findpattern = "*";
+        }
+
+        if (findpattern.equals("*.*")) {
+            findpattern = "*";
+        }
+
+        File fdir = new File(findbase);
+
+        if (!fdir.exists())
+            return null;
+
+        FilenameFilter filter = new FileFilter(findpattern, musthave, canthave);
+
+        return fdir.listFiles(filter);
+    }
+
+    // ok.
+    public static File FindFirst(String path, int musthave, int canthave) {
+
+        if (fdir != null)
+            Sys.Error("Sys_BeginFind without close");
+
+        //     COM_FilePath (path, findbase);
+
+        fdir = FindAll(path, canthave, musthave);
+        fileindex = 0;
+
+        if (fdir == null)
+            return null;
+
+        return FindNext();
+    }
+
+    public static File FindNext() {
+
+        if (fileindex >= fdir.length)
+            return null;
+
+        return fdir[fileindex++];
+    }
+
+    public static void FindClose() {
+        fdir = null;
+    }
+
+    public static void SendKeyEvents() {
+        Globals.re.getKeyboardHandler().Update();
+
+        // grab frame time
+        Globals.sys_frame_time = Timer.getCurrentTimeMillis();
+    }
+
+    public static String GetClipboardData() {
+        // TODO: implement GetClipboardData
+        return null;
+    }
+
+    public static void ConsoleOutput(String msg) {
+        if (Globals.nostdout != null && Globals.nostdout.value != 0)
+            return;
+
+        System.out.print(msg);
+    }
+
+    /**
+     * Match the pattern findpattern against the filename.
+     * <p>
+     * In the pattern string, `*' matches any sequence of characters, `?'
+     * matches any character, [SET] matches any character in the specified set,
+     * [!SET] matches any character not in the specified set. A set is composed
+     * of characters or ranges; a range looks like character hyphen character
+     * (as in 0-9 or A-Z). [0-9a-zA-Z_] is the set of characters allowed in C
+     * identifiers. Any other character in the pattern must be matched exactly.
+     * To suppress the special syntactic significance of any of `[]*?!-\', and
+     * match the character exactly, precede it with a `\'.
+     */
+    static class FileFilter implements FilenameFilter {
+
+        final String regexpr;
+
+        final int musthave;
+        final int canthave;
+
+        FileFilter(String findpattern, int musthave, int canthave) {
+            this.regexpr = convert2regexpr(findpattern);
+            this.musthave = musthave;
+            this.canthave = canthave;
+
+        }
+
+        /*
+         * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
+         */
+        public boolean accept(File dir, String name) {
+            return name.matches(regexpr) && CompareAttributes(dir, musthave, canthave);
+        }
+
+        String convert2regexpr(String pattern) {
+
+            StringBuilder sb = new StringBuilder();
+
+            char c;
+            boolean escape = false;
+
+            String subst;
+
+            // convert pattern
+            for (int i = 0; i < pattern.length(); i++) {
+                c = pattern.charAt(i);
+                subst = null;
+                switch (c) {
+                    case '*':
+                        subst = (!escape) ? ".*" : "*";
+                        break;
+                    case '.':
+                        subst = (!escape) ? "\\." : ".";
+                        break;
+                    case '!':
+                        subst = (!escape) ? "^" : "!";
+                        break;
+                    case '?':
+                        subst = (!escape) ? "." : "?";
+                        break;
+                    case '\\':
+                        escape = !escape;
+                        break;
+                    default:
+                        escape = false;
+                }
+                if (subst != null) {
+                    sb.append(subst);
+                    escape = false;
+                } else
+                    sb.append(c);
+            }
+
+            // the converted pattern
+            String regexpr = sb.toString();
+
+            //Com.DPrintf("pattern: " + pattern + " regexpr: " + regexpr +
+            // '\n');
+            try {
+                Pattern.compile(regexpr);
+            } catch (PatternSyntaxException e) {
+                Com.Printf("invalid file pattern ( *.* is used instead )\n");
+                return ".*"; // the default
+            }
+            return regexpr;
+        }
+
+        boolean CompareAttributes(File dir, int musthave, int canthave) {
+            // . and .. never match
+            String name = dir.getName();
+
+            return !(name.equals(".") || name.equals(".."));
+
+        }
+
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/sys/Timer.java b/src/main/java/lwjake2/sys/Timer.java
new file mode 100644 (file)
index 0000000..7b5eeb0
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.sys;
+
+import lwjake2.Globals;
+import lwjake2.qcommon.Com;
+
+public abstract class Timer {
+
+    private static Timer timer;
+
+    static {
+        try {
+            timer = new NanoTimer();
+        } catch (Throwable e) {
+            timer = new StandardTimer();
+        }
+        Com.Println("using " + timer.getClass().getName());
+    }
+
+    public static int getCurrentTimeMillis() {
+        return Globals.curtime = (int) (timer.currentTimeMillis());
+    }
+
+    protected abstract long currentTimeMillis();
+}
diff --git a/src/main/java/lwjake2/sys/UserInputHandler.java b/src/main/java/lwjake2/sys/UserInputHandler.java
new file mode 100644 (file)
index 0000000..572950d
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.sys;
+
+import lwjake2.Globals;
+import lwjake2.client.CL_input;
+import lwjake2.client.Key;
+import lwjake2.game.Cmd;
+import lwjake2.game.usercmd_t;
+import lwjake2.qcommon.Cvar;
+import lwjake2.qcommon.xcommand_t;
+import lwjake2.util.Math3D;
+
+/**
+ * IN
+ */
+public final class UserInputHandler extends Globals {
+
+    static boolean mouse_active = false;
+    private static boolean mouse_avail = true;
+    private static int mouse_buttonstate;
+
+    private static int mouse_oldbuttonstate;
+
+    private static int old_mouse_x;
+
+    private static int old_mouse_y;
+
+    private static boolean mlooking;
+
+    private static void ActivateMouse() {
+        if (!mouse_avail)
+            return;
+        if (!mouse_active) {
+            KBD.mx = KBD.my = 0; // don't spazz
+            install_grabs();
+            mouse_active = true;
+        }
+    }
+
+    private static void DeactivateMouse() {
+        // if (!mouse_avail || c == null) return;
+        if (mouse_active) {
+            uninstall_grabs();
+            mouse_active = false;
+        }
+    }
+
+    private static void install_grabs() {
+        Globals.re.getKeyboardHandler().installGrabs();
+        boolean ignorefirst = true;
+    }
+
+    private static void uninstall_grabs() {
+        Globals.re.getKeyboardHandler().uninstallGrabs();
+    }
+
+    private static void toggleMouse() {
+        if (mouse_avail) {
+            mouse_avail = false;
+            DeactivateMouse();
+        } else {
+            mouse_avail = true;
+            ActivateMouse();
+        }
+    }
+
+    public static void Init() {
+        in_mouse = Cvar.get("in_mouse", "1", CVAR_ARCHIVE);
+        in_joystick = Cvar.get("in_joystick", "0", CVAR_ARCHIVE);
+    }
+
+    public static void Shutdown() {
+        mouse_avail = false;
+    }
+
+    public static void Real_IN_Init() {
+        // mouse variables
+        Globals.m_filter = Cvar.get("m_filter", "0", 0);
+        Globals.in_mouse = Cvar.get("in_mouse", "1", CVAR_ARCHIVE);
+        Globals.freelook = Cvar.get("freelook", "1", 0);
+        Globals.lookstrafe = Cvar.get("lookstrafe", "0", 0);
+        Globals.sensitivity = Cvar.get("sensitivity", "3", 0);
+        Globals.m_pitch = Cvar.get("m_pitch", "0.022", 0);
+        Globals.m_yaw = Cvar.get("m_yaw", "0.022", 0);
+        Globals.m_forward = Cvar.get("m_forward", "1", 0);
+        Globals.m_side = Cvar.get("m_side", "0.8", 0);
+
+        Cmd.AddCommand("+mlook", new xcommand_t() {
+            public void execute() {
+                MLookDown();
+            }
+        });
+        Cmd.AddCommand("-mlook", new xcommand_t() {
+            public void execute() {
+                MLookUp();
+            }
+        });
+
+        Cmd.AddCommand("force_centerview", new xcommand_t() {
+            public void execute() {
+                Force_CenterView_f();
+            }
+        });
+
+        Cmd.AddCommand("togglemouse", new xcommand_t() {
+            public void execute() {
+                toggleMouse();
+            }
+        });
+
+        UserInputHandler.mouse_avail = true;
+    }
+
+    public static void Commands() {
+        int i;
+
+        if (!UserInputHandler.mouse_avail)
+            return;
+
+        KBD kbd = Globals.re.getKeyboardHandler();
+        for (i = 0; i < 3; i++) {
+            if ((UserInputHandler.mouse_buttonstate & (1 << i)) != 0 && (UserInputHandler.mouse_oldbuttonstate & (1 << i)) == 0)
+                kbd.Do_Key_Event(Key.K_MOUSE1 + i, true);
+
+            if ((UserInputHandler.mouse_buttonstate & (1 << i)) == 0 && (UserInputHandler.mouse_oldbuttonstate & (1 << i)) != 0)
+                kbd.Do_Key_Event(Key.K_MOUSE1 + i, false);
+        }
+        UserInputHandler.mouse_oldbuttonstate = UserInputHandler.mouse_buttonstate;
+    }
+
+    public static void doFrame() {
+
+        if (!clientStateT.refresh_prepped || clientStaticT.key_dest == key_console
+                || clientStaticT.key_dest == key_menu)
+            DeactivateMouse();
+        else
+            ActivateMouse();
+    }
+
+    public static void CenterView() {
+        clientStateT.viewangles[PITCH] = -Math3D
+                .short2Angle(clientStateT.frame.playerstate.pmove.delta_angles[PITCH]);
+    }
+
+    public static void Move(usercmd_t cmd) {
+        if (!UserInputHandler.mouse_avail)
+            return;
+
+        if (Globals.m_filter.value != 0.0f) {
+            KBD.mx = (KBD.mx + UserInputHandler.old_mouse_x) / 2;
+            KBD.my = (KBD.my + UserInputHandler.old_mouse_y) / 2;
+        }
+
+        UserInputHandler.old_mouse_x = KBD.mx;
+        UserInputHandler.old_mouse_y = KBD.my;
+
+        KBD.mx = (int) (KBD.mx * Globals.sensitivity.value);
+        KBD.my = (int) (KBD.my * Globals.sensitivity.value);
+
+        // add mouse X/Y movement to cmd
+        if ((CL_input.in_strafe.state & 1) != 0
+                || ((Globals.lookstrafe.value != 0) && UserInputHandler.mlooking)) {
+            cmd.sidemove += Globals.m_side.value * KBD.mx;
+        } else {
+            Globals.clientStateT.viewangles[YAW] -= Globals.m_yaw.value * KBD.mx;
+        }
+
+        if ((UserInputHandler.mlooking || Globals.freelook.value != 0.0f)
+                && (CL_input.in_strafe.state & 1) == 0) {
+            Globals.clientStateT.viewangles[PITCH] += Globals.m_pitch.value * KBD.my;
+        } else {
+            cmd.forwardmove -= Globals.m_forward.value * KBD.my;
+        }
+        KBD.mx = KBD.my = 0;
+    }
+
+    private static void MLookDown() {
+        mlooking = true;
+    }
+
+    private static void MLookUp() {
+        mlooking = false;
+        CenterView();
+    }
+
+    private static void Force_CenterView_f() {
+        Globals.clientStateT.viewangles[PITCH] = 0;
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/util/Lib.java b/src/main/java/lwjake2/util/Lib.java
new file mode 100644 (file)
index 0000000..3f2ed9b
--- /dev/null
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.util;
+
+import lwjake2.Globals;
+import lwjake2.qcommon.Com;
+
+import java.io.File;
+import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+public class Lib {
+
+
+    public static final int SIZEOF_FLOAT = 4;
+    public static final int SIZEOF_INT = 4;
+    static final byte nullfiller[] = new byte[8192];
+
+    /**
+     * Converts a vector to a string.
+     */
+    public static String vtos(float[] v) {
+        return (int) v[0] + " " + (int) v[1] + " " + (int) v[2];
+    }
+
+    /**
+     * Converts a vector to a beatiful string.
+     */
+    public static String vtofsbeaty(float[] v) {
+        return Com.sprintf("%8.2f %8.2f %8.2f", new Vargs().add(v[0]).add(v[1]).add(v[2]));
+    }
+
+    /**
+     * Like in  libc.
+     */
+    public static short rand() {
+        return (short) Globals.rnd.nextInt(Short.MAX_VALUE + 1);
+    }
+
+    /**
+     * Like in libc.
+     */
+    public static float crandom() {
+        return (Globals.rnd.nextFloat() - 0.5f) * 2.0f;
+    }
+
+    /**
+     * Like in libc.
+     */
+    public static float crand() {
+        return (Globals.rnd.nextFloat() - 0.5f) * 2.0f;
+    }
+
+    /**
+     * Like in libc.
+     */
+    public static int strcmp(String in1, String in2) {
+        return in1.compareTo(in2);
+    }
+
+    /**
+     * Like in libc.
+     */
+    public static float atof(String in) {
+        float res = 0;
+
+        try {
+            res = Float.parseFloat(in);
+        } catch (Exception ignored) {
+        }
+
+        return res;
+    }
+
+    /**
+     * Like in quake2.
+     */
+    public static int Q_stricmp(String in1, String in2) {
+        return in1.compareToIgnoreCase(in2);
+    }
+
+    /**
+     * Like in libc.
+     */
+    public static int atoi(String in) {
+        try {
+            return Integer.parseInt(in);
+        } catch (Exception e) {
+            try {
+                return (int) Double.parseDouble(in);
+            } catch (Exception e1) {
+                return 0;
+            }
+        }
+    }
+
+    /**
+     * Converts a string to a vector. Needs improvement.
+     */
+    public static float[] atov(String v) {
+        float[] res = {0, 0, 0};
+        String strres[] = v.split(" ");
+        for (int n = 0; n < 3 && n < strres.length; n++) {
+            res[n] = atof(strres[n]);
+        }
+        return res;
+    }
+
+    /**
+     * Like in libc.
+     */
+    public static int strlen(char in[]) {
+        for (int i = 0; i < in.length; i++)
+            if (in[i] == 0)
+                return i;
+        return in.length;
+    }
+
+    /**
+     * Like in libc.
+     */
+    public static int strlen(byte in[]) {
+        for (int i = 0; i < in.length; i++)
+            if (in[i] == 0)
+                return i;
+        return in.length;
+    }
+
+    /**
+     * Converts memory to a memory dump string.
+     */
+    public static String hexDump(byte data1[], int len, boolean showAddress) {
+        StringBuilder result = new StringBuilder();
+        StringBuilder charfield = new StringBuilder();
+        int i = 0;
+        while (i < len) {
+            if ((i & 0xf) == 0) {
+                if (showAddress) {
+                    String address = Integer.toHexString(i);
+                    address = ("0000".substring(0, 4 - address.length()) + address).toUpperCase();
+                    result.append(address).append(": ");
+                }
+            }
+            int v = data1[i];
+
+            result.append(hex2(v));
+            result.append(" ");
+
+            charfield.append(readableChar(v));
+            i++;
+
+            // nach dem letzten, newline einfuegen
+            if ((i & 0xf) == 0) {
+                result.append(charfield);
+                result.append("\n");
+                charfield.setLength(0);
+            }
+            // in der Mitte ein Luecke einfuegen ?
+            else if ((i & 0xf) == 8) {
+                result.append(" ");
+            }
+        }
+        return result.toString();
+    }
+
+    /**
+     * Formats an hex byte.
+     */
+    public static String hex2(int i) {
+        String val = Integer.toHexString(i & 0xff);
+        return ("00".substring(0, 2 - val.length()) + val).toUpperCase();
+    }
+
+    /**
+     * Returns true if the char is alphanumeric.
+     */
+    public static char readableChar(int i) {
+        if ((i < 0x20) || (i > 0x7f))
+            return '.';
+        else
+            return (char) i;
+    }
+
+    /**
+     * Like in libc
+     */
+    public static RandomAccessFile fopen(String name, String mode) {
+        try {
+            return new RandomAccessFile(name, mode);
+        } catch (Exception e) {
+            Com.DPrintf("Could not open file:" + name);
+            return null;
+        }
+    }
+
+    /**
+     * Like in libc
+     */
+    public static void fclose(RandomAccessFile f) {
+        try {
+            f.close();
+        } catch (Exception ignored) {
+        }
+    }
+
+    /**
+     * Returns the right part of the string from the last occruence of c.
+     */
+    public static String rightFrom(String in, char c) {
+        int pos = in.lastIndexOf(c);
+        if (pos == -1)
+            return "";
+        else if (pos < in.length())
+            return in.substring(pos + 1, in.length());
+        return "";
+    }
+
+    /**
+     * Returns the left part of the string from the last occruence of c.
+     */
+    public static String leftFrom(String in, char c) {
+        int pos = in.lastIndexOf(c);
+        if (pos == -1)
+            return "";
+        else if (pos < in.length())
+            return in.substring(0, pos);
+        return "";
+    }
+
+    /**
+     * Renames a file.
+     */
+    public static int rename(String oldn, String newn) {
+        try {
+            File f1 = new File(oldn);
+            File f2 = new File(newn);
+            f1.renameTo(f2);
+            return 0;
+        } catch (Exception e) {
+            return 1;
+        }
+    }
+
+    /**
+     * Converts an int to 4 bytes java representation.
+     */
+    public static byte[] getIntBytes(int c) {
+        byte b[] = new byte[4];
+        b[0] = (byte) ((c & 0xff));
+        b[1] = (byte) ((c >>> 8) & 0xff);
+        b[2] = (byte) ((c >>> 16) & 0xff);
+        b[3] = (byte) ((c >>> 24) & 0xff);
+        return b;
+    }
+
+    /**
+     * Duplicates a float array.
+     */
+    public static float[] clone(float in[]) {
+        float out[] = new float[in.length];
+
+        if (in.length != 0)
+            System.arraycopy(in, 0, out, 0, in.length);
+
+        return out;
+    }
+
+    /**
+     * convert a java string to byte[] with 8bit latin 1
+     * <p>
+     * avoid String.getBytes() because it is using system specific character encoding.
+     */
+    public static byte[] stringToBytes(String value) {
+        try {
+            return value.getBytes("ISO-8859-1");
+        } catch (UnsupportedEncodingException e) {
+            // can't happen: Latin 1 is a standard encoding
+            return null;
+        }
+    }
+
+    /**
+     * convert a byte[] with 8bit latin 1 to java string
+     * <p>
+     * avoid new String(bytes) because it is using system specific character encoding.
+     */
+    public static String bytesToString(byte[] value) {
+        try {
+            return new String(value, "ISO-8859-1");
+        } catch (UnsupportedEncodingException e) {
+            // can't happen: Latin 1 is a standard encoding
+            return null;
+        }
+    }
+
+       
+       /* java.nio.* Buffer util functions */
+
+    /**
+     * Helper method that savely handles the null termination of old C String data.
+     */
+    public static String CtoJava(byte[] old) {
+        return CtoJava(old, 0, old.length);
+    }
+
+    /**
+     * Helper method that savely handles the null termination of old C String data.
+     */
+    public static String CtoJava(byte[] old, int offset, int maxLenght) {
+        if (old.length == 0 || old[0] == 0) return "";
+        int i;
+        for (i = offset; (i - offset) < maxLenght && old[i] != 0; i++) ;
+        return new String(old, offset, i - offset);
+    }
+
+    public static FloatBuffer newFloatBuffer(int numElements) {
+        ByteBuffer bb = newByteBuffer(numElements * SIZEOF_FLOAT);
+        return bb.asFloatBuffer();
+    }
+
+    public static IntBuffer newIntBuffer(int numElements) {
+        ByteBuffer bb = newByteBuffer(numElements * SIZEOF_INT);
+        return bb.asIntBuffer();
+    }
+
+    public static IntBuffer newIntBuffer(int numElements, ByteOrder order) {
+        ByteBuffer bb = newByteBuffer(numElements * SIZEOF_INT, order);
+        return bb.asIntBuffer();
+    }
+
+    public static ByteBuffer newByteBuffer(int numElements) {
+        ByteBuffer bb = ByteBuffer.allocateDirect(numElements);
+        bb.order(ByteOrder.nativeOrder());
+        return bb;
+    }
+
+    public static ByteBuffer newByteBuffer(int numElements, ByteOrder order) {
+        ByteBuffer bb = ByteBuffer.allocateDirect(numElements);
+        bb.order(order);
+        return bb;
+    }
+}
diff --git a/src/main/java/lwjake2/util/Math3D.java b/src/main/java/lwjake2/util/Math3D.java
new file mode 100644 (file)
index 0000000..418674b
--- /dev/null
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.util;
+
+import lwjake2.Defines;
+import lwjake2.game.cplane_t;
+import lwjake2.qcommon.Com;
+
+public class Math3D {
+
+    static final float shortratio = 360.0f / 65536.0f;
+    static final float piratio = (float) (Math.PI / 360.0);
+    // to reduce garbage
+    private static final float[] vr = {0, 0, 0};
+    private static final float[] vup = {0, 0, 0};
+    private static final float[] vf = {0, 0, 0};
+    private static final float[][] PLANE_XYZ = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
+    private static final float[][] m = new float[3][3];
+    private static final float[][] im = new float[3][3];
+    private static final float[][] tmpmat = new float[3][3];
+    private static final float[][] zrot = new float[3][3];
+
+    public static void set(float v1[], float v2[]) {
+        v1[0] = v2[0];
+        v1[1] = v2[1];
+        v1[2] = v2[2];
+    }
+
+    public static void vectorSubtract(float[] a, float[] b, float[] c) {
+        c[0] = a[0] - b[0];
+        c[1] = a[1] - b[1];
+        c[2] = a[2] - b[2];
+    }
+
+    public static void vectorSubtract(short[] a, short[] b, int[] c) {
+        c[0] = a[0] - b[0];
+        c[1] = a[1] - b[1];
+        c[2] = a[2] - b[2];
+    }
+
+    public static void vectorAdd(float[] a, float[] b, float[] to) {
+        to[0] = a[0] + b[0];
+        to[1] = a[1] + b[1];
+        to[2] = a[2] + b[2];
+    }
+
+    public static void vectorCopy(float[] from, float[] to) {
+        to[0] = from[0];
+        to[1] = from[1];
+        to[2] = from[2];
+    }
+
+    public static void vectorCopy(short[] from, short[] to) {
+        to[0] = from[0];
+        to[1] = from[1];
+        to[2] = from[2];
+    }
+
+    public static void vectorCopy(short[] from, float[] to) {
+        to[0] = from[0];
+        to[1] = from[1];
+        to[2] = from[2];
+    }
+
+    public static void vectorCopy(float[] from, short[] to) {
+        to[0] = (short) from[0];
+        to[1] = (short) from[1];
+        to[2] = (short) from[2];
+    }
+
+    public static void vectorClear(float[] a) {
+        a[0] = a[1] = a[2] = 0;
+    }
+
+    public static boolean vectorEquals(float[] v1, float[] v2) {
+        return !(v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2]);
+
+    }
+
+    public static void vectorNegate(float[] from, float[] to) {
+        to[0] = -from[0];
+        to[1] = -from[1];
+        to[2] = -from[2];
+    }
+
+    public static void vectorSet(float[] v, float x, float y, float z) {
+        v[0] = (x);
+        v[1] = (y);
+        v[2] = (z);
+    }
+
+    public static void vectorMA(float[] veca, float scale, float[] vecb, float[] to) {
+        to[0] = veca[0] + scale * vecb[0];
+        to[1] = veca[1] + scale * vecb[1];
+        to[2] = veca[2] + scale * vecb[2];
+    }
+
+    public static float vectorNormalize(float[] v) {
+
+        float length = vectorLength(v);
+        if (length != 0.0f) {
+            float ilength = 1.0f / length;
+            v[0] *= ilength;
+            v[1] *= ilength;
+            v[2] *= ilength;
+        }
+        return length;
+    }
+
+    public static float vectorLength(float v[]) {
+        return (float) Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
+    }
+
+    public static void vectorScale(float[] in, float scale, float[] out) {
+        out[0] = in[0] * scale;
+        out[1] = in[1] * scale;
+        out[2] = in[2] * scale;
+    }
+
+    public static float vectoyaw(float[] vec) {
+        float yaw;
+
+        if (/*vec[YAW] == 0 &&*/
+                vec[Defines.PITCH] == 0) {
+            yaw = 0;
+            if (vec[Defines.YAW] > 0)
+                yaw = 90;
+            else if (vec[Defines.YAW] < 0)
+                yaw = -90;
+        } else {
+
+            yaw = (int) (Math.atan2(vec[Defines.YAW], vec[Defines.PITCH]) * 180 / Math.PI);
+            if (yaw < 0)
+                yaw += 360;
+        }
+
+        return yaw;
+    }
+
+    public static void vecToAngles(float[] value1, float[] angles) {
+
+        float yaw, pitch;
+
+        if (value1[1] == 0 && value1[0] == 0) {
+            yaw = 0;
+            if (value1[2] > 0)
+                pitch = 90;
+            else
+                pitch = 270;
+        } else {
+            if (value1[0] != 0)
+                yaw = (int) (Math.atan2(value1[1], value1[0]) * 180 / Math.PI);
+            else if (value1[1] > 0)
+                yaw = 90;
+            else
+                yaw = -90;
+            if (yaw < 0)
+                yaw += 360;
+
+            float forward = (float) Math.sqrt(value1[0] * value1[0] + value1[1] * value1[1]);
+            pitch = (int) (Math.atan2(value1[2], forward) * 180 / Math.PI);
+            if (pitch < 0)
+                pitch += 360;
+        }
+
+        angles[Defines.PITCH] = -pitch;
+        angles[Defines.YAW] = yaw;
+        angles[Defines.ROLL] = 0;
+    }
+
+    public static void rotatePointAroundVector(float[] dst, float[] dir, float[] point, float degrees) {
+        vf[0] = dir[0];
+        vf[1] = dir[1];
+        vf[2] = dir[2];
+
+        perpendicularVector(vr, dir);
+        crossProduct(vr, vf, vup);
+
+        m[0][0] = vr[0];
+        m[1][0] = vr[1];
+        m[2][0] = vr[2];
+
+        m[0][1] = vup[0];
+        m[1][1] = vup[1];
+        m[2][1] = vup[2];
+
+        m[0][2] = vf[0];
+        m[1][2] = vf[1];
+        m[2][2] = vf[2];
+
+        im[0][0] = m[0][0];
+        im[0][1] = m[1][0];
+        im[0][2] = m[2][0];
+        im[1][0] = m[0][1];
+        im[1][1] = m[1][1];
+        im[1][2] = m[2][1];
+        im[2][0] = m[0][2];
+        im[2][1] = m[1][2];
+        im[2][2] = m[2][2];
+
+        zrot[0][2] = zrot[1][2] = zrot[2][0] = zrot[2][1] = 0.0f;
+
+        zrot[2][2] = 1.0F;
+
+        zrot[0][0] = zrot[1][1] = (float) Math.cos(DEG2RAD(degrees));
+        zrot[0][1] = (float) Math.sin(DEG2RAD(degrees));
+        zrot[1][0] = -zrot[0][1];
+
+        R_ConcatRotations(m, zrot, tmpmat);
+        R_ConcatRotations(tmpmat, im, zrot);
+
+        for (int i = 0; i < 3; i++) {
+            dst[i] = zrot[i][0] * point[0] + zrot[i][1] * point[1] + zrot[i][2] * point[2];
+        }
+    }
+
+    public static void makeNormalVectors(float[] forward, float[] right, float[] up) {
+        // this rotate and negat guarantees a vector
+        // not colinear with the original
+        right[1] = -forward[0];
+        right[2] = forward[1];
+        right[0] = forward[2];
+
+        float d = dotProduct(right, forward);
+        vectorMA(right, -d, forward, right);
+        vectorNormalize(right);
+        crossProduct(right, forward, up);
+    }
+
+    public static float short2Angle(int x) {
+        return (x * shortratio);
+    }
+
+    /**
+     * concatenates 2 matrices each [3][3].
+     */
+    public static void R_ConcatRotations(float in1[][], float in2[][], float out[][]) {
+        out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + in1[0][2] * in2[2][0];
+        out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + in1[0][2] * in2[2][1];
+        out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + in1[0][2] * in2[2][2];
+        out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + in1[1][2] * in2[2][0];
+        out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + in1[1][2] * in2[2][1];
+        out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + in1[1][2] * in2[2][2];
+        out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + in1[2][2] * in2[2][0];
+        out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + in1[2][2] * in2[2][1];
+        out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + in1[2][2] * in2[2][2];
+    }
+
+    public static void projectPointOnPlane(float[] dst, float[] p, float[] normal) {
+
+        float inv_denom = 1.0F / dotProduct(normal, normal);
+
+        float d = dotProduct(normal, p) * inv_denom;
+
+        dst[0] = normal[0] * inv_denom;
+        dst[1] = normal[1] * inv_denom;
+        dst[2] = normal[2] * inv_denom;
+
+        dst[0] = p[0] - d * dst[0];
+        dst[1] = p[1] - d * dst[1];
+        dst[2] = p[2] - d * dst[2];
+    }
+    //=====================================================================
+
+    /**
+     * assumes "src" is normalized
+     */
+    public static void perpendicularVector(float[] dst, float[] src) {
+        int pos;
+        int i;
+        float minelem = 1.0F;
+
+        // find the smallest magnitude axially aligned vector
+        for (pos = 0, i = 0; i < 3; i++) {
+            if (Math.abs(src[i]) < minelem) {
+                pos = i;
+                minelem = Math.abs(src[i]);
+            }
+        }
+        // project the point onto the plane defined by src
+        projectPointOnPlane(dst, PLANE_XYZ[pos], src);
+
+        //normalize the result
+        vectorNormalize(dst);
+    }
+
+    /**
+     * stellt fest, auf welcher Seite sich die Kiste befindet, wenn die Ebene
+     * durch Entfernung und Senkrechten-Normale gegeben ist.
+     * erste Version mit vec3_t...
+     */
+    public static int boxOnPlaneSide(float emins[], float emaxs[], cplane_t p) {
+
+        assert (emins.length == 3 && emaxs.length == 3) : "vec3_t bug";
+
+        float dist1, dist2;
+        int sides;
+
+        //        fast axial cases
+        if (p.type < 3) {
+            if (p.dist <= emins[p.type])
+                return 1;
+            if (p.dist >= emaxs[p.type])
+                return 2;
+            return 3;
+        }
+
+        //        general case
+        switch (p.signbits) {
+            case 0:
+                dist1 = p.normal[0] * emaxs[0] + p.normal[1] * emaxs[1] + p.normal[2] * emaxs[2];
+                dist2 = p.normal[0] * emins[0] + p.normal[1] * emins[1] + p.normal[2] * emins[2];
+                break;
+            case 1:
+                dist1 = p.normal[0] * emins[0] + p.normal[1] * emaxs[1] + p.normal[2] * emaxs[2];
+                dist2 = p.normal[0] * emaxs[0] + p.normal[1] * emins[1] + p.normal[2] * emins[2];
+                break;
+            case 2:
+                dist1 = p.normal[0] * emaxs[0] + p.normal[1] * emins[1] + p.normal[2] * emaxs[2];
+                dist2 = p.normal[0] * emins[0] + p.normal[1] * emaxs[1] + p.normal[2] * emins[2];
+                break;
+            case 3:
+                dist1 = p.normal[0] * emins[0] + p.normal[1] * emins[1] + p.normal[2] * emaxs[2];
+                dist2 = p.normal[0] * emaxs[0] + p.normal[1] * emaxs[1] + p.normal[2] * emins[2];
+                break;
+            case 4:
+                dist1 = p.normal[0] * emaxs[0] + p.normal[1] * emaxs[1] + p.normal[2] * emins[2];
+                dist2 = p.normal[0] * emins[0] + p.normal[1] * emins[1] + p.normal[2] * emaxs[2];
+                break;
+            case 5:
+                dist1 = p.normal[0] * emins[0] + p.normal[1] * emaxs[1] + p.normal[2] * emins[2];
+                dist2 = p.normal[0] * emaxs[0] + p.normal[1] * emins[1] + p.normal[2] * emaxs[2];
+                break;
+            case 6:
+                dist1 = p.normal[0] * emaxs[0] + p.normal[1] * emins[1] + p.normal[2] * emins[2];
+                dist2 = p.normal[0] * emins[0] + p.normal[1] * emaxs[1] + p.normal[2] * emaxs[2];
+                break;
+            case 7:
+                dist1 = p.normal[0] * emins[0] + p.normal[1] * emins[1] + p.normal[2] * emins[2];
+                dist2 = p.normal[0] * emaxs[0] + p.normal[1] * emaxs[1] + p.normal[2] * emaxs[2];
+                break;
+            default:
+                dist1 = dist2 = 0;
+                assert (false) : "BoxOnPlaneSide bug";
+                break;
+        }
+
+        sides = 0;
+        if (dist1 >= p.dist)
+            sides = 1;
+        if (dist2 < p.dist)
+            sides |= 2;
+
+        assert (sides != 0) : "BoxOnPlaneSide(): sides == 0 bug";
+
+        return sides;
+    }
+
+    public static void angleVectors(float[] angles, float[] forward, float[] right, float[] up) {
+
+        float cr = 2.0f * piratio;
+        float angle = angles[Defines.YAW] * (cr);
+        float sy = (float) Math.sin(angle);
+        float cy = (float) Math.cos(angle);
+        angle = angles[Defines.PITCH] * (cr);
+        float sp = (float) Math.sin(angle);
+        float cp = (float) Math.cos(angle);
+
+        if (forward != null) {
+            forward[0] = cp * cy;
+            forward[1] = cp * sy;
+            forward[2] = -sp;
+        }
+
+        if (right != null || up != null) {
+            angle = angles[Defines.ROLL] * (cr);
+            float sr = (float) Math.sin(angle);
+            cr = (float) Math.cos(angle);
+
+            if (right != null) {
+                right[0] = (-sr * sp * cy + cr * sy);
+                right[1] = (-sr * sp * sy + -cr * cy);
+                right[2] = -sr * cp;
+            }
+            if (up != null) {
+                up[0] = (cr * sp * cy + sr * sy);
+                up[1] = (cr * sp * sy + -sr * cy);
+                up[2] = cr * cp;
+            }
+        }
+    }
+
+    public static float dotProduct(float[] x, float[] y) {
+        return x[0] * y[0] + x[1] * y[1] + x[2] * y[2];
+    }
+
+    public static void crossProduct(float[] v1, float[] v2, float[] cross) {
+        cross[0] = v1[1] * v2[2] - v1[2] * v2[1];
+        cross[1] = v1[2] * v2[0] - v1[0] * v2[2];
+        cross[2] = v1[0] * v2[1] - v1[1] * v2[0];
+    }
+
+    public static float DEG2RAD(float in) {
+        return (in * (float) Math.PI) / 180.0f;
+    }
+
+    public static float angleMod(float a) {
+        return shortratio * ((int) (a / (shortratio)) & 65535);
+    }
+
+    public static int angle2Short(float x) {
+        return ((int) ((x) / shortratio) & 65535);
+    }
+
+    public static float lerpAngle(float a2, float a1, float frac) {
+        if (a1 - a2 > 180)
+            a1 -= 360;
+        if (a1 - a2 < -180)
+            a1 += 360;
+        return a2 + frac * (a1 - a2);
+    }
+
+    public static float calcFov(float fov_x, float width, float height) {
+        double a = 0.0f;
+        double x;
+
+        if (fov_x < 1.0f || fov_x > 179.0f)
+            Com.Error(Defines.ERR_DROP, "Bad fov: " + fov_x);
+
+        x = width / Math.tan(fov_x * piratio);
+
+        a = Math.atan(height / x);
+
+        a = a / piratio;
+
+        return (float) a;
+    }
+}
diff --git a/src/main/java/lwjake2/util/PrintfFormat.java b/src/main/java/lwjake2/util/PrintfFormat.java
new file mode 100644 (file)
index 0000000..c38a0d7
--- /dev/null
@@ -0,0 +1,3153 @@
+// (c) 2000 Sun Microsystems, Inc.
+// ALL RIGHTS RESERVED
+// 
+// License Grant-
+// 
+// Permission to use, copy, modify, and distribute this Software and its 
+// documentation for NON-COMMERCIAL or COMMERCIAL purposes and without fee is 
+// hereby granted.  
+// 
+// This Software is provided "AS IS".  All express warranties, including any 
+// implied warranty of merchantability, satisfactory quality, fitness for a 
+// particular purpose, or non-infringement, are disclaimed, except to the extent 
+// that such disclaimers are held to be legally invalid.
+// 
+// You acknowledge that Software is not designed, licensed or intended for use in 
+// the design, construction, operation or maintenance of any nuclear facility 
+// ("High Risk Activities").  Sun disclaims any express or implied warranty of 
+// fitness for such uses.  
+//
+// Please refer to the file http://www.sun.com/policies/trademarks/ for further 
+// important trademark information and to 
+// http://java.sun.com/nav/business/index.html for further important licensing 
+// information for the Java Technology.
+
+package lwjake2.util;
+
+import java.text.DecimalFormatSymbols;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.Vector;
+
+/**
+ * PrintfFormat allows the formatting of an array of
+ * objects embedded within a string.  Primitive types
+ * must be passed using wrapper types.  The formatting
+ * is controlled by a control string.
+ * <p>
+ * A control string is a Java string that contains a
+ * control specification.  The control specification
+ * starts at the first percent sign (%) in the string,
+ * provided that this percent sign
+ * <ol>
+ * <li>is not escaped protected by a matching % or is
+ * not an escape % character,
+ * <li>is not at the end of the format string, and
+ * <li>precedes a sequence of characters that parses as
+ * a valid control specification.
+ * </ol>
+ * </p><p>
+ * A control specification usually takes the form:
+ * <pre> % ['-+ #0]* [0..9]* { . [0..9]* }+
+ *                { [hlL] }+ [idfgGoxXeEcs]
+ * </pre>
+ * There are variants of this basic form that are
+ * discussed below.</p>
+ * <p>
+ * The format is composed of zero or more directives
+ * defined as follows:
+ * <ul>
+ * <li>ordinary characters, which are simply copied to
+ * the output stream;
+ * <li>escape sequences, which represent non-graphic
+ * characters; and
+ * <li>conversion specifications,  each of which
+ * results in the fetching of zero or more arguments.
+ * </ul></p>
+ * <p>
+ * The results are undefined if there are insufficient
+ * arguments for the format.  Usually an unchecked
+ * exception will be thrown.  If the format is
+ * exhausted while arguments remain, the excess
+ * arguments are evaluated but are otherwise ignored.
+ * In format strings containing the % form of
+ * conversion specifications, each argument in the
+ * argument list is used exactly once.</p>
+ * <p>
+ * Conversions can be applied to the <code>n</code>th
+ * argument after the format in the argument list,
+ * rather than to the next unused argument.  In this
+ * case, the conversion characer % is replaced by the
+ * sequence %<code>n</code>$, where <code>n</code> is
+ * a decimal integer giving the position of the
+ * argument in the argument list.</p>
+ * <p>
+ * In format strings containing the %<code>n</code>$
+ * form of conversion specifications, each argument
+ * in the argument list is used exactly once.</p>
+ * <p>
+ * <h4>Escape Sequences</h4>
+ * <p>
+ * The following table lists escape sequences and
+ * associated actions on display devices capable of
+ * the action.
+ * <table>
+ * <tr><th align=left>Sequence</th>
+ * <th align=left>Name</th>
+ * <th align=left>Description</th></tr>
+ * <tr><td>\\</td><td>backlash</td><td>None.
+ * </td></tr>
+ * <tr><td>\a</td><td>alert</td><td>Attempts to alert
+ * the user through audible or visible
+ * notification.
+ * </td></tr>
+ * <tr><td>\b</td><td>backspace</td><td>Moves the
+ * printing position to one column before
+ * the current position, unless the
+ * current position is the start of a line.
+ * </td></tr>
+ * <tr><td>\f</td><td>form-feed</td><td>Moves the
+ * printing position to the initial
+ * printing position of the next logical
+ * page.
+ * </td></tr>
+ * <tr><td>\n</td><td>newline</td><td>Moves the
+ * printing position to the start of the
+ * next line.
+ * </td></tr>
+ * <tr><td>\r</td><td>carriage-return</td><td>Moves
+ * the printing position to the start of
+ * the current line.
+ * </td></tr>
+ * <tr><td>\t</td><td>tab</td><td>Moves the printing
+ * position to the next implementation-
+ * defined horizontal tab position.
+ * </td></tr>
+ * <tr><td>\v</td><td>vertical-tab</td><td>Moves the
+ * printing position to the start of the
+ * next implementation-defined vertical
+ * tab position.
+ * </td></tr>
+ * </table></p>
+ * <h4>Conversion Specifications</h4>
+ * <p>
+ * Each conversion specification is introduced by
+ * the percent sign character (%).  After the character
+ * %, the following appear in sequence:</p>
+ * <p>
+ * Zero or more flags (in any order), which modify the
+ * meaning of the conversion specification.</p>
+ * <p>
+ * An optional minimum field width.  If the converted
+ * value has fewer characters than the field width, it
+ * will be padded with spaces by default on the left;
+ * t will be padded on the right, if the left-
+ * adjustment flag (-), described below, is given to
+ * the field width.  The field width takes the form
+ * of a decimal integer.  If the conversion character
+ * is s, the field width is the the minimum number of
+ * characters to be printed.</p>
+ * <p>
+ * An optional precision that gives the minumum number
+ * of digits to appear for the d, i, o, x or X
+ * conversions (the field is padded with leading
+ * zeros); the number of digits to appear after the
+ * radix character for the e, E, and f conversions,
+ * the maximum number of significant digits for the g
+ * and G conversions; or the maximum number of
+ * characters to be written from a string is s and S
+ * conversions.  The precision takes the form of an
+ * optional decimal digit string, where a null digit
+ * string is treated as 0.  If a precision appears
+ * with a c conversion character the precision is
+ * ignored.
+ * </p>
+ * <p>
+ * An optional h specifies that a following d, i, o,
+ * x, or X conversion character applies to a type
+ * short argument (the argument will be promoted
+ * according to the integral promotions and its value
+ * converted to type short before printing).</p>
+ * <p>
+ * An optional l (ell) specifies that a following
+ * d, i, o, x, or X conversion character applies to a
+ * type long argument.</p>
+ * <p>
+ * A field width or precision may be indicated by an
+ * asterisk (*) instead of a digit string.  In this
+ * case, an integer argument supplised the field width
+ * precision.  The argument that is actually converted
+ * is not fetched until the conversion letter is seen,
+ * so the the arguments specifying field width or
+ * precision must appear before the argument (if any)
+ * to be converted.  If the precision argument is
+ * negative, it will be changed to zero.  A negative
+ * field width argument is taken as a - flag, followed
+ * by a positive field width.</p>
+ * <p>
+ * In format strings containing the %<code>n</code>$
+ * form of a conversion specification, a field width
+ * or precision may be indicated by the sequence
+ * *<code>m</code>$, where m is a decimal integer
+ * giving the position in the argument list (after the
+ * format argument) of an integer argument containing
+ * the field width or precision.</p>
+ * <p>
+ * The format can contain either numbered argument
+ * specifications (that is, %<code>n</code>$ and
+ * *<code>m</code>$), or unnumbered argument
+ * specifications (that is % and *), but normally not
+ * both.  The only exception to this is that %% can
+ * be mixed with the %<code>n</code>$ form.  The
+ * results of mixing numbered and unnumbered argument
+ * specifications in a format string are undefined.</p>
+ * <p>
+ * <h4>Flag Characters</h4>
+ * <p>
+ * The flags and their meanings are:</p>
+ * <dl>
+ * <dt>'<dd> integer portion of the result of a
+ * decimal conversion (%i, %d, %f, %g, or %G) will
+ * be formatted with thousands' grouping
+ * characters.  For other conversions the flag
+ * is ignored.  The non-monetary grouping
+ * character is used.
+ * <dt>-<dd> result of the conversion is left-justified
+ * within the field.  (It will be right-justified
+ * if this flag is not specified).</td></tr>
+ * <dt>+<dd> result of a signed conversion always
+ * begins with a sign (+ or -).  (It will begin
+ * with a sign only when a negative value is
+ * converted if this flag is not specified.)
+ * <dt>&lt;space&gt;<dd> If the first character of a
+ * signed conversion is not a sign, a space
+ * character will be placed before the result.
+ * This means that if the space character and +
+ * flags both appear, the space flag will be
+ * ignored.
+ * <dt>#<dd> value is to be converted to an alternative
+ * form.  For c, d, i, and s conversions, the flag
+ * has no effect.  For o conversion, it increases
+ * the precision to force the first digit of the
+ * result to be a zero.  For x or X conversion, a
+ * non-zero result has 0x or 0X prefixed to it,
+ * respectively.  For e, E, f, g, and G
+ * conversions, the result always contains a radix
+ * character, even if no digits follow the radix
+ * character (normally, a decimal point appears in
+ * the result of these conversions only if a digit
+ * follows it).  For g and G conversions, trailing
+ * zeros will not be removed from the result as
+ * they normally are.
+ * <dt>0<dd> d, i, o, x, X, e, E, f, g, and G
+ * conversions, leading zeros (following any
+ * indication of sign or base) are used to pad to
+ * the field width;  no space padding is
+ * performed.  If the 0 and - flags both appear,
+ * the 0 flag is ignored.  For d, i, o, x, and X
+ * conversions, if a precision is specified, the
+ * 0 flag will be ignored. For c conversions,
+ * the flag is ignored.
+ * </dl>
+ * <p>
+ * <h4>Conversion Characters</h4>
+ * <p>
+ * Each conversion character results in fetching zero
+ * or more arguments.  The results are undefined if
+ * there are insufficient arguments for the format.
+ * Usually, an unchecked exception will be thrown.
+ * If the format is exhausted while arguments remain,
+ * the excess arguments are ignored.</p>
+ * <p>
+ * <p>
+ * The conversion characters and their meanings are:
+ * </p>
+ * <dl>
+ * <dt>d,i<dd>The int argument is converted to a
+ * signed decimal in the style [-]dddd.  The
+ * precision specifies the minimum number of
+ * digits to appear;  if the value being
+ * converted can be represented in fewer
+ * digits, it will be expanded with leading
+ * zeros.  The default precision is 1.  The
+ * result of converting 0 with an explicit
+ * precision of 0 is no characters.
+ * <dt>o<dd> The int argument is converted to unsigned
+ * octal format in the style ddddd.  The
+ * precision specifies the minimum number of
+ * digits to appear;  if the value being
+ * converted can be represented in fewer
+ * digits, it will be expanded with leading
+ * zeros.  The default precision is 1.  The
+ * result of converting 0 with an explicit
+ * precision of 0 is no characters.
+ * <dt>x<dd> The int argument is converted to unsigned
+ * hexadecimal format in the style dddd;  the
+ * letters abcdef are used.  The precision
+ * specifies the minimum numberof digits to
+ * appear; if the value being converted can be
+ * represented in fewer digits, it will be
+ * expanded with leading zeros.  The default
+ * precision is 1.  The result of converting 0
+ * with an explicit precision of 0 is no
+ * characters.
+ * <dt>X<dd> Behaves the same as the x conversion
+ * character except that letters ABCDEF are
+ * used instead of abcdef.
+ * <dt>f<dd> The floating point number argument is
+ * written in decimal notation in the style
+ * [-]ddd.ddd, where the number of digits after
+ * the radix character (shown here as a decimal
+ * point) is equal to the precision
+ * specification.  A Locale is used to determine
+ * the radix character to use in this format.
+ * If the precision is omitted from the
+ * argument, six digits are written after the
+ * radix character;  if the precision is
+ * explicitly 0 and the # flag is not specified,
+ * no radix character appears.  If a radix
+ * character appears, at least 1 digit appears
+ * before it.  The value is rounded to the
+ * appropriate number of digits.
+ * <dt>e,E<dd>The floating point number argument is
+ * written in the style [-]d.ddde{+-}dd
+ * (the symbols {+-} indicate either a plus or
+ * minus sign), where there is one digit before
+ * the radix character (shown here as a decimal
+ * point) and the number of digits after it is
+ * equal to the precision.  A Locale is used to
+ * determine the radix character to use in this
+ * format.  When the precision is missing, six
+ * digits are written after the radix character;
+ * if the precision is 0 and the # flag is not
+ * specified, no radix character appears.  The
+ * E conversion will produce a number with E
+ * instead of e introducing the exponent.  The
+ * exponent always contains at least two digits.
+ * However, if the value to be written requires
+ * an exponent greater than two digits,
+ * additional exponent digits are written as
+ * necessary.  The value is rounded to the
+ * appropriate number of digits.
+ * <dt>g,G<dd>The floating point number argument is
+ * written in style f or e (or in sytle E in the
+ * case of a G conversion character), with the
+ * precision specifying the number of
+ * significant digits.  If the precision is
+ * zero, it is taken as one.  The style used
+ * depends on the value converted:  style e
+ * (or E) will be used only if the exponent
+ * resulting from the conversion is less than
+ * -4 or greater than or equal to the precision.
+ * Trailing zeros are removed from the result.
+ * A radix character appears only if it is
+ * followed by a digit.
+ * <dt>c,C<dd>The integer argument is converted to a
+ * char and the result is written.
+ * <p>
+ * <dt>s,S<dd>The argument is taken to be a string and
+ * bytes from the string are written until the
+ * end of the string or the number of bytes
+ * indicated by the precision specification of
+ * the argument is reached.  If the precision
+ * is omitted from the argument, it is taken to
+ * be infinite, so all characters up to the end
+ * of the string are written.
+ * <dt>%<dd>Write a % character;  no argument is
+ * converted.
+ * </dl>
+ * <p>
+ * If a conversion specification does not match one of
+ * the above forms, an IllegalArgumentException is
+ * thrown and the instance of PrintfFormat is not
+ * created.</p>
+ * <p>
+ * If a floating point value is the internal
+ * representation for infinity, the output is
+ * [+]Infinity, where Infinity is either Infinity or
+ * Inf, depending on the desired output string length.
+ * Printing of the sign follows the rules described
+ * above.</p>
+ * <p>
+ * If a floating point value is the internal
+ * representation for "not-a-number," the output is
+ * [+]NaN.  Printing of the sign follows the rules
+ * described above.</p>
+ * <p>
+ * In no case does a non-existent or small field width
+ * cause truncation of a field;  if the result of a
+ * conversion is wider than the field width, the field
+ * is simply expanded to contain the conversion result.
+ * </p>
+ * <p>
+ * The behavior is like printf.  One exception is that
+ * the minimum number of exponent digits is 3 instead
+ * of 2 for e and E formats when the optional L is used
+ * before the e, E, g, or G conversion character.  The
+ * optional L does not imply conversion to a long long
+ * double. </p>
+ * <p>
+ * The biggest divergence from the C printf
+ * specification is in the use of 16 bit characters.
+ * This allows the handling of characters beyond the
+ * small ASCII character set and allows the utility to
+ * interoperate correctly with the rest of the Java
+ * runtime environment.</p>
+ * <p>
+ * Omissions from the C printf specification are
+ * numerous.  All the known omissions are present
+ * because Java never uses bytes to represent
+ * characters and does not have pointers:</p>
+ * <ul>
+ * <li>%c is the same as %C.
+ * <li>%s is the same as %S.
+ * <li>u, p, and n conversion characters.
+ * <li>%ws format.
+ * <li>h modifier applied to an n conversion character.
+ * <li>l (ell) modifier applied to the c, n, or s
+ * conversion characters.
+ * <li>ll (ell ell) modifier to d, i, o, u, x, or X
+ * conversion characters.
+ * <li>ll (ell ell) modifier to an n conversion
+ * character.
+ * <li>c, C, d,i,o,u,x, and X conversion characters
+ * apply to Byte, Character, Short, Integer, Long
+ * types.
+ * <li>f, e, E, g, and G conversion characters apply
+ * to Float and Double types.
+ * <li>s and S conversion characters apply to String
+ * types.
+ * <li>All other reference types can be formatted
+ * using the s or S conversion characters only.
+ * </ul>
+ * <p>
+ * Most of this specification is quoted from the Unix
+ * man page for the sprintf utility.</p>
+ *
+ * @author Allan Jacobs
+ * @version 1
+ * Release 1: Initial release.
+ * Release 2: Asterisk field widths and precisions
+ * %n$ and *m$
+ * Bug fixes
+ * g format fix (2 digits in e form corrupt)
+ * rounding in f format implemented
+ * round up when digit not printed is 5
+ * formatting of -0.0f
+ * round up/down when last digits are 50000...
+ */
+public class PrintfFormat {
+    /**
+     * Vector of control strings and format literals.
+     */
+    private final Vector<Object> vFmt = new Vector<>();
+    /**
+     * Character position.  Used by the constructor.
+     */
+    private int cPos = 0;
+    /**
+     * Character position.  Used by the constructor.
+     */
+    private DecimalFormatSymbols dfs = null;
+
+    /**
+     * Constructs an array of control specifications
+     * possibly preceded, separated, or followed by
+     * ordinary strings.  Control strings begin with
+     * unpaired percent signs.  A pair of successive
+     * percent signs designates a single percent sign in
+     * the format.
+     *
+     * @param fmtArg Control string.
+     * @throws IllegalArgumentException if the control
+     *                                  string is null, zero length, or otherwise
+     *                                  malformed.
+     */
+    public PrintfFormat(String fmtArg) throws IllegalArgumentException {
+        this(Locale.getDefault(), fmtArg);
+    }
+
+    /**
+     * Constructs an array of control specifications
+     * possibly preceded, separated, or followed by
+     * ordinary strings.  Control strings begin with
+     * unpaired percent signs.  A pair of successive
+     * percent signs designates a single percent sign in
+     * the format.
+     *
+     * @param fmtArg Control string.
+     * @throws IllegalArgumentException if the control
+     *                                  string is null, zero length, or otherwise
+     *                                  malformed.
+     */
+    public PrintfFormat(Locale locale, String fmtArg) throws IllegalArgumentException {
+        dfs = new DecimalFormatSymbols(locale);
+        int ePos = 0;
+        ConversionSpecification sFmt = null;
+        String unCS = this.nonControl(fmtArg, 0);
+        if (unCS != null) {
+            sFmt = new ConversionSpecification();
+            sFmt.setLiteral(unCS);
+            vFmt.addElement(sFmt);
+        }
+        while (cPos != -1 && cPos < fmtArg.length()) {
+            for (ePos = cPos + 1; ePos < fmtArg.length(); ePos++) {
+                char c = 0;
+                c = fmtArg.charAt(ePos);
+                if (c == 'i')
+                    break;
+                if (c == 'd')
+                    break;
+                if (c == 'f')
+                    break;
+                if (c == 'g')
+                    break;
+                if (c == 'G')
+                    break;
+                if (c == 'o')
+                    break;
+                if (c == 'x')
+                    break;
+                if (c == 'X')
+                    break;
+                if (c == 'e')
+                    break;
+                if (c == 'E')
+                    break;
+                if (c == 'c')
+                    break;
+                if (c == 's')
+                    break;
+                if (c == '%')
+                    break;
+            }
+            ePos = Math.min(ePos + 1, fmtArg.length());
+            sFmt = new ConversionSpecification(fmtArg.substring(cPos, ePos));
+            vFmt.addElement(sFmt);
+            unCS = this.nonControl(fmtArg, ePos);
+            if (unCS != null) {
+                sFmt = new ConversionSpecification();
+                sFmt.setLiteral(unCS);
+                vFmt.addElement(sFmt);
+            }
+        }
+    }
+
+    /**
+     * Return a substring starting at
+     * <code>start</code> and ending at either the end
+     * of the String <code>s</code>, the next unpaired
+     * percent sign, or at the end of the String if the
+     * last character is a percent sign.
+     *
+     * @param s     Control string.
+     * @param start Position in the string
+     *              <code>s</code> to begin looking for the start
+     *              of a control string.
+     * @return the substring from the start position
+     * to the beginning of the control string.
+     */
+    private String nonControl(String s, int start) {
+        // String ret = "";
+        cPos = s.indexOf("%", start);
+        if (cPos == -1)
+            cPos = s.length();
+        return s.substring(start, cPos);
+    }
+
+    /**
+     * Format an array of objects.  Byte, Short,
+     * Integer, Long, Float, Double, and Character
+     * arguments are treated as wrappers for primitive
+     * types.
+     *
+     * @param o The array of objects to format.
+     * @return The formatted String.
+     */
+    public String sprintf(Object[] o) {
+        Enumeration<Object> e = vFmt.elements();
+        ConversionSpecification cs = null;
+        char c = 0;
+        int i = 0;
+        StringBuilder sb = new StringBuilder();
+        while (e.hasMoreElements()) {
+            cs = (ConversionSpecification) e.nextElement();
+            c = cs.getConversionCharacter();
+            if (c == '\0')
+                sb.append(cs.getLiteral());
+            else if (c == '%')
+                sb.append("%");
+            else {
+                if (cs.isPositionalSpecification()) {
+                    i = cs.getArgumentPosition() - 1;
+                    if (cs.isPositionalFieldWidth()) {
+                        int ifw = cs.getArgumentPositionForFieldWidth() - 1;
+                        cs.setFieldWidthWithArg((Integer) o[ifw]);
+                    }
+                    if (cs.isPositionalPrecision()) {
+                        int ipr = cs.getArgumentPositionForPrecision() - 1;
+                        cs.setPrecisionWithArg((Integer) o[ipr]);
+                    }
+                } else {
+                    if (cs.isVariableFieldWidth()) {
+                        cs.setFieldWidthWithArg((Integer) o[i]);
+                        i++;
+                    }
+                    if (cs.isVariablePrecision()) {
+                        cs.setPrecisionWithArg((Integer) o[i]);
+                        i++;
+                    }
+                }
+                if (o[i] instanceof Byte)
+                    sb.append(cs.internalsprintf(((Byte) o[i]).byteValue()));
+                else if (o[i] instanceof Short)
+                    sb.append(cs.internalsprintf(((Short) o[i]).shortValue()));
+                else if (o[i] instanceof Integer)
+                    sb.append(cs.internalsprintf(((Integer) o[i]).intValue()));
+                else if (o[i] instanceof Long)
+                    sb.append(cs.internalsprintf(((Long) o[i]).longValue()));
+                else if (o[i] instanceof Float)
+                    sb.append(cs.internalsprintf(((Float) o[i]).floatValue()));
+                else if (o[i] instanceof Double)
+                    sb.append(cs.internalsprintf(((Double) o[i]).doubleValue()));
+                else if (o[i] instanceof Character)
+                    sb.append(cs.internalsprintf(((Character) o[i]).charValue()));
+                else if (o[i] instanceof String)
+                    sb.append(cs.internalsprintf((String) o[i]));
+                else
+                    sb.append(cs.internalsprintf(o[i]));
+                if (!cs.isPositionalSpecification())
+                    i++;
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * <p>
+     * ConversionSpecification allows the formatting of
+     * a single primitive or object embedded within a
+     * string.  The formatting is controlled by a
+     * format string.  Only one Java primitive or
+     * object can be formatted at a time.
+     * <p>
+     * A format string is a Java string that contains
+     * a control string.  The control string starts at
+     * the first percent sign (%) in the string,
+     * provided that this percent sign
+     * <ol>
+     * <li>is not escaped protected by a matching % or
+     * is not an escape % character,
+     * <li>is not at the end of the format string, and
+     * <li>precedes a sequence of characters that parses
+     * as a valid control string.
+     * </ol>
+     * <p>
+     * A control string takes the form:
+     * <pre> % ['-+ #0]* [0..9]* { . [0..9]* }+
+     *                { [hlL] }+ [idfgGoxXeEcs]
+     * </pre>
+     * <p>
+     * The behavior is like printf.  One (hopefully the
+     * only) exception is that the minimum number of
+     * exponent digits is 3 instead of 2 for e and E
+     * formats when the optional L is used before the
+     * e, E, g, or G conversion character.  The
+     * optional L does not imply conversion to a long
+     * long double.
+     */
+    private class ConversionSpecification {
+        /**
+         * Default precision.
+         */
+        private final static int defaultDigits = 6;
+        /**
+         * The integer portion of the result of a decimal
+         * conversion (i, d, u, f, g, or G) will be
+         * formatted with thousands' grouping characters.
+         * For other conversions the flag is ignored.
+         */
+        private boolean thousands = false;
+        /**
+         * The result of the conversion will be
+         * left-justified within the field.
+         */
+        private boolean leftJustify = false;
+        /**
+         * The result of a signed conversion will always
+         * begin with a sign (+ or -).
+         */
+        private boolean leadingSign = false;
+        /**
+         * Flag indicating that left padding with spaces is
+         * specified.
+         */
+        private boolean leadingSpace = false;
+        /**
+         * For an o conversion, increase the precision to
+         * force the first digit of the result to be a
+         * zero.  For x (or X) conversions, a non-zero
+         * result will have 0x (or 0X) prepended to it.
+         * For e, E, f, g, or G conversions, the result
+         * will always contain a radix character, even if
+         * no digits follow the point.  For g and G
+         * conversions, trailing zeros will not be removed
+         * from the result.
+         */
+        private boolean alternateForm = false;
+        /**
+         * Flag indicating that left padding with zeroes is
+         * specified.
+         */
+        private boolean leadingZeros = false;
+        /**
+         * Flag indicating that the field width is *.
+         */
+        private boolean variableFieldWidth = false;
+        /**
+         * If the converted value has fewer bytes than the
+         * field width, it will be padded with spaces or
+         * zeroes.
+         */
+        private int fieldWidth = 0;
+        /**
+         * Flag indicating whether or not the field width
+         * has been set.
+         */
+        private boolean fieldWidthSet = false;
+        /**
+         * The minimum number of digits to appear for the
+         * d, i, o, u, x, or X conversions.  The number of
+         * digits to appear after the radix character for
+         * the e, E, and f conversions.  The maximum number
+         * of significant digits for the g and G
+         * conversions.  The maximum number of bytes to be
+         * printed from a string in s and S conversions.
+         */
+        private int precision = 0;
+        /**
+         * Flag indicating that the precision is *.
+         */
+        private boolean variablePrecision = false;
+        /**
+         * Flag indicating whether or not the precision has
+         * been set.
+         */
+        private boolean precisionSet = false;
+        /*
+         */
+        private boolean positionalSpecification = false;
+        private int argumentPosition = 0;
+        private boolean positionalFieldWidth = false;
+        private int argumentPositionForFieldWidth = 0;
+        private boolean positionalPrecision = false;
+        private int argumentPositionForPrecision = 0;
+        /**
+         * Flag specifying that a following d, i, o, u, x,
+         * or X conversion character applies to a type
+         * short int.
+         */
+        private boolean optionalh = false;
+        /**
+         * Flag specifying that a following d, i, o, u, x,
+         * or X conversion character applies to a type lont
+         * int argument.
+         */
+        private boolean optionall = false;
+        /**
+         * Flag specifying that a following e, E, f, g, or
+         * G conversion character applies to a type double
+         * argument.  This is a noop in Java.
+         */
+        private boolean optionalL = false;
+        /**
+         * Control string type.
+         */
+        private char conversionCharacter = '\0';
+        /**
+         * Position within the control string.  Used by
+         * the constructor.
+         */
+        private int pos = 0;
+        /**
+         * Literal or control format string.
+         */
+        private String fmt;
+
+        /**
+         * Constructor.  Used to prepare an instance
+         * to hold a literal, not a control string.
+         */
+        ConversionSpecification() {
+        }
+
+        /**
+         * Constructor for a conversion specification.
+         * The argument must begin with a % and end
+         * with the conversion character for the
+         * conversion specification.
+         *
+         * @param fmtArg String specifying the
+         *               conversion specification.
+         * @throws IllegalArgumentException if the
+         *                                  input string is null, zero length, or
+         *                                  otherwise malformed.
+         */
+        ConversionSpecification(String fmtArg) throws IllegalArgumentException {
+            if (fmtArg == null)
+                throw new NullPointerException();
+            if (fmtArg.length() == 0)
+                throw new IllegalArgumentException("Control strings must have positive" + " lengths.");
+            if (fmtArg.charAt(0) == '%') {
+                fmt = fmtArg;
+                pos = 1;
+                setArgPosition();
+                setFlagCharacters();
+                setFieldWidth();
+                setPrecision();
+                setOptionalHL();
+                if (setConversionCharacter()) {
+                    if (pos == fmtArg.length()) {
+                        if (leadingZeros && leftJustify)
+                            leadingZeros = false;
+                        if (precisionSet && leadingZeros) {
+                            if (conversionCharacter == 'd'
+                                    || conversionCharacter == 'i'
+                                    || conversionCharacter == 'o'
+                                    || conversionCharacter == 'x') {
+                                leadingZeros = false;
+                            }
+                        }
+                    } else
+                        throw new IllegalArgumentException("Malformed conversion specification=" + fmtArg);
+                } else
+                    throw new IllegalArgumentException("Malformed conversion specification=" + fmtArg);
+            } else
+                throw new IllegalArgumentException("Control strings must begin with %.");
+        }
+
+        /**
+         * Get the String for this instance.  Translate
+         * any escape sequences.
+         *
+         * @return s the stored String.
+         */
+        String getLiteral() {
+            StringBuilder sb = new StringBuilder();
+            int i = 0;
+            while (i < fmt.length()) {
+                if (fmt.charAt(i) == '\\') {
+                    i++;
+                    if (i < fmt.length()) {
+                        char c = fmt.charAt(i);
+                        switch (c) {
+                            case 'a':
+                                sb.append((char) 0x07);
+                                break;
+                            case 'b':
+                                sb.append('\b');
+                                break;
+                            case 'f':
+                                sb.append('\f');
+                                break;
+                            case 'n':
+                                sb.append(System.getProperty("line.separator"));
+                                break;
+                            case 'r':
+                                sb.append('\r');
+                                break;
+                            case 't':
+                                sb.append('\t');
+                                break;
+                            case 'v':
+                                sb.append((char) 0x0b);
+                                break;
+                            case '\\':
+                                sb.append('\\');
+                                break;
+                        }
+                        i++;
+                    } else
+                        sb.append('\\');
+                } else
+                    i++;
+            }
+            return fmt;
+        }
+
+        /**
+         * Set the String for this instance.
+         *
+         * @param s the String to store.
+         */
+        void setLiteral(String s) {
+            fmt = s;
+        }
+
+        /**
+         * Get the conversion character that tells what
+         * type of control character this instance has.
+         *
+         * @return the conversion character.
+         */
+        char getConversionCharacter() {
+            return conversionCharacter;
+        }
+
+        /**
+         * Check whether the specifier has a variable
+         * field width that is going to be set by an
+         * argument.
+         *
+         * @return <code>true</code> if the conversion
+         * uses an * field width; otherwise
+         * <code>false</code>.
+         */
+        boolean isVariableFieldWidth() {
+            return variableFieldWidth;
+        }
+
+        /**
+         * Set the field width with an argument.  A
+         * negative field width is taken as a - flag
+         * followed by a positive field width.
+         *
+         * @param fw the field width.
+         */
+        void setFieldWidthWithArg(int fw) {
+            if (fw < 0)
+                leftJustify = true;
+            fieldWidthSet = true;
+            fieldWidth = Math.abs(fw);
+        }
+
+        /**
+         * Check whether the specifier has a variable
+         * precision that is going to be set by an
+         * argument.
+         *
+         * @return <code>true</code> if the conversion
+         * uses an * precision; otherwise
+         * <code>false</code>.
+         */
+        boolean isVariablePrecision() {
+            return variablePrecision;
+        }
+
+        /**
+         * Set the precision with an argument.  A
+         * negative precision will be changed to zero.
+         *
+         * @param pr the precision.
+         */
+        void setPrecisionWithArg(int pr) {
+            precisionSet = true;
+            precision = Math.max(pr, 0);
+        }
+
+        /**
+         * Format an int argument using this conversion
+         * specification.
+         *
+         * @param s the int to format.
+         * @return the formatted String.
+         * @throws IllegalArgumentException if the
+         *                                  conversion character is f, e, E, g, or G.
+         */
+        String internalsprintf(int s) throws IllegalArgumentException {
+            String s2 = "";
+            switch (conversionCharacter) {
+                case 'd':
+                case 'i':
+                    if (optionalh)
+                        s2 = printDFormat((short) s);
+                    else if (optionall)
+                        s2 = printDFormat((long) s);
+                    else
+                        s2 = printDFormat(s);
+                    break;
+                case 'x':
+                case 'X':
+                    if (optionalh)
+                        s2 = printXFormat((short) s);
+                    else if (optionall)
+                        s2 = printXFormat((long) s);
+                    else
+                        s2 = printXFormat(s);
+                    break;
+                case 'o':
+                    if (optionalh)
+                        s2 = printOFormat((short) s);
+                    else if (optionall)
+                        s2 = printOFormat((long) s);
+                    else
+                        s2 = printOFormat(s);
+                    break;
+                case 'c':
+                case 'C':
+                    s2 = printCFormat((char) s);
+                    break;
+                default:
+                    throw new IllegalArgumentException(
+                            "Cannot format a int with a format using a " + conversionCharacter + " conversion character.");
+            }
+            return s2;
+        }
+
+        /**
+         * Format a long argument using this conversion
+         * specification.
+         *
+         * @param s the long to format.
+         * @return the formatted String.
+         * @throws IllegalArgumentException if the
+         *                                  conversion character is f, e, E, g, or G.
+         */
+        String internalsprintf(long s) throws IllegalArgumentException {
+            String s2 = "";
+            switch (conversionCharacter) {
+                case 'd':
+                case 'i':
+                    if (optionalh)
+                        s2 = printDFormat((short) s);
+                    else if (optionall)
+                        s2 = printDFormat(s);
+                    else
+                        s2 = printDFormat((int) s);
+                    break;
+                case 'x':
+                case 'X':
+                    if (optionalh)
+                        s2 = printXFormat((short) s);
+                    else if (optionall)
+                        s2 = printXFormat(s);
+                    else
+                        s2 = printXFormat((int) s);
+                    break;
+                case 'o':
+                    if (optionalh)
+                        s2 = printOFormat((short) s);
+                    else if (optionall)
+                        s2 = printOFormat(s);
+                    else
+                        s2 = printOFormat((int) s);
+                    break;
+                case 'c':
+                case 'C':
+                    s2 = printCFormat((char) s);
+                    break;
+                default:
+                    throw new IllegalArgumentException(
+                            "Cannot format a long with a format using a " + conversionCharacter + " conversion character.");
+            }
+            return s2;
+        }
+
+        /**
+         * Format a double argument using this conversion
+         * specification.
+         *
+         * @param s the double to format.
+         * @return the formatted String.
+         * @throws IllegalArgumentException if the
+         *                                  conversion character is c, C, s, S, i, d,
+         *                                  x, X, or o.
+         */
+        String internalsprintf(double s) throws IllegalArgumentException {
+            String s2 = "";
+            switch (conversionCharacter) {
+                case 'f':
+                    s2 = printFFormat(s);
+                    break;
+                case 'E':
+                case 'e':
+                    s2 = printEFormat(s);
+                    break;
+                case 'G':
+                case 'g':
+                    s2 = printGFormat(s);
+                    break;
+                default:
+                    throw new IllegalArgumentException(
+                            "Cannot " + "format a double with a format using a " + conversionCharacter + " conversion character.");
+            }
+            return s2;
+        }
+
+        /**
+         * Format a String argument using this conversion
+         * specification.
+         *
+         * @param s the String to format.
+         * @return the formatted String.
+         * @throws IllegalArgumentException if the
+         *                                  conversion character is neither s nor S.
+         */
+        String internalsprintf(String s) throws IllegalArgumentException {
+            String s2 = "";
+            if (conversionCharacter == 's' || conversionCharacter == 'S')
+                s2 = printSFormat(s);
+            else
+                throw new IllegalArgumentException(
+                        "Cannot " + "format a String with a format using a " + conversionCharacter + " conversion character.");
+            return s2;
+        }
+
+        /**
+         * Format an Object argument using this conversion
+         * specification.
+         *
+         * @param s the Object to format.
+         * @return the formatted String.
+         * @throws IllegalArgumentException if the
+         *                                  conversion character is neither s nor S.
+         */
+        String internalsprintf(Object s) {
+            String s2 = "";
+            if (conversionCharacter == 's' || conversionCharacter == 'S')
+                s2 = printSFormat(s.toString());
+            else
+                throw new IllegalArgumentException(
+                        "Cannot format a String with a format using" + " a " + conversionCharacter + " conversion character.");
+            return s2;
+        }
+
+        /**
+         * For f format, the flag character '-', means that
+         * the output should be left justified within the
+         * field.  The default is to pad with blanks on the
+         * left.  '+' character means that the conversion
+         * will always begin with a sign (+ or -).  The
+         * blank flag character means that a non-negative
+         * input will be preceded with a blank.  If both
+         * a '+' and a ' ' are specified, the blank flag
+         * is ignored.  The '0' flag character implies that
+         * padding to the field width will be done with
+         * zeros instead of blanks.
+         * <p>
+         * The field width is treated as the minimum number
+         * of characters to be printed.  The default is to
+         * add no padding.  Padding is with blanks by
+         * default.
+         * <p>
+         * The precision, if set, is the number of digits
+         * to appear after the radix character.  Padding is
+         * with trailing 0s.
+         */
+        private char[] fFormatDigits(double x) {
+            // int defaultDigits=6;
+            String sx; // sxOut;
+            int i, j, k;
+            int n1In, n2In;
+            int expon = 0;
+            boolean minusSign = false;
+            if (x > 0.0)
+                sx = Double.toString(x);
+            else if (x < 0.0) {
+                sx = Double.toString(-x);
+                minusSign = true;
+            } else {
+                sx = Double.toString(x);
+                if (sx.charAt(0) == '-') {
+                    minusSign = true;
+                    sx = sx.substring(1);
+                }
+            }
+            int ePos = sx.indexOf('E');
+            int rPos = sx.indexOf('.');
+            if (rPos != -1)
+                n1In = rPos;
+            else if (ePos != -1)
+                n1In = ePos;
+            else
+                n1In = sx.length();
+            if (rPos != -1) {
+                if (ePos != -1)
+                    n2In = ePos - rPos - 1;
+                else
+                    n2In = sx.length() - rPos - 1;
+            } else
+                n2In = 0;
+            if (ePos != -1) {
+                int ie = ePos + 1;
+                expon = 0;
+                if (sx.charAt(ie) == '-') {
+                    for (++ie; ie < sx.length(); ie++)
+                        if (sx.charAt(ie) != '0')
+                            break;
+                    if (ie < sx.length())
+                        expon = -Integer.parseInt(sx.substring(ie));
+                } else {
+                    if (sx.charAt(ie) == '+')
+                        ++ie;
+                    for (; ie < sx.length(); ie++)
+                        if (sx.charAt(ie) != '0')
+                            break;
+                    if (ie < sx.length())
+                        expon = Integer.parseInt(sx.substring(ie));
+                }
+            }
+            int p;
+            if (precisionSet)
+                p = precision;
+            else
+                p = defaultDigits - 1;
+            char[] ca1 = sx.toCharArray();
+            char[] ca2 = new char[n1In + n2In];
+            char[] ca3, ca4, ca5;
+            for (j = 0; j < n1In; j++)
+                ca2[j] = ca1[j];
+            i = j + 1;
+            for (k = 0; k < n2In; j++, i++, k++)
+                ca2[j] = ca1[i];
+            if (n1In + expon <= 0) {
+                ca3 = new char[-expon + n2In];
+                for (j = 0, k = 0; k < (-n1In - expon); k++, j++)
+                    ca3[j] = '0';
+                for (i = 0; i < (n1In + n2In); i++, j++)
+                    ca3[j] = ca2[i];
+            } else
+                ca3 = ca2;
+            boolean carry = false;
+            if (p < -expon + n2In) {
+                if (expon < 0)
+                    i = p;
+                else
+                    i = p + n1In;
+                carry = checkForCarry(ca3, i);
+                if (carry)
+                    carry = startSymbolicCarry(ca3, i - 1, 0);
+            }
+            if (n1In + expon <= 0) {
+                ca4 = new char[2 + p];
+                if (!carry)
+                    ca4[0] = '0';
+                else
+                    ca4[0] = '1';
+                if (alternateForm || !precisionSet || precision != 0) {
+                    ca4[1] = '.';
+                    for (i = 0, j = 2; i < Math.min(p, ca3.length); i++, j++)
+                        ca4[j] = ca3[i];
+                    for (; j < ca4.length; j++)
+                        ca4[j] = '0';
+                }
+            } else {
+                if (!carry) {
+                    if (alternateForm || !precisionSet || precision != 0)
+                        ca4 = new char[n1In + expon + p + 1];
+                    else
+                        ca4 = new char[n1In + expon];
+                    j = 0;
+                } else {
+                    if (alternateForm || !precisionSet || precision != 0)
+                        ca4 = new char[n1In + expon + p + 2];
+                    else
+                        ca4 = new char[n1In + expon + 1];
+                    ca4[0] = '1';
+                    j = 1;
+                }
+                for (i = 0; i < Math.min(n1In + expon, ca3.length); i++, j++)
+                    ca4[j] = ca3[i];
+                for (; i < n1In + expon; i++, j++)
+                    ca4[j] = '0';
+                if (alternateForm || !precisionSet || precision != 0) {
+                    ca4[j] = '.';
+                    j++;
+                    for (k = 0; i < ca3.length && k < p; i++, j++, k++)
+                        ca4[j] = ca3[i];
+                    for (; j < ca4.length; j++)
+                        ca4[j] = '0';
+                }
+            }
+            int nZeros = 0;
+            if (!leftJustify && leadingZeros) {
+                int xThousands = 0;
+                if (thousands) {
+                    int xlead = 0;
+                    if (ca4[0] == '+' || ca4[0] == '-' || ca4[0] == ' ')
+                        xlead = 1;
+                    int xdp = xlead;
+                    for (; xdp < ca4.length; xdp++)
+                        if (ca4[xdp] == '.')
+                            break;
+                    xThousands = (xdp - xlead) / 3;
+                }
+                if (fieldWidthSet)
+                    nZeros = fieldWidth - ca4.length;
+                if ((!minusSign && (leadingSign || leadingSpace)) || minusSign)
+                    nZeros--;
+                nZeros -= xThousands;
+                if (nZeros < 0)
+                    nZeros = 0;
+            }
+            j = 0;
+            if ((!minusSign && (leadingSign || leadingSpace)) || minusSign) {
+                ca5 = new char[ca4.length + nZeros + 1];
+                j++;
+            } else
+                ca5 = new char[ca4.length + nZeros];
+            if (!minusSign) {
+                if (leadingSign)
+                    ca5[0] = '+';
+                if (leadingSpace)
+                    ca5[0] = ' ';
+            } else
+                ca5[0] = '-';
+            for (i = 0; i < nZeros; i++, j++)
+                ca5[j] = '0';
+            for (i = 0; i < ca4.length; i++, j++)
+                ca5[j] = ca4[i];
+
+            int lead = 0;
+            if (ca5[0] == '+' || ca5[0] == '-' || ca5[0] == ' ')
+                lead = 1;
+            int dp = lead;
+            for (; dp < ca5.length; dp++)
+                if (ca5[dp] == '.')
+                    break;
+            int nThousands = (dp - lead) / 3;
+            // Localize the decimal point.
+            if (dp < ca5.length)
+                ca5[dp] = dfs.getDecimalSeparator();
+            char[] ca6 = ca5;
+            if (thousands && nThousands > 0) {
+                ca6 = new char[ca5.length + nThousands + lead];
+                ca6[0] = ca5[0];
+                for (i = lead, k = lead; i < dp; i++) {
+                    if (i > 0 && (dp - i) % 3 == 0) {
+                        // ca6[k]=',';
+                        ca6[k] = dfs.getGroupingSeparator();
+                        ca6[k + 1] = ca5[i];
+                        k += 2;
+                    } else {
+                        ca6[k] = ca5[i];
+                        k++;
+                    }
+                }
+                for (; i < ca5.length; i++, k++) {
+                    ca6[k] = ca5[i];
+                }
+            }
+            return ca6;
+        }
+
+        /**
+         * An intermediate routine on the way to creating
+         * an f format String.  The method decides whether
+         * the input double value is an infinity,
+         * not-a-number, or a finite double and formats
+         * each type of input appropriately.
+         *
+         * @param x the double value to be formatted.
+         * @return the converted double value.
+         */
+        private String fFormatString(double x) {
+            // boolean noDigits = false;
+            char[] ca6, ca7;
+            if (Double.isInfinite(x)) {
+                if (x == Double.POSITIVE_INFINITY) {
+                    if (leadingSign)
+                        ca6 = "+Inf".toCharArray();
+                    else if (leadingSpace)
+                        ca6 = " Inf".toCharArray();
+                    else
+                        ca6 = "Inf".toCharArray();
+                } else
+                    ca6 = "-Inf".toCharArray();
+                // noDigits = true;
+            } else if (Double.isNaN(x)) {
+                if (leadingSign)
+                    ca6 = "+NaN".toCharArray();
+                else if (leadingSpace)
+                    ca6 = " NaN".toCharArray();
+                else
+                    ca6 = "NaN".toCharArray();
+                // noDigits = true;
+            } else
+                ca6 = fFormatDigits(x);
+            ca7 = applyFloatPadding(ca6);
+            return new String(ca7);
+        }
+
+        /**
+         * For e format, the flag character '-', means that
+         * the output should be left justified within the
+         * field.  The default is to pad with blanks on the
+         * left.  '+' character means that the conversion
+         * will always begin with a sign (+ or -).  The
+         * blank flag character means that a non-negative
+         * input will be preceded with a blank.  If both a
+         * '+' and a ' ' are specified, the blank flag is
+         * ignored.  The '0' flag character implies that
+         * padding to the field width will be done with
+         * zeros instead of blanks.
+         * <p>
+         * The field width is treated as the minimum number
+         * of characters to be printed.  The default is to
+         * add no padding.  Padding is with blanks by
+         * default.
+         * <p>
+         * The precision, if set, is the minimum number of
+         * digits to appear after the radix character.
+         * Padding is with trailing 0s.
+         * <p>
+         * The behavior is like printf.  One (hopefully the
+         * only) exception is that the minimum number of
+         * exponent digits is 3 instead of 2 for e and E
+         * formats when the optional L is used before the
+         * e, E, g, or G conversion character. The optional
+         * L does not imply conversion to a long long
+         * double.
+         */
+        private char[] eFormatDigits(double x, char eChar) {
+            char[] ca1, ca2, ca3;
+            // int defaultDigits=6;
+            String sx; //, sxOut;
+            int i, j, k, p;
+            // int n1In, n2In;
+            int expon = 0;
+            int ePos, rPos, eSize;
+            boolean minusSign = false;
+            if (x > 0.0)
+                sx = Double.toString(x);
+            else if (x < 0.0) {
+                sx = Double.toString(-x);
+                minusSign = true;
+            } else {
+                sx = Double.toString(x);
+                if (sx.charAt(0) == '-') {
+                    minusSign = true;
+                    sx = sx.substring(1);
+                }
+            }
+            ePos = sx.indexOf('E');
+            if (ePos == -1)
+                ePos = sx.indexOf('e');
+            rPos = sx.indexOf('.');
+            /* Not exactly sure what the point is here - flibit
+            if (rPos != -1)
+                               n1In = rPos;
+                       else if (ePos != -1)
+                               n1In = ePos;
+                       else
+                               n1In = sx.length();
+                       if (rPos != -1) {
+                               if (ePos != -1)
+                                       n2In = ePos - rPos - 1;
+                               else
+                                       n2In = sx.length() - rPos - 1;
+                       } else
+                               n2In = 0;
+                       */
+            if (ePos != -1) {
+                int ie = ePos + 1;
+                expon = 0;
+                if (sx.charAt(ie) == '-') {
+                    for (++ie; ie < sx.length(); ie++)
+                        if (sx.charAt(ie) != '0')
+                            break;
+                    if (ie < sx.length())
+                        expon = -Integer.parseInt(sx.substring(ie));
+                } else {
+                    if (sx.charAt(ie) == '+')
+                        ++ie;
+                    for (; ie < sx.length(); ie++)
+                        if (sx.charAt(ie) != '0')
+                            break;
+                    if (ie < sx.length())
+                        expon = Integer.parseInt(sx.substring(ie));
+                }
+            }
+            if (rPos != -1)
+                expon += rPos - 1;
+            if (precisionSet)
+                p = precision;
+            else
+                p = defaultDigits - 1;
+            if (rPos != -1 && ePos != -1)
+                ca1 = (sx.substring(0, rPos) + sx.substring(rPos + 1, ePos)).toCharArray();
+            else if (rPos != -1)
+                ca1 = (sx.substring(0, rPos) + sx.substring(rPos + 1)).toCharArray();
+            else if (ePos != -1)
+                ca1 = sx.substring(0, ePos).toCharArray();
+            else
+                ca1 = sx.toCharArray();
+            boolean carry = false;
+            int i0 = 0;
+            if (ca1[0] != '0')
+                i0 = 0;
+            else
+                for (i0 = 0; i0 < ca1.length; i0++)
+                    if (ca1[i0] != '0')
+                        break;
+            if (i0 + p < ca1.length - 1) {
+                carry = checkForCarry(ca1, i0 + p + 1);
+                if (carry)
+                    carry = startSymbolicCarry(ca1, i0 + p, i0);
+                if (carry) {
+                    ca2 = new char[i0 + p + 1];
+                    ca2[i0] = '1';
+                    for (j = 0; j < i0; j++)
+                        ca2[j] = '0';
+                    for (i = i0, j = i0 + 1; j < p + 1; i++, j++)
+                        ca2[j] = ca1[i];
+                    expon++;
+                    ca1 = ca2;
+                }
+            }
+            if (Math.abs(expon) < 100 && !optionalL)
+                eSize = 4;
+            else
+                eSize = 5;
+            if (alternateForm || !precisionSet || precision != 0)
+                ca2 = new char[2 + p + eSize];
+            else
+                ca2 = new char[1 + eSize];
+            if (ca1[0] != '0') {
+                ca2[0] = ca1[0];
+                j = 1;
+            } else {
+                for (j = 1; j < (ePos == -1 ? ca1.length : ePos); j++)
+                    if (ca1[j] != '0')
+                        break;
+                if ((ePos != -1 && j < ePos) || (ePos == -1 && j < ca1.length)) {
+                    ca2[0] = ca1[j];
+                    expon -= j;
+                    j++;
+                } else {
+                    ca2[0] = '0';
+                    j = 2;
+                }
+            }
+            if (alternateForm || !precisionSet || precision != 0) {
+                ca2[1] = '.';
+                i = 2;
+            } else
+                i = 1;
+            for (k = 0; k < p && j < ca1.length; j++, i++, k++)
+                ca2[i] = ca1[j];
+            for (; i < ca2.length - eSize; i++)
+                ca2[i] = '0';
+            ca2[i++] = eChar;
+            if (expon < 0)
+                ca2[i++] = '-';
+            else
+                ca2[i++] = '+';
+            expon = Math.abs(expon);
+            if (expon >= 100) {
+                switch (expon / 100) {
+                    case 1:
+                        ca2[i] = '1';
+                        break;
+                    case 2:
+                        ca2[i] = '2';
+                        break;
+                    case 3:
+                        ca2[i] = '3';
+                        break;
+                    case 4:
+                        ca2[i] = '4';
+                        break;
+                    case 5:
+                        ca2[i] = '5';
+                        break;
+                    case 6:
+                        ca2[i] = '6';
+                        break;
+                    case 7:
+                        ca2[i] = '7';
+                        break;
+                    case 8:
+                        ca2[i] = '8';
+                        break;
+                    case 9:
+                        ca2[i] = '9';
+                        break;
+                }
+                i++;
+            }
+            switch ((expon % 100) / 10) {
+                case 0:
+                    ca2[i] = '0';
+                    break;
+                case 1:
+                    ca2[i] = '1';
+                    break;
+                case 2:
+                    ca2[i] = '2';
+                    break;
+                case 3:
+                    ca2[i] = '3';
+                    break;
+                case 4:
+                    ca2[i] = '4';
+                    break;
+                case 5:
+                    ca2[i] = '5';
+                    break;
+                case 6:
+                    ca2[i] = '6';
+                    break;
+                case 7:
+                    ca2[i] = '7';
+                    break;
+                case 8:
+                    ca2[i] = '8';
+                    break;
+                case 9:
+                    ca2[i] = '9';
+                    break;
+            }
+            i++;
+            switch (expon % 10) {
+                case 0:
+                    ca2[i] = '0';
+                    break;
+                case 1:
+                    ca2[i] = '1';
+                    break;
+                case 2:
+                    ca2[i] = '2';
+                    break;
+                case 3:
+                    ca2[i] = '3';
+                    break;
+                case 4:
+                    ca2[i] = '4';
+                    break;
+                case 5:
+                    ca2[i] = '5';
+                    break;
+                case 6:
+                    ca2[i] = '6';
+                    break;
+                case 7:
+                    ca2[i] = '7';
+                    break;
+                case 8:
+                    ca2[i] = '8';
+                    break;
+                case 9:
+                    ca2[i] = '9';
+                    break;
+            }
+            int nZeros = 0;
+            if (!leftJustify && leadingZeros) {
+                int xThousands = 0;
+                if (thousands) {
+                    int xlead = 0;
+                    if (ca2[0] == '+' || ca2[0] == '-' || ca2[0] == ' ')
+                        xlead = 1;
+                    int xdp = xlead;
+                    for (; xdp < ca2.length; xdp++)
+                        if (ca2[xdp] == '.')
+                            break;
+                    xThousands = (xdp - xlead) / 3;
+                }
+                if (fieldWidthSet)
+                    nZeros = fieldWidth - ca2.length;
+                if ((!minusSign && (leadingSign || leadingSpace)) || minusSign)
+                    nZeros--;
+                nZeros -= xThousands;
+                if (nZeros < 0)
+                    nZeros = 0;
+            }
+            j = 0;
+            if ((!minusSign && (leadingSign || leadingSpace)) || minusSign) {
+                ca3 = new char[ca2.length + nZeros + 1];
+                j++;
+            } else
+                ca3 = new char[ca2.length + nZeros];
+            if (!minusSign) {
+                if (leadingSign)
+                    ca3[0] = '+';
+                if (leadingSpace)
+                    ca3[0] = ' ';
+            } else
+                ca3[0] = '-';
+            for (k = 0; k < nZeros; j++, k++)
+                ca3[j] = '0';
+            for (i = 0; i < ca2.length && j < ca3.length; i++, j++)
+                ca3[j] = ca2[i];
+
+            int lead = 0;
+            if (ca3[0] == '+' || ca3[0] == '-' || ca3[0] == ' ')
+                lead = 1;
+            int dp = lead;
+            for (; dp < ca3.length; dp++)
+                if (ca3[dp] == '.')
+                    break;
+            int nThousands = dp / 3;
+            // Localize the decimal point.
+            if (dp < ca3.length)
+                ca3[dp] = dfs.getDecimalSeparator();
+            char[] ca4 = ca3;
+            if (thousands && nThousands > 0) {
+                ca4 = new char[ca3.length + nThousands + lead];
+                ca4[0] = ca3[0];
+                for (i = lead, k = lead; i < dp; i++) {
+                    if (i > 0 && (dp - i) % 3 == 0) {
+                        // ca4[k]=',';
+                        ca4[k] = dfs.getGroupingSeparator();
+                        ca4[k + 1] = ca3[i];
+                        k += 2;
+                    } else {
+                        ca4[k] = ca3[i];
+                        k++;
+                    }
+                }
+                for (; i < ca3.length; i++, k++)
+                    ca4[k] = ca3[i];
+            }
+            return ca4;
+        }
+
+        /**
+         * Check to see if the digits that are going to
+         * be truncated because of the precision should
+         * force a round in the preceding digits.
+         *
+         * @param ca1    the array of digits
+         * @param icarry the index of the first digit that
+         *               is to be truncated from the print
+         * @return <code>true</code> if the truncation forces
+         * a round that will change the print
+         */
+        private boolean checkForCarry(char[] ca1, int icarry) {
+            boolean carry = false;
+            if (icarry < ca1.length) {
+                if (ca1[icarry] == '6' || ca1[icarry] == '7' || ca1[icarry] == '8' || ca1[icarry] == '9')
+                    carry = true;
+                else if (ca1[icarry] == '5') {
+                    int ii = icarry + 1;
+                    for (; ii < ca1.length; ii++)
+                        if (ca1[ii] != '0')
+                            break;
+                    carry = ii < ca1.length;
+                    if (!carry && icarry > 0) {
+                        carry =
+                                (ca1[icarry - 1] == '1'
+                                        || ca1[icarry - 1] == '3'
+                                        || ca1[icarry - 1] == '5'
+                                        || ca1[icarry - 1] == '7'
+                                        || ca1[icarry - 1] == '9');
+                    }
+                }
+            }
+            return carry;
+        }
+
+        /**
+         * Start the symbolic carry process.  The process
+         * is not quite finished because the symbolic
+         * carry may change the length of the string and
+         * change the exponent (in e format).
+         *
+         * @param cLast  index of the last digit changed
+         *               by the round
+         * @param cFirst index of the first digit allowed
+         *               to be changed by this phase of the round
+         * @return <code>true</code> if the carry forces
+         * a round that will change the print still
+         * more
+         */
+        private boolean startSymbolicCarry(char[] ca, int cLast, int cFirst) {
+            boolean carry = true;
+            for (int i = cLast; carry && i >= cFirst; i--) {
+                carry = false;
+                switch (ca[i]) {
+                    case '0':
+                        ca[i] = '1';
+                        break;
+                    case '1':
+                        ca[i] = '2';
+                        break;
+                    case '2':
+                        ca[i] = '3';
+                        break;
+                    case '3':
+                        ca[i] = '4';
+                        break;
+                    case '4':
+                        ca[i] = '5';
+                        break;
+                    case '5':
+                        ca[i] = '6';
+                        break;
+                    case '6':
+                        ca[i] = '7';
+                        break;
+                    case '7':
+                        ca[i] = '8';
+                        break;
+                    case '8':
+                        ca[i] = '9';
+                        break;
+                    case '9':
+                        ca[i] = '0';
+                        carry = true;
+                        break;
+                }
+            }
+            return carry;
+        }
+
+        /**
+         * An intermediate routine on the way to creating
+         * an e format String.  The method decides whether
+         * the input double value is an infinity,
+         * not-a-number, or a finite double and formats
+         * each type of input appropriately.
+         *
+         * @param x     the double value to be formatted.
+         * @param eChar an 'e' or 'E' to use in the
+         *              converted double value.
+         * @return the converted double value.
+         */
+        private String eFormatString(double x, char eChar) {
+            // boolean noDigits = false;
+            char[] ca4, ca5;
+            if (Double.isInfinite(x)) {
+                if (x == Double.POSITIVE_INFINITY) {
+                    if (leadingSign)
+                        ca4 = "+Inf".toCharArray();
+                    else if (leadingSpace)
+                        ca4 = " Inf".toCharArray();
+                    else
+                        ca4 = "Inf".toCharArray();
+                } else
+                    ca4 = "-Inf".toCharArray();
+                // noDigits = true;
+            } else if (Double.isNaN(x)) {
+                if (leadingSign)
+                    ca4 = "+NaN".toCharArray();
+                else if (leadingSpace)
+                    ca4 = " NaN".toCharArray();
+                else
+                    ca4 = "NaN".toCharArray();
+                // noDigits = true;
+            } else
+                ca4 = eFormatDigits(x, eChar);
+            ca5 = applyFloatPadding(ca4);
+            return new String(ca5);
+        }
+
+        /**
+         * Apply zero or blank, left or right padding.
+         *
+         * @param ca4 array of characters before padding is
+         *            finished
+         * @return a padded array of characters
+         */
+        private char[] applyFloatPadding(char[] ca4) {
+            char[] ca5 = ca4;
+            if (fieldWidthSet) {
+                int i, j, nBlanks;
+                if (leftJustify) {
+                    nBlanks = fieldWidth - ca4.length;
+                    if (nBlanks > 0) {
+                        ca5 = new char[ca4.length + nBlanks];
+                        for (i = 0; i < ca4.length; i++)
+                            ca5[i] = ca4[i];
+                        for (j = 0; j < nBlanks; j++, i++)
+                            ca5[i] = ' ';
+                    }
+                } else if (!leadingZeros) {
+                    nBlanks = fieldWidth - ca4.length;
+                    if (nBlanks > 0) {
+                        ca5 = new char[ca4.length + nBlanks];
+                        for (i = 0; i < nBlanks; i++)
+                            ca5[i] = ' ';
+                        for (j = 0; j < ca4.length; i++, j++)
+                            ca5[i] = ca4[j];
+                    }
+                } else if (leadingZeros) {
+                    nBlanks = fieldWidth - ca4.length;
+                    if (nBlanks > 0) {
+                        ca5 = new char[ca4.length + nBlanks];
+                        i = 0;
+                        j = 0;
+                        if (ca4[0] == '-') {
+                            ca5[0] = '-';
+                            i++;
+                            j++;
+                        }
+                        for (int k = 0; k < nBlanks; i++, k++)
+                            ca5[i] = '0';
+                        for (; j < ca4.length; i++, j++)
+                            ca5[i] = ca4[j];
+                    }
+                }
+            }
+            return ca5;
+        }
+
+        /**
+         * Format method for the f conversion character.
+         *
+         * @param x the double to format.
+         * @return the formatted String.
+         */
+        private String printFFormat(double x) {
+            return fFormatString(x);
+        }
+
+        /**
+         * Format method for the e or E conversion
+         * character.
+         *
+         * @param x the double to format.
+         * @return the formatted String.
+         */
+        private String printEFormat(double x) {
+            if (conversionCharacter == 'e')
+                return eFormatString(x, 'e');
+            else
+                return eFormatString(x, 'E');
+        }
+
+        /**
+         * Format method for the g conversion character.
+         * <p>
+         * For g format, the flag character '-', means that
+         * the output should be left justified within the
+         * field.  The default is to pad with blanks on the
+         * left.  '+' character means that the conversion
+         * will always begin with a sign (+ or -).  The
+         * blank flag character means that a non-negative
+         * input will be preceded with a blank.  If both a
+         * '+' and a ' ' are specified, the blank flag is
+         * ignored.  The '0' flag character implies that
+         * padding to the field width will be done with
+         * zeros instead of blanks.
+         * <p>
+         * The field width is treated as the minimum number
+         * of characters to be printed.  The default is to
+         * add no padding.  Padding is with blanks by
+         * default.
+         * <p>
+         * The precision, if set, is the minimum number of
+         * digits to appear after the radix character.
+         * Padding is with trailing 0s.
+         *
+         * @param x the double to format.
+         * @return the formatted String.
+         */
+        private String printGFormat(double x) {
+            String sx, sy, sz, ret;
+            int savePrecision = precision;
+            int i;
+            char[] ca4, ca5;
+            // boolean noDigits = false;
+            if (Double.isInfinite(x)) {
+                if (x == Double.POSITIVE_INFINITY) {
+                    if (leadingSign)
+                        ca4 = "+Inf".toCharArray();
+                    else if (leadingSpace)
+                        ca4 = " Inf".toCharArray();
+                    else
+                        ca4 = "Inf".toCharArray();
+                } else
+                    ca4 = "-Inf".toCharArray();
+                // noDigits = true;
+            } else if (Double.isNaN(x)) {
+                if (leadingSign)
+                    ca4 = "+NaN".toCharArray();
+                else if (leadingSpace)
+                    ca4 = " NaN".toCharArray();
+                else
+                    ca4 = "NaN".toCharArray();
+                // noDigits = true;
+            } else {
+                if (!precisionSet)
+                    precision = defaultDigits;
+                if (precision == 0)
+                    precision = 1;
+                int ePos = -1;
+                if (conversionCharacter == 'g') {
+                    sx = eFormatString(x, 'e').trim();
+                    ePos = sx.indexOf('e');
+                } else {
+                    sx = eFormatString(x, 'E').trim();
+                    ePos = sx.indexOf('E');
+                }
+                i = ePos + 1;
+                int expon = 0;
+                if (sx.charAt(i) == '-') {
+                    for (++i; i < sx.length(); i++)
+                        if (sx.charAt(i) != '0')
+                            break;
+                    if (i < sx.length())
+                        expon = -Integer.parseInt(sx.substring(i));
+                } else {
+                    if (sx.charAt(i) == '+')
+                        ++i;
+                    for (; i < sx.length(); i++)
+                        if (sx.charAt(i) != '0')
+                            break;
+                    if (i < sx.length())
+                        expon = Integer.parseInt(sx.substring(i));
+                }
+                // Trim trailing zeros.
+                // If the radix character is not followed by
+                // a digit, trim it, too.
+                if (!alternateForm) {
+                    if (expon >= -4 && expon < precision)
+                        sy = fFormatString(x).trim();
+                    else
+                        sy = sx.substring(0, ePos);
+                    i = sy.length() - 1;
+                    for (; i >= 0; i--)
+                        if (sy.charAt(i) != '0')
+                            break;
+                    if (i >= 0 && sy.charAt(i) == '.')
+                        i--;
+                    if (i == -1)
+                        sz = "0";
+                    else if (!Character.isDigit(sy.charAt(i)))
+                        sz = sy.substring(0, i + 1) + "0";
+                    else
+                        sz = sy.substring(0, i + 1);
+                    if (expon >= -4 && expon < precision)
+                        ret = sz;
+                    else
+                        ret = sz + sx.substring(ePos);
+                } else {
+                    if (expon >= -4 && expon < precision)
+                        ret = fFormatString(x).trim();
+                    else
+                        ret = sx;
+                }
+                // leading space was trimmed off during
+                // construction
+                if (leadingSpace)
+                    if (x >= 0)
+                        ret = " " + ret;
+                ca4 = ret.toCharArray();
+            }
+            // Pad with blanks or zeros.
+            ca5 = applyFloatPadding(ca4);
+            precision = savePrecision;
+            return new String(ca5);
+        }
+
+        /**
+         * Format method for the d conversion specifer and
+         * short argument.
+         * <p>
+         * For d format, the flag character '-', means that
+         * the output should be left justified within the
+         * field.  The default is to pad with blanks on the
+         * left.  A '+' character means that the conversion
+         * will always begin with a sign (+ or -).  The
+         * blank flag character means that a non-negative
+         * input will be preceded with a blank.  If both a
+         * '+' and a ' ' are specified, the blank flag is
+         * ignored.  The '0' flag character implies that
+         * padding to the field width will be done with
+         * zeros instead of blanks.
+         * <p>
+         * The field width is treated as the minimum number
+         * of characters to be printed.  The default is to
+         * add no padding.  Padding is with blanks by
+         * default.
+         * <p>
+         * The precision, if set, is the minimum number of
+         * digits to appear.  Padding is with leading 0s.
+         *
+         * @param x the short to format.
+         * @return the formatted String.
+         */
+        private String printDFormat(short x) {
+            return printDFormat(Short.toString(x));
+        }
+
+        /**
+         * Format method for the d conversion character and
+         * long argument.
+         * <p>
+         * For d format, the flag character '-', means that
+         * the output should be left justified within the
+         * field.  The default is to pad with blanks on the
+         * left.  A '+' character means that the conversion
+         * will always begin with a sign (+ or -).  The
+         * blank flag character means that a non-negative
+         * input will be preceded with a blank.  If both a
+         * '+' and a ' ' are specified, the blank flag is
+         * ignored.  The '0' flag character implies that
+         * padding to the field width will be done with
+         * zeros instead of blanks.
+         * <p>
+         * The field width is treated as the minimum number
+         * of characters to be printed.  The default is to
+         * add no padding.  Padding is with blanks by
+         * default.
+         * <p>
+         * The precision, if set, is the minimum number of
+         * digits to appear.  Padding is with leading 0s.
+         *
+         * @param x the long to format.
+         * @return the formatted String.
+         */
+        private String printDFormat(long x) {
+            return printDFormat(Long.toString(x));
+        }
+
+        /**
+         * Format method for the d conversion character and
+         * int argument.
+         * <p>
+         * For d format, the flag character '-', means that
+         * the output should be left justified within the
+         * field.  The default is to pad with blanks on the
+         * left.  A '+' character means that the conversion
+         * will always begin with a sign (+ or -).  The
+         * blank flag character means that a non-negative
+         * input will be preceded with a blank.  If both a
+         * '+' and a ' ' are specified, the blank flag is
+         * ignored.  The '0' flag character implies that
+         * padding to the field width will be done with
+         * zeros instead of blanks.
+         * <p>
+         * The field width is treated as the minimum number
+         * of characters to be printed.  The default is to
+         * add no padding.  Padding is with blanks by
+         * default.
+         * <p>
+         * The precision, if set, is the minimum number of
+         * digits to appear.  Padding is with leading 0s.
+         *
+         * @param x the int to format.
+         * @return the formatted String.
+         */
+        private String printDFormat(int x) {
+            return printDFormat(Integer.toString(x));
+        }
+
+        /**
+         * Utility method for formatting using the d
+         * conversion character.
+         *
+         * @param sx the String to format, the result of
+         *           converting a short, int, or long to a
+         *           String.
+         * @return the formatted String.
+         */
+        private String printDFormat(String sx) {
+            int nLeadingZeros = 0;
+            int nBlanks = 0, n = 0;
+            int i = 0, jFirst = 0;
+            boolean neg = sx.charAt(0) == '-';
+            if (sx.equals("0") && precisionSet && precision == 0)
+                sx = "";
+            if (!neg) {
+                if (precisionSet && sx.length() < precision)
+                    nLeadingZeros = precision - sx.length();
+            } else {
+                if (precisionSet && (sx.length() - 1) < precision)
+                    nLeadingZeros = precision - sx.length() + 1;
+            }
+            if (nLeadingZeros < 0)
+                nLeadingZeros = 0;
+            if (fieldWidthSet) {
+                nBlanks = fieldWidth - nLeadingZeros - sx.length();
+                if (!neg && (leadingSign || leadingSpace))
+                    nBlanks--;
+            }
+            if (nBlanks < 0)
+                nBlanks = 0;
+            if (leadingSign)
+                n++;
+            else if (leadingSpace)
+                n++;
+            n += nBlanks;
+            n += nLeadingZeros;
+            n += sx.length();
+            char[] ca = new char[n];
+            if (leftJustify) {
+                if (neg)
+                    ca[i++] = '-';
+                else if (leadingSign)
+                    ca[i++] = '+';
+                else if (leadingSpace)
+                    ca[i++] = ' ';
+                char[] csx = sx.toCharArray();
+                jFirst = neg ? 1 : 0;
+                for (int j = 0; j < nLeadingZeros; i++, j++)
+                    ca[i] = '0';
+                for (int j = jFirst; j < csx.length; j++, i++)
+                    ca[i] = csx[j];
+                for (int j = 0; j < nBlanks; i++, j++)
+                    ca[i] = ' ';
+            } else {
+                if (!leadingZeros) {
+                    for (i = 0; i < nBlanks; i++)
+                        ca[i] = ' ';
+                    if (neg)
+                        ca[i++] = '-';
+                    else if (leadingSign)
+                        ca[i++] = '+';
+                    else if (leadingSpace)
+                        ca[i++] = ' ';
+                } else {
+                    if (neg)
+                        ca[i++] = '-';
+                    else if (leadingSign)
+                        ca[i++] = '+';
+                    else if (leadingSpace)
+                        ca[i++] = ' ';
+                    for (int j = 0; j < nBlanks; j++, i++)
+                        ca[i] = '0';
+                }
+                for (int j = 0; j < nLeadingZeros; j++, i++)
+                    ca[i] = '0';
+                char[] csx = sx.toCharArray();
+                jFirst = neg ? 1 : 0;
+                for (int j = jFirst; j < csx.length; j++, i++)
+                    ca[i] = csx[j];
+            }
+            return new String(ca);
+        }
+
+        /**
+         * Format method for the x conversion character and
+         * short argument.
+         * <p>
+         * For x format, the flag character '-', means that
+         * the output should be left justified within the
+         * field.  The default is to pad with blanks on the
+         * left.  The '#' flag character means to lead with
+         * '0x'.
+         * <p>
+         * The field width is treated as the minimum number
+         * of characters to be printed.  The default is to
+         * add no padding.  Padding is with blanks by
+         * default.
+         * <p>
+         * The precision, if set, is the minimum number of
+         * digits to appear.  Padding is with leading 0s.
+         *
+         * @param x the short to format.
+         * @return the formatted String.
+         */
+        private String printXFormat(short x) {
+            String sx = null;
+            if (x == Short.MIN_VALUE)
+                sx = "8000";
+            else if (x < 0) {
+                String t;
+                if (x == Short.MIN_VALUE)
+                    t = "0";
+                else {
+                    t = Integer.toString((~(-x - 1)) ^ Short.MIN_VALUE, 16);
+                    if (t.charAt(0) == 'F' || t.charAt(0) == 'f')
+                        t = t.substring(16, 32);
+                }
+                switch (t.length()) {
+                    case 1:
+                        sx = "800" + t;
+                        break;
+                    case 2:
+                        sx = "80" + t;
+                        break;
+                    case 3:
+                        sx = "8" + t;
+                        break;
+                    case 4:
+                        switch (t.charAt(0)) {
+                            case '1':
+                                sx = "9" + t.substring(1, 4);
+                                break;
+                            case '2':
+                                sx = "a" + t.substring(1, 4);
+                                break;
+                            case '3':
+                                sx = "b" + t.substring(1, 4);
+                                break;
+                            case '4':
+                                sx = "c" + t.substring(1, 4);
+                                break;
+                            case '5':
+                                sx = "d" + t.substring(1, 4);
+                                break;
+                            case '6':
+                                sx = "e" + t.substring(1, 4);
+                                break;
+                            case '7':
+                                sx = "f" + t.substring(1, 4);
+                                break;
+                        }
+                        break;
+                }
+            } else
+                sx = Integer.toString((int) x, 16);
+            return printXFormat(sx);
+        }
+
+        /**
+         * Format method for the x conversion character and
+         * long argument.
+         * <p>
+         * For x format, the flag character '-', means that
+         * the output should be left justified within the
+         * field.  The default is to pad with blanks on the
+         * left.  The '#' flag character means to lead with
+         * '0x'.
+         * <p>
+         * The field width is treated as the minimum number
+         * of characters to be printed.  The default is to
+         * add no padding.  Padding is with blanks by
+         * default.
+         * <p>
+         * The precision, if set, is the minimum number of
+         * digits to appear.  Padding is with leading 0s.
+         *
+         * @param x the long to format.
+         * @return the formatted String.
+         */
+        private String printXFormat(long x) {
+            String sx = null;
+            if (x == Long.MIN_VALUE)
+                sx = "8000000000000000";
+            else if (x < 0) {
+                String t = Long.toString((~(-x - 1)) ^ Long.MIN_VALUE, 16);
+                switch (t.length()) {
+                    case 1:
+                        sx = "800000000000000" + t;
+                        break;
+                    case 2:
+                        sx = "80000000000000" + t;
+                        break;
+                    case 3:
+                        sx = "8000000000000" + t;
+                        break;
+                    case 4:
+                        sx = "800000000000" + t;
+                        break;
+                    case 5:
+                        sx = "80000000000" + t;
+                        break;
+                    case 6:
+                        sx = "8000000000" + t;
+                        break;
+                    case 7:
+                        sx = "800000000" + t;
+                        break;
+                    case 8:
+                        sx = "80000000" + t;
+                        break;
+                    case 9:
+                        sx = "8000000" + t;
+                        break;
+                    case 10:
+                        sx = "800000" + t;
+                        break;
+                    case 11:
+                        sx = "80000" + t;
+                        break;
+                    case 12:
+                        sx = "8000" + t;
+                        break;
+                    case 13:
+                        sx = "800" + t;
+                        break;
+                    case 14:
+                        sx = "80" + t;
+                        break;
+                    case 15:
+                        sx = "8" + t;
+                        break;
+                    case 16:
+                        switch (t.charAt(0)) {
+                            case '1':
+                                sx = "9" + t.substring(1, 16);
+                                break;
+                            case '2':
+                                sx = "a" + t.substring(1, 16);
+                                break;
+                            case '3':
+                                sx = "b" + t.substring(1, 16);
+                                break;
+                            case '4':
+                                sx = "c" + t.substring(1, 16);
+                                break;
+                            case '5':
+                                sx = "d" + t.substring(1, 16);
+                                break;
+                            case '6':
+                                sx = "e" + t.substring(1, 16);
+                                break;
+                            case '7':
+                                sx = "f" + t.substring(1, 16);
+                                break;
+                        }
+                        break;
+                }
+            } else
+                sx = Long.toString(x, 16);
+            return printXFormat(sx);
+        }
+
+        /**
+         * Format method for the x conversion character and
+         * int argument.
+         * <p>
+         * For x format, the flag character '-', means that
+         * the output should be left justified within the
+         * field.  The default is to pad with blanks on the
+         * left.  The '#' flag character means to lead with
+         * '0x'.
+         * <p>
+         * The field width is treated as the minimum number
+         * of characters to be printed.  The default is to
+         * add no padding.  Padding is with blanks by
+         * default.
+         * <p>
+         * The precision, if set, is the minimum number of
+         * digits to appear.  Padding is with leading 0s.
+         *
+         * @param x the int to format.
+         * @return the formatted String.
+         */
+        private String printXFormat(int x) {
+            String sx = null;
+            if (x == Integer.MIN_VALUE)
+                sx = "80000000";
+            else if (x < 0) {
+                String t = Integer.toString((~(-x - 1)) ^ Integer.MIN_VALUE, 16);
+                switch (t.length()) {
+                    case 1:
+                        sx = "8000000" + t;
+                        break;
+                    case 2:
+                        sx = "800000" + t;
+                        break;
+                    case 3:
+                        sx = "80000" + t;
+                        break;
+                    case 4:
+                        sx = "8000" + t;
+                        break;
+                    case 5:
+                        sx = "800" + t;
+                        break;
+                    case 6:
+                        sx = "80" + t;
+                        break;
+                    case 7:
+                        sx = "8" + t;
+                        break;
+                    case 8:
+                        switch (t.charAt(0)) {
+                            case '1':
+                                sx = "9" + t.substring(1, 8);
+                                break;
+                            case '2':
+                                sx = "a" + t.substring(1, 8);
+                                break;
+                            case '3':
+                                sx = "b" + t.substring(1, 8);
+                                break;
+                            case '4':
+                                sx = "c" + t.substring(1, 8);
+                                break;
+                            case '5':
+                                sx = "d" + t.substring(1, 8);
+                                break;
+                            case '6':
+                                sx = "e" + t.substring(1, 8);
+                                break;
+                            case '7':
+                                sx = "f" + t.substring(1, 8);
+                                break;
+                        }
+                        break;
+                }
+            } else
+                sx = Integer.toString(x, 16);
+            return printXFormat(sx);
+        }
+
+        /**
+         * Utility method for formatting using the x
+         * conversion character.
+         *
+         * @param sx the String to format, the result of
+         *           converting a short, int, or long to a
+         *           String.
+         * @return the formatted String.
+         */
+        private String printXFormat(String sx) {
+            int nLeadingZeros = 0;
+            int nBlanks = 0;
+            if (sx.equals("0") && precisionSet && precision == 0)
+                sx = "";
+            if (precisionSet)
+                nLeadingZeros = precision - sx.length();
+            if (nLeadingZeros < 0)
+                nLeadingZeros = 0;
+            if (fieldWidthSet) {
+                nBlanks = fieldWidth - nLeadingZeros - sx.length();
+                if (alternateForm)
+                    nBlanks = nBlanks - 2;
+            }
+            if (nBlanks < 0)
+                nBlanks = 0;
+            int n = 0;
+            if (alternateForm)
+                n += 2;
+            n += nLeadingZeros;
+            n += sx.length();
+            n += nBlanks;
+            char[] ca = new char[n];
+            int i = 0;
+            if (leftJustify) {
+                if (alternateForm) {
+                    ca[i++] = '0';
+                    ca[i++] = 'x';
+                }
+                for (int j = 0; j < nLeadingZeros; j++, i++)
+                    ca[i] = '0';
+                char[] csx = sx.toCharArray();
+                for (int j = 0; j < csx.length; j++, i++)
+                    ca[i] = csx[j];
+                for (int j = 0; j < nBlanks; j++, i++)
+                    ca[i] = ' ';
+            } else {
+                if (!leadingZeros)
+                    for (int j = 0; j < nBlanks; j++, i++)
+                        ca[i] = ' ';
+                if (alternateForm) {
+                    ca[i++] = '0';
+                    ca[i++] = 'x';
+                }
+                if (leadingZeros)
+                    for (int j = 0; j < nBlanks; j++, i++)
+                        ca[i] = '0';
+                for (int j = 0; j < nLeadingZeros; j++, i++)
+                    ca[i] = '0';
+                char[] csx = sx.toCharArray();
+                for (int j = 0; j < csx.length; j++, i++)
+                    ca[i] = csx[j];
+            }
+            String caReturn = new String(ca);
+            if (conversionCharacter == 'X')
+                caReturn = caReturn.toUpperCase();
+            return caReturn;
+        }
+
+        /**
+         * Format method for the o conversion character and
+         * short argument.
+         * <p>
+         * For o format, the flag character '-', means that
+         * the output should be left justified within the
+         * field.  The default is to pad with blanks on the
+         * left.  The '#' flag character means that the
+         * output begins with a leading 0 and the precision
+         * is increased by 1.
+         * <p>
+         * The field width is treated as the minimum number
+         * of characters to be printed.  The default is to
+         * add no padding.  Padding is with blanks by
+         * default.
+         * <p>
+         * The precision, if set, is the minimum number of
+         * digits to appear.  Padding is with leading 0s.
+         *
+         * @param x the short to format.
+         * @return the formatted String.
+         */
+        private String printOFormat(short x) {
+            String sx = null;
+            if (x == Short.MIN_VALUE)
+                sx = "100000";
+            else if (x < 0) {
+                String t = Integer.toString((~(-x - 1)) ^ Short.MIN_VALUE, 8);
+                switch (t.length()) {
+                    case 1:
+                        sx = "10000" + t;
+                        break;
+                    case 2:
+                        sx = "1000" + t;
+                        break;
+                    case 3:
+                        sx = "100" + t;
+                        break;
+                    case 4:
+                        sx = "10" + t;
+                        break;
+                    case 5:
+                        sx = "1" + t;
+                        break;
+                }
+            } else
+                sx = Integer.toString((int) x, 8);
+            return printOFormat(sx);
+        }
+
+        /**
+         * Format method for the o conversion character and
+         * long argument.
+         * <p>
+         * For o format, the flag character '-', means that
+         * the output should be left justified within the
+         * field.  The default is to pad with blanks on the
+         * left.  The '#' flag character means that the
+         * output begins with a leading 0 and the precision
+         * is increased by 1.
+         * <p>
+         * The field width is treated as the minimum number
+         * of characters to be printed.  The default is to
+         * add no padding.  Padding is with blanks by
+         * default.
+         * <p>
+         * The precision, if set, is the minimum number of
+         * digits to appear.  Padding is with leading 0s.
+         *
+         * @param x the long to format.
+         * @return the formatted String.
+         */
+        private String printOFormat(long x) {
+            String sx = null;
+            if (x == Long.MIN_VALUE)
+                sx = "1000000000000000000000";
+            else if (x < 0) {
+                String t = Long.toString((~(-x - 1)) ^ Long.MIN_VALUE, 8);
+                switch (t.length()) {
+                    case 1:
+                        sx = "100000000000000000000" + t;
+                        break;
+                    case 2:
+                        sx = "10000000000000000000" + t;
+                        break;
+                    case 3:
+                        sx = "1000000000000000000" + t;
+                        break;
+                    case 4:
+                        sx = "100000000000000000" + t;
+                        break;
+                    case 5:
+                        sx = "10000000000000000" + t;
+                        break;
+                    case 6:
+                        sx = "1000000000000000" + t;
+                        break;
+                    case 7:
+                        sx = "100000000000000" + t;
+                        break;
+                    case 8:
+                        sx = "10000000000000" + t;
+                        break;
+                    case 9:
+                        sx = "1000000000000" + t;
+                        break;
+                    case 10:
+                        sx = "100000000000" + t;
+                        break;
+                    case 11:
+                        sx = "10000000000" + t;
+                        break;
+                    case 12:
+                        sx = "1000000000" + t;
+                        break;
+                    case 13:
+                        sx = "100000000" + t;
+                        break;
+                    case 14:
+                        sx = "10000000" + t;
+                        break;
+                    case 15:
+                        sx = "1000000" + t;
+                        break;
+                    case 16:
+                        sx = "100000" + t;
+                        break;
+                    case 17:
+                        sx = "10000" + t;
+                        break;
+                    case 18:
+                        sx = "1000" + t;
+                        break;
+                    case 19:
+                        sx = "100" + t;
+                        break;
+                    case 20:
+                        sx = "10" + t;
+                        break;
+                    case 21:
+                        sx = "1" + t;
+                        break;
+                }
+            } else
+                sx = Long.toString(x, 8);
+            return printOFormat(sx);
+        }
+
+        /**
+         * Format method for the o conversion character and
+         * int argument.
+         * <p>
+         * For o format, the flag character '-', means that
+         * the output should be left justified within the
+         * field.  The default is to pad with blanks on the
+         * left.  The '#' flag character means that the
+         * output begins with a leading 0 and the precision
+         * is increased by 1.
+         * <p>
+         * The field width is treated as the minimum number
+         * of characters to be printed.  The default is to
+         * add no padding.  Padding is with blanks by
+         * default.
+         * <p>
+         * The precision, if set, is the minimum number of
+         * digits to appear.  Padding is with leading 0s.
+         *
+         * @param x the int to format.
+         * @return the formatted String.
+         */
+        private String printOFormat(int x) {
+            String sx = null;
+            if (x == Integer.MIN_VALUE)
+                sx = "20000000000";
+            else if (x < 0) {
+                String t = Integer.toString((~(-x - 1)) ^ Integer.MIN_VALUE, 8);
+                switch (t.length()) {
+                    case 1:
+                        sx = "2000000000" + t;
+                        break;
+                    case 2:
+                        sx = "200000000" + t;
+                        break;
+                    case 3:
+                        sx = "20000000" + t;
+                        break;
+                    case 4:
+                        sx = "2000000" + t;
+                        break;
+                    case 5:
+                        sx = "200000" + t;
+                        break;
+                    case 6:
+                        sx = "20000" + t;
+                        break;
+                    case 7:
+                        sx = "2000" + t;
+                        break;
+                    case 8:
+                        sx = "200" + t;
+                        break;
+                    case 9:
+                        sx = "20" + t;
+                        break;
+                    case 10:
+                        sx = "2" + t;
+                        break;
+                    case 11:
+                        sx = "3" + t.substring(1);
+                        break;
+                }
+            } else
+                sx = Integer.toString(x, 8);
+            return printOFormat(sx);
+        }
+
+        /**
+         * Utility method for formatting using the o
+         * conversion character.
+         *
+         * @param sx the String to format, the result of
+         *           converting a short, int, or long to a
+         *           String.
+         * @return the formatted String.
+         */
+        private String printOFormat(String sx) {
+            int nLeadingZeros = 0;
+            int nBlanks = 0;
+            if (sx.equals("0") && precisionSet && precision == 0)
+                sx = "";
+            if (precisionSet)
+                nLeadingZeros = precision - sx.length();
+            if (alternateForm)
+                nLeadingZeros++;
+            if (nLeadingZeros < 0)
+                nLeadingZeros = 0;
+            if (fieldWidthSet)
+                nBlanks = fieldWidth - nLeadingZeros - sx.length();
+            if (nBlanks < 0)
+                nBlanks = 0;
+            int n = nLeadingZeros + sx.length() + nBlanks;
+            char[] ca = new char[n];
+            int i;
+            if (leftJustify) {
+                for (i = 0; i < nLeadingZeros; i++)
+                    ca[i] = '0';
+                char[] csx = sx.toCharArray();
+                for (int j = 0; j < csx.length; j++, i++)
+                    ca[i] = csx[j];
+                for (int j = 0; j < nBlanks; j++, i++)
+                    ca[i] = ' ';
+            } else {
+                if (leadingZeros)
+                    for (i = 0; i < nBlanks; i++)
+                        ca[i] = '0';
+                else
+                    for (i = 0; i < nBlanks; i++)
+                        ca[i] = ' ';
+                for (int j = 0; j < nLeadingZeros; j++, i++)
+                    ca[i] = '0';
+                char[] csx = sx.toCharArray();
+                for (int j = 0; j < csx.length; j++, i++)
+                    ca[i] = csx[j];
+            }
+            return new String(ca);
+        }
+
+        /**
+         * Format method for the c conversion character and
+         * char argument.
+         * <p>
+         * The only flag character that affects c format is
+         * the '-', meaning that the output should be left
+         * justified within the field.  The default is to
+         * pad with blanks on the left.
+         * <p>
+         * The field width is treated as the minimum number
+         * of characters to be printed.  Padding is with
+         * blanks by default.  The default width is 1.
+         * <p>
+         * The precision, if set, is ignored.
+         *
+         * @param x the char to format.
+         * @return the formatted String.
+         */
+        private String printCFormat(char x) {
+            int nPrint = 1;
+            int width = fieldWidth;
+            if (!fieldWidthSet)
+                width = nPrint;
+            char[] ca = new char[width];
+            int i = 0;
+            if (leftJustify) {
+                ca[0] = x;
+                for (i = 1; i <= width - nPrint; i++)
+                    ca[i] = ' ';
+            } else {
+                for (i = 0; i < width - nPrint; i++)
+                    ca[i] = ' ';
+                ca[i] = x;
+            }
+            return new String(ca);
+        }
+
+        /**
+         * Format method for the s conversion character and
+         * String argument.
+         * <p>
+         * The only flag character that affects s format is
+         * the '-', meaning that the output should be left
+         * justified within the field.  The default is to
+         * pad with blanks on the left.
+         * <p>
+         * The field width is treated as the minimum number
+         * of characters to be printed.  The default is the
+         * smaller of the number of characters in the the
+         * input and the precision.  Padding is with blanks
+         * by default.
+         * <p>
+         * The precision, if set, specifies the maximum
+         * number of characters to be printed from the
+         * string.  A null digit string is treated
+         * as a 0.  The default is not to set a maximum
+         * number of characters to be printed.
+         *
+         * @param x the String to format.
+         * @return the formatted String.
+         */
+        private String printSFormat(String x) {
+            int nPrint = x.length();
+            int width = fieldWidth;
+            if (precisionSet && nPrint > precision)
+                nPrint = precision;
+            if (!fieldWidthSet)
+                width = nPrint;
+            int n = 0;
+            if (width > nPrint)
+                n += width - nPrint;
+            if (nPrint >= x.length())
+                n += x.length();
+            else
+                n += nPrint;
+            char[] ca = new char[n];
+            int i = 0;
+            if (leftJustify) {
+                if (nPrint >= x.length()) {
+                    char[] csx = x.toCharArray();
+                    for (i = 0; i < x.length(); i++)
+                        ca[i] = csx[i];
+                } else {
+                    char[] csx = x.substring(0, nPrint).toCharArray();
+                    for (i = 0; i < nPrint; i++)
+                        ca[i] = csx[i];
+                }
+                for (int j = 0; j < width - nPrint; j++, i++)
+                    ca[i] = ' ';
+            } else {
+                for (i = 0; i < width - nPrint; i++)
+                    ca[i] = ' ';
+                if (nPrint >= x.length()) {
+                    char[] csx = x.toCharArray();
+                    for (int j = 0; j < x.length(); i++, j++)
+                        ca[i] = csx[j];
+                } else {
+                    char[] csx = x.substring(0, nPrint).toCharArray();
+                    for (int j = 0; j < nPrint; i++, j++)
+                        ca[i] = csx[j];
+                }
+            }
+            return new String(ca);
+        }
+
+        /**
+         * Check for a conversion character.  If it is
+         * there, store it.
+         *
+         * @param x the String to format.
+         * @return <code>true</code> if the conversion
+         * character is there, and
+         * <code>false</code> otherwise.
+         */
+        private boolean setConversionCharacter() {
+            /* idfgGoxXeEcs */
+            boolean ret = false;
+            conversionCharacter = '\0';
+            if (pos < fmt.length()) {
+                char c = fmt.charAt(pos);
+                if (c == 'i'
+                        || c == 'd'
+                        || c == 'f'
+                        || c == 'g'
+                        || c == 'G'
+                        || c == 'o'
+                        || c == 'x'
+                        || c == 'X'
+                        || c == 'e'
+                        || c == 'E'
+                        || c == 'c'
+                        || c == 's'
+                        || c == '%') {
+                    conversionCharacter = c;
+                    pos++;
+                    ret = true;
+                }
+            }
+            return ret;
+        }
+
+        /**
+         * Check for an h, l, or L in a format.  An L is
+         * used to control the minimum number of digits
+         * in an exponent when using floating point
+         * formats.  An l or h is used to control
+         * conversion of the input to a long or short,
+         * respectively, before formatting.  If any of
+         * these is present, store them.
+         */
+        private void setOptionalHL() {
+            optionalh = false;
+            optionall = false;
+            optionalL = false;
+            if (pos < fmt.length()) {
+                char c = fmt.charAt(pos);
+                if (c == 'h') {
+                    optionalh = true;
+                    pos++;
+                } else if (c == 'l') {
+                    optionall = true;
+                    pos++;
+                } else if (c == 'L') {
+                    optionalL = true;
+                    pos++;
+                }
+            }
+        }
+
+        /**
+         * Set the precision.
+         */
+        private void setPrecision() {
+            int firstPos = pos;
+            precisionSet = false;
+            if (pos < fmt.length() && fmt.charAt(pos) == '.') {
+                pos++;
+                if ((pos < fmt.length()) && (fmt.charAt(pos) == '*')) {
+                    pos++;
+                    if (!setPrecisionArgPosition()) {
+                        variablePrecision = true;
+                        precisionSet = true;
+                    }
+                } else {
+                    while (pos < fmt.length()) {
+                        char c = fmt.charAt(pos);
+                        if (Character.isDigit(c))
+                            pos++;
+                        else
+                            break;
+                    }
+                    if (pos > firstPos + 1) {
+                        String sz = fmt.substring(firstPos + 1, pos);
+                        precision = Integer.parseInt(sz);
+                        precisionSet = true;
+                    }
+                }
+            }
+        }
+
+        /**
+         * Set the field width.
+         */
+        private void setFieldWidth() {
+            int firstPos = pos;
+            fieldWidth = 0;
+            fieldWidthSet = false;
+            if ((pos < fmt.length()) && (fmt.charAt(pos) == '*')) {
+                pos++;
+                if (!setFieldWidthArgPosition()) {
+                    variableFieldWidth = true;
+                    fieldWidthSet = true;
+                }
+            } else {
+                while (pos < fmt.length()) {
+                    char c = fmt.charAt(pos);
+                    if (Character.isDigit(c))
+                        pos++;
+                    else
+                        break;
+                }
+                if (firstPos < pos && firstPos < fmt.length()) {
+                    String sz = fmt.substring(firstPos, pos);
+                    fieldWidth = Integer.parseInt(sz);
+                    fieldWidthSet = true;
+                }
+            }
+        }
+
+        /**
+         * Store the digits <code>n</code> in %n$ forms.
+         */
+        private void setArgPosition() {
+            int xPos;
+            for (xPos = pos; xPos < fmt.length(); xPos++) {
+                if (!Character.isDigit(fmt.charAt(xPos)))
+                    break;
+            }
+            if (xPos > pos && xPos < fmt.length()) {
+                if (fmt.charAt(xPos) == '$') {
+                    positionalSpecification = true;
+                    argumentPosition = Integer.parseInt(fmt.substring(pos, xPos));
+                    pos = xPos + 1;
+                }
+            }
+        }
+
+        /**
+         * Store the digits <code>n</code> in *n$ forms.
+         */
+        private boolean setFieldWidthArgPosition() {
+            boolean ret = false;
+            int xPos;
+            for (xPos = pos; xPos < fmt.length(); xPos++) {
+                if (!Character.isDigit(fmt.charAt(xPos)))
+                    break;
+            }
+            if (xPos > pos && xPos < fmt.length()) {
+                if (fmt.charAt(xPos) == '$') {
+                    positionalFieldWidth = true;
+                    argumentPositionForFieldWidth = Integer.parseInt(fmt.substring(pos, xPos));
+                    pos = xPos + 1;
+                    ret = true;
+                }
+            }
+            return ret;
+        }
+
+        /**
+         * Store the digits <code>n</code> in *n$ forms.
+         */
+        private boolean setPrecisionArgPosition() {
+            boolean ret = false;
+            int xPos;
+            for (xPos = pos; xPos < fmt.length(); xPos++) {
+                if (!Character.isDigit(fmt.charAt(xPos)))
+                    break;
+            }
+            if (xPos > pos && xPos < fmt.length()) {
+                if (fmt.charAt(xPos) == '$') {
+                    positionalPrecision = true;
+                    argumentPositionForPrecision = Integer.parseInt(fmt.substring(pos, xPos));
+                    pos = xPos + 1;
+                    ret = true;
+                }
+            }
+            return ret;
+        }
+
+        boolean isPositionalSpecification() {
+            return positionalSpecification;
+        }
+
+        int getArgumentPosition() {
+            return argumentPosition;
+        }
+
+        boolean isPositionalFieldWidth() {
+            return positionalFieldWidth;
+        }
+
+        int getArgumentPositionForFieldWidth() {
+            return argumentPositionForFieldWidth;
+        }
+
+        boolean isPositionalPrecision() {
+            return positionalPrecision;
+        }
+
+        int getArgumentPositionForPrecision() {
+            return argumentPositionForPrecision;
+        }
+
+        /**
+         * Set flag characters, one of '-+#0 or a space.
+         */
+        private void setFlagCharacters() {
+            /* '-+ #0 */
+            thousands = false;
+            leftJustify = false;
+            leadingSign = false;
+            leadingSpace = false;
+            alternateForm = false;
+            leadingZeros = false;
+            for (; pos < fmt.length(); pos++) {
+                char c = fmt.charAt(pos);
+                if (c == '\'')
+                    thousands = true;
+                else if (c == '-') {
+                    leftJustify = true;
+                    leadingZeros = false;
+                } else if (c == '+') {
+                    leadingSign = true;
+                    leadingSpace = false;
+                } else if (c == ' ') {
+                    if (!leadingSign)
+                        leadingSpace = true;
+                } else if (c == '#')
+                    alternateForm = true;
+                else if (c == '0') {
+                    if (!leftJustify)
+                        leadingZeros = true;
+                } else
+                    break;
+            }
+        }
+    }
+}
diff --git a/src/main/java/lwjake2/util/QuakeFile.java b/src/main/java/lwjake2/util/QuakeFile.java
new file mode 100644 (file)
index 0000000..6e3537b
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.util;
+
+import lwjake2.game.EDict;
+import lwjake2.game.GameBase;
+import lwjake2.game.SuperAdapter;
+import lwjake2.game.gitem_t;
+import lwjake2.qcommon.Com;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * RandomAccessFile, but handles readString/WriteString specially and offers
+ * other helper functions
+ */
+public class QuakeFile extends RandomAccessFile {
+
+    /**
+     * Standard Constructor.
+     */
+    public QuakeFile(String filename, String mode) throws FileNotFoundException {
+        super(filename, mode);
+    }
+
+    /**
+     * Writes a Vector to a RandomAccessFile.
+     */
+    public void writeVector(float v[]) throws IOException {
+        for (int n = 0; n < 3; n++)
+            writeFloat(v[n]);
+    }
+
+    /**
+     * Writes a Vector to a RandomAccessFile.
+     */
+    public float[] readVector() throws IOException {
+        float res[] = {0, 0, 0};
+        for (int n = 0; n < 3; n++)
+            res[n] = readFloat();
+
+        return res;
+    }
+
+    /**
+     * Reads a length specified string from a file.
+     */
+    public String readString() throws IOException {
+        int len = readInt();
+
+        if (len == -1)
+            return null;
+
+        if (len == 0)
+            return "";
+
+        byte bb[] = new byte[len];
+
+        super.read(bb, 0, len);
+
+        return new String(bb, 0, len);
+    }
+
+    /**
+     * Writes a length specified string to a file.
+     */
+    public void writeString(String s) throws IOException {
+        if (s == null) {
+            writeInt(-1);
+            return;
+        }
+
+        writeInt(s.length());
+        if (s.length() != 0)
+            writeBytes(s);
+    }
+
+    /**
+     * Writes the edict reference.
+     */
+    public void writeEdictRef(EDict ent) throws IOException {
+        if (ent == null)
+            writeInt(-1);
+        else {
+            writeInt(ent.s.number);
+        }
+    }
+
+    /**
+     * Reads an edict index from a file and returns the edict.
+     */
+
+    public EDict readEdictRef() throws IOException {
+        int i = readInt();
+
+        // handle -1
+        if (i < 0)
+            return null;
+
+        if (i > GameBase.g_edicts.length) {
+            Com.DPrintf("jake2: illegal edict num:" + i + "\n");
+            return null;
+        }
+
+        // valid edict.
+        return GameBase.g_edicts[i];
+    }
+
+    /**
+     * Writes the Adapter-ID to the file.
+     */
+    public void writeAdapter(SuperAdapter a) throws IOException {
+        writeInt(3988);
+        if (a == null)
+            writeString(null);
+        else {
+            String str = a.getID();
+            if (str == null) {
+                Com.DPrintf("writeAdapter: invalid Adapter id for " + a + "\n");
+            }
+            writeString(str);
+        }
+    }
+
+    /**
+     * Reads the adapter id and returns the adapter.
+     */
+    public SuperAdapter readAdapter() throws IOException {
+        if (readInt() != 3988)
+            Com.DPrintf("wrong read position: readadapter 3988 \n");
+
+        String id = readString();
+
+        if (id == null) {
+            // null adapter. :-)
+            return null;
+        }
+
+        return SuperAdapter.getFromID(id);
+    }
+
+    /**
+     * Writes an item reference.
+     */
+    public void writeItem(gitem_t item) throws IOException {
+        if (item == null)
+            writeInt(-1);
+        else
+            writeInt(item.index);
+    }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/lwjake2/util/Vargs.java b/src/main/java/lwjake2/util/Vargs.java
new file mode 100644 (file)
index 0000000..ddf85a8
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.util;
+
+import java.util.Vector;
+
+/**
+ * Vargs is a helper class to encapsulate printf arguments.
+ *
+ * @author cwei
+ */
+public class Vargs {
+
+    // initial capacity
+    static final int SIZE = 5;
+
+    Vector<Object> v;
+
+    public Vargs() {
+        this(SIZE);
+    }
+
+    public Vargs(int initialSize) {
+        if (v != null)
+            v.clear(); // clear previous list for GC
+        v = new Vector<>(initialSize);
+    }
+
+    public Vargs add(boolean value) {
+        v.add(value);
+        return this;
+    }
+
+    public Vargs add(byte value) {
+        v.add(value);
+        return this;
+    }
+
+    public Vargs add(char value) {
+        v.add(value);
+        return this;
+    }
+
+    public Vargs add(short value) {
+        v.add(value);
+        return this;
+    }
+
+    public Vargs add(int value) {
+        v.add(value);
+        return this;
+    }
+
+    public Vargs add(long value) {
+        v.add(value);
+        return this;
+    }
+
+    public Vargs add(float value) {
+        v.add(value);
+        return this;
+    }
+
+    public Vargs add(double value) {
+        v.add(value);
+        return this;
+    }
+
+    public Vargs add(String value) {
+        v.add(value);
+        return this;
+    }
+
+    public Vargs add(Object value) {
+        v.add(value);
+        return this;
+    }
+
+    public Vargs clear() {
+        v.clear();
+        return this;
+    }
+
+       /* This apparently isn't even used? - flibit
+    public Vector toVector() {
+               //              Vector tmp = v;
+               //              v = null;
+               //              return tmp;
+               return (Vector) v.clone();
+       }
+       */
+
+    public Object[] toArray() {
+        return v.toArray();
+    }
+
+    public int size() {
+        return v.size();
+    }
+}
diff --git a/src/main/java/lwjake2/util/Vec3Cache.java b/src/main/java/lwjake2/util/Vec3Cache.java
new file mode 100644 (file)
index 0000000..d3189d4
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2003 Carsten "cwei" Weisse
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * See the GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+package lwjake2.util;
+
+/**
+ * Vec3Cache contains float[3] for temporary usage.
+ * The usage can reduce the garbage at runtime.
+ *
+ * @author cwei
+ */
+public final class Vec3Cache {
+
+    //private static Stack cache = new Stack();
+    private static final float[][] cache = new float[64][3];
+    private static int index = 0;
+
+    public static float[] get() {
+        //max = Math.max(index, max);
+        return cache[index++];
+    }
+
+    public static void release() {
+        index--;
+    }
+
+    public static void release(int count) {
+        index -= count;
+    }
+
+    public static void debug() {
+        int max = 0;
+        System.err.println("Vec3Cache: max. " + (max + 1) + " vectors used.");
+    }
+}
\ No newline at end of file
diff --git a/src/main/resources/rebel.xml b/src/main/resources/rebel.xml
new file mode 100644 (file)
index 0000000..33f120d
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" generated-by="intellij"
+             xmlns="http://www.zeroturnaround.com"
+             xsi:schemaLocation="http://www.zeroturnaround.com http://update.zeroturnaround.com/jrebel/rebel-2_1.xsd">
+
+    <classpath>
+        <dir name="${rebel.project.path}/target/classes">
+        </dir>
+    </classpath>
+
+</application>
diff --git a/tools/commit and push b/tools/commit and push
new file mode 100755 (executable)
index 0000000..057b511
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+cd "${0%/*}"; if [ "$1" != "T" ]; then gnome-terminal -e "'$0' T"; exit; fi;
+
+cd ..
+
+cola
+git push
+
+echo ""
+echo "Press ENTER to close this window."
+read
diff --git a/tools/debug b/tools/debug
new file mode 100755 (executable)
index 0000000..8482fc9
--- /dev/null
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+if [ "$1" != "T" ]; then gnome-terminal -e "'$0' T"; exit; fi
+
+#
+# This is a helper bash script that starts current Java project in debug mode
+# with JRebel attached. It also opens its own terminal window, so you can run
+# this script by simply clicking on it in file navigator.
+#
+#
+# Script assumes:
+#
+#    + GNU OS 
+#    + Gnome workspace
+#    + JRebel is installed in /opt/jrebel
+#
+
+
+cd "${0%/*}"
+cd ..
+
+
+while true; do
+
+    # clear screen
+    printf "\033c"
+
+    # enable debugging
+    export DEBUG_OPTIONS="-Xdebug -Xrunjdwp:transport=dt_socket,address=127.0.0.1:8000,server=y,suspend=n"
+
+    # enable JRebel
+    export REBEL_BASE="$HOME/.jrebel"
+    export JREBEL_OPTS="-agentpath:/opt/jrebel/libjrebel64.so -Drebel.project.path=`pwd`"
+
+    # enable LWJGL native libraries
+    export LWJGL_OPTS="-Djava.library.path=target/natives"
+    
+    # define Maven options
+    export MAVEN_OPTS="-Xmx4000m $DEBUG_OPTIONS $JREBEL_OPTS $LWJGL_OPTS"
+    
+    mvn compile exec:java -Dexec.mainClass="lwjake2.LWJake2"
+    
+    echo "press ENTER to reload application"
+    read
+
+done
diff --git a/tools/open with IntelliJ IDEA b/tools/open with IntelliJ IDEA
new file mode 100755 (executable)
index 0000000..de9bae5
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+#
+# This is a helper bash script that starts IntelliJ with the current project.
+# Script is written is such a way that you can simply click on it in file
+# navigator to run it.
+#
+#
+# Script assumes:
+#
+#    + GNU operating system
+#    + IntelliJ is installed and commandline launcher "idea" is enabled.
+#
+
+cd "${0%/*}"
+cd ..
+
+setsid idea . &>/dev/null
diff --git a/tools/update web site b/tools/update web site
new file mode 100755 (executable)
index 0000000..bb49510
--- /dev/null
@@ -0,0 +1,10 @@
+#!/bin/bash
+cd "${0%/*}"; if [ "$1" != "T" ]; then gnome-terminal -e "'$0' T"; exit; fi;
+
+cd ..
+
+rsync -avz --delete  -e 'ssh -p 10006' doc/ n0@www3.svjatoslav.eu:/mnt/big/projects/quake2/
+
+echo ""
+echo "Press ENTER to close this window."
+read