aboutsummaryrefslogtreecommitdiffstats
path: root/src/jake2/game/GameUtil.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/jake2/game/GameUtil.java')
-rw-r--r--src/jake2/game/GameUtil.java3038
1 files changed, 1788 insertions, 1250 deletions
diff --git a/src/jake2/game/GameUtil.java b/src/jake2/game/GameUtil.java
index c76c6b0..90c266c 100644
--- a/src/jake2/game/GameUtil.java
+++ b/src/jake2/game/GameUtil.java
@@ -1,1260 +1,1798 @@
/*
-Copyright (C) 1997-2001 Id Software, Inc.
-
-This program is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-
-See the GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-
-*/
+ * Copyright (C) 1997-2001 Id Software, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.
+ *
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
// Created on 01.11.2003 by RST.
-// $Id: GameUtil.java,v 1.6 2004-09-12 18:25:49 salomo Exp $
-
+// $Id: GameUtil.java,v 1.7 2004-09-22 19:22:05 salomo Exp $
package jake2.game;
+import jake2.Defines;
+import jake2.Globals;
import jake2.client.M;
import jake2.qcommon.Com;
import jake2.util.Lib;
import jake2.util.Math3D;
-public class GameUtil extends GameBase {
-
- public static void checkClassname(edict_t ent) {
-
- if (ent.classname == null) {
- Com.Printf("edict with classname = null: " + ent.index);
- }
- }
-
- /**
- 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_t ent, edict_t activator) {
- edict_t 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= level.time + ent.delay;
- t.think= GameUtilAdapters.Think_Delay;
- t.activator= activator;
- if (activator == null)
- gi.dprintf("Think_Delay with no activator\n");
- t.message= ent.message;
- t.target= ent.target;
- t.killtarget= ent.killtarget;
- return;
- }
-
- //
- // print the message
- //
- if ((ent.message != null) && (activator.svflags & SVF_MONSTER) == 0) {
- gi.centerprintf(activator, "" + ent.message);
- if (ent.noise_index != 0)
- gi.sound(activator, CHAN_AUTO, ent.noise_index, 1, ATTN_NORM, 0);
- else
- gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0);
- }
-
- //
- // kill killtargets
- //
-
- EdictIterator edit= null;
-
- if (ent.killtarget != null) {
- while ((edit= G_Find(edit, findByTarget, ent.killtarget)) != null) {
- t= edit.o;
- G_FreeEdict(t);
- if (!ent.inuse) {
- gi.dprintf("entity was removed while using killtargets\n");
- return;
- }
- }
- }
-
- // fire targets
-
- if (ent.target != null) {
- edit= null;
- while ((edit= G_Find(edit, 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) {
- gi.dprintf("WARNING: Entity used itself.\n");
- }
- else {
- if (t.use != null)
- t.use.use(t, ent, activator);
- }
- if (!ent.inuse) {
- gi.dprintf("entity was removed while using targets\n");
- return;
- }
- }
- }
- }
-
- public static void G_InitEdict(edict_t 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_t G_Spawn() {
- int i;
- edict_t e= null;
-
- for (i= (int) maxclients.value + 1; i < num_edicts; i++) {
- e= 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 || level.time - e.freetime > 0.5)) {
- e= g_edicts[i]= new edict_t(i);
- G_InitEdict(e, i);
- return e;
- }
- }
-
- if (i == game.maxentities)
- gi.error("ED_Alloc: no free edicts");
-
- e= g_edicts[i]= new edict_t(i);
- num_edicts++;
- G_InitEdict(e, i);
- return e;
- }
-
- /**
- * Marks the edict as free
- */
- public static void G_FreeEdict(edict_t ed) {
- gi.unlinkentity(ed); // unlink from world
-
- //if ((ed - g_edicts) <= (maxclients.value + BODY_QUEUE_SIZE))
- if (ed.index <= (maxclients.value + BODY_QUEUE_SIZE)) {
- // gi.dprintf("tried to free special edict\n");
- return;
- }
-
- //memset(ed, 0, sizeof(* ed));
- g_edicts[ed.index]= new edict_t(ed.index);
- //ed.clear();
- ed.classname= "freed";
- ed.freetime= 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_t ent) {
- int i= ent.index;
- g_edicts[i]= new edict_t(i);
- }
-
- public static void G_TouchSolids(edict_t ent) {
- int i, num;
- edict_t touch[]= new edict_t[MAX_EDICTS], hit;
-
- num= gi.BoxEdicts(ent.absmin, ent.absmax, touch, MAX_EDICTS, 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++) {
- hit= touch[i];
- if (!hit.inuse)
- continue;
- if (ent.touch != null) {
- ent.touch.touch(hit, ent, GameBase.dummyplane, null);
- }
- if (!ent.inuse)
- break;
- }
- }
-
- /**
- * Kills all entities that would touch the proposed new positioning
- * of ent. Ent should be unlinked before calling this!
- */
-
- public static boolean KillBox(edict_t ent) {
- trace_t tr;
-
- while (true) {
- tr= gi.trace(ent.s.origin, ent.mins, ent.maxs, ent.s.origin, null, MASK_PLAYERSOLID);
- if (tr.ent == null || tr.ent == g_edicts[0])
- break;
-
- // nail it
- T_Damage(tr.ent, ent, ent, vec3_origin, ent.s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
-
- // if we didn't kill it, fail
- if (tr.ent.solid != 0)
- return false;
- }
-
- return true; // all clear
- }
-
- public static boolean OnSameTeam(edict_t ent1, edict_t ent2) {
- if (0 == ((int) (dmflags.value) & (DF_MODELTEAMS | DF_SKINTEAMS)))
- return false;
-
- if (ClientTeam(ent1).equals(ClientTeam(ent2)))
- return true;
- return false;
- }
-
- static String ClientTeam(edict_t 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) (dmflags.value) & DF_MODELTEAMS) != 0) {
- return value.substring(0, p);
- }
-
- return value.substring(p + 1, value.length());
- }
-
- static void SetRespawn(edict_t ent, float delay) {
- ent.flags |= FL_RESPAWN;
- ent.svflags |= SVF_NOCLIENT;
- ent.solid= SOLID_NOT;
- ent.nextthink= level.time + delay;
- ent.think= GameUtilAdapters.DoRespawn;
- gi.linkentity(ent);
- }
-
- static int ITEM_INDEX(gitem_t item) {
- return item.index;
- }
-
- static edict_t Drop_Item(edict_t ent, gitem_t item) {
- edict_t dropped;
- float[] forward= { 0, 0, 0 };
- float[] right= { 0, 0, 0 };
- float[] offset= { 0, 0, 0 };
-
- dropped= G_Spawn();
-
- dropped.classname= item.classname;
- dropped.item= item;
- dropped.spawnflags= DROPPED_ITEM;
- dropped.s.effects= item.world_model_flags;
- dropped.s.renderfx= RF_GLOW;
- Math3D.VectorSet(dropped.mins, -15, -15, -15);
- Math3D.VectorSet(dropped.maxs, 15, 15, 15);
- gi.setmodel(dropped, dropped.item.world_model);
- dropped.solid= SOLID_TRIGGER;
- dropped.movetype= MOVETYPE_TOSS;
-
- dropped.touch= GameUtilAdapters.drop_temp_touch;
-
- dropped.owner= ent;
-
- if (ent.client != null) {
- trace_t trace;
-
- Math3D.AngleVectors(ent.client.v_angle, forward, right, null);
- Math3D.VectorSet(offset, 24, 0, -16);
- Math3D.G_ProjectSource(ent.s.origin, offset, forward, right, dropped.s.origin);
- trace= gi.trace(ent.s.origin, dropped.mins, dropped.maxs, dropped.s.origin, ent, CONTENTS_SOLID);
- Math3D.VectorCopy(trace.endpos, dropped.s.origin);
- }
- else {
- Math3D.AngleVectors(ent.s.angles, forward, right, null);
- Math3D.VectorCopy(ent.s.origin, dropped.s.origin);
- }
-
- Math3D.VectorScale(forward, 100, dropped.velocity);
- dropped.velocity[2]= 300;
-
- dropped.think= GameUtilAdapters.drop_make_touchable;
- dropped.nextthink= level.time + 1;
-
- gi.linkentity(dropped);
-
- return dropped;
- }
-
- static void ValidateSelectedItem(edict_t ent) {
- gclient_t cl;
-
- cl= ent.client;
-
- if (cl.pers.inventory[cl.pers.selected_item] != 0)
- return; // valid
-
- GameAI.SelectNextItem(ent, -1);
- }
-
- static void Use_Item(edict_t ent, edict_t other, edict_t activator) {
- ent.svflags &= ~SVF_NOCLIENT;
- ent.use= null;
-
- if ((ent.spawnflags & ITEM_NO_TOUCH) != 0) {
- ent.solid= SOLID_BBOX;
- ent.touch= null;
- }
- else {
- ent.solid= SOLID_TRIGGER;
- ent.touch= GameUtilAdapters.Touch_Item;
- }
-
- gi.linkentity(ent);
- }
-
- /*
- ============
- CanDamage
-
- Returns true if the inflictor can directly damage the target. Used for
- explosions and melee attacks.
- ============
- */
- static boolean CanDamage(edict_t targ, edict_t inflictor) {
- float[] dest= { 0, 0, 0 };
- trace_t trace;
-
- // bmodels need special checking because their origin is 0,0,0
- if (targ.movetype == MOVETYPE_PUSH) {
- Math3D.VectorAdd(targ.absmin, targ.absmax, dest);
- Math3D.VectorScale(dest, 0.5f, dest);
- trace= gi.trace(inflictor.s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
- if (trace.fraction == 1.0f)
- return true;
- if (trace.ent == targ)
- return true;
- return false;
- }
-
- trace= gi.trace(inflictor.s.origin, vec3_origin, vec3_origin, targ.s.origin, inflictor, MASK_SOLID);
- if (trace.fraction == 1.0)
- return true;
-
- Math3D.VectorCopy(targ.s.origin, dest);
- dest[0] += 15.0;
- dest[1] += 15.0;
- trace= gi.trace(inflictor.s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
- if (trace.fraction == 1.0)
- return true;
-
- Math3D.VectorCopy(targ.s.origin, dest);
- dest[0] += 15.0;
- dest[1] -= 15.0;
- trace= gi.trace(inflictor.s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
- if (trace.fraction == 1.0)
- return true;
-
- Math3D.VectorCopy(targ.s.origin, dest);
- dest[0] -= 15.0;
- dest[1] += 15.0;
- trace= gi.trace(inflictor.s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
- if (trace.fraction == 1.0)
- return true;
-
- Math3D.VectorCopy(targ.s.origin, dest);
- dest[0] -= 15.0;
- dest[1] -= 15.0;
- trace= gi.trace(inflictor.s.origin, vec3_origin, vec3_origin, dest, inflictor, MASK_SOLID);
- if (trace.fraction == 1.0)
- return true;
-
- return false;
- }
-
- public static void T_Damage(
- edict_t targ,
- edict_t inflictor,
- edict_t attacker,
- float[] dir,
- float[] point,
- float[] normal,
- int damage,
- int knockback,
- int dflags,
- int mod) {
- gclient_t client;
- int take;
- int save;
- int asave;
- int psave;
- int te_sparks;
-
- if (targ.takedamage == 0)
- return;
-
- // friendly fire avoidance
- // if enabled you can't hurt teammates (but you can hurt yourself)
- // knockback still occurs
- if ((targ != attacker)
- && ((deathmatch.value != 0 && 0 != ((int) (dmflags.value) & (DF_MODELTEAMS | DF_SKINTEAMS))) || coop.value != 0)) {
- if (OnSameTeam(targ, attacker)) {
- if (((int) (dmflags.value) & DF_NO_FRIENDLY_FIRE) != 0)
- damage= 0;
- else
- mod |= MOD_FRIENDLY_FIRE;
- }
- }
- meansOfDeath= mod;
-
- // easy mode takes half damage
- if (skill.value == 0 && deathmatch.value == 0 && targ.client != null) {
- damage *= 0.5;
- if (damage == 0)
- damage= 1;
- }
-
- client= targ.client;
-
- if ((dflags & DAMAGE_BULLET) != 0)
- te_sparks= TE_BULLET_SPARKS;
- else
- te_sparks= TE_SPARKS;
-
- Math3D.VectorNormalize(dir);
-
- // bonus damage for suprising a monster
- if (0 == (dflags & DAMAGE_RADIUS)
- && (targ.svflags & SVF_MONSTER) != 0
- && (attacker.client != null)
- && (targ.enemy == null)
- && (targ.health > 0))
- damage *= 2;
-
- if ((targ.flags & FL_NO_KNOCKBACK) != 0)
- knockback= 0;
-
- // figure momentum add
- if (0 == (dflags & DAMAGE_NO_KNOCKBACK)) {
- if ((knockback != 0)
- && (targ.movetype != MOVETYPE_NONE)
- && (targ.movetype != MOVETYPE_BOUNCE)
- && (targ.movetype != MOVETYPE_PUSH)
- && (targ.movetype != MOVETYPE_STOP)) {
- float[] kvel= { 0, 0, 0 };
- float mass;
-
- if (targ.mass < 50)
- mass= 50;
- else
- mass= targ.mass;
-
- if (targ.client != null && attacker == targ)
- Math3D.VectorScale(dir, 1600.0f * (float) knockback / mass, kvel);
- // the rocket jump hack...
- else
- Math3D.VectorScale(dir, 500.0f * (float) knockback / mass, kvel);
-
- Math3D.VectorAdd(targ.velocity, kvel, targ.velocity);
- }
- }
-
- take= damage;
- save= 0;
-
- // check for godmode
- if ((targ.flags & FL_GODMODE) != 0 && 0 == (dflags & DAMAGE_NO_PROTECTION)) {
- take= 0;
- save= damage;
- SpawnDamage(te_sparks, point, normal, save);
- }
-
- // check for invincibility
- if ((client != null && client.invincible_framenum > level.framenum) && 0 == (dflags & DAMAGE_NO_PROTECTION)) {
- if (targ.pain_debounce_time < level.time) {
- gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0);
- targ.pain_debounce_time= level.time + 2;
- }
- take= 0;
- save= damage;
- }
-
- psave= CheckPowerArmor(targ, point, normal, take, dflags);
- take -= psave;
-
- asave= CheckArmor(targ, point, normal, take, te_sparks, dflags);
- take -= asave;
-
- // treat cheat/powerup savings the same as armor
- asave += save;
-
- // team damage avoidance
- if (0 == (dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage(targ, attacker))
- return;
-
- // do the damage
- if (take != 0) {
- if (0 != (targ.svflags & SVF_MONSTER) || (client != null))
- SpawnDamage(TE_BLOOD, point, normal, take);
- else
- SpawnDamage(te_sparks, point, normal, take);
-
- targ.health= targ.health - take;
-
- if (targ.health <= 0) {
- if ((targ.svflags & SVF_MONSTER) != 0 || (client != null))
- targ.flags |= FL_NO_KNOCKBACK;
- Killed(targ, inflictor, attacker, take, point);
- return;
- }
- }
-
- if ((targ.svflags & SVF_MONSTER) != 0) {
- M.M_ReactToDamage(targ, attacker);
- if (0 == (targ.monsterinfo.aiflags & AI_DUCKED) && (take != 0)) {
- targ.pain.pain(targ, attacker, knockback, take);
- // nightmare mode monsters don't go into pain frames often
- if (skill.value == 3)
- targ.pain_debounce_time= level.time + 5;
- }
- }
- else if (client != null) {
- if (((targ.flags & FL_GODMODE) == 0) && (take != 0))
- targ.pain.pain(targ, attacker, knockback, take);
- }
- else if (take != 0) {
- if (targ.pain != null)
- targ.pain.pain(targ, attacker, knockback, take);
- }
-
- // add to the damage inflicted on a player this frame
- // the total will be turned into screen blends and view angle kicks
- // at the end of the frame
- if (client != null) {
- client.damage_parmor += psave;
- client.damage_armor += asave;
- client.damage_blood += take;
- client.damage_knockback += knockback;
- Math3D.VectorCopy(point, client.damage_from);
- }
- }
-
- /*
- ============
- Killed
- ============
- */
- public static void Killed(edict_t targ, edict_t inflictor, edict_t attacker, int damage, float[] point) {
- if (targ.health < -999)
- targ.health= -999;
-
- //Com.Println("Killed:" + targ.classname);
- targ.enemy= attacker;
-
- if ((targ.svflags & SVF_MONSTER) != 0 && (targ.deadflag != DEAD_DEAD)) {
- // targ.svflags |= SVF_DEADMONSTER; // now treat as a different content type
- if (0 == (targ.monsterinfo.aiflags & AI_GOOD_GUY)) {
- level.killed_monsters++;
- if (coop.value != 0 && attacker.client != null)
- attacker.client.resp.score++;
- // medics won't heal monsters that they kill themselves
- if (attacker.classname.equals("monster_medic"))
- targ.owner= attacker;
- }
- }
-
- if (targ.movetype == MOVETYPE_PUSH
- || targ.movetype == MOVETYPE_STOP
- || targ.movetype == MOVETYPE_NONE) { // doors, triggers, etc
- targ.die.die(targ, inflictor, attacker, damage, point);
- return;
- }
-
- if ((targ.svflags & SVF_MONSTER) != 0 && (targ.deadflag != DEAD_DEAD)) {
- targ.touch= null;
- Monster.monster_death_use(targ);
- }
-
- targ.die.die(targ, inflictor, attacker, damage, point);
- }
-
- /*
- ================
- SpawnDamage
- ================
- */
- static void SpawnDamage(int type, float[] origin, float[] normal, int damage) {
- if (damage > 255)
- damage= 255;
- gi.WriteByte(svc_temp_entity);
- gi.WriteByte(type);
- // gi.WriteByte (damage);
- gi.WritePosition(origin);
- gi.WriteDir(normal);
- gi.multicast(origin, MULTICAST_PVS);
- }
-
- static int PowerArmorType(edict_t ent) {
- if (ent.client == null)
- return POWER_ARMOR_NONE;
-
- if (0 == (ent.flags & FL_POWER_ARMOR))
- return POWER_ARMOR_NONE;
-
- if (ent.client.pers.inventory[GameUtilAdapters.power_shield_index] > 0)
- return POWER_ARMOR_SHIELD;
-
- if (ent.client.pers.inventory[GameUtilAdapters.power_screen_index] > 0)
- return POWER_ARMOR_SCREEN;
-
- return POWER_ARMOR_NONE;
- }
-
- static int CheckPowerArmor(edict_t ent, float[] point, float[] normal, int damage, int dflags) {
- gclient_t client;
- int save;
- int power_armor_type;
- int index= 0;
- int damagePerCell;
- int pa_te_type;
- int power= 0;
- int power_used;
-
- if (damage != 0)
- return 0;
-
- client= ent.client;
-
- if ((dflags & DAMAGE_NO_ARMOR) != 0)
- return 0;
-
- if (client != null) {
- power_armor_type= PowerArmorType(ent);
- if (power_armor_type != POWER_ARMOR_NONE) {
- index= ITEM_INDEX(FindItem("Cells"));
- power= client.pers.inventory[index];
- }
- }
- else if ((ent.svflags & SVF_MONSTER) != 0) {
- power_armor_type= ent.monsterinfo.power_armor_type;
- power= ent.monsterinfo.power_armor_power;
- }
- else
- return 0;
-
- if (power_armor_type == POWER_ARMOR_NONE)
- return 0;
- if (power == 0)
- return 0;
-
- if (power_armor_type == POWER_ARMOR_SCREEN) {
- float[] vec= { 0, 0, 0 };
- float dot;
- float[] forward= { 0, 0, 0 };
-
- // only works if damage point is in front
- Math3D.AngleVectors(ent.s.angles, forward, null, null);
- Math3D.VectorSubtract(point, ent.s.origin, vec);
- Math3D.VectorNormalize(vec);
- dot= Math3D.DotProduct(vec, forward);
- if (dot <= 0.3)
- return 0;
-
- damagePerCell= 1;
- pa_te_type= TE_SCREEN_SPARKS;
- damage= damage / 3;
- }
- else {
- damagePerCell= 2;
- pa_te_type= TE_SHIELD_SPARKS;
- damage= (2 * damage) / 3;
- }
-
- save= power * damagePerCell;
-
- if (save == 0)
- return 0;
- if (save > damage)
- save= damage;
-
- SpawnDamage(pa_te_type, point, normal, save);
- ent.powerarmor_time= level.time + 0.2f;
-
- power_used= save / damagePerCell;
-
- if (client != null)
- client.pers.inventory[index] -= power_used;
- else
- ent.monsterinfo.power_armor_power -= power_used;
- return save;
- }
-
- /**
- * The monster is walking it's beat.
- *
- */
- static void ai_walk(edict_t self, float dist) {
- M.M_MoveToGoal(self, dist);
-
- // check for noticing a player
- if (FindTarget(self))
- return;
-
- if ((self.monsterinfo.search != null) && (level.time > self.monsterinfo.idle_time)) {
- if (self.monsterinfo.idle_time != 0) {
- self.monsterinfo.search.think(self);
- self.monsterinfo.idle_time= level.time + 15 + Lib.random() * 15;
- }
- else {
- self.monsterinfo.idle_time= level.time + Lib.random() * 15;
- }
- }
- }
-
- /*
- =============
- range
-
- returns the range catagorization of an entity reletive to self
- 0 melee range, will become hostile even if back is turned
- 1 visibility and infront, or visibility and show hostile
- 2 infront and show hostile
- 3 only triggered by damage
- =============
- */
- static int range(edict_t self, edict_t other) {
- float[] v= { 0, 0, 0 };
- float len;
-
- Math3D.VectorSubtract(self.s.origin, other.s.origin, v);
- len= Math3D.VectorLength(v);
- if (len < MELEE_DISTANCE)
- return RANGE_MELEE;
- if (len < 500)
- return RANGE_NEAR;
- if (len < 1000)
- return RANGE_MID;
- return RANGE_FAR;
- }
-
- /*
- ===============
- FindItemByClassname
-
- ===============
- */
- static gitem_t FindItemByClassname(String classname) {
-
- for (int i= 1; i < game.num_items; i++) {
- gitem_t it= GameAI.itemlist[i];
-
- if (it.classname == null)
- continue;
- if (it.classname.equalsIgnoreCase(classname))
- return it;
- }
-
- return null;
- }
-
- /*
- ===============
- FindItem
- ===============
- */
- //geht.
- static gitem_t FindItem(String pickup_name) {
- for (int i= 1; i < game.num_items; i++) {
- gitem_t it= GameAI.itemlist[i];
-
- if (it.pickup_name == null)
- continue;
- if (it.pickup_name.equalsIgnoreCase(pickup_name))
- return it;
- }
- Com.p("Item not found:" + pickup_name);
- return null;
- }
-
- static int ArmorIndex(edict_t ent) {
- if (ent.client == null)
- return 0;
-
- if (ent.client.pers.inventory[GameUtilAdapters.jacket_armor_index] > 0)
- return GameUtilAdapters.jacket_armor_index;
-
- if (ent.client.pers.inventory[GameUtilAdapters.combat_armor_index] > 0)
- return GameUtilAdapters.combat_armor_index;
-
- if (ent.client.pers.inventory[GameUtilAdapters.body_armor_index] > 0)
- return GameUtilAdapters.body_armor_index;
-
- return 0;
- }
-
- static int CheckArmor(edict_t ent, float[] point, float[] normal, int damage, int te_sparks, int dflags) {
- gclient_t client;
- int save;
- int index;
- gitem_t armor;
-
- if (damage == 0)
- return 0;
-
- client= ent.client;
-
- if (client != null)
- return 0;
-
- if ((dflags & DAMAGE_NO_ARMOR) != 0)
- return 0;
-
- index= ArmorIndex(ent);
-
- if (index == 0)
- return 0;
-
- armor= GameAI.GetItemByIndex(index);
- gitem_armor_t garmor= (gitem_armor_t) armor.info;
-
- if (0 != (dflags & DAMAGE_ENERGY))
- save= (int) Math.ceil(garmor.energy_protection * damage);
- else
- save= (int) Math.ceil(garmor.normal_protection * damage);
-
- if (save >= client.pers.inventory[index])
- save= client.pers.inventory[index];
-
- if (save == 0)
- return 0;
-
- client.pers.inventory[index] -= save;
- SpawnDamage(te_sparks, point, normal, save);
-
- return save;
- }
-
- static void AttackFinished(edict_t self, float time) {
- self.monsterinfo.attack_finished= level.time + time;
- }
-
- /*
- =============
- infront
-
- returns true if the entity is in front (in sight) of self
- =============
- */
- static boolean infront(edict_t self, edict_t other) {
- float[] vec= { 0, 0, 0 };
- float dot;
- float[] forward= { 0, 0, 0 };
-
- Math3D.AngleVectors(self.s.angles, forward, null, null);
- Math3D.VectorSubtract(other.s.origin, self.s.origin, vec);
- Math3D.VectorNormalize(vec);
- dot= Math3D.DotProduct(vec, forward);
-
- if (dot > 0.3)
- return true;
- return false;
- }
-
- /*
- =============
- visible
-
- returns 1 if the entity is visible to self, even if not infront ()
- =============
- */
- public static boolean visible(edict_t self, edict_t 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= gi.trace(spot1, vec3_origin, vec3_origin, spot2, self, MASK_OPAQUE);
-
- if (trace.fraction == 1.0)
- return true;
- return false;
- }
-
- /*
- =================
- AI_SetSightClient
-
- 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_t ent;
- int start, check;
-
- if (level.sight_client == null)
- start= 1;
- else
- start= level.sight_client.index;
-
- check= start;
- while (true) {
- check++;
- if (check > game.maxclients)
- check= 1;
- ent= g_edicts[check];
-
- if (ent.inuse && ent.health > 0 && (ent.flags & FL_NOTARGET) == 0) {
- level.sight_client= ent;
- return; // got one
- }
- if (check == start) {
- level.sight_client= null;
- return; // nobody to see
- }
- }
- }
-
- /*
- =============
- ai_move
-
- Move the specified distance at current facing.
- This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward
- ==============
- */
- static void ai_move(edict_t self, float dist) {
- M.M_walkmove(self, self.s.angles[YAW], dist);
- }
-
- /*
- ===========
- FindTarget
-
- Self is currently not attacking anything, so try to find a target
-
- Returns TRUE if an enemy was sighted
-
- When a player fires a missile, the point of impact becomes a fakeplayer so
- that monsters that see the impact will respond as if they had seen the
- player.
-
- To avoid spending too much time, only a single client (or fakeclient) is
- checked each frame. This means multi player games will have slightly
- slower noticing monsters.
- ============
- */
- static boolean FindTarget(edict_t self) {
- edict_t client;
- boolean heardit;
- int r;
-
- if ((self.monsterinfo.aiflags & AI_GOOD_GUY) != 0) {
- if (self.goalentity != null && self.goalentity.inuse && self.goalentity.classname != null) {
- if (self.goalentity.classname.equals("target_actor"))
- return false;
- }
-
- //FIXME look for monsters?
- return false;
- }
-
- // if we're going to a combat point, just proceed
- if ((self.monsterinfo.aiflags & AI_COMBAT_POINT) != 0)
- return false;
-
- // if the first spawnflag bit is set, the monster will only wake up on
- // really seeing the player, not another monster getting angry or hearing
- // something
-
- // revised behavior so they will wake up if they "see" a player make a noise
- // but not weapon impact/explosion noises
-
- heardit= false;
- if ((level.sight_entity_framenum >= (level.framenum - 1)) && 0 == (self.spawnflags & 1)) {
- client= level.sight_entity;
- if (client.enemy == self.enemy) {
- return false;
- }
- }
- else if (level.sound_entity_framenum >= (level.framenum - 1)) {
- client= level.sound_entity;
- heardit= true;
- }
- else if (null != (self.enemy) && (level.sound2_entity_framenum >= (level.framenum - 1)) && 0 != (self.spawnflags & 1)) {
- client= level.sound2_entity;
- heardit= true;
- }
- else {
- client= level.sight_client;
- if (client == null)
- return false; // no clients to get mad at
- }
-
- // if the entity went away, forget it
- if (!client.inuse)
- return false;
-
- if (client == self.enemy)
- return true; // JDC false;
-
- if (client.client != null) {
- if ((client.flags & FL_NOTARGET) != 0)
- return false;
- }
- else if ((client.svflags & SVF_MONSTER) != 0) {
- if (client.enemy == null)
- return false;
- if ((client.enemy.flags & FL_NOTARGET) != 0)
- return false;
- }
- else if (heardit) {
- if ((client.owner.flags & FL_NOTARGET) != 0)
- return false;
- }
- else
- return false;
-
- if (!heardit) {
- r= range(self, client);
-
- if (r == RANGE_FAR)
- return false;
-
- // this is where we would check invisibility
-
- // is client in an spot too dark to be seen?
- if (client.light_level <= 5)
- return false;
-
- if (!visible(self, client)) {
- return false;
- }
-
- if (r == RANGE_NEAR) {
- if (client.show_hostile < level.time && !infront(self, client)) {
- return false;
- }
- }
- else if (r == RANGE_MID) {
- if (!infront(self, client)) {
- return false;
- }
- }
-
- self.enemy= client;
-
- if (!self.enemy.classname.equals("player_noise")) {
- self.monsterinfo.aiflags &= ~AI_SOUND_TARGET;
-
- if (self.enemy.client == null) {
- self.enemy= self.enemy.enemy;
- if (self.enemy.client == null) {
- self.enemy= null;
- return false;
- }
- }
- }
- }
- else {
- // heard it
- float[] temp= { 0, 0, 0 };
-
- if ((self.spawnflags & 1) != 0) {
- if (!visible(self, client))
- return false;
- }
- else {
- if (!gi.inPHS(self.s.origin, client.s.origin))
- return false;
- }
-
- Math3D.VectorSubtract(client.s.origin, self.s.origin, temp);
-
- if (Math3D.VectorLength(temp) > 1000) // too far to hear
- {
- return false;
- }
-
- // check area portals - if they are different and not connected then we can't hear it
- if (client.areanum != self.areanum)
- if (!gi.AreasConnected(self.areanum, client.areanum))
- return false;
-
- self.ideal_yaw= Math3D.vectoyaw(temp);
- M.M_ChangeYaw(self);
-
- // hunt the sound for a bit; hopefully find the real player
- self.monsterinfo.aiflags |= AI_SOUND_TARGET;
- self.enemy= client;
- }
-
- //
- // got one
- //
- FoundTarget(self);
-
- if (0 == (self.monsterinfo.aiflags & AI_SOUND_TARGET) && (self.monsterinfo.sight != null))
- self.monsterinfo.sight.interact(self, self.enemy);
-
- return true;
- }
-
- // ============================================================================
- //ok
- static void HuntTarget(edict_t self) {
- float[] vec= { 0, 0, 0 };
-
- self.goalentity= self.enemy;
- if ((self.monsterinfo.aiflags & AI_STAND_GROUND) != 0)
- self.monsterinfo.stand.think(self);
- else
- self.monsterinfo.run.think(self);
- Math3D.VectorSubtract(self.enemy.s.origin, self.s.origin, vec);
- self.ideal_yaw= Math3D.vectoyaw(vec);
- // wait a while before first attack
- if (0 == (self.monsterinfo.aiflags & AI_STAND_GROUND))
- AttackFinished(self, 1);
- }
-
- public static void FoundTarget(edict_t self) {
- // let other monsters see this monster for a while
- if (self.enemy.client != null) {
- level.sight_entity= self;
- level.sight_entity_framenum= level.framenum;
- level.sight_entity.light_level= 128;
- }
-
- self.show_hostile= (int) level.time + 1; // wake up other monsters
-
- Math3D.VectorCopy(self.enemy.s.origin, self.monsterinfo.last_sighting);
- self.monsterinfo.trail_time= level.time;
-
- if (self.combattarget == null) {
- HuntTarget(self);
- return;
- }
-
- self.goalentity= self.movetarget= G_PickTarget(self.combattarget);
- if (self.movetarget == null) {
- self.goalentity= self.movetarget= self.enemy;
- HuntTarget(self);
- gi.dprintf("" + self.classname + "at " + Lib.vtos(self.s.origin) + ", combattarget " + self.combattarget + " not found\n");
- return;
- }
-
- // clear out our combattarget, these are a one shot deal
- self.combattarget= null;
- self.monsterinfo.aiflags |= AI_COMBAT_POINT;
-
- // clear the targetname, that point is ours!
- self.movetarget.targetname= null;
- self.monsterinfo.pausetime= 0;
-
- // run for it
- self.monsterinfo.run.think(self);
- }
-
- static boolean CheckTeamDamage(edict_t targ, edict_t attacker) {
- //FIXME make the next line real and uncomment this block
- // if ((ability to damage a teammate == OFF) && (targ's team == attacker's team))
- return false;
- }
-
- /*
- ============
- T_RadiusDamage
- ============
- */
- static void T_RadiusDamage(edict_t inflictor, edict_t attacker, float damage, edict_t ignore, float radius, int mod) {
- float points;
- EdictIterator edictit= null;
-
- float[] v= { 0, 0, 0 };
- float[] dir= { 0, 0, 0 };
-
- while ((edictit= findradius(edictit, inflictor.s.origin, radius)) != null) {
- edict_t ent= edictit.o;
- if (ent == ignore)
- continue;
- if (ent.takedamage == 0)
- continue;
-
- Math3D.VectorAdd(ent.mins, ent.maxs, v);
- Math3D.VectorMA(ent.s.origin, 0.5f, v, v);
- Math3D.VectorSubtract(inflictor.s.origin, v, v);
- points= damage - 0.5f * Math3D.VectorLength(v);
- if (ent == attacker)
- points= points * 0.5f;
- if (points > 0) {
- if (CanDamage(ent, inflictor)) {
- Math3D.VectorSubtract(ent.s.origin, inflictor.s.origin, dir);
- T_Damage(
- ent,
- inflictor,
- attacker,
- dir,
- inflictor.s.origin,
- vec3_origin,
- (int) points,
- (int) points,
- DAMAGE_RADIUS,
- mod);
- }
- }
- }
- }
-}
+public class GameUtil {
+
+ public static void checkClassname(edict_t ent) {
+
+ if (ent.classname == null) {
+ Com.Printf("edict with classname = null: " + ent.index);
+ }
+ }
+
+ /**
+ * 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_t ent, edict_t activator) {
+ edict_t 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 = GameUtil.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;
+ t.killtarget = ent.killtarget;
+ 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;
+
+ if (ent.killtarget != null) {
+ while ((edit = GameBase.G_Find(edit, GameBase.findByTarget,
+ ent.killtarget)) != null) {
+ t = edit.o;
+ G_FreeEdict(t);
+ if (!ent.inuse) {
+ GameBase.gi
+ .dprintf("entity was removed while using killtargets\n");
+ return;
+ }
+ }
+ }
+
+ // 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_t 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_t G_Spawn() {
+ int i;
+ edict_t 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_t(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_t(i);
+ GameBase.num_edicts++;
+ G_InitEdict(e, i);
+ return e;
+ }
+
+ /**
+ * Marks the edict as free
+ */
+ public static void G_FreeEdict(edict_t 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;
+ }
+
+ //memset(ed, 0, sizeof(* ed));
+ GameBase.g_edicts[ed.index] = new edict_t(ed.index);
+ //ed.clear();
+ 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_t ent) {
+ int i = ent.index;
+ GameBase.g_edicts[i] = new edict_t(i);
+ }
+
+ public static void G_TouchSolids(edict_t ent) {
+ int i, num;
+ edict_t touch[] = new edict_t[Defines.MAX_EDICTS], hit;
+
+ num = GameBase.gi.BoxEdicts(ent.absmin, ent.absmax, touch,
+ 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++) {
+ hit = touch[i];
+ if (!hit.inuse)
+ continue;
+ if (ent.touch != null) {
+ ent.touch.touch(hit, ent, GameBase.dummyplane, null);
+ }
+ if (!ent.inuse)
+ break;
+ }
+ }
+
+ /**
+ * Kills all entities that would touch the proposed new positioning of ent.
+ * Ent should be unlinked before calling this!
+ */
+
+ public static boolean KillBox(edict_t ent) {
+ trace_t tr;
+
+ while (true) {
+ tr = GameBase.gi.trace(ent.s.origin, ent.mins, ent.maxs,
+ ent.s.origin, null, Defines.MASK_PLAYERSOLID);
+ if (tr.ent == null || tr.ent == GameBase.g_edicts[0])
+ break;
+
+ // nail it
+ T_Damage(tr.ent, ent, ent, Globals.vec3_origin, ent.s.origin,
+ Globals.vec3_origin, 100000, 0,
+ Defines.DAMAGE_NO_PROTECTION, Defines.MOD_TELEFRAG);
+
+ // if we didn't kill it, fail
+ if (tr.ent.solid != 0)
+ return false;
+ }
+
+ return true; // all clear
+ }
+
+ public static boolean OnSameTeam(edict_t ent1, edict_t ent2) {
+ if (0 == ((int) (GameBase.dmflags.value) & (Defines.DF_MODELTEAMS | Defines.DF_SKINTEAMS)))
+ return false;
+
+ if (ClientTeam(ent1).equals(ClientTeam(ent2)))
+ return true;
+ return false;
+ }
+
+ static String ClientTeam(edict_t 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());
+ }
+
+ static void SetRespawn(edict_t ent, float delay) {
+ ent.flags |= Defines.FL_RESPAWN;
+ ent.svflags |= Defines.SVF_NOCLIENT;
+ ent.solid = Defines.SOLID_NOT;
+ ent.nextthink = GameBase.level.time + delay;
+ ent.think = GameUtil.DoRespawn;
+ GameBase.gi.linkentity(ent);
+ }
+
+ static int ITEM_INDEX(gitem_t item) {
+ return item.index;
+ }
+
+ static edict_t Drop_Item(edict_t ent, gitem_t item) {
+ edict_t dropped;
+ float[] forward = { 0, 0, 0 };
+ float[] right = { 0, 0, 0 };
+ float[] offset = { 0, 0, 0 };
+
+ dropped = G_Spawn();
+
+ dropped.classname = item.classname;
+ dropped.item = item;
+ dropped.spawnflags = Defines.DROPPED_ITEM;
+ dropped.s.effects = item.world_model_flags;
+ dropped.s.renderfx = Defines.RF_GLOW;
+ Math3D.VectorSet(dropped.mins, -15, -15, -15);
+ Math3D.VectorSet(dropped.maxs, 15, 15, 15);
+ GameBase.gi.setmodel(dropped, dropped.item.world_model);
+ dropped.solid = Defines.SOLID_TRIGGER;
+ dropped.movetype = Defines.MOVETYPE_TOSS;
+
+ dropped.touch = GameUtil.drop_temp_touch;
+
+ dropped.owner = ent;
+
+ if (ent.client != null) {
+ trace_t trace;
+
+ Math3D.AngleVectors(ent.client.v_angle, forward, right, null);
+ Math3D.VectorSet(offset, 24, 0, -16);
+ Math3D.G_ProjectSource(ent.s.origin, offset, forward, right,
+ dropped.s.origin);
+ trace = GameBase.gi.trace(ent.s.origin, dropped.mins, dropped.maxs,
+ dropped.s.origin, ent, Defines.CONTENTS_SOLID);
+ Math3D.VectorCopy(trace.endpos, dropped.s.origin);
+ } else {
+ Math3D.AngleVectors(ent.s.angles, forward, right, null);
+ Math3D.VectorCopy(ent.s.origin, dropped.s.origin);
+ }
+
+ Math3D.VectorScale(forward, 100, dropped.velocity);
+ dropped.velocity[2] = 300;
+
+ dropped.think = GameUtil.drop_make_touchable;
+ dropped.nextthink = GameBase.level.time + 1;
+
+ GameBase.gi.linkentity(dropped);
+
+ return dropped;
+ }
+
+ static void ValidateSelectedItem(edict_t ent) {
+ gclient_t cl;
+
+ cl = ent.client;
+
+ if (cl.pers.inventory[cl.pers.selected_item] != 0)
+ return; // valid
+
+ GameAI.SelectNextItem(ent, -1);
+ }
+
+ static void Use_Item(edict_t ent, edict_t other, edict_t activator) {
+ ent.svflags &= ~Defines.SVF_NOCLIENT;
+ ent.use = null;
+
+ if ((ent.spawnflags & Defines.ITEM_NO_TOUCH) != 0) {
+ ent.solid = Defines.SOLID_BBOX;
+ ent.touch = null;
+ } else {
+ ent.solid = Defines.SOLID_TRIGGER;
+ ent.touch = GameUtil.Touch_Item;
+ }
+
+ GameBase.gi.linkentity(ent);
+ }
+
+ /*
+ * ============ CanDamage
+ *
+ * Returns true if the inflictor can directly damage the target. Used for
+ * explosions and melee attacks. ============
+ */
+ static boolean CanDamage(edict_t targ, edict_t inflictor) {
+ float[] dest = { 0, 0, 0 };
+ trace_t trace;
+
+ // bmodels need special checking because their origin is 0,0,0
+ if (targ.movetype == Defines.MOVETYPE_PUSH) {
+ Math3D.VectorAdd(targ.absmin, targ.absmax, dest);
+ Math3D.VectorScale(dest, 0.5f, dest);
+ trace = GameBase.gi.trace(inflictor.s.origin, Globals.vec3_origin,
+ Globals.vec3_origin, dest, inflictor, Defines.MASK_SOLID);
+ if (trace.fraction == 1.0f)
+ return true;
+ if (trace.ent == targ)
+ return true;
+ return false;
+ }
+
+ trace = GameBase.gi.trace(inflictor.s.origin, Globals.vec3_origin,
+ Globals.vec3_origin, targ.s.origin, inflictor,
+ Defines.MASK_SOLID);
+ if (trace.fraction == 1.0)
+ return true;
+
+ Math3D.VectorCopy(targ.s.origin, dest);
+ dest[0] += 15.0;
+ dest[1] += 15.0;
+ trace = GameBase.gi.trace(inflictor.s.origin, Globals.vec3_origin,
+ Globals.vec3_origin, dest, inflictor, Defines.MASK_SOLID);
+ if (trace.fraction == 1.0)
+ return true;
+
+ Math3D.VectorCopy(targ.s.origin, dest);
+ dest[0] += 15.0;
+ dest[1] -= 15.0;
+ trace = GameBase.gi.trace(inflictor.s.origin, Globals.vec3_origin,
+ Globals.vec3_origin, dest, inflictor, Defines.MASK_SOLID);
+ if (trace.fraction == 1.0)
+ return true;
+
+ Math3D.VectorCopy(targ.s.origin, dest);
+ dest[0] -= 15.0;
+ dest[1] += 15.0;
+ trace = GameBase.gi.trace(inflictor.s.origin, Globals.vec3_origin,
+ Globals.vec3_origin, dest, inflictor, Defines.MASK_SOLID);
+ if (trace.fraction == 1.0)
+ return true;
+
+ Math3D.VectorCopy(targ.s.origin, dest);
+ dest[0] -= 15.0;
+ dest[1] -= 15.0;
+ trace = GameBase.gi.trace(inflictor.s.origin, Globals.vec3_origin,
+ Globals.vec3_origin, dest, inflictor, Defines.MASK_SOLID);
+ if (trace.fraction == 1.0)
+ return true;
+
+ return false;
+ }
+
+ public static void T_Damage(edict_t targ, edict_t inflictor,
+ edict_t attacker, float[] dir, float[] point, float[] normal,
+ int damage, int knockback, int dflags, int mod) {
+ gclient_t client;
+ int take;
+ int save;
+ int asave;
+ int psave;
+ int te_sparks;
+
+ if (targ.takedamage == 0)
+ return;
+
+ // friendly fire avoidance
+ // if enabled you can't hurt teammates (but you can hurt yourself)
+ // knockback still occurs
+ if ((targ != attacker)
+ && ((GameBase.deathmatch.value != 0 && 0 != ((int) (GameBase.dmflags.value) & (Defines.DF_MODELTEAMS | Defines.DF_SKINTEAMS))) || GameBase.coop.value != 0)) {
+ if (OnSameTeam(targ, attacker)) {
+ if (((int) (GameBase.dmflags.value) & Defines.DF_NO_FRIENDLY_FIRE) != 0)
+ damage = 0;
+ else
+ mod |= Defines.MOD_FRIENDLY_FIRE;
+ }
+ }
+ GameBase.meansOfDeath = mod;
+
+ // easy mode takes half damage
+ if (GameBase.skill.value == 0 && GameBase.deathmatch.value == 0
+ && targ.client != null) {
+ damage *= 0.5;
+ if (damage == 0)
+ damage = 1;
+ }
+
+ client = targ.client;
+
+ if ((dflags & Defines.DAMAGE_BULLET) != 0)
+ te_sparks = Defines.TE_BULLET_SPARKS;
+ else
+ te_sparks = Defines.TE_SPARKS;
+
+ Math3D.VectorNormalize(dir);
+
+ // bonus damage for suprising a monster
+ if (0 == (dflags & Defines.DAMAGE_RADIUS)
+ && (targ.svflags & Defines.SVF_MONSTER) != 0
+ && (attacker.client != null) && (targ.enemy == null)
+ && (targ.health > 0))
+ damage *= 2;
+
+ if ((targ.flags & Defines.FL_NO_KNOCKBACK) != 0)
+ knockback = 0;
+
+ // figure momentum add
+ if (0 == (dflags & Defines.DAMAGE_NO_KNOCKBACK)) {
+ if ((knockback != 0) && (targ.movetype != Defines.MOVETYPE_NONE)
+ && (targ.movetype != Defines.MOVETYPE_BOUNCE)
+ && (targ.movetype != Defines.MOVETYPE_PUSH)
+ && (targ.movetype != Defines.MOVETYPE_STOP)) {
+ float[] kvel = { 0, 0, 0 };
+ float mass;
+
+ if (targ.mass < 50)
+ mass = 50;
+ else
+ mass = targ.mass;
+
+ if (targ.client != null && attacker == targ)
+ Math3D.VectorScale(dir, 1600.0f * (float) knockback / mass,
+ kvel);
+ // the rocket jump hack...
+ else
+ Math3D.VectorScale(dir, 500.0f * (float) knockback / mass,
+ kvel);
+
+ Math3D.VectorAdd(targ.velocity, kvel, targ.velocity);
+ }
+ }
+
+ take = damage;
+ save = 0;
+
+ // check for godmode
+ if ((targ.flags & Defines.FL_GODMODE) != 0
+ && 0 == (dflags & Defines.DAMAGE_NO_PROTECTION)) {
+ take = 0;
+ save = damage;
+ SpawnDamage(te_sparks, point, normal, save);
+ }
+
+ // check for invincibility
+ if ((client != null && client.invincible_framenum > GameBase.level.framenum)
+ && 0 == (dflags & Defines.DAMAGE_NO_PROTECTION)) {
+ if (targ.pain_debounce_time < GameBase.level.time) {
+ GameBase.gi.sound(targ, Defines.CHAN_ITEM, GameBase.gi
+ .soundindex("items/protect4.wav"), 1,
+ Defines.ATTN_NORM, 0);
+ targ.pain_debounce_time = GameBase.level.time + 2;
+ }
+ take = 0;
+ save = damage;
+ }
+
+ psave = CheckPowerArmor(targ, point, normal, take, dflags);
+ take -= psave;
+
+ asave = CheckArmor(targ, point, normal, take, te_sparks, dflags);
+ take -= asave;
+
+ // treat cheat/powerup savings the same as armor
+ asave += save;
+
+ // team damage avoidance
+ if (0 == (dflags & Defines.DAMAGE_NO_PROTECTION)
+ && CheckTeamDamage(targ, attacker))
+ return;
+
+ // do the damage
+ if (take != 0) {
+ if (0 != (targ.svflags & Defines.SVF_MONSTER) || (client != null))
+ SpawnDamage(Defines.TE_BLOOD, point, normal, take);
+ else
+ SpawnDamage(te_sparks, point, normal, take);
+
+ targ.health = targ.health - take;
+
+ if (targ.health <= 0) {
+ if ((targ.svflags & Defines.SVF_MONSTER) != 0
+ || (client != null))
+ targ.flags |= Defines.FL_NO_KNOCKBACK;
+ Killed(targ, inflictor, attacker, take, point);
+ return;
+ }
+ }
+
+ if ((targ.svflags & Defines.SVF_MONSTER) != 0) {
+ M.M_ReactToDamage(targ, attacker);
+ if (0 == (targ.monsterinfo.aiflags & Defines.AI_DUCKED)
+ && (take != 0)) {
+ targ.pain.pain(targ, attacker, knockback, take);
+ // nightmare mode monsters don't go into pain frames often
+ if (GameBase.skill.value == 3)
+ targ.pain_debounce_time = GameBase.level.time + 5;
+ }
+ } else if (client != null) {
+ if (((targ.flags & Defines.FL_GODMODE) == 0) && (take != 0))
+ targ.pain.pain(targ, attacker, knockback, take);
+ } else if (take != 0) {
+ if (targ.pain != null)
+ targ.pain.pain(targ, attacker, knockback, take);
+ }
+
+ // add to the damage inflicted on a player this frame
+ // the total will be turned into screen blends and view angle kicks
+ // at the end of the frame
+ if (client != null) {
+ client.damage_parmor += psave;
+ client.damage_armor += asave;
+ client.damage_blood += take;
+ client.damage_knockback += knockback;
+ Math3D.VectorCopy(point, client.damage_from);
+ }
+ }
+
+ /*
+ * ============ Killed ============
+ */
+ public static void Killed(edict_t targ, edict_t inflictor,
+ edict_t attacker, int damage, float[] point) {
+ if (targ.health < -999)
+ targ.health = -999;
+
+ //Com.Println("Killed:" + targ.classname);
+ targ.enemy = attacker;
+
+ if ((targ.svflags & Defines.SVF_MONSTER) != 0
+ && (targ.deadflag != Defines.DEAD_DEAD)) {
+ // targ.svflags |= SVF_DEADMONSTER; // now treat as a different
+ // content type
+ if (0 == (targ.monsterinfo.aiflags & Defines.AI_GOOD_GUY)) {
+ GameBase.level.killed_monsters++;
+ if (GameBase.coop.value != 0 && attacker.client != null)
+ attacker.client.resp.score++;
+ // medics won't heal monsters that they kill themselves
+ if (attacker.classname.equals("monster_medic"))
+ targ.owner = attacker;
+ }
+ }
+
+ if (targ.movetype == Defines.MOVETYPE_PUSH
+ || targ.movetype == Defines.MOVETYPE_STOP
+ || targ.movetype == Defines.MOVETYPE_NONE) { // doors, triggers,
+ // etc
+ targ.die.die(targ, inflictor, attacker, damage, point);
+ return;
+ }
+
+ if ((targ.svflags & Defines.SVF_MONSTER) != 0
+ && (targ.deadflag != Defines.DEAD_DEAD)) {
+ targ.touch = null;
+ Monster.monster_death_use(targ);
+ }
+
+ targ.die.die(targ, inflictor, attacker, damage, point);
+ }
+
+ /*
+ * ================ SpawnDamage ================
+ */
+ static void SpawnDamage(int type, float[] origin, float[] normal, int damage) {
+ if (damage > 255)
+ damage = 255;
+ GameBase.gi.WriteByte(Defines.svc_temp_entity);
+ GameBase.gi.WriteByte(type);
+ // gi.WriteByte (damage);
+ GameBase.gi.WritePosition(origin);
+ GameBase.gi.WriteDir(normal);
+ GameBase.gi.multicast(origin, Defines.MULTICAST_PVS);
+ }
+
+ static int PowerArmorType(edict_t ent) {
+ if (ent.client == null)
+ return Defines.POWER_ARMOR_NONE;
+
+ if (0 == (ent.flags & Defines.FL_POWER_ARMOR))
+ return Defines.POWER_ARMOR_NONE;
+
+ if (ent.client.pers.inventory[GameUtil.power_shield_index] > 0)
+ return Defines.POWER_ARMOR_SHIELD;
+
+ if (ent.client.pers.inventory[GameUtil.power_screen_index] > 0)
+ return Defines.POWER_ARMOR_SCREEN;
+
+ return Defines.POWER_ARMOR_NONE;
+ }
+
+ static int CheckPowerArmor(edict_t ent, float[] point, float[] normal,
+ int damage, int dflags) {
+ gclient_t client;
+ int save;
+ int power_armor_type;
+ int index = 0;
+ int damagePerCell;
+ int pa_te_type;
+ int power = 0;
+ int power_used;
+
+ if (damage != 0)
+ return 0;
+
+ client = ent.client;
+
+ if ((dflags & Defines.DAMAGE_NO_ARMOR) != 0)
+ return 0;
+
+ if (client != null) {
+ power_armor_type = PowerArmorType(ent);
+ if (power_armor_type != Defines.POWER_ARMOR_NONE) {
+ index = ITEM_INDEX(FindItem("Cells"));
+ power = client.pers.inventory[index];
+ }
+ } else if ((ent.svflags & Defines.SVF_MONSTER) != 0) {
+ power_armor_type = ent.monsterinfo.power_armor_type;
+ power = ent.monsterinfo.power_armor_power;
+ } else
+ return 0;
+
+ if (power_armor_type == Defines.POWER_ARMOR_NONE)
+ return 0;
+ if (power == 0)
+ return 0;
+
+ if (power_armor_type == Defines.POWER_ARMOR_SCREEN) {
+ float[] vec = { 0, 0, 0 };
+ float dot;
+ float[] forward = { 0, 0, 0 };
+
+ // only works if damage point is in front
+ Math3D.AngleVectors(ent.s.angles, forward, null, null);
+ Math3D.VectorSubtract(point, ent.s.origin, vec);
+ Math3D.VectorNormalize(vec);
+ dot = Math3D.DotProduct(vec, forward);
+ if (dot <= 0.3)
+ return 0;
+
+ damagePerCell = 1;
+ pa_te_type = Defines.TE_SCREEN_SPARKS;
+ damage = damage / 3;
+ } else {
+ damagePerCell = 2;
+ pa_te_type = Defines.TE_SHIELD_SPARKS;
+ damage = (2 * damage) / 3;
+ }
+
+ save = power * damagePerCell;
+
+ if (save == 0)
+ return 0;
+ if (save > damage)
+ save = damage;
+
+ SpawnDamage(pa_te_type, point, normal, save);
+ ent.powerarmor_time = GameBase.level.time + 0.2f;
+
+ power_used = save / damagePerCell;
+
+ if (client != null)
+ client.pers.inventory[index] -= power_used;
+ else
+ ent.monsterinfo.power_armor_power -= power_used;
+ return save;
+ }
+
+ /**
+ * The monster is walking it's beat.
+ *
+ */
+ static void ai_walk(edict_t self, float dist) {
+ M.M_MoveToGoal(self, dist);
+
+ // check for noticing a player
+ if (FindTarget(self))
+ return;
+
+ if ((self.monsterinfo.search != null)
+ && (GameBase.level.time > self.monsterinfo.idle_time)) {
+ if (self.monsterinfo.idle_time != 0) {
+ self.monsterinfo.search.think(self);
+ self.monsterinfo.idle_time = GameBase.level.time + 15
+ + Lib.random() * 15;
+ } else {
+ self.monsterinfo.idle_time = GameBase.level.time + Lib.random()
+ * 15;
+ }
+ }
+ }
+
+ /*
+ * ============= range
+ *
+ * returns the range catagorization of an entity reletive to self 0 melee
+ * range, will become hostile even if back is turned 1 visibility and
+ * infront, or visibility and show hostile 2 infront and show hostile 3 only
+ * triggered by damage =============
+ */
+ static int range(edict_t self, edict_t other) {
+ float[] v = { 0, 0, 0 };
+ float len;
+
+ Math3D.VectorSubtract(self.s.origin, other.s.origin, v);
+ len = Math3D.VectorLength(v);
+ if (len < Defines.MELEE_DISTANCE)
+ return Defines.RANGE_MELEE;
+ if (len < 500)
+ return Defines.RANGE_NEAR;
+ if (len < 1000)
+ return Defines.RANGE_MID;
+ return Defines.RANGE_FAR;
+ }
+
+ /*
+ * =============== FindItemByClassname
+ *
+ * ===============
+ */
+ static gitem_t FindItemByClassname(String classname) {
+
+ for (int i = 1; i < GameBase.game.num_items; i++) {
+ gitem_t it = GameAI.itemlist[i];
+
+ if (it.classname == null)
+ continue;
+ if (it.classname.equalsIgnoreCase(classname))
+ return it;
+ }
+
+ return null;
+ }
+
+ /*
+ * =============== FindItem ===============
+ */
+ //geht.
+ static gitem_t FindItem(String pickup_name) {
+ for (int i = 1; i < GameBase.game.num_items; i++) {
+ gitem_t it = GameAI.itemlist[i];
+
+ if (it.pickup_name == null)
+ continue;
+ if (it.pickup_name.equalsIgnoreCase(pickup_name))
+ return it;
+ }
+ Com.p("Item not found:" + pickup_name);
+ return null;
+ }
+
+ static int ArmorIndex(edict_t ent) {
+ if (ent.client == null)
+ return 0;
+
+ if (ent.client.pers.inventory[GameUtil.jacket_armor_index] > 0)
+ return GameUtil.jacket_armor_index;
+
+ if (ent.client.pers.inventory[GameUtil.combat_armor_index] > 0)
+ return GameUtil.combat_armor_index;
+
+ if (ent.client.pers.inventory[GameUtil.body_armor_index] > 0)
+ return GameUtil.body_armor_index;
+
+ return 0;
+ }
+
+ static int CheckArmor(edict_t ent, float[] point, float[] normal,
+ int damage, int te_sparks, int dflags) {
+ gclient_t client;
+ int save;
+ int index;
+ gitem_t armor;
+
+ if (damage == 0)
+ return 0;
+
+ client = ent.client;
+
+ if (client != null)
+ return 0;
+
+ if ((dflags & Defines.DAMAGE_NO_ARMOR) != 0)
+ return 0;
+
+ index = ArmorIndex(ent);
+
+ if (index == 0)
+ return 0;
+
+ armor = GameAI.GetItemByIndex(index);
+ gitem_armor_t garmor = (gitem_armor_t) armor.info;
+
+ if (0 != (dflags & Defines.DAMAGE_ENERGY))
+ save = (int) Math.ceil(garmor.energy_protection * damage);
+ else
+ save = (int) Math.ceil(garmor.normal_protection * damage);
+
+ if (save >= client.pers.inventory[index])
+ save = client.pers.inventory[index];
+
+ if (save == 0)
+ return 0;
+
+ client.pers.inventory[index] -= save;
+ SpawnDamage(te_sparks, point, normal, save);
+
+ return save;
+ }
+
+ static void AttackFinished(edict_t self, float time) {
+ self.monsterinfo.attack_finished = GameBase.level.time + time;
+ }
+
+ /*
+ * ============= infront
+ *
+ * returns true if the entity is in front (in sight) of self =============
+ */
+ static boolean infront(edict_t self, edict_t other) {
+ float[] vec = { 0, 0, 0 };
+ float dot;
+ float[] forward = { 0, 0, 0 };
+
+ Math3D.AngleVectors(self.s.angles, forward, null, null);
+ Math3D.VectorSubtract(other.s.origin, self.s.origin, vec);
+ Math3D.VectorNormalize(vec);
+ dot = Math3D.DotProduct(vec, forward);
+
+ if (dot > 0.3)
+ return true;
+ return false;
+ }
+
+ /*
+ * ============= visible
+ *
+ * returns 1 if the entity is visible to self, even if not infront ()
+ * =============
+ */
+ public static boolean visible(edict_t self, edict_t 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);
+
+ if (trace.fraction == 1.0)
+ return true;
+ return false;
+ }
+
+ /*
+ * ================= AI_SetSightClient
+ *
+ * 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_t 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
+ }
+ }
+ }
+
+ /*
+ * ============= ai_move
+ *
+ * Move the specified distance at current facing. This replaces the QC
+ * functions: ai_forward, ai_back, ai_pain, and ai_painforward
+ * ==============
+ */
+ static void ai_move(edict_t self, float dist) {
+ M.M_walkmove(self, self.s.angles[Defines.YAW], dist);
+ }
+
+ /*
+ * =========== FindTarget
+ *
+ * Self is currently not attacking anything, so try to find a target
+ *
+ * Returns TRUE if an enemy was sighted
+ *
+ * When a player fires a missile, the point of impact becomes a fakeplayer
+ * so that monsters that see the impact will respond as if they had seen the
+ * player.
+ *
+ * To avoid spending too much time, only a single client (or fakeclient) is
+ * checked each frame. This means multi player games will have slightly
+ * slower noticing monsters. ============
+ */
+ static boolean FindTarget(edict_t self) {
+ edict_t client;
+ boolean heardit;
+ int r;
+
+ if ((self.monsterinfo.aiflags & Defines.AI_GOOD_GUY) != 0) {
+ if (self.goalentity != null && self.goalentity.inuse
+ && self.goalentity.classname != null) {
+ if (self.goalentity.classname.equals("target_actor"))
+ return false;
+ }
+
+ //FIXME look for monsters?
+ return false;
+ }
+
+ // if we're going to a combat point, just proceed
+ if ((self.monsterinfo.aiflags & Defines.AI_COMBAT_POINT) != 0)
+ return false;
+
+ // if the first spawnflag bit is set, the monster will only wake up on
+ // really seeing the player, not another monster getting angry or
+ // hearing
+ // something
+
+ // revised behavior so they will wake up if they "see" a player make a
+ // noise
+ // but not weapon impact/explosion noises
+
+ heardit = false;
+ if ((GameBase.level.sight_entity_framenum >= (GameBase.level.framenum - 1))
+ && 0 == (self.spawnflags & 1)) {
+ client = GameBase.level.sight_entity;
+ if (client.enemy == self.enemy) {
+ return false;
+ }
+ } else if (GameBase.level.sound_entity_framenum >= (GameBase.level.framenum - 1)) {
+ client = GameBase.level.sound_entity;
+ heardit = true;
+ } else if (null != (self.enemy)
+ && (GameBase.level.sound2_entity_framenum >= (GameBase.level.framenum - 1))
+ && 0 != (self.spawnflags & 1)) {
+ client = GameBase.level.sound2_entity;
+ heardit = true;
+ } else {
+ client = GameBase.level.sight_client;
+ if (client == null)
+ return false; // no clients to get mad at
+ }
+
+ // if the entity went away, forget it
+ if (!client.inuse)
+ return false;
+
+ if (client == self.enemy)
+ return true; // JDC false;
+
+ if (client.client != null) {
+ if ((client.flags & Defines.FL_NOTARGET) != 0)
+ return false;
+ } else if ((client.svflags & Defines.SVF_MONSTER) != 0) {
+ if (client.enemy == null)
+ return false;
+ if ((client.enemy.flags & Defines.FL_NOTARGET) != 0)
+ return false;
+ } else if (heardit) {
+ if ((client.owner.flags & Defines.FL_NOTARGET) != 0)
+ return false;
+ } else
+ return false;
+
+ if (!heardit) {
+ r = range(self, client);
+
+ if (r == Defines.RANGE_FAR)
+ return false;
+
+ // this is where we would check invisibility
+
+ // is client in an spot too dark to be seen?
+ if (client.light_level <= 5)
+ return false;
+
+ if (!visible(self, client)) {
+ return false;
+ }
+
+ if (r == Defines.RANGE_NEAR) {
+ if (client.show_hostile < GameBase.level.time
+ && !infront(self, client)) {
+ return false;
+ }
+ } else if (r == Defines.RANGE_MID) {
+ if (!infront(self, client)) {
+ return false;
+ }
+ }
+
+ self.enemy = client;
+
+ if (!self.enemy.classname.equals("player_noise")) {
+ self.monsterinfo.aiflags &= ~Defines.AI_SOUND_TARGET;
+
+ if (self.enemy.client == null) {
+ self.enemy = self.enemy.enemy;
+ if (self.enemy.client == null) {
+ self.enemy = null;
+ return false;
+ }
+ }
+ }
+ } else {
+ // heard it
+ float[] temp = { 0, 0, 0 };
+
+ if ((self.spawnflags & 1) != 0) {
+ if (!visible(self, client))
+ return false;
+ } else {
+ if (!GameBase.gi.inPHS(self.s.origin, client.s.origin))
+ return false;
+ }
+
+ Math3D.VectorSubtract(client.s.origin, self.s.origin, temp);
+
+ if (Math3D.VectorLength(temp) > 1000) // too far to hear
+ {
+ return false;
+ }
+
+ // check area portals - if they are different and not connected then
+ // we can't hear it
+ if (client.areanum != self.areanum)
+ if (!GameBase.gi.AreasConnected(self.areanum, client.areanum))
+ return false;
+
+ self.ideal_yaw = Math3D.vectoyaw(temp);
+ M.M_ChangeYaw(self);
+
+ // hunt the sound for a bit; hopefully find the real player
+ self.monsterinfo.aiflags |= Defines.AI_SOUND_TARGET;
+ self.enemy = client;
+ }
+
+ //
+ // got one
+ //
+ FoundTarget(self);
+
+ if (0 == (self.monsterinfo.aiflags & Defines.AI_SOUND_TARGET)
+ && (self.monsterinfo.sight != null))
+ self.monsterinfo.sight.interact(self, self.enemy);
+
+ return true;
+ }
+
+ // ============================================================================
+ //ok
+ static void HuntTarget(edict_t self) {
+ float[] vec = { 0, 0, 0 };
+
+ self.goalentity = self.enemy;
+ if ((self.monsterinfo.aiflags & Defines.AI_STAND_GROUND) != 0)
+ self.monsterinfo.stand.think(self);
+ else
+ self.monsterinfo.run.think(self);
+ Math3D.VectorSubtract(self.enemy.s.origin, self.s.origin, vec);
+ self.ideal_yaw = Math3D.vectoyaw(vec);
+ // wait a while before first attack
+ if (0 == (self.monsterinfo.aiflags & Defines.AI_STAND_GROUND))
+ AttackFinished(self, 1);
+ }
+
+ public static void FoundTarget(edict_t self) {
+ // let other monsters see this monster for a while
+ if (self.enemy.client != null) {
+ GameBase.level.sight_entity = self;
+ GameBase.level.sight_entity_framenum = GameBase.level.framenum;
+ GameBase.level.sight_entity.light_level = 128;
+ }
+
+ self.show_hostile = (int) GameBase.level.time + 1; // wake up other
+ // monsters
+
+ Math3D.VectorCopy(self.enemy.s.origin, self.monsterinfo.last_sighting);
+ self.monsterinfo.trail_time = GameBase.level.time;
+
+ if (self.combattarget == null) {
+ HuntTarget(self);
+ return;
+ }
+
+ self.goalentity = self.movetarget = GameBase
+ .G_PickTarget(self.combattarget);
+ if (self.movetarget == null) {
+ self.goalentity = self.movetarget = self.enemy;
+ HuntTarget(self);
+ GameBase.gi.dprintf("" + self.classname + "at "
+ + Lib.vtos(self.s.origin) + ", combattarget "
+ + self.combattarget + " not found\n");
+ return;
+ }
+
+ // clear out our combattarget, these are a one shot deal
+ self.combattarget = null;
+ self.monsterinfo.aiflags |= Defines.AI_COMBAT_POINT;
+
+ // clear the targetname, that point is ours!
+ self.movetarget.targetname = null;
+ self.monsterinfo.pausetime = 0;
+
+ // run for it
+ self.monsterinfo.run.think(self);
+ }
+
+ static boolean CheckTeamDamage(edict_t targ, edict_t attacker) {
+ //FIXME make the next line real and uncomment this block
+ // if ((ability to damage a teammate == OFF) && (targ's team ==
+ // attacker's team))
+ return false;
+ }
+
+ /*
+ * ============ T_RadiusDamage ============
+ */
+ static void T_RadiusDamage(edict_t inflictor, edict_t attacker,
+ float damage, edict_t ignore, float radius, int mod) {
+ float points;
+ EdictIterator edictit = null;
+
+ float[] v = { 0, 0, 0 };
+ float[] dir = { 0, 0, 0 };
+
+ while ((edictit = GameBase.findradius(edictit, inflictor.s.origin,
+ radius)) != null) {
+ edict_t ent = edictit.o;
+ if (ent == ignore)
+ continue;
+ if (ent.takedamage == 0)
+ continue;
+
+ Math3D.VectorAdd(ent.mins, ent.maxs, v);
+ Math3D.VectorMA(ent.s.origin, 0.5f, v, v);
+ Math3D.VectorSubtract(inflictor.s.origin, v, v);
+ points = damage - 0.5f * Math3D.VectorLength(v);
+ if (ent == attacker)
+ points = points * 0.5f;
+ if (points > 0) {
+ if (CanDamage(ent, inflictor)) {
+ Math3D
+ .VectorSubtract(ent.s.origin, inflictor.s.origin,
+ dir);
+ T_Damage(ent, inflictor, attacker, dir, inflictor.s.origin,
+ Globals.vec3_origin, (int) points, (int) points,
+ Defines.DAMAGE_RADIUS, mod);
+ }
+ }
+ }
+ }
+
+ public static EntThinkAdapter Think_Delay = new EntThinkAdapter() {
+ public boolean think(edict_t ent) {
+ GameUtil.G_UseTargets(ent, ent.activator);
+ GameUtil.G_FreeEdict(ent);
+ return true;
+ }
+ };
+
+ public static EntThinkAdapter G_FreeEdictA = new EntThinkAdapter() {
+ public boolean think(edict_t ent) {
+ GameUtil.G_FreeEdict(ent);
+ return false;
+ }
+ };
+
+ static EntThinkAdapter MegaHealth_think = new EntThinkAdapter() {
+ public boolean think(edict_t self) {
+ if (self.owner.health > self.owner.max_health) {
+ self.nextthink = GameBase.level.time + 1;
+ self.owner.health -= 1;
+ return false;
+ }
+
+ if (!((self.spawnflags & Defines.DROPPED_ITEM) != 0)
+ && (GameBase.deathmatch.value != 0))
+ GameUtil.SetRespawn(self, 20);
+ else
+ GameUtil.G_FreeEdict(self);
+
+ return false;
+ }
+ };
+
+ static EntThinkAdapter DoRespawn = new EntThinkAdapter() {
+ public boolean think(edict_t ent) {
+ if (ent.team != null) {
+ edict_t master;
+ int count;
+ int choice = 0;
+
+ master = ent.teammaster;
+
+ // count the depth
+ for (count = 0, ent = master; ent != null; ent = ent.chain, count++)
+ ;
+
+ choice = Lib.rand() % count;
+
+ for (count = 0, ent = master; count < choice; ent = ent.chain, count++)
+ ;
+ }
+
+ ent.svflags &= ~Defines.SVF_NOCLIENT;
+ ent.solid = Defines.SOLID_TRIGGER;
+ GameBase.gi.linkentity(ent);
+
+ // send an effect
+ ent.s.event = Defines.EV_ITEM_RESPAWN;
+
+ return false;
+ }
+ };
+
+ static EntInteractAdapter Pickup_Pack = new EntInteractAdapter() {
+ public boolean interact(edict_t ent, edict_t other) {
+
+ gitem_t item;
+ int index;
+
+ if (other.client.pers.max_bullets < 300)
+ other.client.pers.max_bullets = 300;
+ if (other.client.pers.max_shells < 200)
+ other.client.pers.max_shells = 200;
+ if (other.client.pers.max_rockets < 100)
+ other.client.pers.max_rockets = 100;
+ if (other.client.pers.max_grenades < 100)
+ other.client.pers.max_grenades = 100;
+ if (other.client.pers.max_cells < 300)
+ other.client.pers.max_cells = 300;
+ if (other.client.pers.max_slugs < 100)
+ other.client.pers.max_slugs = 100;
+
+ item = GameUtil.FindItem("Bullets");
+ if (item != null) {
+ index = GameUtil.ITEM_INDEX(item);
+ other.client.pers.inventory[index] += item.quantity;
+ if (other.client.pers.inventory[index] > other.client.pers.max_bullets)
+ other.client.pers.inventory[index] = other.client.pers.max_bullets;
+ }
+
+ item = GameUtil.FindItem("Shells");
+ if (item != null) {
+ index = GameUtil.ITEM_INDEX(item);
+ other.client.pers.inventory[index] += item.quantity;
+ if (other.client.pers.inventory[index] > other.client.pers.max_shells)
+ other.client.pers.inventory[index] = other.client.pers.max_shells;
+ }
+
+ item = GameUtil.FindItem("Cells");
+ if (item != null) {
+ index = GameUtil.ITEM_INDEX(item);
+ other.client.pers.inventory[index] += item.quantity;
+ if (other.client.pers.inventory[index] > other.client.pers.max_cells)
+ other.client.pers.inventory[index] = other.client.pers.max_cells;
+ }
+
+ item = GameUtil.FindItem("Grenades");
+ if (item != null) {
+ index = GameUtil.ITEM_INDEX(item);
+ other.client.pers.inventory[index] += item.quantity;
+ if (other.client.pers.inventory[index] > other.client.pers.max_grenades)
+ other.client.pers.inventory[index] = other.client.pers.max_grenades;
+ }
+
+ item = GameUtil.FindItem("Rockets");
+ if (item != null) {
+ index = GameUtil.ITEM_INDEX(item);
+ other.client.pers.inventory[index] += item.quantity;
+ if (other.client.pers.inventory[index] > other.client.pers.max_rockets)
+ other.client.pers.inventory[index] = other.client.pers.max_rockets;
+ }
+
+ item = GameUtil.FindItem("Slugs");
+ if (item != null) {
+ index = GameUtil.ITEM_INDEX(item);
+ other.client.pers.inventory[index] += item.quantity;
+ if (other.client.pers.inventory[index] > other.client.pers.max_slugs)
+ other.client.pers.inventory[index] = other.client.pers.max_slugs;
+ }
+
+ if (0 == (ent.spawnflags & Defines.DROPPED_ITEM)
+ && (GameBase.deathmatch.value != 0))
+ GameUtil.SetRespawn(ent, ent.item.quantity);
+
+ return true;
+ }
+ };
+
+ final static EntInteractAdapter Pickup_Health = new EntInteractAdapter() {
+ public boolean interact(edict_t ent, edict_t other) {
+
+ if (0 == (ent.style & Defines.HEALTH_IGNORE_MAX))
+ if (other.health >= other.max_health)
+ return false;
+
+ other.health += ent.count;
+
+ if (0 == (ent.style & Defines.HEALTH_IGNORE_MAX)) {
+ if (other.health > other.max_health)
+ other.health = other.max_health;
+ }
+
+ if (0 != (ent.style & Defines.HEALTH_TIMED)) {
+ ent.think = MegaHealth_think;
+ ent.nextthink = GameBase.level.time + 5f;
+ ent.owner = other;
+ ent.flags |= Defines.FL_RESPAWN;
+ ent.svflags |= Defines.SVF_NOCLIENT;
+ ent.solid = Defines.SOLID_NOT;
+ } else {
+ if (!((ent.spawnflags & Defines.DROPPED_ITEM) != 0)
+ && (GameBase.deathmatch.value != 0))
+ GameUtil.SetRespawn(ent, 30);
+ }
+
+ return true;
+ }
+
+ };
+
+ /*
+ * =============== Touch_Item ===============
+ */
+
+ static EntTouchAdapter Touch_Item = new EntTouchAdapter() {
+ public void touch(edict_t ent, edict_t other, cplane_t plane,
+ csurface_t surf) {
+ boolean taken;
+
+ if (ent.classname.equals("item_breather"))
+ taken = false;
+
+ if (other.client == null)
+ return;
+ if (other.health < 1)
+ return; // dead people can't pickup
+ if (ent.item.pickup == null)
+ return; // not a grabbable item?
+
+ taken = ent.item.pickup.interact(ent, other);
+
+ if (taken) {
+ // flash the screen
+ other.client.bonus_alpha = 0.25f;
+
+ // show icon and name on status bar
+ other.client.ps.stats[Defines.STAT_PICKUP_ICON] = (short) GameBase.gi
+ .imageindex(ent.item.icon);
+ other.client.ps.stats[Defines.STAT_PICKUP_STRING] = (short) (Defines.CS_ITEMS + GameUtil
+ .ITEM_INDEX(ent.item));
+ other.client.pickup_msg_time = GameBase.level.time + 3.0f;
+
+ // change selected item
+ if (ent.item.use != null)
+ other.client.pers.selected_item = other.client.ps.stats[Defines.STAT_SELECTED_ITEM] = (short) GameUtil
+ .ITEM_INDEX(ent.item);
+
+ if (ent.item.pickup == Pickup_Health) {
+ if (ent.count == 2)
+ GameBase.gi.sound(other, Defines.CHAN_ITEM, GameBase.gi
+ .soundindex("items/s_health.wav"), 1,
+ Defines.ATTN_NORM, 0);
+ else if (ent.count == 10)
+ GameBase.gi.sound(other, Defines.CHAN_ITEM, GameBase.gi
+ .soundindex("items/n_health.wav"), 1,
+ Defines.ATTN_NORM, 0);
+ else if (ent.count == 25)
+ GameBase.gi.sound(other, Defines.CHAN_ITEM, GameBase.gi
+ .soundindex("items/l_health.wav"), 1,
+ Defines.ATTN_NORM, 0);
+ else
+ // (ent.count == 100)
+ GameBase.gi.sound(other, Defines.CHAN_ITEM, GameBase.gi
+ .soundindex("items/m_health.wav"), 1,
+ Defines.ATTN_NORM, 0);
+ } else if (ent.item.pickup_sound != null) {
+ GameBase.gi.sound(other, Defines.CHAN_ITEM, GameBase.gi
+ .soundindex(ent.item.pickup_sound), 1,
+ Defines.ATTN_NORM, 0);
+ }
+ }
+
+ if (0 == (ent.spawnflags & Defines.ITEM_TARGETS_USED)) {
+ GameUtil.G_UseTargets(ent, other);
+ ent.spawnflags |= Defines.ITEM_TARGETS_USED;
+ }
+
+ if (!taken)
+ return;
+ //Com.p("Picked up:" + ent.classname);
+
+ if (!((GameBase.coop.value != 0) && (ent.item.flags & Defines.IT_STAY_COOP) != 0)
+ || 0 != (ent.spawnflags & (Defines.DROPPED_ITEM | Defines.DROPPED_PLAYER_ITEM))) {
+ if ((ent.flags & Defines.FL_RESPAWN) != 0)
+ ent.flags &= ~Defines.FL_RESPAWN;
+ else
+ GameUtil.G_FreeEdict(ent);
+ }
+ }
+ };
+
+ static EntTouchAdapter drop_temp_touch = new EntTouchAdapter() {
+ public void touch(edict_t ent, edict_t other, cplane_t plane,
+ csurface_t surf) {
+ if (other == ent.owner)
+ return;
+
+ Touch_Item.touch(ent, other, plane, surf);
+ }
+ };
+
+ static EntThinkAdapter drop_make_touchable = new EntThinkAdapter() {
+ public boolean think(edict_t ent) {
+ ent.touch = Touch_Item;
+ if (GameBase.deathmatch.value != 0) {
+ ent.nextthink = GameBase.level.time + 29;
+ ent.think = G_FreeEdictA;
+ }
+ return false;
+ }
+ };
+
+ static int quad_drop_timeout_hack = 0;
+
+ static ItemUseAdapter Use_Quad = new ItemUseAdapter() {
+
+ public void use(edict_t ent, gitem_t item) {
+ int timeout;
+
+ ent.client.pers.inventory[GameUtil.ITEM_INDEX(item)]--;
+ GameUtil.ValidateSelectedItem(ent);
+
+ if (quad_drop_timeout_hack != 0) {
+ timeout = quad_drop_timeout_hack;
+ quad_drop_timeout_hack = 0;
+ } else {
+ timeout = 300;
+ }
+
+ if (ent.client.quad_framenum > GameBase.level.framenum)
+ ent.client.quad_framenum += timeout;
+ else
+ ent.client.quad_framenum = GameBase.level.framenum + timeout;
+
+ GameBase.gi.sound(ent, Defines.CHAN_ITEM, GameBase.gi
+ .soundindex("items/damage.wav"), 1, Defines.ATTN_NORM, 0);
+ }
+ };
+
+ static ItemUseAdapter Use_Invulnerability = new ItemUseAdapter() {
+ public void use(edict_t ent, gitem_t item) {
+ ent.client.pers.inventory[GameUtil.ITEM_INDEX(item)]--;
+ GameUtil.ValidateSelectedItem(ent);
+
+ if (ent.client.invincible_framenum > GameBase.level.framenum)
+ ent.client.invincible_framenum += 300;
+ else
+ ent.client.invincible_framenum = GameBase.level.framenum + 300;
+
+ GameBase.gi.sound(ent, Defines.CHAN_ITEM, GameBase.gi
+ .soundindex("items/protect.wav"), 1, Defines.ATTN_NORM, 0);
+ }
+ };
+
+ // ======================================================================
+
+ static ItemUseAdapter Use_Breather = new ItemUseAdapter() {
+ public void use(edict_t ent, gitem_t item) {
+ ent.client.pers.inventory[GameUtil.ITEM_INDEX(item)]--;
+
+ GameUtil.ValidateSelectedItem(ent);
+
+ if (ent.client.breather_framenum > GameBase.level.framenum)
+ ent.client.breather_framenum += 300;
+ else
+ ent.client.breather_framenum = GameBase.level.framenum + 300;
+
+ GameBase.gi.sound(ent, Defines.CHAN_ITEM, GameBase.gi
+ .soundindex("items/damage.wav"), 1, Defines.ATTN_NORM, 0);
+ }
+ };
+
+ // ======================================================================
+
+ static ItemUseAdapter Use_Envirosuit = new ItemUseAdapter() {
+ public void use(edict_t ent, gitem_t item) {
+ ent.client.pers.inventory[GameUtil.ITEM_INDEX(item)]--;
+ GameUtil.ValidateSelectedItem(ent);
+
+ if (ent.client.enviro_framenum > GameBase.level.framenum)
+ ent.client.enviro_framenum += 300;
+ else
+ ent.client.enviro_framenum = GameBase.level.framenum + 300;
+
+ GameBase.gi.sound(ent, Defines.CHAN_ITEM, GameBase.gi
+ .soundindex("items/damage.wav"), 1, Defines.ATTN_NORM, 0);
+ }
+ };
+
+ // ======================================================================
+
+ static ItemUseAdapter Use_Silencer = new ItemUseAdapter() {
+ public void use(edict_t ent, gitem_t item) {
+
+ ent.client.pers.inventory[GameUtil.ITEM_INDEX(item)]--;
+ GameUtil.ValidateSelectedItem(ent);
+ ent.client.silencer_shots += 30;
+
+ GameBase.gi.sound(ent, Defines.CHAN_ITEM, GameBase.gi
+ .soundindex("items/damage.wav"), 1, Defines.ATTN_NORM, 0);
+ }
+ };
+
+ // ======================================================================
+
+ static EntInteractAdapter Pickup_Key = new EntInteractAdapter() {
+ public boolean interact(edict_t ent, edict_t other) {
+ if (GameBase.coop.value != 0) {
+ if (Lib.strcmp(ent.classname, "key_power_cube") == 0) {
+ if ((other.client.pers.power_cubes & ((ent.spawnflags & 0x0000ff00) >> 8)) != 0)
+ return false;
+ other.client.pers.inventory[GameUtil.ITEM_INDEX(ent.item)]++;
+ other.client.pers.power_cubes |= ((ent.spawnflags & 0x0000ff00) >> 8);
+ } else {
+ if (other.client.pers.inventory[GameUtil
+ .ITEM_INDEX(ent.item)] != 0)
+ return false;
+ other.client.pers.inventory[GameUtil.ITEM_INDEX(ent.item)] = 1;
+ }
+ return true;
+ }
+ other.client.pers.inventory[GameUtil.ITEM_INDEX(ent.item)]++;
+ return true;
+ }
+ };
+
+ static int jacket_armor_index;
+
+ static int combat_armor_index;
+
+ static int body_armor_index;
+
+ static int power_screen_index;
+
+ static int power_shield_index;
+
+ /*
+ * ============= range
+ *
+ * returns the range catagorization of an entity reletive to self. 0 melee
+ * range, will become hostile even if back is turned 1 visibility and
+ * infront, or visibility and show hostile 2 infront and show hostile 3 only
+ * triggered by damage
+ *
+ */
+ // static int range(edict_t self, edict_t other)
+ // {
+ // float[] v= { 0, 0, 0 };
+ // float len;
+ //
+ // VectorSubtract(self.s.origin, other.s.origin, v);
+ // len= VectorLength(v);
+ // if (len < MELEE_DISTANCE)
+ // return RANGE_MELEE;
+ // if (len < 500)
+ // return RANGE_NEAR;
+ // if (len < 1000)
+ // return RANGE_MID;
+ // return RANGE_FAR;
+ // }
+ // ============================================================================
+ static EntThinkAdapter M_CheckAttack = new EntThinkAdapter() {
+
+ public boolean think(edict_t self) {
+ float[] spot1 = { 0, 0, 0 };
+
+ float[] spot2 = { 0, 0, 0 };
+ float chance;
+ trace_t tr;
+
+ if (self.enemy.health > 0) {
+ // see if any entities are in the way of the shot
+ Math3D.VectorCopy(self.s.origin, spot1);
+ spot1[2] += self.viewheight;
+ Math3D.VectorCopy(self.enemy.s.origin, spot2);
+ spot2[2] += self.enemy.viewheight;
+
+ tr = GameBase.gi.trace(spot1, null, null, spot2, self,
+ Defines.CONTENTS_SOLID | Defines.CONTENTS_MONSTER
+ | Defines.CONTENTS_SLIME
+ | Defines.CONTENTS_LAVA
+ | Defines.CONTENTS_WINDOW);
+
+ // do we have a clear shot?
+ if (tr.ent != self.enemy)
+ return false;
+ }
+
+ // melee attack
+ if (enemy_range == Defines.RANGE_MELEE) {
+ // don't always melee in easy mode
+ if (GameBase.skill.value == 0 && (Lib.rand() & 3) != 0)
+ return false;
+ if (self.monsterinfo.melee != null)
+ self.monsterinfo.attack_state = Defines.AS_MELEE;
+ else
+ self.monsterinfo.attack_state = Defines.AS_MISSILE;
+ return true;
+ }
+
+ // missile attack
+ if (self.monsterinfo.attack == null)
+ return false;
+
+ if (GameBase.level.time < self.monsterinfo.attack_finished)
+ return false;
+
+ if (enemy_range == Defines.RANGE_FAR)
+ return false;
+
+ if ((self.monsterinfo.aiflags & Defines.AI_STAND_GROUND) != 0) {
+ chance = 0.4f;
+ } else if (enemy_range == Defines.RANGE_MELEE) {
+ chance = 0.2f;
+ } else if (enemy_range == Defines.RANGE_NEAR) {
+ chance = 0.1f;
+ } else if (enemy_range == Defines.RANGE_MID) {
+ chance = 0.02f;
+ } else {
+ return false;
+ }
+
+ if (GameBase.skill.value == 0)
+ chance *= 0.5;
+ else if (GameBase.skill.value >= 2)
+ chance *= 2;
+
+ if (Lib.random() < chance) {
+ self.monsterinfo.attack_state = Defines.AS_MISSILE;
+ self.monsterinfo.attack_finished = GameBase.level.time + 2
+ * Lib.random();
+ return true;
+ }
+
+ if ((self.flags & Defines.FL_FLY) != 0) {
+ if (Lib.random() < 0.3f)
+ self.monsterinfo.attack_state = Defines.AS_SLIDING;
+ else
+ self.monsterinfo.attack_state = Defines.AS_STRAIGHT;
+ }
+
+ return false;
+
+ }
+ };
+
+ static EntUseAdapter monster_use = new EntUseAdapter() {
+ public void use(edict_t self, edict_t other, edict_t activator) {
+ if (self.enemy != null)
+ return;
+ if (self.health <= 0)
+ return;
+ if ((activator.flags & Defines.FL_NOTARGET) != 0)
+ return;
+ if ((null == activator.client)
+ && 0 == (activator.monsterinfo.aiflags & Defines.AI_GOOD_GUY))
+ return;
+
+ // delay reaction so if the monster is teleported, its sound is
+ // still heard
+ self.enemy = activator;
+ GameUtil.FoundTarget(self);
+ }
+ };
+
+ static boolean enemy_vis;
+
+ static boolean enemy_infront;
+
+ static int enemy_range;
+
+ static float enemy_yaw;
+} \ No newline at end of file