aboutsummaryrefslogtreecommitdiffstats
path: root/src/jake2/client/M.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/jake2/client/M.java')
-rw-r--r--src/jake2/client/M.java573
1 files changed, 573 insertions, 0 deletions
diff --git a/src/jake2/client/M.java b/src/jake2/client/M.java
new file mode 100644
index 0000000..ebd9ebd
--- /dev/null
+++ b/src/jake2/client/M.java
@@ -0,0 +1,573 @@
+/*
+ * M.java
+ * Copyright (C) 2003
+ *
+ * $Id: M.java,v 1.1 2004-07-07 19:58:42 hzi Exp $
+ */
+/*
+Copyright (C) 1997-2001 Id Software, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+package jake2.client;
+
+import jake2.Defines;
+import jake2.game.*;
+import jake2.server.SV;
+import jake2.util.Lib;
+import jake2.util.Math3D;
+
+/**
+ * M
+ */
+public final class M {
+
+ public static void M_CheckGround(edict_t 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;
+ }
+ }
+
+ public static boolean M_CheckBottom(edict_t 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, GameBase.vec3_origin, GameBase.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,
+ GameBase.vec3_origin,
+ GameBase.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
+
+ ===============
+ *///ok
+ public static void M_ChangeYaw(edict_t 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);
+ }
+
+ /*
+ ======================
+ M_MoveToGoal
+ ======================
+ */
+ public static void M_MoveToGoal(edict_t ent, float dist) {
+ edict_t goal = ent.goalentity;
+
+ if (ent.groundentity == null && (ent.flags & (Defines.FL_FLY | Defines.FL_SWIM)) == 0)
+ return;
+
+ // if the next step hits the enemy, return immediately
+ if (ent.enemy != null && SV.SV_CloseEnough(ent, ent.enemy, dist))
+ return;
+
+ // bump around...
+ if ((Lib.rand() & 3) == 1 || !SV.SV_StepDirection(ent, ent.ideal_yaw, dist)) {
+ if (ent.inuse)
+ SV.SV_NewChaseDir(ent, goal, dist);
+ }
+ }
+
+ /*
+ ===============
+ M_walkmove
+ ===============
+ */
+ public static boolean M_walkmove(edict_t ent, float yaw, float dist) {
+ float[] move = { 0, 0, 0 };
+
+ if ((ent.groundentity == null) && (ent.flags & (Defines.FL_FLY | Defines.FL_SWIM)) == 0)
+ return false;
+
+ yaw = (float) (yaw * Math.PI * 2 / 360);
+
+ move[0] = (float) Math.cos(yaw) * dist;
+ move[1] = (float) Math.sin(yaw) * dist;
+ move[2] = 0;
+
+ return SV.SV_movestep(ent, move, true);
+ }
+
+ public static void M_CatagorizePosition(edict_t 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 = Game.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;
+ }
+
+ public static void M_WorldEffects(edict_t ent) {
+ int dmg;
+
+ if (ent.health > 0) {
+ if (0 == (ent.flags & Defines.FL_SWIM)) {
+ if (ent.waterlevel < 3) {
+ ent.air_finished = GameBase.level.time + 12;
+ }
+ else if (ent.air_finished < GameBase.level.time) {
+ // drown!
+ if (ent.pain_debounce_time < GameBase.level.time) {
+ dmg = (int) (2f + 2f * Math.floor(GameBase.level.time - ent.air_finished));
+ if (dmg > 15)
+ dmg = 15;
+ GameUtil.T_Damage(
+ ent,
+ GameBase.g_edicts[0],
+ GameBase.g_edicts[0],
+ GameBase.vec3_origin,
+ ent.s.origin,
+ GameBase.vec3_origin,
+ dmg,
+ 0,
+ Defines.DAMAGE_NO_ARMOR,
+ Defines.MOD_WATER);
+ ent.pain_debounce_time = GameBase.level.time + 1;
+ }
+ }
+ }
+ else {
+ if (ent.waterlevel > 0) {
+ ent.air_finished = GameBase.level.time + 9;
+ }
+ else if (ent.air_finished < GameBase.level.time) {
+ // suffocate!
+ if (ent.pain_debounce_time < GameBase.level.time) {
+ dmg = (int) (2 + 2 * Math.floor(GameBase.level.time - ent.air_finished));
+ if (dmg > 15)
+ dmg = 15;
+ GameUtil.T_Damage(
+ ent,
+ GameBase.g_edicts[0],
+ GameBase.g_edicts[0],
+ GameBase.vec3_origin,
+ ent.s.origin,
+ GameBase.vec3_origin,
+ dmg,
+ 0,
+ Defines.DAMAGE_NO_ARMOR,
+ Defines.MOD_WATER);
+ ent.pain_debounce_time = GameBase.level.time + 1;
+ }
+ }
+ }
+ }
+
+ if (ent.waterlevel == 0) {
+ if ((ent.flags & Defines.FL_INWATER) != 0) {
+ GameBase.gi.sound(ent, Defines.CHAN_BODY, GameBase.gi.soundindex("player/watr_out.wav"), 1, Defines.ATTN_NORM, 0);
+ ent.flags &= ~Defines.FL_INWATER;
+ }
+ return;
+ }
+
+ if ((ent.watertype & Defines.CONTENTS_LAVA) != 0 && 0 == (ent.flags & Defines.FL_IMMUNE_LAVA)) {
+ if (ent.damage_debounce_time < GameBase.level.time) {
+ ent.damage_debounce_time = GameBase.level.time + 0.2f;
+ GameUtil.T_Damage(
+ ent,
+ GameBase.g_edicts[0],
+ GameBase.g_edicts[0],
+ GameBase.vec3_origin,
+ ent.s.origin,
+ GameBase.vec3_origin,
+ 10 * ent.waterlevel,
+ 0,
+ 0,
+ Defines.MOD_LAVA);
+ }
+ }
+ if ((ent.watertype & Defines.CONTENTS_SLIME) != 0 && 0 == (ent.flags & Defines.FL_IMMUNE_SLIME)) {
+ if (ent.damage_debounce_time < GameBase.level.time) {
+ ent.damage_debounce_time = GameBase.level.time + 1;
+ GameUtil.T_Damage(
+ ent,
+ GameBase.g_edicts[0],
+ GameBase.g_edicts[0],
+ GameBase.vec3_origin,
+ ent.s.origin,
+ GameBase.vec3_origin,
+ 4 * ent.waterlevel,
+ 0,
+ 0,
+ Defines.MOD_SLIME);
+ }
+ }
+
+ if (0 == (ent.flags & Defines.FL_INWATER)) {
+ if (0 == (ent.svflags & Defines.SVF_DEADMONSTER)) {
+ if ((ent.watertype & Defines.CONTENTS_LAVA) != 0)
+ if (Lib.random() <= 0.5)
+ GameBase.gi.sound(ent, Defines.CHAN_BODY, GameBase.gi.soundindex("player/lava1.wav"), 1, Defines.ATTN_NORM, 0);
+ else
+ GameBase.gi.sound(ent, Defines.CHAN_BODY, GameBase.gi.soundindex("player/lava2.wav"), 1, Defines.ATTN_NORM, 0);
+ else if ((ent.watertype & Defines.CONTENTS_SLIME) != 0)
+ GameBase.gi.sound(ent, Defines.CHAN_BODY, GameBase.gi.soundindex("player/watr_in.wav"), 1, Defines.ATTN_NORM, 0);
+ else if ((ent.watertype & Defines.CONTENTS_WATER) != 0)
+ GameBase.gi.sound(ent, Defines.CHAN_BODY, GameBase.gi.soundindex("player/watr_in.wav"), 1, Defines.ATTN_NORM, 0);
+ }
+
+ ent.flags |= Defines.FL_INWATER;
+ ent.damage_debounce_time = 0;
+ }
+ }
+
+ public static EntThinkAdapter M_droptofloor = new EntThinkAdapter() {
+ public boolean think(edict_t 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 true;
+
+ Math3D.VectorCopy(trace.endpos, ent.s.origin);
+
+ GameBase.gi.linkentity(ent);
+ M.M_CheckGround(ent);
+ M_CatagorizePosition(ent);
+ return true;
+ }
+ };
+
+ public static void M_SetEffects(edict_t ent) {
+ ent.s.effects &= ~(Defines.EF_COLOR_SHELL | Defines.EF_POWERSCREEN);
+ ent.s.renderfx &= ~(Defines.RF_SHELL_RED | Defines.RF_SHELL_GREEN | Defines.RF_SHELL_BLUE);
+
+ if ((ent.monsterinfo.aiflags & Defines.AI_RESURRECTING) != 0) {
+ ent.s.effects |= Defines.EF_COLOR_SHELL;
+ ent.s.renderfx |= Defines.RF_SHELL_RED;
+ }
+
+ if (ent.health <= 0)
+ return;
+
+ if (ent.powerarmor_time > GameBase.level.time) {
+ if (ent.monsterinfo.power_armor_type == Defines.POWER_ARMOR_SCREEN) {
+ ent.s.effects |= Defines.EF_POWERSCREEN;
+ }
+ else if (ent.monsterinfo.power_armor_type == Defines.POWER_ARMOR_SHIELD) {
+ ent.s.effects |= Defines.EF_COLOR_SHELL;
+ ent.s.renderfx |= Defines.RF_SHELL_GREEN;
+ }
+ }
+ };
+
+ public static void M_MoveFrame(edict_t self) {
+ mmove_t move;
+ int index;
+
+ move = self.monsterinfo.currentmove;
+ self.nextthink = GameBase.level.time + Defines.FRAMETIME;
+
+ if ((self.monsterinfo.nextframe != 0)
+ && (self.monsterinfo.nextframe >= move.firstframe)
+ && (self.monsterinfo.nextframe <= move.lastframe)) {
+ self.s.frame = self.monsterinfo.nextframe;
+ self.monsterinfo.nextframe = 0;
+ }
+ else {
+ if (self.s.frame == move.lastframe) {
+ if (move.endfunc != null) {
+ move.endfunc.think(self);
+
+ // regrab move, endfunc is very likely to change it
+ move = self.monsterinfo.currentmove;
+
+ // check for death
+ if ((self.svflags & Defines.SVF_DEADMONSTER) != 0)
+ return;
+ }
+ }
+
+ if (self.s.frame < move.firstframe || self.s.frame > move.lastframe) {
+ self.monsterinfo.aiflags &= ~Defines.AI_HOLD_FRAME;
+ self.s.frame = move.firstframe;
+ }
+ else {
+ if (0 == (self.monsterinfo.aiflags & Defines.AI_HOLD_FRAME)) {
+ self.s.frame++;
+ if (self.s.frame > move.lastframe)
+ self.s.frame = move.firstframe;
+ }
+ }
+ }
+
+ index = self.s.frame - move.firstframe;
+ if (move.frame[index].ai != null)
+ if (0 == (self.monsterinfo.aiflags & Defines.AI_HOLD_FRAME))
+ move.frame[index].ai.ai(self, move.frame[index].dist * self.monsterinfo.scale);
+ else
+ move.frame[index].ai.ai(self, 0);
+
+ if (move.frame[index].think != null)
+ move.frame[index].think.think(self);
+ }
+
+ public static void M_ReactToDamage(edict_t targ, edict_t attacker) {
+ if ((null != attacker.client) && 0 != (attacker.svflags & Defines.SVF_MONSTER))
+ return;
+
+ if (attacker == targ || attacker == targ.enemy)
+ return;
+
+ // if we are a good guy monster and our attacker is a player
+ // or another good guy, do not get mad at them
+ if (0 != (targ.monsterinfo.aiflags & Defines.AI_GOOD_GUY)) {
+ if (attacker.client != null || (attacker.monsterinfo.aiflags & Defines.AI_GOOD_GUY) != 0)
+ return;
+ }
+
+ // we now know that we are not both good guys
+
+ // if attacker is a client, get mad at them because he's good and we're not
+ if (attacker.client != null) {
+ targ.monsterinfo.aiflags &= ~Defines.AI_SOUND_TARGET;
+
+ // this can only happen in coop (both new and old enemies are clients)
+ // only switch if can't see the current enemy
+ if (targ.enemy != null && targ.enemy.client != null) {
+ if (GameUtil.visible(targ, targ.enemy)) {
+ targ.oldenemy = attacker;
+ return;
+ }
+ targ.oldenemy = targ.enemy;
+ }
+ targ.enemy = attacker;
+ if (0 != (targ.monsterinfo.aiflags & Defines.AI_DUCKED))
+ GameUtil.FoundTarget(targ);
+ return;
+ }
+
+ // it's the same base (walk/swim/fly) type and a different classname and it's not a tank
+ // (they spray too much), get mad at them
+ if (((targ.flags & (Defines.FL_FLY | Defines.FL_SWIM)) == (attacker.flags & (Defines.FL_FLY | Defines.FL_SWIM)))
+ && (Lib.strcmp(targ.classname, attacker.classname) != 0)
+ && (Lib.strcmp(attacker.classname, "monster_tank") != 0)
+ && (Lib.strcmp(attacker.classname, "monster_supertank") != 0)
+ && (Lib.strcmp(attacker.classname, "monster_makron") != 0)
+ && (Lib.strcmp(attacker.classname, "monster_jorg") != 0)) {
+ if (targ.enemy != null && targ.enemy.client != null)
+ targ.oldenemy = targ.enemy;
+ targ.enemy = attacker;
+ if (0 == (targ.monsterinfo.aiflags & Defines.AI_DUCKED))
+ GameUtil.FoundTarget(targ);
+ }
+ // if they *meant* to shoot us, then shoot back
+ else if (attacker.enemy == targ) {
+ if (targ.enemy != null && targ.enemy.client != null)
+ targ.oldenemy = targ.enemy;
+ targ.enemy = attacker;
+ if (0 == (targ.monsterinfo.aiflags & Defines.AI_DUCKED))
+ GameUtil.FoundTarget(targ);
+ }
+ // otherwise get mad at whoever they are mad at (help our buddy) unless it is us!
+ else if (attacker.enemy != null && attacker.enemy != targ) {
+ if (targ.enemy != null && targ.enemy.client != null)
+ targ.oldenemy = targ.enemy;
+ targ.enemy = attacker.enemy;
+ if (0 == (targ.monsterinfo.aiflags & Defines.AI_DUCKED))
+ GameUtil.FoundTarget(targ);
+ }
+ }
+ /** Stops the Flies. */
+ public static EntThinkAdapter M_FliesOff = new EntThinkAdapter() {
+ public boolean think(edict_t self) {
+ self.s.effects &= ~Defines.EF_FLIES;
+ self.s.sound = 0;
+ return true;
+ }
+ };
+ /** Starts the Flies as setting the animation flag in the entity. */
+ public static EntThinkAdapter M_FliesOn = new EntThinkAdapter() {
+ public boolean think(edict_t self) {
+ if (self.waterlevel != 0)
+ return true;
+
+ 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;
+ return true;
+ }
+ };
+ /** Adds some flies after a random time */
+ public static EntThinkAdapter M_FlyCheck = new EntThinkAdapter() {
+ public boolean think(edict_t self) {
+
+ if (self.waterlevel != 0)
+ return true;
+
+ if (Lib.random() > 0.5)
+ return true;
+
+ self.think = M_FliesOn;
+ self.nextthink = GameBase.level.time + 5 + 10 * Lib.random();
+ return true;
+ }
+ };
+
+}