diff options
author | Holger Zickner <[email protected]> | 2004-07-07 19:59:59 +0000 |
---|---|---|
committer | Holger Zickner <[email protected]> | 2004-07-07 19:59:59 +0000 |
commit | 6e23fc1074d1f0c2c2812f4c2e663f5a21a43c20 (patch) | |
tree | 46ecc6d0255c874ba4cd26dc3d0733f785019896 /src/jake2/game/GameUtil.java |
import of Jake2 version sunrisesunrise
Diffstat (limited to 'src/jake2/game/GameUtil.java')
-rw-r--r-- | src/jake2/game/GameUtil.java | 1840 |
1 files changed, 1840 insertions, 0 deletions
diff --git a/src/jake2/game/GameUtil.java b/src/jake2/game/GameUtil.java new file mode 100644 index 0000000..e959106 --- /dev/null +++ b/src/jake2/game/GameUtil.java @@ -0,0 +1,1840 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public 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.1 2004-07-07 19:59:05 hzi Exp $ + +package jake2.game; + +import java.sql.Savepoint; + +import jake2.Defines; +import jake2.client.M; +import jake2.qcommon.Com; +import jake2.util.*; + +public class GameUtil extends GameBase { + + public static EntThinkAdapter Think_Delay = new EntThinkAdapter() { + public boolean think(edict_t ent) { + G_UseTargets(ent, ent.activator); + G_FreeEdict(ent); + return true; + } + }; + + 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 = 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.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 < globals.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)) { + G_InitEdict(e, i); + return e; + } + } + + if (i == game.maxentities) + gi.error("ED_Alloc: no free edicts"); + + e = g_edicts[i]; + globals.num_edicts++; + G_InitEdict(e, i); + return e; + } + + public static EntThinkAdapter G_FreeEdictA = new EntThinkAdapter() { + public boolean think(edict_t ent) { + G_FreeEdict(ent); + return false; + } + }; + + /** + * 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)); + 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_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, null, 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; + } + + /** TODO: test, / replaced the string operations. */ + 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 EntThinkAdapter MegaHealth_think = new EntThinkAdapter() { + public boolean think(edict_t self) { + if (self.owner.health > self.owner.max_health) { + self.nextthink = level.time + 1; + self.owner.health -= 1; + return false; + } + + if (!((self.spawnflags & DROPPED_ITEM) != 0) && (deathmatch.value != 0)) + SetRespawn(self, 20); + else + 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; + + // tiefe z�hlen + // 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 &= ~SVF_NOCLIENT; + ent.solid = SOLID_TRIGGER; + gi.linkentity(ent); + + // send an effect + ent.s.event = EV_ITEM_RESPAWN; + + return false; + } + }; + + 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 = DoRespawn; + gi.linkentity(ent); + } + + 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 = FindItem("Bullets"); + if (item != null) { + index = 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 = FindItem("Shells"); + if (item != null) { + index = 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 = FindItem("Cells"); + if (item != null) { + index = 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 = FindItem("Grenades"); + if (item != null) { + index = 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 = FindItem("Rockets"); + if (item != null) { + index = 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 = FindItem("Slugs"); + if (item != null) { + index = 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 & DROPPED_ITEM) && (deathmatch.value != 0)) + 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 & HEALTH_IGNORE_MAX)) + if (other.health >= other.max_health) + return false; + + other.health += ent.count; + + if (0 == (ent.style & HEALTH_IGNORE_MAX)) { + if (other.health > other.max_health) + other.health = other.max_health; + } + + if (0 != (ent.style & HEALTH_TIMED)) { + ent.think = MegaHealth_think; + ent.nextthink = level.time + 5f; + ent.owner = other; + ent.flags |= FL_RESPAWN; + ent.svflags |= SVF_NOCLIENT; + ent.solid = SOLID_NOT; + } + else { + if (!((ent.spawnflags & DROPPED_ITEM) != 0) && (deathmatch.value != 0)) + SetRespawn(ent, 30); + } + + return true; + } + + }; + + static int ITEM_INDEX(gitem_t item) { + return item.index; + } + + /* + =============== + 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 (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[STAT_PICKUP_ICON] = (short) gi.imageindex(ent.item.icon); + other.client.ps.stats[STAT_PICKUP_STRING] = (short) (CS_ITEMS + ITEM_INDEX(ent.item)); + other.client.pickup_msg_time = level.time + 3.0f; + + // change selected item + if (ent.item.use != null) + other.client.pers.selected_item = other.client.ps.stats[STAT_SELECTED_ITEM] = (short) ITEM_INDEX(ent.item); + + if (ent.item.pickup == Pickup_Health) { + if (ent.count == 2) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/s_health.wav"), 1, ATTN_NORM, 0); + else if (ent.count == 10) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/n_health.wav"), 1, ATTN_NORM, 0); + else if (ent.count == 25) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/l_health.wav"), 1, ATTN_NORM, 0); + else // (ent.count == 100) + gi.sound(other, CHAN_ITEM, gi.soundindex("items/m_health.wav"), 1, ATTN_NORM, 0); + } + else if (ent.item.pickup_sound != null) { + gi.sound(other, CHAN_ITEM, gi.soundindex(ent.item.pickup_sound), 1, ATTN_NORM, 0); + } + } + + if (0 == (ent.spawnflags & ITEM_TARGETS_USED)) { + G_UseTargets(ent, other); + ent.spawnflags |= ITEM_TARGETS_USED; + } + + if (!taken) + return; + + if (!((coop.value != 0) && (ent.item.flags & IT_STAY_COOP) != 0) + || 0 != (ent.spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM))) { + if ((ent.flags & FL_RESPAWN) != 0) + ent.flags &= ~FL_RESPAWN; + else + 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 (deathmatch.value != 0) { + ent.nextthink = level.time + 29; + ent.think = G_FreeEdictA; + } + return false; + } + }; + + 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 = 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 = 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 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[ITEM_INDEX(item)]--; + 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 > level.framenum) + ent.client.quad_framenum += timeout; + else + ent.client.quad_framenum = level.framenum + timeout; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); + } + }; + + static ItemUseAdapter Use_Invulnerability = new ItemUseAdapter() { + public void use(edict_t ent, gitem_t item) { + ent.client.pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem(ent); + + if (ent.client.invincible_framenum > level.framenum) + ent.client.invincible_framenum += 300; + else + ent.client.invincible_framenum = level.framenum + 300; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect.wav"), 1, ATTN_NORM, 0); + } + }; + + 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 = Touch_Item; + } + + gi.linkentity(ent); + } + + // ====================================================================== + + static ItemUseAdapter Use_Breather = new ItemUseAdapter() { + public void use(edict_t ent, gitem_t item) { + ent.client.pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem(ent); + + if (ent.client.breather_framenum > level.framenum) + ent.client.breather_framenum += 300; + else + ent.client.breather_framenum = level.framenum + 300; + + // gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); + } + }; + + // ====================================================================== + + static ItemUseAdapter Use_Envirosuit = new ItemUseAdapter() { + public void use(edict_t ent, gitem_t item) { + ent.client.pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem(ent); + + if (ent.client.enviro_framenum > level.framenum) + ent.client.enviro_framenum += 300; + else + ent.client.enviro_framenum = level.framenum + 300; + + // gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); + } + }; + + // ====================================================================== + /* + static ItemUseAdapter Use_Invulnerability = new ItemUseAdapter() + { + public void use(edict_t ent, gitem_t item) + { + + ent.client.pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem(ent); + + if (ent.client.invincible_framenum > level.framenum) + ent.client.invincible_framenum += 300; + else + ent.client.invincible_framenum = level.framenum + 300; + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/protect.wav"), 1, ATTN_NORM, 0); + } + }; + */ + + // ====================================================================== + + static ItemUseAdapter Use_Silencer = new ItemUseAdapter() { + public void use(edict_t ent, gitem_t item) { + + ent.client.pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem(ent); + ent.client.silencer_shots += 30; + + // gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); + } + }; + + // ====================================================================== + + static EntInteractAdapter Pickup_Key = new EntInteractAdapter() { + public boolean interact(edict_t ent, edict_t other) { + if (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[ITEM_INDEX(ent.item)]++; + other.client.pers.power_cubes |= ((ent.spawnflags & 0x0000ff00) >> 8); + } + else { + if (other.client.pers.inventory[ITEM_INDEX(ent.item)] != 0) + return false; + other.client.pers.inventory[ITEM_INDEX(ent.item)] = 1; + } + return true; + } + other.client.pers.inventory[ITEM_INDEX(ent.item)]++; + return true; + } + }; + + /* + ============ + 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[power_shield_index] > 0) + return POWER_ARMOR_SHIELD; + + if (ent.client.pers.inventory[power_screen_index] > 0) + return POWER_ARMOR_SCREEN; + + return POWER_ARMOR_NONE; + } + + 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; + + 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; + // + // 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 = + gi.trace( + spot1, + null, + null, + spot2, + self, + CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_SLIME | CONTENTS_LAVA | CONTENTS_WINDOW); + + // do we have a clear shot? + if (tr.ent != self.enemy) + return false; + } + + // melee attack + if (enemy_range == RANGE_MELEE) { + // don't always melee in easy mode + if (skill.value == 0 && (Lib.rand() & 3) != 0) + return false; + if (self.monsterinfo.melee != null) + self.monsterinfo.attack_state = AS_MELEE; + else + self.monsterinfo.attack_state = AS_MISSILE; + return true; + } + + // missile attack + if (self.monsterinfo.attack == null) + return false; + + if (level.time < self.monsterinfo.attack_finished) + return false; + + if (enemy_range == RANGE_FAR) + return false; + + if ((self.monsterinfo.aiflags & AI_STAND_GROUND) != 0) { + chance = 0.4f; + } + else if (enemy_range == RANGE_MELEE) { + chance = 0.2f; + } + else if (enemy_range == RANGE_NEAR) { + chance = 0.1f; + } + else if (enemy_range == RANGE_MID) { + chance = 0.02f; + } + else { + return false; + } + + if (skill.value == 0) + chance *= 0.5; + else if (skill.value >= 2) + chance *= 2; + + if (Lib.random() < chance) { + self.monsterinfo.attack_state = AS_MISSILE; + self.monsterinfo.attack_finished = level.time + 2 * Lib.random(); + return true; + } + + if ((self.flags & FL_FLY) != 0) { + if (Lib.random() < 0.3f) + self.monsterinfo.attack_state = AS_SLIDING; + else + self.monsterinfo.attack_state = 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 & FL_NOTARGET) != 0) + return; + if ((null == activator.client) && 0 == (activator.monsterinfo.aiflags & AI_GOOD_GUY)) + return; + + // delay reaction so if the monster is teleported, its sound is still heard + self.enemy = activator; + FoundTarget(self); + } + }; + + static boolean monster_start(edict_t self) { + if (deathmatch.value != 0) { + G_FreeEdict(self); + return false; + } + + if ((self.spawnflags & 4) != 0 && 0 == (self.monsterinfo.aiflags & AI_GOOD_GUY)) { + self.spawnflags &= ~4; + self.spawnflags |= 1; + // gi.dprintf("fixed spawnflags on %s at %s\n", self.classname, vtos(self.s.origin)); + } + + if (0 == (self.monsterinfo.aiflags & AI_GOOD_GUY)) + level.total_monsters++; + + self.nextthink = level.time + FRAMETIME; + self.svflags |= SVF_MONSTER; + self.s.renderfx |= RF_FRAMELERP; + self.takedamage = DAMAGE_AIM; + self.air_finished = level.time + 12; + + // monster_use() + self.use = monster_use; + + self.max_health = self.health; + self.clipmask = MASK_MONSTERSOLID; + + self.s.skinnum = 0; + self.deadflag = DEAD_NO; + self.svflags &= ~SVF_DEADMONSTER; + + if (self.monsterinfo.checkattack == null) + // M_CheckAttack; + self.monsterinfo.checkattack = M_CheckAttack; + + Math3D.VectorCopy(self.s.origin, self.s.old_origin); + + if (st.item != null) { + self.item = FindItemByClassname(st.item); + if (self.item == null) + gi.dprintf("" + self.classname + " at " + Lib.vtos(self.s.origin) + " has bad item: " + st.item + "\n"); + } + + // randomize what frame they start on + if (self.monsterinfo.currentmove != null) + self.s.frame = + self.monsterinfo.currentmove.firstframe + + (Lib.rand() % (self.monsterinfo.currentmove.lastframe - self.monsterinfo.currentmove.firstframe + 1)); + + return true; + } + + /* + ============= + 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) { + Com.Printf("FindItem:" + pickup_name + "\n"); + 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[jacket_armor_index] > 0) + return jacket_armor_index; + + if (ent.client.pers.inventory[combat_armor_index] > 0) + return combat_armor_index; + + if (ent.client.pers.inventory[body_armor_index] > 0) + return 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 boolean enemy_vis; + static boolean enemy_infront; + static int enemy_range; + static float enemy_yaw; + + 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 // heardit + { + 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; + } + + // ============================================================================ + + 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); + } + } + } + } +} |