From: Svjatoslav Agejenko Date: Sat, 21 Sep 2024 02:06:46 +0000 (+0300) Subject: Initial commit X-Git-Url: http://www2.svjatoslav.eu/gitweb/?a=commitdiff_plain;h=83a2cb83b70a8fd605cb072ff6d9a8444c60b1eb;p=quake2.git Initial commit --- 83a2cb83b70a8fd605cb072ff6d9a8444c60b1eb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2047d25 --- /dev/null +++ b/.gitignore @@ -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 index 0000000..c4a59e8 --- /dev/null +++ b/LWJake2 README.html @@ -0,0 +1,325 @@ + + + +LWJake2 README + + + + + + + + + + + + + +
+

LWJake2 README

+
+

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

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

+
+
+
+

3 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/LWJake2 README.org b/LWJake2 README.org new file mode 100644 index 0000000..a475836 --- /dev/null +++ b/LWJake2 README.org @@ -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 +Carsten Weisse +Rene Stoeckel + +Jake2 Community - Other Jake2 contributors +------------------------------------------ +David Sanders - Original LWJGL implementation + +12characters Games - LWJake2 authors +------------------------------------ +Ethan Lee diff --git a/baseq2 b/baseq2 new file mode 120000 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 index 0000000..f0f224e --- /dev/null +++ b/diagnostic.iml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 0000000..f7b9d7d --- /dev/null +++ b/doc/index.html @@ -0,0 +1,341 @@ + + + +LWJake2 - experiments + + + + + + + + + + + + + +
+

LWJake2 - experiments

+ +
+

1 General

+
+ +
+
+

1.1 Source code

+
+ +
+
+
+ +
+

2 Current status and goals

+
+

+Recent screenshot: +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 CSG instead. +
    • +
    • Add support for other formats (Perhaps Wavefront OBJ). +
    • +
    +
  • + +
  • Learn in the process, how 3D engine is done. + +
  • +
+ + +

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

Author: Svjatoslav Agejenko

+

Created: 2019-01-27 P 00:36

+

Emacs 26.1 (Org-mode 9.1.9)

+
+
+ + diff --git a/doc/index.org b/doc/index.org new file mode 100644 index 0000000..761211e --- /dev/null +++ b/doc/index.org @@ -0,0 +1,67 @@ +#+TITLE: LWJake2 - experiments + +* (document settings) :noexport: +** use dark style for TWBS-HTML exporter +#+HTML_HEAD: +#+HTML_HEAD: +#+HTML_HEAD: +#+HTML_HEAD: + +* 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 + - Carsten Weisse + - Rene Stoeckel + + 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 + + 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 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 index 0000000..8558741 --- /dev/null +++ b/pom.xml @@ -0,0 +1,126 @@ + + 4.0.0 + lwjake2 + quake2 + 1.0-SNAPSHOT + Quake2 + jar + + + + + junit + junit + 4.8.1 + test + + + + org.apache.commons + commons-lang3 + 3.0 + + + + org.lwjgl.lwjgl + lwjgl + 2.9.3 + + + + org.lwjgl.lwjgl + lwjgl_util + 2.9.3 + + + + eu.svjatoslav + javainspect + 1.6 + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + true + UTF-8 + + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9 + + + attach-javadocs + + jar + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + true + lib/ + lwjake2.LWJake2 + + + + + + + com.googlecode.mavennatives + maven-nativedependencies-plugin + 0.0.5 + + + unpacknatives + generate-resources + + copy + + + + + + + + + + + svjatoslav.eu + Svjatoslav repository + http://www2.svjatoslav.eu/maven/ + + + diff --git a/src/main/java/lwjake2/Defines.java b/src/main/java/lwjake2/Defines.java new file mode 100644 index 0000000..8412d89 --- /dev/null +++ b/src/main/java/lwjake2/Defines.java @@ -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; // + 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 = ""; + 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 index 0000000..d04ccbf --- /dev/null +++ b/src/main/java/lwjake2/EFX/EFXEffect.java @@ -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 . + */ + +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 index 0000000..368da99 --- /dev/null +++ b/src/main/java/lwjake2/EFX/EFXEffectEcho.java @@ -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 . + */ + +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 index 0000000..cac408c --- /dev/null +++ b/src/main/java/lwjake2/EFX/EFXEffectReverb.java @@ -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 . + */ + +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 index 0000000..d2b2802 --- /dev/null +++ b/src/main/java/lwjake2/EFX/EFXEffectRingModulator.java @@ -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 . + */ + +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 index 0000000..f7da051 --- /dev/null +++ b/src/main/java/lwjake2/EFX/EFXFilter.java @@ -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 . + */ + +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 index 0000000..67395d4 --- /dev/null +++ b/src/main/java/lwjake2/EFX/EFXFilterLowPass.java @@ -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 . + */ + +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 index 0000000..3ef60f5 --- /dev/null +++ b/src/main/java/lwjake2/Globals.java @@ -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. + *

+ * 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 index 0000000..7612fcc --- /dev/null +++ b/src/main/java/lwjake2/LWJake2.java @@ -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 index 0000000..921598b --- /dev/null +++ b/src/main/java/lwjake2/VisualizeCode.java @@ -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 index 0000000..97138a2 --- /dev/null +++ b/src/main/java/lwjake2/client/CL_ents.java @@ -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 index 0000000..3428d2e --- /dev/null +++ b/src/main/java/lwjake2/client/CL_fx.java @@ -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 index 0000000..06b9e19 --- /dev/null +++ b/src/main/java/lwjake2/client/CL_input.java @@ -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 index 0000000..acc38bb --- /dev/null +++ b/src/main/java/lwjake2/client/CL_inv.java @@ -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 index 0000000..cc27b6a --- /dev/null +++ b/src/main/java/lwjake2/client/CL_newfx.java @@ -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 index 0000000..845c2e1 --- /dev/null +++ b/src/main/java/lwjake2/client/CL_parse.java @@ -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 \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 index 0000000..cd0d7e8 --- /dev/null +++ b/src/main/java/lwjake2/client/CL_pred.java @@ -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 index 0000000..b203be6 --- /dev/null +++ b/src/main/java/lwjake2/client/CL_tent.java @@ -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 index 0000000..75213d9 --- /dev/null +++ b/src/main/java/lwjake2/client/CL_view.java @@ -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 index 0000000..3cd4a26 --- /dev/null +++ b/src/main/java/lwjake2/client/Client.java @@ -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 + *

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

+ * record <demoname> + * 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 \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 \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 + *

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

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

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

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

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

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

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

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

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

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

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

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

+ * 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 index 0000000..cbe3afa --- /dev/null +++ b/src/main/java/lwjake2/client/ClientInfo.java @@ -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 index 0000000..c9b5163 --- /dev/null +++ b/src/main/java/lwjake2/client/ClientStateT.java @@ -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 index 0000000..0187e13 --- /dev/null +++ b/src/main/java/lwjake2/client/ClientStaticT.java @@ -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 index 0000000..12fc6fe --- /dev/null +++ b/src/main/java/lwjake2/client/Console.java @@ -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 \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 index 0000000..9420746 --- /dev/null +++ b/src/main/java/lwjake2/client/Key.java @@ -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 ""; + if (keynum > 32 && keynum < 127) + return Character.toString((char) keynum); + + if (keynames[keynum] != null) + return keynames[keynum]; + + return ""; + } + + /** + * 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 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 cmds = Cmd.CompleteCommand(s); + Vector 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 [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 : 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 index 0000000..a3a99fd --- /dev/null +++ b/src/main/java/lwjake2/client/M.java @@ -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 index 0000000..c3f3eb8 --- /dev/null +++ b/src/main/java/lwjake2/client/Menu.java @@ -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] = ""; + 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] = ""; + 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 index 0000000..25e4d57 --- /dev/null +++ b/src/main/java/lwjake2/client/SCR.java @@ -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 \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 + *

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

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

+ * 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 index 0000000..9292a0f --- /dev/null +++ b/src/main/java/lwjake2/client/V.java @@ -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 index 0000000..3468806 --- /dev/null +++ b/src/main/java/lwjake2/client/VideoDriver.java @@ -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. + *

+ * 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 index 0000000..a11c983 --- /dev/null +++ b/src/main/java/lwjake2/client/centity_t.java @@ -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 index 0000000..0257b01 --- /dev/null +++ b/src/main/java/lwjake2/client/cl_sustain_t.java @@ -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 index 0000000..fcc8c67 --- /dev/null +++ b/src/main/java/lwjake2/client/console_t.java @@ -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 index 0000000..a6ea9db --- /dev/null +++ b/src/main/java/lwjake2/client/cparticle_t.java @@ -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 index 0000000..6cea0a3 --- /dev/null +++ b/src/main/java/lwjake2/client/dlight_t.java @@ -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 index 0000000..892adba --- /dev/null +++ b/src/main/java/lwjake2/client/entity_t.java @@ -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 index 0000000..4e6fa2d --- /dev/null +++ b/src/main/java/lwjake2/client/frame_t.java @@ -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 index 0000000..b86afd4 --- /dev/null +++ b/src/main/java/lwjake2/client/kbutton_t.java @@ -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 index 0000000..28dff1c --- /dev/null +++ b/src/main/java/lwjake2/client/lightstyle_t.java @@ -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 index 0000000..c731c16 --- /dev/null +++ b/src/main/java/lwjake2/client/particle_t.java @@ -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 index 0000000..181945c --- /dev/null +++ b/src/main/java/lwjake2/client/refdef_t.java @@ -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 index 0000000..3a02124 --- /dev/null +++ b/src/main/java/lwjake2/client/refexport_t.java @@ -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 index 0000000..0dd3cef --- /dev/null +++ b/src/main/java/lwjake2/client/viddef_t.java @@ -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 index 0000000..af85d67 --- /dev/null +++ b/src/main/java/lwjake2/client/vidmode_t.java @@ -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 index 0000000..bbbaf91 --- /dev/null +++ b/src/main/java/lwjake2/client/vrect_t.java @@ -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 index 0000000..1b7cf0f --- /dev/null +++ b/src/main/java/lwjake2/game/Client.java @@ -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 index 0000000..e555117 --- /dev/null +++ b/src/main/java/lwjake2/game/Cmd.java @@ -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 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 : 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 + *

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

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

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

+ * Sets client to godmode + *

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

+ * Sets client to notarget + *

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

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

+ * Use an inventory item. + */ + private static void Use_f(EDict ent) { + + } + + /** + * Cmd_Drop_f + *

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

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

+ * 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 CompleteCommand(String partial) { + Vector 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 index 0000000..e280fa4 --- /dev/null +++ b/src/main/java/lwjake2/game/CvarT.java @@ -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 index 0000000..5edd919 --- /dev/null +++ b/src/main/java/lwjake2/game/EDict.java @@ -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 index 0000000..de31128 --- /dev/null +++ b/src/main/java/lwjake2/game/EdictFindFilter.java @@ -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 index 0000000..6e2295f --- /dev/null +++ b/src/main/java/lwjake2/game/EdictIterator.java @@ -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 index 0000000..adb26f9 --- /dev/null +++ b/src/main/java/lwjake2/game/EndianHandler.java @@ -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 index 0000000..2286dfe --- /dev/null +++ b/src/main/java/lwjake2/game/EntBlockedAdapter.java @@ -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 index 0000000..d2495b1 --- /dev/null +++ b/src/main/java/lwjake2/game/EntDodgeAdapter.java @@ -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 index 0000000..28c1030 --- /dev/null +++ b/src/main/java/lwjake2/game/EntInteractAdapter.java @@ -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 index 0000000..b663b05 --- /dev/null +++ b/src/main/java/lwjake2/game/EntThinkAdapter.java @@ -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 index 0000000..04d8c9b --- /dev/null +++ b/src/main/java/lwjake2/game/EntTouchAdapter.java @@ -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 index 0000000..cd79e31 --- /dev/null +++ b/src/main/java/lwjake2/game/EntUseAdapter.java @@ -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 index 0000000..8a8ec4b --- /dev/null +++ b/src/main/java/lwjake2/game/GameAI.java @@ -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. + *

+ * If all clients are either dead or in notarget, sight_client will be null. + *

+ * 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 index 0000000..ca1c79c --- /dev/null +++ b/src/main/java/lwjake2/game/GameBase.java @@ -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. + *

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

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

+ * 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 index 0000000..1dc23d2 --- /dev/null +++ b/src/main/java/lwjake2/game/GameFunc.java @@ -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 + *

+ * movement options: + *

+ * linear smooth start, hard stop smooth start, smooth stop + *

+ * start end acceleration speed deceleration begin sound end sound target + * fired when reaching end wait at end + *

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

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

+ * Plats are always drawn in the extended position, so they will light + * correctly. + *

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

+ * "speed" overrides default 200. "accel" overrides default 500 "lip" + * overrides default 8 pixel lip + *

+ * If the "height" key is set, that will determine the amount the plat + * moves, instead of being implicitly determoveinfoned by the model's + * height. + *

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

+ * "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 index 0000000..63317ea --- /dev/null +++ b/src/main/java/lwjake2/game/GameMisc.java @@ -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) ? + *

+ * 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 index 0000000..a0cee9c --- /dev/null +++ b/src/main/java/lwjake2/game/GameSVCmds.java @@ -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 \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 \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 + *

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

+ *

+ * You can add or remove addresses from the filter list with: + *

+ * addip removeip + *

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

+ * Removeip will only remove an address specified exactly the same way. You + * cannot addip a subnet, then removeip a single host. + *

+ * listip Prints the current list of filters. + *

+ * writeip Dumps "addip " 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. + *

+ * filterban <0 or 1> + *

+ * If 1 (the default), then ip addresses matching the current list will be + * prohibited from entering the game. This is the default setting. + *

+ * 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 index 0000000..d3b2bd9 --- /dev/null +++ b/src/main/java/lwjake2/game/GameSave.java @@ -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 + *

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

+ * This will be called whenever the game goes to a new level, and when the + * user explicitly saves the game. + *

+ * Game information include cross level data, like multi level triggers, + * help computer info, and all client states. + *

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

+ * SpawnEntities will allready have been called on the level the same way it + * was when the level was saved. + *

+ * That is necessary to get the baselines set up identically. + *

+ * The server will have cleared all of the world links before calling + * ReadLevel. + *

+ * 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 index 0000000..9eb69d3 --- /dev/null +++ b/src/main/java/lwjake2/game/GameSpawn.java @@ -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) ? + *

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

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

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

+ * Chain together all entities with a matching team field. + *

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

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

+ * 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 index 0000000..788722c --- /dev/null +++ b/src/main/java/lwjake2/game/GameTarget.java @@ -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 + *

+ * Normal sounds play each time the target is used. The reliable flag can be + * set for crucial voiceovers. + *

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

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

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

+ * Set "sounds" to one of the following: 1) sparks 2) blue water 3) brown + * water 4) slime 5) lava 6) blood + *

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

+ * For monsters: Set direction to the facing you want it to have. + *

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

+ * "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 index 0000000..04acf6a --- /dev/null +++ b/src/main/java/lwjake2/game/GameTrigger.java @@ -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. + *

+ * If nomessage is not set, t will print "1 more.. " etc when triggered and + * "sequence complete" when finished. + *

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

+ * It does dmg points of damage each server frame + *

+ * SILENT supresses playing the sound SLOW changes the damage rate to once + * per second NO_PROTECTION *nothing* stops the damage + *

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

+ * If TRIGGERED, this trigger must be triggered before it is live. + *

+ * sounds 1) secret 2) beep beep 3) large switch 4) + *

+ * "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 index 0000000..0452684 --- /dev/null +++ b/src/main/java/lwjake2/game/GameUtil.java @@ -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. + *

+ * The global "activator" should be set to the entity that initiated the + * firing. + *

+ * If self.delay is set, a DelayedUse entity will be created that will + * actually do the SUB_UseTargets after that many seconds have passed. + *

+ * Centerprints any self.message to the activator. + *

+ * 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 index 0000000..eaff1a6 --- /dev/null +++ b/src/main/java/lwjake2/game/Info.java @@ -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 index 0000000..55f871b --- /dev/null +++ b/src/main/java/lwjake2/game/ItemDropAdapter.java @@ -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 index 0000000..7bebfd2 --- /dev/null +++ b/src/main/java/lwjake2/game/ItemUseAdapter.java @@ -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 index 0000000..7a19d13 --- /dev/null +++ b/src/main/java/lwjake2/game/M_Player.java @@ -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 index 0000000..26d15ca --- /dev/null +++ b/src/main/java/lwjake2/game/Move.java @@ -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 index 0000000..4f4d736 --- /dev/null +++ b/src/main/java/lwjake2/game/PlayerClient.java @@ -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. + *

+ * 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 index 0000000..e4f904c --- /dev/null +++ b/src/main/java/lwjake2/game/PlayerHud.java @@ -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 index 0000000..e8e455d --- /dev/null +++ b/src/main/java/lwjake2/game/PlayerTrail.java @@ -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 index 0000000..2f4f079 --- /dev/null +++ b/src/main/java/lwjake2/game/PlayerView.java @@ -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 index 0000000..a6d094d --- /dev/null +++ b/src/main/java/lwjake2/game/SuperAdapter.java @@ -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 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 index 0000000..68cca88 --- /dev/null +++ b/src/main/java/lwjake2/game/client_persistant_t.java @@ -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 index 0000000..03aaf32 --- /dev/null +++ b/src/main/java/lwjake2/game/client_respawn_t.java @@ -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 index 0000000..71358bf --- /dev/null +++ b/src/main/java/lwjake2/game/cmdalias_t.java @@ -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 index 0000000..3020998 --- /dev/null +++ b/src/main/java/lwjake2/game/cmodel_t.java @@ -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 index 0000000..a431c8f --- /dev/null +++ b/src/main/java/lwjake2/game/cplane_t.java @@ -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 index 0000000..e27b882 --- /dev/null +++ b/src/main/java/lwjake2/game/csurface_t.java @@ -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 index 0000000..1d065bb --- /dev/null +++ b/src/main/java/lwjake2/game/entity_state_t.java @@ -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 index 0000000..4de0539 --- /dev/null +++ b/src/main/java/lwjake2/game/game_import_t.java @@ -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 index 0000000..1298a73 --- /dev/null +++ b/src/main/java/lwjake2/game/game_locals_t.java @@ -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 index 0000000..5256a27 --- /dev/null +++ b/src/main/java/lwjake2/game/gitem_armor_t.java @@ -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 index 0000000..d21b751 --- /dev/null +++ b/src/main/java/lwjake2/game/gitem_t.java @@ -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 index 0000000..3048625 --- /dev/null +++ b/src/main/java/lwjake2/game/level_locals_t.java @@ -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 index 0000000..4e5667a --- /dev/null +++ b/src/main/java/lwjake2/game/link_t.java @@ -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 index 0000000..8ea29eb --- /dev/null +++ b/src/main/java/lwjake2/game/mapsurface_t.java @@ -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 index 0000000..b6cabd8 --- /dev/null +++ b/src/main/java/lwjake2/game/mframe_t.java @@ -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 index 0000000..3af3cd0 --- /dev/null +++ b/src/main/java/lwjake2/game/mmove_t.java @@ -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 index 0000000..84883dd --- /dev/null +++ b/src/main/java/lwjake2/game/monsterinfo_t.java @@ -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 index 0000000..400ef2b --- /dev/null +++ b/src/main/java/lwjake2/game/moveinfo_t.java @@ -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 index 0000000..3da55f9 --- /dev/null +++ b/src/main/java/lwjake2/game/player_state_t.java @@ -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 index 0000000..bda385a --- /dev/null +++ b/src/main/java/lwjake2/game/pmove_state_t.java @@ -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 index 0000000..ca42283 --- /dev/null +++ b/src/main/java/lwjake2/game/pushed_t.java @@ -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 index 0000000..7dc3ddc --- /dev/null +++ b/src/main/java/lwjake2/game/spawn_t.java @@ -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 index 0000000..12006c2 --- /dev/null +++ b/src/main/java/lwjake2/game/spawn_temp_t.java @@ -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 index 0000000..9938e6d --- /dev/null +++ b/src/main/java/lwjake2/game/trace_t.java @@ -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 index 0000000..4858655 --- /dev/null +++ b/src/main/java/lwjake2/game/usercmd_t.java @@ -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 index 0000000..6fc47d0 --- /dev/null +++ b/src/main/java/lwjake2/qcommon/CM.java @@ -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, , , )\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 "*" + + */ + 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 index 0000000..a25b1d1 --- /dev/null +++ b/src/main/java/lwjake2/qcommon/CRC.java @@ -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 index 0000000..bc16d95 --- /dev/null +++ b/src/main/java/lwjake2/qcommon/Com.java @@ -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 index 0000000..745b2e6 --- /dev/null +++ b/src/main/java/lwjake2/qcommon/CommandBuffer.java @@ -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 index 0000000..010e4e6 --- /dev/null +++ b/src/main/java/lwjake2/qcommon/Cvar.java @@ -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 [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. + *

+ * 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 completeVariable(String partial) { + + Vector 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 index 0000000..9a655a2 --- /dev/null +++ b/src/main/java/lwjake2/qcommon/FS.java @@ -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 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 + *

+ * 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 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 /.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 \n"); + return; + } + + // see if the link already exists + for (Iterator 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 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 + // allows the game to run from outside the data tree + // + fs_basedir = Cvar.get("basedir", ".", CVAR_NOSET); + + // + // cddir + // 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 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 index 0000000..76e7aeb --- /dev/null +++ b/src/main/java/lwjake2/qcommon/MD4.java @@ -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. + *

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

+ * 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 index 0000000..5b80768 --- /dev/null +++ b/src/main/java/lwjake2/qcommon/MSG.java @@ -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 index 0000000..32a1e06 --- /dev/null +++ b/src/main/java/lwjake2/qcommon/NetadrT.java @@ -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 index 0000000..6dbc055 --- /dev/null +++ b/src/main/java/lwjake2/qcommon/Netchan.java @@ -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, ). + * + * 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 index 0000000..15c9f8a --- /dev/null +++ b/src/main/java/lwjake2/qcommon/PMove.java @@ -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. + *

+ * 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 index 0000000..84c770a --- /dev/null +++ b/src/main/java/lwjake2/qcommon/QCommon.java @@ -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 index 0000000..578b99d --- /dev/null +++ b/src/main/java/lwjake2/qcommon/SZ.java @@ -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 index 0000000..84e863f --- /dev/null +++ b/src/main/java/lwjake2/qcommon/cmd_function_t.java @@ -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 index 0000000..ac69426 --- /dev/null +++ b/src/main/java/lwjake2/qcommon/longjmpException.java @@ -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 index 0000000..510329c --- /dev/null +++ b/src/main/java/lwjake2/qcommon/lump_t.java @@ -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 index 0000000..d9c11d5 --- /dev/null +++ b/src/main/java/lwjake2/qcommon/netchan_t.java @@ -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 index 0000000..71cff45 --- /dev/null +++ b/src/main/java/lwjake2/qcommon/qfiles.java @@ -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 index 0000000..eeb6322 --- /dev/null +++ b/src/main/java/lwjake2/qcommon/sizebuf_t.java @@ -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 index 0000000..1052e8d --- /dev/null +++ b/src/main/java/lwjake2/qcommon/texinfo_t.java @@ -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 index 0000000..7850141 --- /dev/null +++ b/src/main/java/lwjake2/qcommon/xcommand_t.java @@ -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 index 0000000..b00bc2b --- /dev/null +++ b/src/main/java/lwjake2/render/DummyRenderer.java @@ -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 index 0000000..1232dc6 --- /dev/null +++ b/src/main/java/lwjake2/render/Image.java @@ -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 index 0000000..44b3617 --- /dev/null +++ b/src/main/java/lwjake2/render/LWJGLRenderer.java @@ -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 index 0000000..e409339 --- /dev/null +++ b/src/main/java/lwjake2/render/Model.java @@ -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 index 0000000..4c58289 --- /dev/null +++ b/src/main/java/lwjake2/render/Ref.java @@ -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 index 0000000..e3448ec --- /dev/null +++ b/src/main/java/lwjake2/render/Renderer.java @@ -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 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 index 0000000..26e8710 --- /dev/null +++ b/src/main/java/lwjake2/render/glconfig_t.java @@ -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 index 0000000..3e2614b --- /dev/null +++ b/src/main/java/lwjake2/render/glpoly_t.java @@ -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 index 0000000..ee6ccc7 --- /dev/null +++ b/src/main/java/lwjake2/render/glstate_t.java @@ -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 index 0000000..8f8329d --- /dev/null +++ b/src/main/java/lwjake2/render/lwjgl/Anorms.java @@ -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 index 0000000..90769a7 --- /dev/null +++ b/src/main/java/lwjake2/render/lwjgl/Base.java @@ -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 index 0000000..95ed2a7 --- /dev/null +++ b/src/main/java/lwjake2/render/lwjgl/Draw.java @@ -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 index 0000000..e7449a5 --- /dev/null +++ b/src/main/java/lwjake2/render/lwjgl/Image.java @@ -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>16); + // frac += fracstep; + // } + // frac = 3*(fracstep>>2); + // for (i=0 ; i>16); + // frac += fracstep; + // } + // + // for (i=0 ; i> 1; + // for (j=0 ; j>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 index 0000000..e0d562e --- /dev/null +++ b/src/main/java/lwjake2/render/lwjgl/LWJGLBase.java @@ -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 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 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 index 0000000..d29d1a0 --- /dev/null +++ b/src/main/java/lwjake2/render/lwjgl/Light.java @@ -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 + *

+ * 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 index 0000000..b481e7c --- /dev/null +++ b/src/main/java/lwjake2/render/lwjgl/Main.java @@ -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 index 0000000..d00934b --- /dev/null +++ b/src/main/java/lwjake2/render/lwjgl/Mesh.java @@ -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 + *

+ * 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 index 0000000..d8bebbc --- /dev/null +++ b/src/main/java/lwjake2/render/lwjgl/Misc.java @@ -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 index 0000000..93b27fc --- /dev/null +++ b/src/main/java/lwjake2/render/lwjgl/Model.java @@ -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 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 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 index 0000000..506c59d --- /dev/null +++ b/src/main/java/lwjake2/render/lwjgl/Polygon.java @@ -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 index 0000000..7eb0834 --- /dev/null +++ b/src/main/java/lwjake2/render/lwjgl/Surf.java @@ -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 = ¤tmodel->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 index 0000000..2f56534 --- /dev/null +++ b/src/main/java/lwjake2/render/lwjgl/Warp.java @@ -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 index 0000000..9b88070 --- /dev/null +++ b/src/main/java/lwjake2/render/medge_t.java @@ -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 index 0000000..ca77edf --- /dev/null +++ b/src/main/java/lwjake2/render/mleaf_t.java @@ -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 index 0000000..f046baa --- /dev/null +++ b/src/main/java/lwjake2/render/mmodel_t.java @@ -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 index 0000000..207724b --- /dev/null +++ b/src/main/java/lwjake2/render/mnode_t.java @@ -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 index 0000000..145763f --- /dev/null +++ b/src/main/java/lwjake2/render/msurface_t.java @@ -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 index 0000000..047e0cf --- /dev/null +++ b/src/main/java/lwjake2/render/mtexinfo_t.java @@ -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 index 0000000..a975513 --- /dev/null +++ b/src/main/java/lwjake2/render/mvertex_t.java @@ -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 index 0000000..4d10d09 --- /dev/null +++ b/src/main/java/lwjake2/server/SV.java @@ -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 + *

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

+ * 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 index 0000000..5e0daa8 --- /dev/null +++ b/src/main/java/lwjake2/server/SV_CCMDS.java @@ -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 \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 \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 \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 \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 \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 \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 index 0000000..39982e5 --- /dev/null +++ b/src/main/java/lwjake2/server/SV_ENTS.java @@ -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 { + + /** + * ============================================================================= + *

+ * Build a client frame structure + *

+ * ============================================================================= + */ + + 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 + * 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 + *

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

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

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

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

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

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

+ * 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 index 0000000..7ca15ec --- /dev/null +++ b/src/main/java/lwjake2/server/SV_INIT.java @@ -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 + *

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

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

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

+ * the full syntax is: + *

+ * map [*] $ + + *

+ * command from the console or progs. Map can also be a.cin, .pcx, or .dm2 file. + *

+ * Nextserver is used to allow a cinematic to play, then proceed to + * another level: + *

+ * 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 index 0000000..8deef97 --- /dev/null +++ b/src/main/java/lwjake2/server/SV_SEND.java @@ -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 index 0000000..e4efb94 --- /dev/null +++ b/src/main/java/lwjake2/server/SV_USER.java @@ -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 index 0000000..aa0256a --- /dev/null +++ b/src/main/java/lwjake2/server/SV_WORLD.java @@ -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 index 0000000..c2b5a2d --- /dev/null +++ b/src/main/java/lwjake2/server/Server.java @@ -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. + *

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

+ * 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 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 index 0000000..12f9218 --- /dev/null +++ b/src/main/java/lwjake2/server/areanode_t.java @@ -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 index 0000000..bc9b56a --- /dev/null +++ b/src/main/java/lwjake2/server/challenge_t.java @@ -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 index 0000000..ea3635a --- /dev/null +++ b/src/main/java/lwjake2/server/client_frame_t.java @@ -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 index 0000000..f3344dd --- /dev/null +++ b/src/main/java/lwjake2/server/client_t.java @@ -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 index 0000000..89e7540 --- /dev/null +++ b/src/main/java/lwjake2/server/moveclip_t.java @@ -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 index 0000000..ae27580 --- /dev/null +++ b/src/main/java/lwjake2/server/server_static_t.java @@ -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 index 0000000..3a34a08 --- /dev/null +++ b/src/main/java/lwjake2/server/server_t.java @@ -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 index 0000000..7fd8ef3 --- /dev/null +++ b/src/main/java/lwjake2/sound/DummyDriver.java @@ -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 index 0000000..49a7197 --- /dev/null +++ b/src/main/java/lwjake2/sound/S.java @@ -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 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 index 0000000..2c9cd67 --- /dev/null +++ b/src/main/java/lwjake2/sound/Sound.java @@ -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 index 0000000..76d4186 --- /dev/null +++ b/src/main/java/lwjake2/sound/WaveLoader.java @@ -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 index 0000000..8e2299a --- /dev/null +++ b/src/main/java/lwjake2/sound/lwjgl/Channel.java @@ -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 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 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 index 0000000..e70f99a --- /dev/null +++ b/src/main/java/lwjake2/sound/lwjgl/LWJGLSoundImpl.java @@ -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 index 0000000..02e7c6d --- /dev/null +++ b/src/main/java/lwjake2/sound/lwjgl/PlaySound.java @@ -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 index 0000000..22dbcdd --- /dev/null +++ b/src/main/java/lwjake2/sound/sfx_t.java @@ -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 index 0000000..b780f05 --- /dev/null +++ b/src/main/java/lwjake2/sound/sfxcache_t.java @@ -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 index 0000000..77b938f --- /dev/null +++ b/src/main/java/lwjake2/sound/wavinfo_t.java @@ -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 index 0000000..3b9abfb --- /dev/null +++ b/src/main/java/lwjake2/sys/InputListener.java @@ -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 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 index 0000000..e64eb84 --- /dev/null +++ b/src/main/java/lwjake2/sys/JOGLKBD.java @@ -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 index 0000000..4d89405 --- /dev/null +++ b/src/main/java/lwjake2/sys/KBD.java @@ -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 index 0000000..1cd752e --- /dev/null +++ b/src/main/java/lwjake2/sys/LWJGLKBD.java @@ -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 index 0000000..47fa016 --- /dev/null +++ b/src/main/java/lwjake2/sys/LWJake2InputEvent.java @@ -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 index 0000000..0e704ef --- /dev/null +++ b/src/main/java/lwjake2/sys/NET.java @@ -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 index 0000000..5f9e923 --- /dev/null +++ b/src/main/java/lwjake2/sys/NanoTimer.java @@ -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 index 0000000..1ca47e8 --- /dev/null +++ b/src/main/java/lwjake2/sys/StandardTimer.java @@ -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 index 0000000..eea35d8 --- /dev/null +++ b/src/main/java/lwjake2/sys/Sys.java @@ -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. + *

+ * 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 index 0000000..7b5eeb0 --- /dev/null +++ b/src/main/java/lwjake2/sys/Timer.java @@ -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 index 0000000..572950d --- /dev/null +++ b/src/main/java/lwjake2/sys/UserInputHandler.java @@ -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 index 0000000..3f2ed9b --- /dev/null +++ b/src/main/java/lwjake2/util/Lib.java @@ -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 + *

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

+ * 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 index 0000000..418674b --- /dev/null +++ b/src/main/java/lwjake2/util/Math3D.java @@ -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 index 0000000..c38a0d7 --- /dev/null +++ b/src/main/java/lwjake2/util/PrintfFormat.java @@ -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. + *

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

    + *
  1. is not escaped protected by a matching % or is + * not an escape % character, + *
  2. is not at the end of the format string, and + *
  3. precedes a sequence of characters that parses as + * a valid control specification. + *
+ *

+ * A control specification usually takes the form: + *

 % ['-+ #0]* [0..9]* { . [0..9]* }+
+ *                { [hlL] }+ [idfgGoxXeEcs]
+ * 
+ * There are variants of this basic form that are + * discussed below.

+ *

+ * The format is composed of zero or more directives + * defined as follows: + *

    + *
  • ordinary characters, which are simply copied to + * the output stream; + *
  • escape sequences, which represent non-graphic + * characters; and + *
  • conversion specifications, each of which + * results in the fetching of 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 evaluated but are otherwise ignored. + * In format strings containing the % form of + * conversion specifications, each argument in the + * argument list is used exactly once.

+ *

+ * Conversions can be applied to the nth + * 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 %n$, where n is + * a decimal integer giving the position of the + * argument in the argument list.

+ *

+ * In format strings containing the %n$ + * form of conversion specifications, each argument + * in the argument list is used exactly once.

+ *

+ *

Escape Sequences

+ *

+ * The following table lists escape sequences and + * associated actions on display devices capable of + * the action. + * + * + * + * + * + * + * + * + * + * + * + * + *
SequenceNameDescription
\\backlashNone. + *
\aalertAttempts to alert + * the user through audible or visible + * notification. + *
\bbackspaceMoves the + * printing position to one column before + * the current position, unless the + * current position is the start of a line. + *
\fform-feedMoves the + * printing position to the initial + * printing position of the next logical + * page. + *
\nnewlineMoves the + * printing position to the start of the + * next line. + *
\rcarriage-returnMoves + * the printing position to the start of + * the current line. + *
\ttabMoves the printing + * position to the next implementation- + * defined horizontal tab position. + *
\vvertical-tabMoves the + * printing position to the start of the + * next implementation-defined vertical + * tab position. + *

+ *

Conversion Specifications

+ *

+ * Each conversion specification is introduced by + * the percent sign character (%). After the character + * %, the following appear in sequence:

+ *

+ * Zero or more flags (in any order), which modify the + * meaning of the conversion specification.

+ *

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

+ *

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

+ *

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

+ *

+ * An optional l (ell) specifies that a following + * d, i, o, x, or X conversion character applies to a + * type long argument.

+ *

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

+ *

+ * In format strings containing the %n$ + * form of a conversion specification, a field width + * or precision may be indicated by the sequence + * *m$, 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.

+ *

+ * The format can contain either numbered argument + * specifications (that is, %n$ and + * *m$), or unnumbered argument + * specifications (that is % and *), but normally not + * both. The only exception to this is that %% can + * be mixed with the %n$ form. The + * results of mixing numbered and unnumbered argument + * specifications in a format string are undefined.

+ *

+ *

Flag Characters

+ *

+ * The flags and their meanings are:

+ *
+ *
'
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. + *
-
result of the conversion is left-justified + * within the field. (It will be right-justified + * if this flag is not specified). + *
+
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.) + *
<space>
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. + *
#
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. + *
0
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. + *
+ *

+ *

Conversion Characters

+ *

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

+ *

+ *

+ * The conversion characters and their meanings are: + *

+ *
+ *
d,i
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. + *
o
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. + *
x
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. + *
X
Behaves the same as the x conversion + * character except that letters ABCDEF are + * used instead of abcdef. + *
f
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. + *
e,E
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. + *
g,G
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. + *
c,C
The integer argument is converted to a + * char and the result is written. + *

+ *

s,S
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. + *
%
Write a % character; no argument is + * converted. + *
+ *

+ * If a conversion specification does not match one of + * the above forms, an IllegalArgumentException is + * thrown and the instance of PrintfFormat is not + * created.

+ *

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

+ *

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

+ *

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

+ *

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

+ *

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

+ *

+ * 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:

+ *
    + *
  • %c is the same as %C. + *
  • %s is the same as %S. + *
  • u, p, and n conversion characters. + *
  • %ws format. + *
  • h modifier applied to an n conversion character. + *
  • l (ell) modifier applied to the c, n, or s + * conversion characters. + *
  • ll (ell ell) modifier to d, i, o, u, x, or X + * conversion characters. + *
  • ll (ell ell) modifier to an n conversion + * character. + *
  • c, C, d,i,o,u,x, and X conversion characters + * apply to Byte, Character, Short, Integer, Long + * types. + *
  • f, e, E, g, and G conversion characters apply + * to Float and Double types. + *
  • s and S conversion characters apply to String + * types. + *
  • All other reference types can be formatted + * using the s or S conversion characters only. + *
+ *

+ * Most of this specification is quoted from the Unix + * man page for the sprintf utility.

+ * + * @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 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 + * start and ending at either the end + * of the String s, 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 + * s 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 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(); + } + + /** + *

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

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

    + *
  1. is not escaped protected by a matching % or + * is not an escape % character, + *
  2. is not at the end of the format string, and + *
  3. precedes a sequence of characters that parses + * as a valid control string. + *
+ *

+ * A control string takes the form: + *

 % ['-+ #0]* [0..9]* { . [0..9]* }+
+     *                { [hlL] }+ [idfgGoxXeEcs]
+     * 
+ *

+ * 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 true if the conversion + * uses an * field width; otherwise + * false. + */ + 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 true if the conversion + * uses an * precision; otherwise + * false. + */ + 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. + *

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

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

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

+ * The precision, if set, is the minimum number of + * digits to appear after the radix character. + * Padding is with trailing 0s. + *

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ * 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 true if the conversion + * character is there, and + * false 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 n 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 n 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 n 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 index 0000000..6e3537b --- /dev/null +++ b/src/main/java/lwjake2/util/QuakeFile.java @@ -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 index 0000000..ddf85a8 --- /dev/null +++ b/src/main/java/lwjake2/util/Vargs.java @@ -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 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 index 0000000..d3189d4 --- /dev/null +++ b/src/main/java/lwjake2/util/Vec3Cache.java @@ -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 index 0000000..33f120d --- /dev/null +++ b/src/main/resources/rebel.xml @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/tools/commit and push b/tools/commit and push new file mode 100755 index 0000000..057b511 --- /dev/null +++ b/tools/commit and push @@ -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 index 0000000..8482fc9 --- /dev/null +++ b/tools/debug @@ -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 index 0000000..de9bae5 --- /dev/null +++ b/tools/open with IntelliJ IDEA @@ -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 index 0000000..bb49510 --- /dev/null +++ b/tools/update web site @@ -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