diff options
Diffstat (limited to 'src/jake2/game/GameAIAdapters.java')
-rw-r--r-- | src/jake2/game/GameAIAdapters.java | 1175 |
1 files changed, 1175 insertions, 0 deletions
diff --git a/src/jake2/game/GameAIAdapters.java b/src/jake2/game/GameAIAdapters.java new file mode 100644 index 0000000..ea20995 --- /dev/null +++ b/src/jake2/game/GameAIAdapters.java @@ -0,0 +1,1175 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public 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 26.02.2004 by RST. +// $Id: GameAIAdapters.java,v 1.1 2004-07-08 15:58:44 hzi Exp $ + +package jake2.game; + +import jake2.Defines; +import jake2.client.M; +import jake2.qcommon.Com; +import jake2.util.*; + +import java.util.*; + +public class GameAIAdapters +{ + /** Common Boss explode animation.*/ + + public static EntThinkAdapter BossExplode = new EntThinkAdapter() + { + public boolean think(edict_t self) + { + float[] org = { 0, 0, 0 }; + + int n; + + self.think = BossExplode; + Math3D.VectorCopy(self.s.origin, org); + org[2] += 24 + (Lib.rand() & 15); + switch (self.count++) + { + case 0 : + org[0] -= 24; + org[1] -= 24; + break; + case 1 : + org[0] += 24; + org[1] += 24; + break; + case 2 : + org[0] += 24; + org[1] -= 24; + break; + case 3 : + org[0] -= 24; + org[1] += 24; + break; + case 4 : + org[0] -= 48; + org[1] -= 48; + break; + case 5 : + org[0] += 48; + org[1] += 48; + break; + case 6 : + org[0] -= 48; + org[1] += 48; + break; + case 7 : + org[0] += 48; + org[1] -= 48; + break; + case 8 : + self.s.sound = 0; + for (n = 0; n < 4; n++) + GameAI.ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", 500, Defines.GIB_ORGANIC); + for (n = 0; n < 8; n++) + GameAI.ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", 500, Defines.GIB_METALLIC); + GameAI.ThrowGib(self, "models/objects/gibs/chest/tris.md2", 500, Defines.GIB_ORGANIC); + GameAI.ThrowHead(self, "models/objects/gibs/gear/tris.md2", 500, Defines.GIB_METALLIC); + self.deadflag = Defines.DEAD_DEAD; + return true; + } + + GameBase.gi.WriteByte(Defines.svc_temp_entity); + GameBase.gi.WriteByte(Defines.TE_EXPLOSION1); + GameBase.gi.WritePosition(org); + GameBase.gi.multicast(self.s.origin, Defines.MULTICAST_PVS); + + self.nextthink = GameBase.level.time + 0.1f; + return true; + } + }; + public static EntThinkAdapter walkmonster_start_go = new EntThinkAdapter() + { + public boolean think(edict_t self) + { + + if (0 == (self.spawnflags & 2) && GameBase.level.time < 1) + { + M.M_droptofloor.think(self); + + if (self.groundentity != null) + if (!M.M_walkmove(self, 0, 0)) + GameBase.gi.dprintf(self.classname + " in solid at " + Lib.vtos(self.s.origin) + "\n"); + } + + if (0 == self.yaw_speed) + self.yaw_speed = 20; + self.viewheight = 25; + + Monster.monster_start_go(self); + + if ((self.spawnflags & 2) != 0) + MonsterAdapters.monster_triggered_start.think(self); + return true; + } + }; + public static EntThinkAdapter walkmonster_start = new EntThinkAdapter() + { + public boolean think(edict_t self) + { + + self.think = walkmonster_start_go; + Monster.monster_start(self); + return true; + } + }; + public static EntThinkAdapter flymonster_start_go = new EntThinkAdapter() + { + public boolean think(edict_t self) + { + if (!M.M_walkmove(self, 0, 0)) + GameBase.gi.dprintf(self.classname + " in solid at " + Lib.vtos(self.s.origin) + "\n"); + + if (0 == self.yaw_speed) + self.yaw_speed = 10; + self.viewheight = 25; + + Monster.monster_start_go(self); + + if ((self.spawnflags & 2) != 0) + MonsterAdapters.monster_triggered_start.think(self); + return true; + } + }; + public static EntThinkAdapter flymonster_start = new EntThinkAdapter() + { + public boolean think(edict_t self) + { + self.flags |= Defines.FL_FLY; + self.think = flymonster_start_go; + Monster.monster_start(self); + return true; + } + }; + public static EntThinkAdapter swimmonster_start_go = new EntThinkAdapter() + { + public boolean think(edict_t self) + { + if (0 == self.yaw_speed) + self.yaw_speed = 10; + self.viewheight = 10; + + Monster.monster_start_go(self); + + if ((self.spawnflags & 2) != 0) + MonsterAdapters.monster_triggered_start.think(self); + return true; + } + }; + public static EntThinkAdapter swimmonster_start = new EntThinkAdapter() + { + public boolean think(edict_t self) + { + + { + self.flags |= Defines.FL_SWIM; + self.think = swimmonster_start_go; + Monster.monster_start(self); + return true; + } + } + }; + /* + ============= + ai_turn + + don't move, but turn towards ideal_yaw + Distance is for slight position adjustments needed by the animations + ============= + */ + public static AIAdapter ai_turn = new AIAdapter() + { + public void ai(edict_t self, float dist) + { + + if (dist != 0) + M.M_walkmove(self, self.s.angles[Defines.YAW], dist); + + if (GameUtil.FindTarget(self)) + return; + + M.M_ChangeYaw(self); + } + }; + /* + ============= + ai_move + + Move the specified distance at current facing. + This replaces the QC functions: ai_forward, ai_back, ai_pain, and ai_painforward + ============== + */ + public static AIAdapter ai_move = new AIAdapter() + { + public void ai(edict_t self, float dist) + { + M.M_walkmove(self, self.s.angles[Defines.YAW], dist); + } + }; + /* + ============= + ai_walk + + The monster is walking it's beat + ============= + */ + public static AIAdapter ai_walk = new AIAdapter() + { + public void ai(edict_t self, float dist) + { + if (self.index == 312) + self.index = 312; + + M.M_MoveToGoal(self, dist); + + // check for noticing a player + if (GameUtil.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; + } + } + } + }; + /* + ============= + ai_stand + + Used for standing around and looking for players + Distance is for slight position adjustments needed by the animations + ============== + */ + + public static AIAdapter ai_stand = new AIAdapter() + { + public void ai(edict_t self, float dist) + { + float[] v = { 0, 0, 0 }; + + if (dist != 0) + M.M_walkmove(self, self.s.angles[Defines.YAW], dist); + + if ((self.monsterinfo.aiflags & Defines.AI_STAND_GROUND) != 0) + { + if (self.enemy != null) + { + Math3D.VectorSubtract(self.enemy.s.origin, self.s.origin, v); + self.ideal_yaw = Math3D.vectoyaw(v); + if (self.s.angles[Defines.YAW] != self.ideal_yaw + && 0 != (self.monsterinfo.aiflags & Defines.AI_TEMP_STAND_GROUND)) + { + self.monsterinfo.aiflags &= ~(Defines.AI_STAND_GROUND | Defines.AI_TEMP_STAND_GROUND); + self.monsterinfo.run.think(self); + } + M.M_ChangeYaw(self); + GameAI.ai_checkattack(self, 0); + } + else + GameUtil.FindTarget(self); + return; + } + + if (GameUtil.FindTarget(self)) + return; + + if (GameBase.level.time > self.monsterinfo.pausetime) + { + self.monsterinfo.walk.think(self); + return; + } + + if (0 == (self.spawnflags & 1) && (self.monsterinfo.idle != null) && (GameBase.level.time > self.monsterinfo.idle_time)) + { + if (self.monsterinfo.idle_time != 0) + { + self.monsterinfo.idle.think(self); + self.monsterinfo.idle_time = GameBase.level.time + 15 + Lib.random() * 15; + } + else + { + self.monsterinfo.idle_time = GameBase.level.time + Lib.random() * 15; + } + } + } + }; + /* + ============= + ai_charge + + Turns towards target and advances + Use this call with a distnace of 0 to replace ai_face + ============== + */ + public static AIAdapter ai_charge = new AIAdapter() + { + + public void ai(edict_t self, float dist) + { + float[] v = { 0, 0, 0 }; + + Math3D.VectorSubtract(self.enemy.s.origin, self.s.origin, v); + self.ideal_yaw = Math3D.vectoyaw(v); + M.M_ChangeYaw(self); + + if (dist != 0) + M.M_walkmove(self, self.s.angles[Defines.YAW], dist); + } + }; + /* + ============= + ai_run + + The monster has an enemy it is trying to kill + ============= + */ //ok + public static AIAdapter ai_run = new AIAdapter() + { + public void ai(edict_t self, float dist) + { + float[] v = { 0, 0, 0 }; + + edict_t tempgoal; + edict_t save; + boolean new1; + edict_t marker; + float d1, d2; + trace_t tr; // mem + float[] v_forward = { 0, 0, 0 }, v_right = { 0, 0, 0 }; + float left, center, right; + float[] left_target = { 0, 0, 0 }, right_target = { 0, 0, 0 }; + + // if we're going to a combat point, just proceed + if ((self.monsterinfo.aiflags & Defines.AI_COMBAT_POINT) != 0) + { + M.M_MoveToGoal(self, dist); + return; + } + + if ((self.monsterinfo.aiflags & Defines.AI_SOUND_TARGET) != 0) + { + Math3D.VectorSubtract(self.s.origin, self.enemy.s.origin, v); + if (Math3D.VectorLength(v) < 64) + { + self.monsterinfo.aiflags |= (Defines.AI_STAND_GROUND | Defines.AI_TEMP_STAND_GROUND); + self.monsterinfo.stand.think(self); + return; + } + + M.M_MoveToGoal(self, dist); + + if (!GameUtil.FindTarget(self)) + return; + } + + if (GameAI.ai_checkattack(self, dist)) + return; + + if (self.monsterinfo.attack_state == Defines.AS_SLIDING) + { + GameAI.ai_run_slide(self, dist); + return; + } + + if (GameUtilAdapters.enemy_vis) + { + // if (self.aiflags & AI_LOST_SIGHT) + // dprint("regained sight\n"); + M.M_MoveToGoal(self, dist); + self.monsterinfo.aiflags &= ~Defines.AI_LOST_SIGHT; + Math3D.VectorCopy(self.enemy.s.origin, self.monsterinfo.last_sighting); + self.monsterinfo.trail_time = GameBase.level.time; + return; + } + + // coop will change to another enemy if visible + if (GameBase.coop.value != 0) + { + // FIXME: insane guys get mad with this, which causes crashes! + if (GameUtil.FindTarget(self)) + return; + } + + if ((self.monsterinfo.search_time != 0) && (GameBase.level.time > (self.monsterinfo.search_time + 20))) + { + M.M_MoveToGoal(self, dist); + self.monsterinfo.search_time = 0; + // dprint("search timeout\n"); + return; + } + + save = self.goalentity; + tempgoal = GameUtil.G_Spawn(); + self.goalentity = tempgoal; + + new1 = false; + + if (0 == (self.monsterinfo.aiflags & Defines.AI_LOST_SIGHT)) + { + // just lost sight of the player, decide where to go first + // dprint("lost sight of player, last seen at "); dprint(vtos(self.last_sighting)); dprint("\n"); + self.monsterinfo.aiflags |= (Defines.AI_LOST_SIGHT | Defines.AI_PURSUIT_LAST_SEEN); + self.monsterinfo.aiflags &= ~(Defines.AI_PURSUE_NEXT | Defines.AI_PURSUE_TEMP); + new1 = true; + } + + if ((self.monsterinfo.aiflags & Defines.AI_PURSUE_NEXT) != 0) + { + self.monsterinfo.aiflags &= ~Defines.AI_PURSUE_NEXT; + // dprint("reached current goal: "); dprint(vtos(self.origin)); dprint(" "); dprint(vtos(self.last_sighting)); dprint(" "); dprint(ftos(vlen(self.origin - self.last_sighting))); dprint("\n"); + + // give ourself more time since we got this far + self.monsterinfo.search_time = GameBase.level.time + 5; + + if ((self.monsterinfo.aiflags & Defines.AI_PURSUE_TEMP) != 0) + { + // dprint("was temp goal; retrying original\n"); + self.monsterinfo.aiflags &= ~Defines.AI_PURSUE_TEMP; + marker = null; + Math3D.VectorCopy(self.monsterinfo.saved_goal, self.monsterinfo.last_sighting); + new1 = true; + } + else if ((self.monsterinfo.aiflags & Defines.AI_PURSUIT_LAST_SEEN) != 0) + { + self.monsterinfo.aiflags &= ~Defines.AI_PURSUIT_LAST_SEEN; + marker = PlayerTrail.PickFirst(self); + } + else + { + marker = PlayerTrail.PickNext(self); + } + + if (marker != null) + { + Math3D.VectorCopy(marker.s.origin, self.monsterinfo.last_sighting); + self.monsterinfo.trail_time = marker.timestamp; + self.s.angles[Defines.YAW] = self.ideal_yaw = marker.s.angles[Defines.YAW]; + // dprint("heading is "); dprint(ftos(self.ideal_yaw)); dprint("\n"); + + // debug_drawline(self.origin, self.last_sighting, 52); + new1 = true; + } + } + + Math3D.VectorSubtract(self.s.origin, self.monsterinfo.last_sighting, v); + d1 = Math3D.VectorLength(v); + if (d1 <= dist) + { + self.monsterinfo.aiflags |= Defines.AI_PURSUE_NEXT; + dist = d1; + } + + Math3D.VectorCopy(self.monsterinfo.last_sighting, self.goalentity.s.origin); + + if (new1) + { + // gi.dprintf("checking for course correction\n"); + + tr = + GameBase.gi.trace( + self.s.origin, + self.mins, + self.maxs, + self.monsterinfo.last_sighting, + self, + Defines.MASK_PLAYERSOLID); + if (tr.fraction < 1) + { + Math3D.VectorSubtract(self.goalentity.s.origin, self.s.origin, v); + d1 = Math3D.VectorLength(v); + center = tr.fraction; + d2 = d1 * ((center + 1) / 2); + self.s.angles[Defines.YAW] = self.ideal_yaw = Math3D.vectoyaw(v); + Math3D.AngleVectors(self.s.angles, v_forward, v_right, null); + + Math3D.VectorSet(v, d2, -16, 0); + Math3D.G_ProjectSource(self.s.origin, v, v_forward, v_right, left_target); + tr = GameBase.gi.trace(self.s.origin, self.mins, self.maxs, left_target, self, Defines.MASK_PLAYERSOLID); + left = tr.fraction; + + Math3D.VectorSet(v, d2, 16, 0); + Math3D.G_ProjectSource(self.s.origin, v, v_forward, v_right, right_target); + tr = GameBase.gi.trace(self.s.origin, self.mins, self.maxs, right_target, self, Defines.MASK_PLAYERSOLID); + right = tr.fraction; + + center = (d1 * center) / d2; + if (left >= center && left > right) + { + if (left < 1) + { + Math3D.VectorSet(v, d2 * left * 0.5f, -16f, 0f); + Math3D.G_ProjectSource(self.s.origin, v, v_forward, v_right, left_target); + // gi.dprintf("incomplete path, go part way and adjust again\n"); + } + Math3D.VectorCopy(self.monsterinfo.last_sighting, self.monsterinfo.saved_goal); + self.monsterinfo.aiflags |= Defines.AI_PURSUE_TEMP; + Math3D.VectorCopy(left_target, self.goalentity.s.origin); + Math3D.VectorCopy(left_target, self.monsterinfo.last_sighting); + Math3D.VectorSubtract(self.goalentity.s.origin, self.s.origin, v); + self.s.angles[Defines.YAW] = self.ideal_yaw = Math3D.vectoyaw(v); + // gi.dprintf("adjusted left\n"); + // debug_drawline(self.origin, self.last_sighting, 152); + } + else if (right >= center && right > left) + { + if (right < 1) + { + Math3D.VectorSet(v, d2 * right * 0.5f, 16f, 0f); + Math3D.G_ProjectSource(self.s.origin, v, v_forward, v_right, right_target); + // gi.dprintf("incomplete path, go part way and adjust again\n"); + } + Math3D.VectorCopy(self.monsterinfo.last_sighting, self.monsterinfo.saved_goal); + self.monsterinfo.aiflags |= Defines.AI_PURSUE_TEMP; + Math3D.VectorCopy(right_target, self.goalentity.s.origin); + Math3D.VectorCopy(right_target, self.monsterinfo.last_sighting); + Math3D.VectorSubtract(self.goalentity.s.origin, self.s.origin, v); + self.s.angles[Defines.YAW] = self.ideal_yaw = Math3D.vectoyaw(v); + // gi.dprintf("adjusted right\n"); + // debug_drawline(self.origin, self.last_sighting, 152); + } + } + // else gi.dprintf("course was fine\n"); + } + + M.M_MoveToGoal(self, dist); + + GameUtil.G_FreeEdict(tempgoal); + + if (self != null) + self.goalentity = save; + } + }; + public static EntInteractAdapter Pickup_Ammo = new EntInteractAdapter() + { + public boolean interact(edict_t ent, edict_t other) + { + int oldcount; + int count; + boolean weapon; + + weapon = (ent.item.flags & Defines.IT_WEAPON) != 0; + if ((weapon) && ((int) GameBase.dmflags.value & Defines.DF_INFINITE_AMMO) != 0) + count = 1000; + else if (ent.count != 0) + count = ent.count; + else + count = ent.item.quantity; + + oldcount = other.client.pers.inventory[GameUtil.ITEM_INDEX(ent.item)]; + + if (!GameAI.Add_Ammo(other, ent.item, count)) + return false; + + if (weapon && 0 == oldcount) + { + if (other.client.pers.weapon != ent.item + && (0 == GameBase.deathmatch.value || other.client.pers.weapon == GameUtil.FindItem("blaster"))) + other.client.newweapon = ent.item; + } + + if (0 == (ent.spawnflags & (Defines.DROPPED_ITEM | Defines.DROPPED_PLAYER_ITEM)) && (GameBase.deathmatch.value != 0)) + GameUtil.SetRespawn(ent, 30); + return true; + } + }; + public static EntInteractAdapter Pickup_Armor = new EntInteractAdapter() + { + public boolean interact(edict_t ent, edict_t other) + { + int old_armor_index; + gitem_armor_t oldinfo; + gitem_armor_t newinfo; + int newcount; + float salvage; + int salvagecount; + + // get info on new armor + newinfo = (gitem_armor_t) ent.item.info; + + old_armor_index = GameUtil.ArmorIndex(other); + + // handle armor shards specially + if (ent.item.tag == Defines.ARMOR_SHARD) + { + if (0 == old_armor_index) + other.client.pers.inventory[GameUtilAdapters.jacket_armor_index] = 2; + else + other.client.pers.inventory[old_armor_index] += 2; + } + + // if player has no armor, just use it + else if (0 == old_armor_index) + { + other.client.pers.inventory[GameUtil.ITEM_INDEX(ent.item)] = newinfo.base_count; + } + + // use the better armor + else + { + // get info on old armor + if (old_armor_index == GameUtilAdapters.jacket_armor_index) + oldinfo = jacketarmor_info; + + else if (old_armor_index == GameUtilAdapters.combat_armor_index) + oldinfo = combatarmor_info; + + else // (old_armor_index == body_armor_index) + oldinfo = bodyarmor_info; + + if (newinfo.normal_protection > oldinfo.normal_protection) + { + // calc new armor values + salvage = oldinfo.normal_protection / newinfo.normal_protection; + salvagecount = (int) salvage * other.client.pers.inventory[old_armor_index]; + newcount = newinfo.base_count + salvagecount; + if (newcount > newinfo.max_count) + newcount = newinfo.max_count; + + // zero count of old armor so it goes away + other.client.pers.inventory[old_armor_index] = 0; + + // change armor to new item with computed value + other.client.pers.inventory[GameUtil.ITEM_INDEX(ent.item)] = newcount; + } + else + { + // calc new armor values + salvage = newinfo.normal_protection / oldinfo.normal_protection; + salvagecount = (int) salvage * newinfo.base_count; + newcount = other.client.pers.inventory[old_armor_index] + salvagecount; + if (newcount > oldinfo.max_count) + newcount = oldinfo.max_count; + + // if we're already maxed out then we don't need the new armor + if (other.client.pers.inventory[old_armor_index] >= newcount) + return false; + + // update current armor value + other.client.pers.inventory[old_armor_index] = newcount; + } + } + + if (0 == (ent.spawnflags & Defines.DROPPED_ITEM) && (GameBase.deathmatch.value == 0)) + GameUtil.SetRespawn(ent, 20); + + return true; + } + }; + public static EntInteractAdapter Pickup_PowerArmor = new EntInteractAdapter() + { + public boolean interact(edict_t ent, edict_t other) + { + + int quantity; + + quantity = other.client.pers.inventory[GameUtil.ITEM_INDEX(ent.item)]; + + other.client.pers.inventory[GameUtil.ITEM_INDEX(ent.item)]++; + + if (GameBase.deathmatch.value != 0) + { + if (0 == (ent.spawnflags & Defines.DROPPED_ITEM)) + GameUtil.SetRespawn(ent, ent.item.quantity); + // auto-use for DM only if we didn't already have one + if (0 == quantity) + ent.item.use.use(other, ent.item); + } + return true; + } + }; + // ====================================================================== + + public static EntInteractAdapter Pickup_Powerup = new EntInteractAdapter() + { + + public boolean interact(edict_t ent, edict_t other) + { + int quantity; + + quantity = other.client.pers.inventory[GameUtil.ITEM_INDEX(ent.item)]; + if ((GameBase.skill.value == 1 && quantity >= 2) || (GameBase.skill.value >= 2 && quantity >= 1)) + return false; + + if ((GameBase.coop.value != 0) && (ent.item.flags & Defines.IT_STAY_COOP) != 0 && (quantity > 0)) + return false; + + other.client.pers.inventory[GameUtil.ITEM_INDEX(ent.item)]++; + + if (GameBase.deathmatch.value != 0) + { + if (0 == (ent.spawnflags & Defines.DROPPED_ITEM)) + GameUtil.SetRespawn(ent, ent.item.quantity); + if (((int) GameBase.dmflags.value & Defines.DF_INSTANT_ITEMS) != 0 + || ((ent.item.use == GameUtilAdapters.Use_Quad) && 0 != (ent.spawnflags & Defines.DROPPED_PLAYER_ITEM))) + { + if ((ent.item.use == GameUtilAdapters.Use_Quad) && 0 != (ent.spawnflags & Defines.DROPPED_PLAYER_ITEM)) + GameUtilAdapters.quad_drop_timeout_hack = (int) ((ent.nextthink - GameBase.level.time) / Defines.FRAMETIME); + + ent.item.use.use(other, ent.item); + } + } + + return true; + } + }; + public static EntInteractAdapter Pickup_Adrenaline = new EntInteractAdapter() + { + public boolean interact(edict_t ent, edict_t other) + { + if (GameBase.deathmatch.value == 0) + other.max_health += 1; + + if (other.health < other.max_health) + other.health = other.max_health; + + if (0 == (ent.spawnflags & Defines.DROPPED_ITEM) && (GameBase.deathmatch.value != 0)) + GameUtil.SetRespawn(ent, ent.item.quantity); + + return true; + + } + }; + public static EntInteractAdapter Pickup_AncientHead = new EntInteractAdapter() + { + public boolean interact(edict_t ent, edict_t other) + { + other.max_health += 2; + + if (0 == (ent.spawnflags & Defines.DROPPED_ITEM) && (GameBase.deathmatch.value != 0)) + GameUtil.SetRespawn(ent, ent.item.quantity); + + return true; + } + }; + public static EntInteractAdapter Pickup_Bandolier = new EntInteractAdapter() + { + public boolean interact(edict_t ent, edict_t other) + { + gitem_t item; + int index; + + if (other.client.pers.max_bullets < 250) + other.client.pers.max_bullets = 250; + if (other.client.pers.max_shells < 150) + other.client.pers.max_shells = 150; + if (other.client.pers.max_cells < 250) + other.client.pers.max_cells = 250; + if (other.client.pers.max_slugs < 75) + other.client.pers.max_slugs = 75; + + 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; + } + + if (0 == (ent.spawnflags & Defines.DROPPED_ITEM) && (GameBase.deathmatch.value != 0)) + GameUtil.SetRespawn(ent, ent.item.quantity); + + return true; + + } + }; + public static EntUseAdapter Use_Item = new EntUseAdapter() + { + public void use(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 = GameUtilAdapters.Touch_Item; + } + + GameBase.gi.linkentity(ent); + } + }; + /* + ================ + droptofloor + ================ + */ + + public static EntThinkAdapter droptofloor = new EntThinkAdapter() + { + public boolean think(edict_t ent) + { + trace_t tr; + float[] dest = { 0, 0, 0 }; + + float v[]; + + v = Lib.tv(-15, -15, -15); + Math3D.VectorCopy(v, ent.mins); + v = Lib.tv(15, 15, 15); + Math3D.VectorCopy(v, ent.maxs); + + if (ent.model != null) + GameBase.gi.setmodel(ent, ent.model); + else + GameBase.gi.setmodel(ent, ent.item.world_model); + ent.solid = Defines.SOLID_TRIGGER; + ent.movetype = Defines.MOVETYPE_TOSS; + ent.touch = GameUtilAdapters.Touch_Item; + + v = Lib.tv(0, 0, -128); + Math3D.VectorAdd(ent.s.origin, v, dest); + + tr = GameBase.gi.trace(ent.s.origin, ent.mins, ent.maxs, dest, ent, Defines.MASK_SOLID); + if (tr.startsolid) + { + GameBase.gi.dprintf("droptofloor: " + ent.classname + " startsolid at " + Lib.vtos(ent.s.origin) + "\n"); + GameUtil.G_FreeEdict(ent); + return true; + } + + Math3D.VectorCopy(tr.endpos, ent.s.origin); + + if (ent.team != null) + { + ent.flags &= ~Defines.FL_TEAMSLAVE; + ent.chain = ent.teamchain; + ent.teamchain = null; + + ent.svflags |= Defines.SVF_NOCLIENT; + ent.solid = Defines.SOLID_NOT; + if (ent == ent.teammaster) + { + ent.nextthink = GameBase.level.time + Defines.FRAMETIME; + ent.think = GameUtilAdapters.DoRespawn; + } + } + + if ((ent.spawnflags & Defines.ITEM_NO_TOUCH) != 0) + { + ent.solid = Defines.SOLID_BBOX; + ent.touch = null; + ent.s.effects &= ~Defines.EF_ROTATE; + ent.s.renderfx &= ~Defines.RF_GLOW; + } + + if ((ent.spawnflags & Defines.ITEM_TRIGGER_SPAWN) != 0) + { + ent.svflags |= Defines.SVF_NOCLIENT; + ent.solid = Defines.SOLID_NOT; + ent.use = Use_Item; + } + + GameBase.gi.linkentity(ent); + return true; + } + }; + public static EntThinkAdapter gib_think = new EntThinkAdapter() + { + public boolean think(edict_t self) + { + self.s.frame++; + self.nextthink = GameBase.level.time + Defines.FRAMETIME; + + if (self.s.frame == 10) + { + self.think = GameUtilAdapters.G_FreeEdictA; + self.nextthink = GameBase.level.time + 8 + Lib.random() * 10; + } + return true; + } + }; + public static EntTouchAdapter gib_touch = new EntTouchAdapter() + { + public void touch(edict_t self, edict_t other, cplane_t plane, csurface_t surf) + { + float[] normal_angles = { 0, 0, 0 }, right = { 0, 0, 0 }; + + if (null == self.groundentity) + return; + + self.touch = null; + + if (plane != null) + { + GameBase.gi.sound(self, Defines.CHAN_VOICE, GameBase.gi.soundindex("misc/fhit3.wav"), 1, Defines.ATTN_NORM, 0); + + Math3D.vectoangles(plane.normal, normal_angles); + Math3D.AngleVectors(normal_angles, null, right, null); + Math3D.vectoangles(right, self.s.angles); + + if (self.s.modelindex == GameBase.sm_meat_index) + { + self.s.frame++; + self.think = gib_think; + self.nextthink = GameBase.level.time + Defines.FRAMETIME; + } + } + } + }; + public static EntDieAdapter gib_die = new EntDieAdapter() + { + public void die(edict_t self, edict_t inflictor, edict_t attacker, int damage, float[] point) + { + GameUtil.G_FreeEdict(self); + } + }; + /* + ================= + debris + ================= + */ + public static EntDieAdapter debris_die = new EntDieAdapter() + { + + public void die(edict_t self, edict_t inflictor, edict_t attacker, int damage, float[] point) + { + GameUtil.G_FreeEdict(self); + } + }; + public static int player_die_i = 0; + /* + ================== + player_die + ================== + */ + static EntDieAdapter player_die = new EntDieAdapter() + { + public void die(edict_t self, edict_t inflictor, edict_t attacker, int damage, float[] point) + { + int n; + + Math3D.VectorClear(self.avelocity); + + self.takedamage = Defines.DAMAGE_YES; + self.movetype = Defines.MOVETYPE_TOSS; + + self.s.modelindex2 = 0; // remove linked weapon model + + self.s.angles[0] = 0; + self.s.angles[2] = 0; + + self.s.sound = 0; + self.client.weapon_sound = 0; + + self.maxs[2] = -8; + + // self.solid = SOLID_NOT; + self.svflags |= Defines.SVF_DEADMONSTER; + + if (self.deadflag == 0) + { + self.client.respawn_time = GameBase.level.time + 1.0f; + GameAI.LookAtKiller(self, inflictor, attacker); + self.client.ps.pmove.pm_type = Defines.PM_DEAD; + GameAI.ClientObituary(self, inflictor, attacker); + GameAI.TossClientWeapon(self); + if (GameBase.deathmatch.value != 0) + Cmd.Help_f(self); // show scores + + // clear inventory + // this is kind of ugly, but it's how we want to handle keys in coop + for (n = 0; n < GameBase.game.num_items; n++) + { + if (GameBase.coop.value != 0 && (GameAI.itemlist[n].flags & Defines.IT_KEY) != 0) + self.client.resp.coop_respawn.inventory[n] = self.client.pers.inventory[n]; + self.client.pers.inventory[n] = 0; + } + } + + // remove powerups + self.client.quad_framenum = 0; + self.client.invincible_framenum = 0; + self.client.breather_framenum = 0; + self.client.enviro_framenum = 0; + self.flags &= ~Defines.FL_POWER_ARMOR; + + if (self.health < -40) + { // gib + GameBase.gi.sound(self, Defines.CHAN_BODY, GameBase.gi.soundindex("misc/udeath.wav"), 1, Defines.ATTN_NORM, 0); + for (n = 0; n < 4; n++) + GameAI.ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, Defines.GIB_ORGANIC); + GameAI.ThrowClientHead(self, damage); + + self.takedamage = Defines.DAMAGE_NO; + } + else + { // normal death + if (self.deadflag == 0) + { + + player_die_i = (player_die_i + 1) % 3; + // start a death animation + self.client.anim_priority = Defines.ANIM_DEATH; + if ((self.client.ps.pmove.pm_flags & Defines.PMF_DUCKED) != 0) + { + self.s.frame = M_Player.FRAME_crdeath1 - 1; + self.client.anim_end = M_Player.FRAME_crdeath5; + } + else + switch (player_die_i) + { + case 0 : + self.s.frame = M_Player.FRAME_death101 - 1; + self.client.anim_end = M_Player.FRAME_death106; + break; + case 1 : + self.s.frame = M_Player.FRAME_death201 - 1; + self.client.anim_end = M_Player.FRAME_death206; + break; + case 2 : + self.s.frame = M_Player.FRAME_death301 - 1; + self.client.anim_end = M_Player.FRAME_death308; + break; + } + + GameBase.gi.sound( + self, + Defines.CHAN_VOICE, + GameBase.gi.soundindex("*death" + ((Lib.rand() % 4) + 1) + ".wav"), + 1, + Defines.ATTN_NORM, + 0); + } + } + + self.deadflag = Defines.DEAD_DEAD; + + GameBase.gi.linkentity(self); + } + }; + public static Comparator PlayerSort = new Comparator() + { + public int compare(Object o1, Object o2) + { + int anum = ((Integer) o1).intValue(); + int bnum = ((Integer) o2).intValue(); + + int anum1 = GameBase.game.clients[anum].ps.stats[Defines.STAT_FRAGS]; + int bnum1 = GameBase.game.clients[bnum].ps.stats[Defines.STAT_FRAGS]; + + if (anum1 < bnum1) + return -1; + if (anum1 > bnum1) + return 1; + return 0; + } + }; + public static ItemUseAdapter Use_PowerArmor = new ItemUseAdapter() + { + public void use(edict_t ent, gitem_t item) + { + int index; + + if ((ent.flags & Defines.FL_POWER_ARMOR) != 0) + { + ent.flags &= ~Defines.FL_POWER_ARMOR; + GameBase.gi.sound(ent, Defines.CHAN_AUTO, GameBase.gi.soundindex("misc/power2.wav"), 1, Defines.ATTN_NORM, 0); + } + else + { + index = GameUtil.ITEM_INDEX(GameUtil.FindItem("cells")); + if (0 == ent.client.pers.inventory[index]) + { + GameBase.gi.cprintf(ent, Defines.PRINT_HIGH, "No cells for power armor.\n"); + return; + } + ent.flags |= Defines.FL_POWER_ARMOR; + GameBase.gi.sound(ent, Defines.CHAN_AUTO, GameBase.gi.soundindex("misc/power1.wav"), 1, Defines.ATTN_NORM, 0); + } + } + }; + public static ItemDropAdapter Drop_Ammo = new ItemDropAdapter() + { + public void drop(edict_t ent, gitem_t item) + { + edict_t dropped; + int index; + + index = GameUtil.ITEM_INDEX(item); + dropped = GameUtil.Drop_Item(ent, item); + if (ent.client.pers.inventory[index] >= item.quantity) + dropped.count = item.quantity; + else + dropped.count = ent.client.pers.inventory[index]; + + if (ent.client.pers.weapon != null + && ent.client.pers.weapon.tag == Defines.AMMO_GRENADES + && item.tag == Defines.AMMO_GRENADES + && ent.client.pers.inventory[index] - dropped.count <= 0) + { + GameBase.gi.cprintf(ent, Defines.PRINT_HIGH, "Can't drop current weapon\n"); + GameUtil.G_FreeEdict(dropped); + return; + } + + ent.client.pers.inventory[index] -= dropped.count; + GameAI.ValidateSelectedItem(ent); + } + }; + public static ItemDropAdapter Drop_General = new ItemDropAdapter() + { + public void drop(edict_t ent, gitem_t item) + { + GameUtil.Drop_Item(ent, item); + ent.client.pers.inventory[GameUtil.ITEM_INDEX(item)]--; + GameAI.ValidateSelectedItem(ent); + } + }; + public static ItemDropAdapter Drop_PowerArmor = new ItemDropAdapter() + { + public void drop(edict_t ent, gitem_t item) + { + if (0 != (ent.flags & Defines.FL_POWER_ARMOR) && (ent.client.pers.inventory[GameUtil.ITEM_INDEX(item)] == 1)) + Use_PowerArmor.use(ent, item); + Drop_General.drop(ent, item); + } + }; + public static gitem_armor_t jacketarmor_info = new gitem_armor_t(25, 50, .30f, .00f, Defines.ARMOR_JACKET); + public static gitem_armor_t combatarmor_info = new gitem_armor_t(50, 100, .60f, .30f, Defines.ARMOR_COMBAT); + public static gitem_armor_t bodyarmor_info = new gitem_armor_t(100, 200, .80f, .60f, Defines.ARMOR_BODY); +} |