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/GameAI.java |
import of Jake2 version sunrisesunrise
Diffstat (limited to 'src/jake2/game/GameAI.java')
-rw-r--r-- | src/jake2/game/GameAI.java | 3520 |
1 files changed, 3520 insertions, 0 deletions
diff --git a/src/jake2/game/GameAI.java b/src/jake2/game/GameAI.java new file mode 100644 index 0000000..2e32ed8 --- /dev/null +++ b/src/jake2/game/GameAI.java @@ -0,0 +1,3520 @@ +/* +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 02.11.2003 by RST. +// $Id: GameAI.java,v 1.1 2004-07-07 19:58:55 hzi Exp $ + +package jake2.game; + +import jake2.Defines; +import jake2.client.M; +import jake2.util.*; + +import java.util.*; + +public class GameAI extends M_Flash { + + /* + =============== + GetItemByIndex + =============== + */ + public static gitem_t GetItemByIndex(int index) { + + if (index == 0 || index >= game.num_items) + return null; + + return itemlist[index]; + } + + + /** 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++) + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", 500, GIB_ORGANIC); + for (n = 0; n < 8; n++) + ThrowGib(self, "models/objects/gibs/sm_metal/tris.md2", 500, GIB_METALLIC); + ThrowGib(self, "models/objects/gibs/chest/tris.md2", 500, GIB_ORGANIC); + ThrowHead(self, "models/objects/gibs/gear/tris.md2", 500, GIB_METALLIC); + self.deadflag = DEAD_DEAD; + return true; + } + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(org); + gi.multicast(self.s.origin, MULTICAST_PVS); + + self.nextthink = level.time + 0.1f; + return true; + } + }; + + public static void AttackFinished(edict_t self, float time) { + self.monsterinfo.attack_finished = level.time + time; + } + + public static EntThinkAdapter walkmonster_start_go = new EntThinkAdapter() { + public boolean think(edict_t self) { + + if (0 == (self.spawnflags & 2) && level.time < 1) { + M.M_droptofloor.think(self); + + if (self.groundentity != null) + if (!M.M_walkmove(self, 0, 0)) + 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) + Monster.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)) + 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) + Monster.monster_triggered_start.think(self); + return true; + } + }; + + public static EntThinkAdapter flymonster_start = new EntThinkAdapter() { + public boolean think(edict_t self) { + self.flags |= 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) + Monster.monster_triggered_start.think(self); + return true; + } + }; + + public static EntThinkAdapter swimmonster_start = new EntThinkAdapter() { + public boolean think(edict_t self) { + + { + self.flags |= 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[YAW], dist); + + if (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[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) { + 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; + } + } + } + }; + + /* + ============= + 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[YAW], dist); + + if ((self.monsterinfo.aiflags & 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[YAW] != self.ideal_yaw && 0 != (self.monsterinfo.aiflags & AI_TEMP_STAND_GROUND)) { + self.monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + self.monsterinfo.run.think(self); + } + M.M_ChangeYaw(self); + ai_checkattack(self, 0); + } + else + FindTarget(self); + return; + } + + if (FindTarget(self)) + return; + + if (level.time > self.monsterinfo.pausetime) { + self.monsterinfo.walk.think(self); + return; + } + + if (0 == (self.spawnflags & 1) && (self.monsterinfo.idle != null) && (level.time > self.monsterinfo.idle_time)) { + if (self.monsterinfo.idle_time != 0) { + self.monsterinfo.idle.think(self); + self.monsterinfo.idle_time = level.time + 15 + Lib.random() * 15; + } + else { + self.monsterinfo.idle_time = 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[YAW], dist); + } + }; + + /* + ============= + ai_turn + + don't move, but turn towards ideal_yaw + Distance is for slight position adjustments needed by the animations + ============= + */ + public static void ai_turn(edict_t self, float dist) { + if (dist != 0) + M.M_walkmove(self, self.s.angles[YAW], dist); + + if (FindTarget(self)) + return; + + M.M_ChangeYaw(self); + } + + /* + + .enemy + Will be world if not currently angry at anyone. + + .movetarget + The next path spot to walk toward. If .enemy, ignore .movetarget. + When an enemy is killed, the monster will try to return to it's path. + + .hunt_time + Set to time + something when the player is in sight, but movement straight for + him is blocked. This causes the monster to use wall following code for + movement direction instead of sighting on the player. + + .ideal_yaw + A yaw angle of the intended direction, which will be turned towards at up + to 45 deg / state. If the enemy is in view and hunt_time is not active, + this will be the exact line towards the enemy. + + .pausetime + A monster will leave it's stand state and head towards it's .movetarget when + time > .pausetime. + + walkmove(angle, speed) primitive is all or nothing + */ + + /* + ============ + FacingIdeal + + ============ + */ + + public static boolean FacingIdeal(edict_t self) { + float delta; + + delta = Math3D.anglemod(self.s.angles[YAW] - self.ideal_yaw); + if (delta > 45 && delta < 315) + return false; + return true; + } + + /* + ============= + ai_run_melee + + Turn and close until within an angle to launch a melee attack + ============= + */ + public static void ai_run_melee(edict_t self) { + self.ideal_yaw = enemy_yaw; + M.M_ChangeYaw(self); + + if (FacingIdeal(self)) { + self.monsterinfo.melee.think(self); + self.monsterinfo.attack_state = AS_STRAIGHT; + } + } + + /* + ============= + ai_run_missile + + Turn in place until within an angle to launch a missile attack + ============= + */ + public static void ai_run_missile(edict_t self) { + self.ideal_yaw = enemy_yaw; + M.M_ChangeYaw(self); + + if (FacingIdeal(self)) { + self.monsterinfo.attack.think(self); + self.monsterinfo.attack_state = AS_STRAIGHT; + } + }; + + /* + ============= + ai_run_slide + + Strafe sideways, but stay at aproximately the same range + ============= + */ + public static void ai_run_slide(edict_t self, float distance) { + float ofs; + + self.ideal_yaw = enemy_yaw; + M.M_ChangeYaw(self); + + if (self.monsterinfo.lefty != 0) + ofs = 90; + else + ofs = -90; + + if (M.M_walkmove(self, self.ideal_yaw + ofs, distance)) + return; + + self.monsterinfo.lefty = 1 - self.monsterinfo.lefty; + M.M_walkmove(self, self.ideal_yaw - ofs, distance); + } + + /* + ============= + ai_checkattack + + Decides if we're going to attack or do something else + used by ai_run and ai_stand + ============= + */ + public static boolean ai_checkattack(edict_t self, float dist) { + float temp[] = { 0, 0, 0 }; + + boolean hesDeadJim; + + // this causes monsters to run blindly to the combat point w/o firing + if (self.goalentity != null) { + if ((self.monsterinfo.aiflags & AI_COMBAT_POINT) != 0) + return false; + + if ((self.monsterinfo.aiflags & AI_SOUND_TARGET) != 0) { + if ((level.time - self.enemy.teleport_time) > 5.0) { + if (self.goalentity == self.enemy) + if (self.movetarget != null) + self.goalentity = self.movetarget; + else + self.goalentity = null; + self.monsterinfo.aiflags &= ~AI_SOUND_TARGET; + if ((self.monsterinfo.aiflags & AI_TEMP_STAND_GROUND) != 0) + self.monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + } + else { + self.show_hostile = (int) level.time + 1; + return false; + } + } + } + + enemy_vis = false; + + // see if the enemy is dead + hesDeadJim = false; + if ((null == self.enemy) || (self.enemy.inuse)) { + hesDeadJim = true; + } + else if ((self.monsterinfo.aiflags & AI_MEDIC) != 0) { + if (self.enemy.health > 0) { + hesDeadJim = true; + self.monsterinfo.aiflags &= ~AI_MEDIC; + } + } + else { + if ((self.monsterinfo.aiflags & AI_BRUTAL) != 0) { + if (self.enemy.health <= -80) + hesDeadJim = true; + } + else { + if (self.enemy.health <= 0) + hesDeadJim = true; + } + } + + if (hesDeadJim) { + self.enemy = null; + // FIXME: look all around for other targets + if (self.oldenemy != null && self.oldenemy.health > 0) { + self.enemy = self.oldenemy; + self.oldenemy = null; + HuntTarget(self); + } + else { + if (self.movetarget != null) { + self.goalentity = self.movetarget; + self.monsterinfo.walk.think(self); + } + else { + // we need the pausetime otherwise the stand code + // will just revert to walking with no target and + // the monsters will wonder around aimlessly trying + // to hunt the world entity + self.monsterinfo.pausetime = level.time + 100000000; + self.monsterinfo.stand.think(self); + } + return true; + } + } + + self.show_hostile = (int) level.time + 1; // wake up other monsters + + // check knowledge of enemy + enemy_vis = visible(self, self.enemy); + if (enemy_vis) { + self.monsterinfo.search_time = level.time + 5; + Math3D.VectorCopy(self.enemy.s.origin, self.monsterinfo.last_sighting); + } + + // look for other coop players here + // if (coop && self.monsterinfo.search_time < level.time) + // { + // if (FindTarget (self)) + // return true; + // } + + enemy_infront = infront(self, self.enemy); + enemy_range = range(self, self.enemy); + Math3D.VectorSubtract(self.enemy.s.origin, self.s.origin, temp); + enemy_yaw = Math3D.vectoyaw(temp); + + // JDC self.ideal_yaw = enemy_yaw; + + if (self.monsterinfo.attack_state == AS_MISSILE) { + ai_run_missile(self); + return true; + } + if (self.monsterinfo.attack_state == AS_MELEE) { + ai_run_melee(self); + return true; + } + + // if enemy is not currently visible, we will never attack + if (!enemy_vis) + return false; + + return self.monsterinfo.checkattack.think(self); + } + + /* + ============= + ai_run + + The monster has an enemy it is trying to kill + ============= + */ + public static AIAdapter ai_run = new AIAdapter() { + public void ai_run(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; + 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 & AI_COMBAT_POINT) != 0) { + M.M_MoveToGoal(self, dist); + return; + } + + if ((self.monsterinfo.aiflags & AI_SOUND_TARGET) != 0) { + Math3D.VectorSubtract(self.s.origin, self.enemy.s.origin, v); + if (Math3D.VectorLength(v) < 64) { + self.monsterinfo.aiflags |= (AI_STAND_GROUND | AI_TEMP_STAND_GROUND); + self.monsterinfo.stand.think(self); + return; + } + + M.M_MoveToGoal(self, dist); + + if (!FindTarget(self)) + return; + } + + if (ai_checkattack(self, dist)) + return; + + if (self.monsterinfo.attack_state == AS_SLIDING) { + ai_run_slide(self, dist); + return; + } + + if (enemy_vis) { + // if (self.aiflags & AI_LOST_SIGHT) + // dprint("regained sight\n"); + M.M_MoveToGoal(self, dist); + self.monsterinfo.aiflags &= ~AI_LOST_SIGHT; + Math3D.VectorCopy(self.enemy.s.origin, self.monsterinfo.last_sighting); + self.monsterinfo.trail_time = level.time; + return; + } + + // coop will change to another enemy if visible + if (coop.value != 0) { + // FIXME: insane guys get mad with this, which causes crashes! + if (FindTarget(self)) + return; + } + + if ((self.monsterinfo.search_time != 0) && (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 = G_Spawn(); + self.goalentity = tempgoal; + + new1 = false; + + if (0 == (self.monsterinfo.aiflags & 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 |= (AI_LOST_SIGHT | AI_PURSUIT_LAST_SEEN); + self.monsterinfo.aiflags &= ~(AI_PURSUE_NEXT | AI_PURSUE_TEMP); + new1 = true; + } + + if ((self.monsterinfo.aiflags & AI_PURSUE_NEXT) != 0) { + self.monsterinfo.aiflags &= ~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 = level.time + 5; + + if ((self.monsterinfo.aiflags & AI_PURSUE_TEMP) != 0) { + // dprint("was temp goal; retrying original\n"); + self.monsterinfo.aiflags &= ~AI_PURSUE_TEMP; + marker = null; + Math3D.VectorCopy(self.monsterinfo.saved_goal, self.monsterinfo.last_sighting); + new1 = true; + } + else if ((self.monsterinfo.aiflags & AI_PURSUIT_LAST_SEEN) != 0) { + self.monsterinfo.aiflags &= ~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[YAW] = self.ideal_yaw = marker.s.angles[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 |= 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 = gi.trace(self.s.origin, self.mins, self.maxs, self.monsterinfo.last_sighting, self, 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[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 = gi.trace(self.s.origin, self.mins, self.maxs, left_target, self, 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 = gi.trace(self.s.origin, self.mins, self.maxs, right_target, self, 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 |= 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[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 |= 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[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); + + G_FreeEdict(tempgoal); + + if (self != null) + self.goalentity = save; + } + }; + + public static void UpdateChaseCam(edict_t ent) { + float[] o = { 0, 0, 0 }, ownerv = { 0, 0, 0 }, goal = { 0, 0, 0 }; + edict_t targ; + float[] forward = { 0, 0, 0 }, right = { 0, 0, 0 }; + trace_t trace; + int i; + float[] oldgoal = { 0, 0, 0 }; + float[] angles = { 0, 0, 0 }; + + // is our chase target gone? + if (!ent.client.chase_target.inuse || ent.client.chase_target.client.resp.spectator) { + edict_t old = ent.client.chase_target; + ChaseNext(ent); + if (ent.client.chase_target == old) { + ent.client.chase_target = null; + ent.client.ps.pmove.pm_flags &= ~PMF_NO_PREDICTION; + return; + } + } + + targ = ent.client.chase_target; + + Math3D.VectorCopy(targ.s.origin, ownerv); + Math3D.VectorCopy(ent.s.origin, oldgoal); + + ownerv[2] += targ.viewheight; + + Math3D.VectorCopy(targ.client.v_angle, angles); + if (angles[PITCH] > 56) + angles[PITCH] = 56; + Math3D.AngleVectors(angles, forward, right, null); + Math3D.VectorNormalize(forward); + Math3D.VectorMA(ownerv, -30, forward, o); + + if (o[2] < targ.s.origin[2] + 20) + o[2] = targ.s.origin[2] + 20; + + // jump animation lifts + if (targ.groundentity == null) + o[2] += 16; + + trace = gi.trace(ownerv, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + + Math3D.VectorCopy(trace.endpos, goal); + + Math3D.VectorMA(goal, 2, forward, goal); + + // pad for floors and ceilings + Math3D.VectorCopy(goal, o); + o[2] += 6; + trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + if (trace.fraction < 1) { + Math3D.VectorCopy(trace.endpos, goal); + goal[2] -= 6; + } + + Math3D.VectorCopy(goal, o); + o[2] -= 6; + trace = gi.trace(goal, vec3_origin, vec3_origin, o, targ, MASK_SOLID); + if (trace.fraction < 1) { + Math3D.VectorCopy(trace.endpos, goal); + goal[2] += 6; + } + + if (targ.deadflag != 0) + ent.client.ps.pmove.pm_type = PM_DEAD; + else + ent.client.ps.pmove.pm_type = PM_FREEZE; + + Math3D.VectorCopy(goal, ent.s.origin); + for (i = 0; i < 3; i++) + ent.client.ps.pmove.delta_angles[i] = (short) Math3D.ANGLE2SHORT(targ.client.v_angle[i] - ent.client.resp.cmd_angles[i]); + + if (targ.deadflag != 0) { + ent.client.ps.viewangles[ROLL] = 40; + ent.client.ps.viewangles[PITCH] = -15; + ent.client.ps.viewangles[YAW] = targ.client.killer_yaw; + } + else { + Math3D.VectorCopy(targ.client.v_angle, ent.client.ps.viewangles); + Math3D.VectorCopy(targ.client.v_angle, ent.client.v_angle); + } + + ent.viewheight = 0; + ent.client.ps.pmove.pm_flags |= PMF_NO_PREDICTION; + gi.linkentity(ent); + } + + public static void ChaseNext(edict_t ent) { + int i; + edict_t e; + + if (null == ent.client.chase_target) + return; + + i = ent.client.chase_target.index; + do { + i++; + if (i > maxclients.value) + i = 1; + e = g_edicts[i]; + + if (!e.inuse) + continue; + if (!e.client.resp.spectator) + break; + } + while (e != ent.client.chase_target); + + ent.client.chase_target = e; + ent.client.update_chase = true; + } + + public static void ChasePrev(edict_t ent) { + int i; + edict_t e; + + if (ent.client.chase_target == null) + return; + + i = ent.client.chase_target.index; + do { + i--; + if (i < 1) + i = (int) maxclients.value; + e = g_edicts[i]; + if (!e.inuse) + continue; + if (!e.client.resp.spectator) + break; + } + while (e != ent.client.chase_target); + + ent.client.chase_target = e; + ent.client.update_chase = true; + } + + public static void GetChaseTarget(edict_t ent) { + int i; + edict_t other; + + for (i = 1; i <= maxclients.value; i++) { + other = g_edicts[i]; + if (other.inuse && !other.client.resp.spectator) { + ent.client.chase_target = other; + ent.client.update_chase = true; + UpdateChaseCam(ent); + return; + } + } + gi.centerprintf(ent, "No other players to chase."); + } + + /* + =============== + SetItemNames + + Called by worldspawn + =============== + */ + public static void SetItemNames() { + int i; + gitem_t it; + + for (i = 1; i < game.num_items; i++) { + it = itemlist[i]; + gi.configstring(CS_ITEMS + i, it.pickup_name); + } + + jacket_armor_index = ITEM_INDEX(FindItem("Jacket Armor")); + combat_armor_index = ITEM_INDEX(FindItem("Combat Armor")); + body_armor_index = ITEM_INDEX(FindItem("Body Armor")); + power_screen_index = ITEM_INDEX(FindItem("Power Screen")); + power_shield_index = ITEM_INDEX(FindItem("Power Shield")); + } + + public static void SelectNextItem(edict_t ent, int itflags) { + gclient_t cl; + int i, index; + gitem_t it; + + cl = ent.client; + + if (cl.chase_target != null) { + ChaseNext(ent); + return; + } + + // scan for the next valid one + for (i = 1; i <= MAX_ITEMS; i++) { + index = (cl.pers.selected_item + i) % MAX_ITEMS; + if (0 == cl.pers.inventory[index]) + continue; + it = itemlist[index]; + if (it.use == null) + continue; + if (0 == (it.flags & itflags)) + continue; + + cl.pers.selected_item = index; + return; + } + + cl.pers.selected_item = -1; + } + + public static void SelectPrevItem(edict_t ent, int itflags) { + gclient_t cl; + int i, index; + gitem_t it; + + cl = ent.client; + + if (cl.chase_target != null) { + ChasePrev(ent); + return; + } + + // scan for the next valid one + for (i = 1; i <= MAX_ITEMS; i++) { + index = (cl.pers.selected_item + MAX_ITEMS - i) % MAX_ITEMS; + if (0 == cl.pers.inventory[index]) + continue; + it = itemlist[index]; + if (null == it.use) + continue; + if (0 == (it.flags & itflags)) + continue; + + cl.pers.selected_item = index; + return; + } + + cl.pers.selected_item = -1; + } + + public static void ValidateSelectedItem(edict_t ent) { + gclient_t cl; + + cl = ent.client; + + if (cl.pers.inventory[cl.pers.selected_item] != 0) + return; // valid + + SelectNextItem(ent, -1); + } + + //====================================================================== + + public static boolean Add_Ammo(edict_t ent, gitem_t item, int count) { + int index; + int max; + + if (null == ent.client) + return false; + + if (item.tag == AMMO_BULLETS) + max = ent.client.pers.max_bullets; + else if (item.tag == AMMO_SHELLS) + max = ent.client.pers.max_shells; + else if (item.tag == AMMO_ROCKETS) + max = ent.client.pers.max_rockets; + else if (item.tag == AMMO_GRENADES) + max = ent.client.pers.max_grenades; + else if (item.tag == AMMO_CELLS) + max = ent.client.pers.max_cells; + else if (item.tag == AMMO_SLUGS) + max = ent.client.pers.max_slugs; + else + return false; + + index = ITEM_INDEX(item); + + if (ent.client.pers.inventory[index] == max) + return false; + + ent.client.pers.inventory[index] += count; + + if (ent.client.pers.inventory[index] > max) + ent.client.pers.inventory[index] = max; + + return true; + } + + /* + =============== + PrecacheItem + + Precaches all data needed for a given item. + This will be called for each item spawned in a level, + and for each item in each client's inventory. + =============== + */ + public static void PrecacheItem(gitem_t it) { + String s; + String data; + int len; + gitem_t ammo; + + if (it == null) + return; + + if (it.pickup_sound != null) + gi.soundindex(it.pickup_sound); + + if (it.world_model != null) + gi.modelindex(it.world_model); + + if (it.view_model != null) + gi.modelindex(it.view_model); + + if (it.icon != null) + gi.imageindex(it.icon); + + // parse everything for its ammo + if (it.ammo != null && it.ammo.length() != 0) { + ammo = FindItem(it.ammo); + if (ammo != it) + PrecacheItem(ammo); + } + + // parse the space seperated precache string for other items + s = it.precaches; + if (s == null || s.length() != 0) + return; + + StringTokenizer tk = new StringTokenizer(s); + + + while (tk.hasMoreTokens()) { + data = tk.nextToken(); + + len = data.length(); + + if (len >= MAX_QPATH || len < 5) + gi.error("PrecacheItem: it.classname has bad precache string: " + s); + + // determine type based on extension + if (data.endsWith("md2")) + gi.modelindex(data); + else if (data.endsWith("sp2")) + gi.modelindex(data); + else if (data.endsWith("wav")) + gi.soundindex(data); + else if (data.endsWith("pcx")) + gi.imageindex(data); + else + gi.error("PrecacheItem: bad precache string: " + data); + } + } + + 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 & IT_WEAPON) != 0; + if ((weapon) && ((int) dmflags.value & DF_INFINITE_AMMO) != 0) + count = 1000; + else if (ent.count != 0) + count = ent.count; + else + count = ent.item.quantity; + + oldcount = other.client.pers.inventory[ITEM_INDEX(ent.item)]; + + if (!Add_Ammo(other, ent.item, count)) + return false; + + if (weapon && 0 == oldcount) { + if (other.client.pers.weapon != ent.item + && (0 == deathmatch.value || other.client.pers.weapon == FindItem("blaster"))) + other.client.newweapon = ent.item; + } + + if (0 == (ent.spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) && (deathmatch.value != 0)) + 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 = ArmorIndex(other); + + // handle armor shards specially + if (ent.item.tag == ARMOR_SHARD) { + if (0 == old_armor_index) + other.client.pers.inventory[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[ITEM_INDEX(ent.item)] = newinfo.base_count; + } + + // use the better armor + else { + // get info on old armor + if (old_armor_index == jacket_armor_index) + oldinfo = jacketarmor_info; + + else if (old_armor_index == 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[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 & DROPPED_ITEM) && (deathmatch.value == 0)) + 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[ITEM_INDEX(ent.item)]; + + other.client.pers.inventory[ITEM_INDEX(ent.item)]++; + + if (deathmatch.value != 0) { + if (0 == (ent.spawnflags & DROPPED_ITEM)) + 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[ITEM_INDEX(ent.item)]; + if ((skill.value == 1 && quantity >= 2) || (skill.value >= 2 && quantity >= 1)) + return false; + + if ((coop.value != 0) && (ent.item.flags & IT_STAY_COOP) != 0 && (quantity > 0)) + return false; + + other.client.pers.inventory[ITEM_INDEX(ent.item)]++; + + if (deathmatch.value != 0) { + if (0 == (ent.spawnflags & DROPPED_ITEM)) + SetRespawn(ent, ent.item.quantity); + if (((int) dmflags.value & DF_INSTANT_ITEMS) != 0 + || ((ent.item.use == Use_Quad) && 0 != (ent.spawnflags & DROPPED_PLAYER_ITEM))) { + if ((ent.item.use == Use_Quad) && 0 != (ent.spawnflags & DROPPED_PLAYER_ITEM)) + quad_drop_timeout_hack = (int) ((ent.nextthink - level.time) / 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 (deathmatch.value == 0) + other.max_health += 1; + + if (other.health < other.max_health) + other.health = other.max_health; + + if (0 == (ent.spawnflags & DROPPED_ITEM) && (deathmatch.value != 0)) + 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 & DROPPED_ITEM) && (deathmatch.value != 0)) + 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 = 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; + } + + if (0 == (ent.spawnflags & DROPPED_ITEM) && (deathmatch.value != 0)) + 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 &= ~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); + } + }; + + /* + ================ + 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) + gi.setmodel(ent, ent.model); + else + gi.setmodel(ent, ent.item.world_model); + ent.solid = SOLID_TRIGGER; + ent.movetype = MOVETYPE_TOSS; + ent.touch = Touch_Item; + + v = Lib.tv(0, 0, -128); + Math3D.VectorAdd(ent.s.origin, v, dest); + + tr = gi.trace(ent.s.origin, ent.mins, ent.maxs, dest, ent, MASK_SOLID); + if (tr.startsolid) { + gi.dprintf("droptofloor: " + ent.classname + " startsolid at " + Lib.vtos(ent.s.origin) + "\n"); + G_FreeEdict(ent); + return true; + } + + Math3D.VectorCopy(tr.endpos, ent.s.origin); + + if (ent.team != null) { + ent.flags &= ~FL_TEAMSLAVE; + ent.chain = ent.teamchain; + ent.teamchain = null; + + ent.svflags |= SVF_NOCLIENT; + ent.solid = SOLID_NOT; + if (ent == ent.teammaster) { + ent.nextthink = level.time + FRAMETIME; + ent.think = DoRespawn; + } + } + + if ((ent.spawnflags & ITEM_NO_TOUCH) != 0) { + ent.solid = SOLID_BBOX; + ent.touch = null; + ent.s.effects &= ~EF_ROTATE; + ent.s.renderfx &= ~RF_GLOW; + } + + if ((ent.spawnflags & ITEM_TRIGGER_SPAWN) != 0) { + ent.svflags |= SVF_NOCLIENT; + ent.solid = SOLID_NOT; + ent.use = Use_Item; + } + + gi.linkentity(ent); + return true; + } + }; + + /* + ============ + SpawnItem + + Sets the clipping size and plants the object on the floor. + + Items can't be immediately dropped to floor, because they might + be on an entity that hasn't spawned yet. + ============ + */ + public static void SpawnItem(edict_t ent, gitem_t item) { + PrecacheItem(item); + + if (ent.spawnflags != 0) { + if (Lib.strcmp(ent.classname, "key_power_cube") != 0) { + ent.spawnflags = 0; + gi.dprintf("" + ent.classname + " at " + Lib.vtos(ent.s.origin) + " has invalid spawnflags set\n"); + } + } + + // some items will be prevented in deathmatch + if (deathmatch.value != 0) { + if (((int) dmflags.value & DF_NO_ARMOR) != 0) { + if (item.pickup == Pickup_Armor || item.pickup == Pickup_PowerArmor) { + G_FreeEdict(ent); + return; + } + } + if (((int) dmflags.value & DF_NO_ITEMS) != 0) { + if (item.pickup == Pickup_Powerup) { + G_FreeEdict(ent); + return; + } + } + if (((int) dmflags.value & DF_NO_HEALTH) != 0) { + if (item.pickup == Pickup_Health || item.pickup == Pickup_Adrenaline || item.pickup == Pickup_AncientHead) { + G_FreeEdict(ent); + return; + } + } + if (((int) dmflags.value & DF_INFINITE_AMMO) != 0) { + if ((item.flags == IT_AMMO) || (Lib.strcmp(ent.classname, "weapon_bfg") == 0)) { + G_FreeEdict(ent); + return; + } + } + } + + if (coop.value != 0 && (Lib.strcmp(ent.classname, "key_power_cube") == 0)) { + ent.spawnflags |= (1 << (8 + level.power_cubes)); + level.power_cubes++; + } + + // don't let them drop items that stay in a coop game + if ((coop.value != 0) && (item.flags & IT_STAY_COOP) != 0) { + item.drop = null; + } + + ent.item = item; + ent.nextthink = level.time + 2 * FRAMETIME; + // items start after other solids + ent.think = droptofloor; + ent.s.effects = item.world_model_flags; + ent.s.renderfx = RF_GLOW; + + if (ent.model != null) + gi.modelindex(ent.model); + } + + /* + =============== + Touch_Item + =============== + */ + public static void Touch_Item(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); + } + } + + /* + ================== + LookAtKiller + ================== + */ + public static void LookAtKiller(edict_t self, edict_t inflictor, edict_t attacker) { + float dir[] = { 0, 0, 0 }; + + edict_t world = g_edicts[0]; + + if (attacker != null && attacker != world && attacker != self) { + Math3D.VectorSubtract(attacker.s.origin, self.s.origin, dir); + } + else if (inflictor != null && inflictor != world && inflictor != self) { + Math3D.VectorSubtract(inflictor.s.origin, self.s.origin, dir); + } + else { + self.client.killer_yaw = self.s.angles[YAW]; + return; + } + + if (dir[0] != 0) + self.client.killer_yaw = (float) (180 / Math.PI * Math.atan2(dir[1], dir[0])); + else { + self.client.killer_yaw = 0; + if (dir[1] > 0) + self.client.killer_yaw = 90; + else if (dir[1] < 0) + self.client.killer_yaw = -90; + } + if (self.client.killer_yaw < 0) + self.client.killer_yaw += 360; + + } + + public static void TossClientWeapon(edict_t self) { + gitem_t item; + edict_t drop; + boolean quad; + float spread; + + if (deathmatch.value == 0) + return; + + item = self.client.pers.weapon; + if (0 == self.client.pers.inventory[self.client.ammo_index]) + item = null; + if (item != null && (Lib.strcmp(item.pickup_name, "Blaster") == 0)) + item = null; + + if (0 == ((int) (dmflags.value) & DF_QUAD_DROP)) + quad = false; + else + quad = (self.client.quad_framenum > (level.framenum + 10)); + + if (item != null && quad) + spread = 22.5f; + else + spread = 0.0f; + + if (item != null) { + self.client.v_angle[YAW] -= spread; + drop = Drop_Item(self, item); + self.client.v_angle[YAW] += spread; + drop.spawnflags = DROPPED_PLAYER_ITEM; + } + + if (quad) { + self.client.v_angle[YAW] += spread; + drop = Drop_Item(self, FindItemByClassname("item_quad")); + self.client.v_angle[YAW] -= spread; + drop.spawnflags |= DROPPED_PLAYER_ITEM; + + drop.touch = Touch_Item; + drop.nextthink = level.time + (self.client.quad_framenum - level.framenum) * FRAMETIME; + drop.think = G_FreeEdictA; + } + } + + public static EntThinkAdapter gib_think = new EntThinkAdapter() { + public boolean think(edict_t self) { + self.s.frame++; + self.nextthink = level.time + FRAMETIME; + + if (self.s.frame == 10) { + self.think = G_FreeEdictA; + self.nextthink = 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) { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/fhit3.wav"), 1, 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 == sm_meat_index) { + self.s.frame++; + self.think = gib_think; + self.nextthink = level.time + FRAMETIME; + } + } + } + }; + + public static EntDieAdapter gib_die = new EntDieAdapter() { + public void die(edict_t self, edict_t inflictor, edict_t attacker, int damage, float[] point) { + G_FreeEdict(self); + } + }; + + public static void ThrowGib(edict_t self, String gibname, int damage, int type) { + edict_t gib; + + float[] vd = { 0, 0, 0 }; + float[] origin = { 0, 0, 0 }; + float[] size = { 0, 0, 0 }; + float vscale; + + gib = G_Spawn(); + + Math3D.VectorScale(self.size, 0.5f, size); + Math3D.VectorAdd(self.absmin, size, origin); + gib.s.origin[0] = origin[0] + Lib.crandom() * size[0]; + gib.s.origin[1] = origin[1] + Lib.crandom() * size[1]; + gib.s.origin[2] = origin[2] + Lib.crandom() * size[2]; + + gi.setmodel(gib, gibname); + gib.solid = SOLID_NOT; + gib.s.effects |= EF_GIB; + gib.flags |= FL_NO_KNOCKBACK; + gib.takedamage = DAMAGE_YES; + gib.die = gib_die; + + if (type == GIB_ORGANIC) { + gib.movetype = MOVETYPE_TOSS; + gib.touch = gib_touch; + vscale = 0.5f; + } + else { + gib.movetype = MOVETYPE_BOUNCE; + vscale = 1.0f; + } + + VelocityForDamage(damage, vd); + Math3D.VectorMA(self.velocity, vscale, vd, gib.velocity); + ClipGibVelocity(gib); + gib.avelocity[0] = Lib.random() * 600; + gib.avelocity[1] = Lib.random() * 600; + gib.avelocity[2] = Lib.random() * 600; + + gib.think = G_FreeEdictA; + gib.nextthink = level.time + 10 + Lib.random() * 10; + + gi.linkentity(gib); + } + + public static void ThrowHead(edict_t self, String gibname, int damage, int type) { + float vd[] = { 0, 0, 0 }; + + float vscale; + + self.s.skinnum = 0; + self.s.frame = 0; + Math3D.VectorClear(self.mins); + Math3D.VectorClear(self.maxs); + + self.s.modelindex2 = 0; + gi.setmodel(self, gibname); + self.solid = SOLID_NOT; + self.s.effects |= EF_GIB; + self.s.effects &= ~EF_FLIES; + self.s.sound = 0; + self.flags |= FL_NO_KNOCKBACK; + self.svflags &= ~SVF_MONSTER; + self.takedamage = DAMAGE_YES; + self.die = gib_die; + + if (type == GIB_ORGANIC) { + self.movetype = MOVETYPE_TOSS; + self.touch = gib_touch; + vscale = 0.5f; + } + else { + self.movetype = MOVETYPE_BOUNCE; + vscale = 1.0f; + } + + VelocityForDamage(damage, vd); + Math3D.VectorMA(self.velocity, vscale, vd, self.velocity); + ClipGibVelocity(self); + + self.avelocity[YAW] = Lib.crandom() * 600f; + + self.think = G_FreeEdictA; + self.nextthink = level.time + 10 + Lib.random() * 10; + + gi.linkentity(self); + } + + public static void VelocityForDamage(int damage, float[] v) { + v[0] = 100.0f * Lib.crandom(); + v[1] = 100.0f * Lib.crandom(); + v[2] = 200.0f + 100.0f * Lib.random(); + + if (damage < 50) + Math3D.VectorScale(v, 0.7f, v); + else + Math3D.VectorScale(v, 1.2f, v); + } + + public static void ClipGibVelocity(edict_t ent) { + if (ent.velocity[0] < -300) + ent.velocity[0] = -300; + else if (ent.velocity[0] > 300) + ent.velocity[0] = 300; + if (ent.velocity[1] < -300) + ent.velocity[1] = -300; + else if (ent.velocity[1] > 300) + ent.velocity[1] = 300; + if (ent.velocity[2] < 200) + ent.velocity[2] = 200; // always some upwards + else if (ent.velocity[2] > 500) + ent.velocity[2] = 500; + } + + public static void ThrowClientHead(edict_t self, int damage) { + float vd[] = { 0, 0, 0 }; + String gibname; + + if ((Lib.rand() & 1) != 0) { + gibname = "models/objects/gibs/head2/tris.md2"; + self.s.skinnum = 1; // second skin is player + } + else { + gibname = "models/objects/gibs/skull/tris.md2"; + self.s.skinnum = 0; + } + + self.s.origin[2] += 32; + self.s.frame = 0; + gi.setmodel(self, gibname); + Math3D.VectorSet(self.mins, -16, -16, 0); + Math3D.VectorSet(self.maxs, 16, 16, 16); + + self.takedamage = DAMAGE_NO; + self.solid = SOLID_NOT; + self.s.effects = EF_GIB; + self.s.sound = 0; + self.flags |= FL_NO_KNOCKBACK; + + self.movetype = MOVETYPE_BOUNCE; + VelocityForDamage(damage, vd); + Math3D.VectorAdd(self.velocity, vd, self.velocity); + + if (self.client != null) + // bodies in the queue don't have a client anymore + { + self.client.anim_priority = ANIM_DEATH; + self.client.anim_end = self.s.frame; + } + else { + self.think = null; + self.nextthink = 0; + } + + gi.linkentity(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) { + G_FreeEdict(self); + } + }; + + public static void ThrowDebris(edict_t self, String modelname, float speed, float[] origin) { + edict_t chunk; + float[] v = { 0, 0, 0 }; + + chunk = G_Spawn(); + Math3D.VectorCopy(origin, chunk.s.origin); + gi.setmodel(chunk, modelname); + v[0] = 100 * Lib.crandom(); + v[1] = 100 * Lib.crandom(); + v[2] = 100 + 100 * Lib.crandom(); + Math3D.VectorMA(self.velocity, speed, v, chunk.velocity); + chunk.movetype = MOVETYPE_BOUNCE; + chunk.solid = SOLID_NOT; + chunk.avelocity[0] = Lib.random() * 600; + chunk.avelocity[1] = Lib.random() * 600; + chunk.avelocity[2] = Lib.random() * 600; + chunk.think = G_FreeEdictA; + chunk.nextthink = level.time + 5 + Lib.random() * 5; + chunk.s.frame = 0; + chunk.flags = 0; + chunk.classname = "debris"; + chunk.takedamage = DAMAGE_YES; + chunk.die = debris_die; + gi.linkentity(chunk); + } + + public static void BecomeExplosion1(edict_t self) { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION1); + gi.WritePosition(self.s.origin); + gi.multicast(self.s.origin, MULTICAST_PVS); + + G_FreeEdict(self); + } + + public static void BecomeExplosion2(edict_t self) { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_EXPLOSION2); + gi.WritePosition(self.s.origin); + gi.multicast(self.s.origin, MULTICAST_PVS); + + G_FreeEdict(self); + } + + /** Returns true, if the players gender flag was set to female .*/ + public static boolean IsFemale(edict_t ent) { + char info; + + if (null == ent.client) + return false; + + info = Info.Info_ValueForKey(ent.client.pers.userinfo, "gender").charAt(0); + if (info == 'f' || info == 'F') + return true; + return false; + } + + /** Returns true, if the players gender flag was neither set to female nor to male.*/ + public static boolean IsNeutral(edict_t ent) { + char info; + + if (ent.client == null) + return false; + + info = Info.Info_ValueForKey(ent.client.pers.userinfo, "gender").charAt(0); + + if (info != 'f' && info != 'F' && info != 'm' && info != 'M') + return true; + return false; + } + + /** Some reports about the cause of the players death. */ + public static void ClientObituary(edict_t self, edict_t inflictor, edict_t attacker) { + int mod; + String message; + String message2; + boolean ff; + + if (coop.value != 0 && attacker.client != null) + meansOfDeath |= MOD_FRIENDLY_FIRE; + + if (deathmatch.value != 0 || coop.value != 0) { + ff = (meansOfDeath & MOD_FRIENDLY_FIRE) != 0; + mod = meansOfDeath & ~MOD_FRIENDLY_FIRE; + message = null; + message2 = ""; + + switch (mod) { + case MOD_SUICIDE : + message = "suicides"; + break; + case MOD_FALLING : + message = "cratered"; + break; + case MOD_CRUSH : + message = "was squished"; + break; + case MOD_WATER : + message = "sank like a rock"; + break; + case MOD_SLIME : + message = "melted"; + break; + case MOD_LAVA : + message = "does a back flip into the lava"; + break; + case MOD_EXPLOSIVE : + case MOD_BARREL : + message = "blew up"; + break; + case MOD_EXIT : + message = "found a way out"; + break; + case MOD_TARGET_LASER : + message = "saw the light"; + break; + case MOD_TARGET_BLASTER : + message = "got blasted"; + break; + case MOD_BOMB : + case MOD_SPLASH : + case MOD_TRIGGER_HURT : + message = "was in the wrong place"; + break; + } + if (attacker == self) { + switch (mod) { + case MOD_HELD_GRENADE : + message = "tried to put the pin back in"; + break; + case MOD_HG_SPLASH : + case MOD_G_SPLASH : + if (IsNeutral(self)) + message = "tripped on its own grenade"; + else if (IsFemale(self)) + message = "tripped on her own grenade"; + else + message = "tripped on his own grenade"; + break; + case MOD_R_SPLASH : + if (IsNeutral(self)) + message = "blew itself up"; + else if (IsFemale(self)) + message = "blew herself up"; + else + message = "blew himself up"; + break; + case MOD_BFG_BLAST : + message = "should have used a smaller gun"; + break; + default : + if (IsNeutral(self)) + message = "killed itself"; + else if (IsFemale(self)) + message = "killed herself"; + else + message = "killed himself"; + break; + } + } + if (message != null) { + gi.bprintf(PRINT_MEDIUM, "" + self.client.pers.netname + " " + message + ".\n"); + if (deathmatch.value != 0) + self.client.resp.score--; + self.enemy = null; + return; + } + + self.enemy = attacker; + + if (attacker != null && attacker.client != null) { + switch (mod) { + case MOD_BLASTER : + message = "was blasted by"; + break; + case MOD_SHOTGUN : + message = "was gunned down by"; + break; + case MOD_SSHOTGUN : + message = "was blown away by"; + message2 = "'s super shotgun"; + break; + case MOD_MACHINEGUN : + message = "was machinegunned by"; + break; + case MOD_CHAINGUN : + message = "was cut in half by"; + message2 = "'s chaingun"; + break; + case MOD_GRENADE : + message = "was popped by"; + message2 = "'s grenade"; + break; + case MOD_G_SPLASH : + message = "was shredded by"; + message2 = "'s shrapnel"; + break; + case MOD_ROCKET : + message = "ate"; + message2 = "'s rocket"; + break; + case MOD_R_SPLASH : + message = "almost dodged"; + message2 = "'s rocket"; + break; + case MOD_HYPERBLASTER : + message = "was melted by"; + message2 = "'s hyperblaster"; + break; + case MOD_RAILGUN : + message = "was railed by"; + break; + case MOD_BFG_LASER : + message = "saw the pretty lights from"; + message2 = "'s BFG"; + break; + case MOD_BFG_BLAST : + message = "was disintegrated by"; + message2 = "'s BFG blast"; + break; + case MOD_BFG_EFFECT : + message = "couldn't hide from"; + message2 = "'s BFG"; + break; + case MOD_HANDGRENADE : + message = "caught"; + message2 = "'s handgrenade"; + break; + case MOD_HG_SPLASH : + message = "didn't see"; + message2 = "'s handgrenade"; + break; + case MOD_HELD_GRENADE : + message = "feels"; + message2 = "'s pain"; + break; + case MOD_TELEFRAG : + message = "tried to invade"; + message2 = "'s personal space"; + break; + } + if (message != null) { + gi.bprintf( + PRINT_MEDIUM, + self.client.pers.netname + " " + message + " " + attacker.client.pers.netname + "" + message2); + if (deathmatch.value != 0) { + if (ff) + attacker.client.resp.score--; + else + attacker.client.resp.score++; + } + return; + } + } + } + + gi.bprintf(PRINT_MEDIUM, self.client.pers.netname + " died.\n"); + if (deathmatch.value != 0) + self.client.resp.score--; + } + + /* + ================== + DeathmatchScoreboardMessage + + ================== + */ + public static void DeathmatchScoreboardMessage(edict_t ent, edict_t killer) { + String entry; + String string; + int stringlength; + int i, j, k; + int sorted[] = new int[MAX_CLIENTS]; + int sortedscores[] = new int[MAX_CLIENTS]; + int score, total; + int picnum; + int x, y; + gclient_t cl; + edict_t cl_ent; + String tag; + + // sort the clients by score + total = 0; + for (i = 0; i < game.maxclients; i++) { + cl_ent = g_edicts[1 + i]; + if (!cl_ent.inuse || game.clients[i].resp.spectator) + continue; + score = game.clients[i].resp.score; + for (j = 0; j < total; j++) { + if (score > sortedscores[j]) + break; + } + for (k = total; k > j; k--) { + sorted[k] = sorted[k - 1]; + sortedscores[k] = sortedscores[k - 1]; + } + sorted[j] = i; + sortedscores[j] = score; + total++; + } + + // print level name and exit rules + string = ""; + + stringlength = Lib.strlen(string); + + // add the clients in sorted order + if (total > 12) + total = 12; + + for (i = 0; i < total; i++) { + cl = game.clients[sorted[i]]; + cl_ent = g_edicts[1 + sorted[i]]; + + picnum = gi.imageindex("i_fixme"); + x = (i >= 6) ? 160 : 0; + y = 32 + 32 * (i % 6); + + // add a dogtag + if (cl_ent == ent) + tag = "tag1"; + else if (cl_ent == killer) + tag = "tag2"; + else + tag = null; + if (tag != null) { + entry = "xv " + (x + 32) + " yv " + y + " picn " + tag + " "; + j = Lib.strlen(entry); + if (stringlength + j > 1024) + break; + + string = string + entry; + + //was: strcpy (string + stringlength, entry); + stringlength += j; + } + + // send the layout + entry = + "client " + + x + + " " + + y + + " " + + sorted[i] + + " " + + cl.resp.score + + " " + + cl.ping + + " " + + (level.framenum - cl.resp.enterframe) / 600f + + " "; + + j = Lib.strlen(entry); + + if (stringlength + j > 1024) + break; + + string += entry; + // was: strcpy(string + stringlength, entry); + stringlength += j; + } + + gi.WriteByte(svc_layout); + gi.WriteString(string); + } + + /* + ================== + DeathmatchScoreboard + + Draw instead of help message. + Note that it isn't that hard to overflow the 1400 byte message limit! + ================== + */ + public static void DeathmatchScoreboard(edict_t ent) { + DeathmatchScoreboardMessage(ent, ent.enemy); + gi.unicast(ent, true); + } + + /* + ================== + HelpComputer + + Draw help computer. + ================== + */ + public static void HelpComputer(edict_t ent) { + String string; + String sk; + + if (skill.value == 0) + sk = "easy"; + else if (skill.value == 1) + sk = "medium"; + else if (skill.value == 2) + sk = "hard"; + else + sk = "hard+"; + + // send the layout + string = "xv 32 yv 8 picn help " + // background + "xv 202 yv 12 string2 \"" + sk + "\" " + // skill + "xv 0 yv 24 cstring2 \"" + level.level_name + "\" " + // level name + "xv 0 yv 54 cstring2 \"" + game.helpmessage1 + "\" " + // help 1 + "xv 0 yv 110 cstring2 \"" + game.helpmessage2 + "\" " + // help 2 + "xv 50 yv 164 string2 \" kills goals secrets\" " + + "xv 50 yv 172 string2 \"" + + level.killed_monsters + + "/" + + level.total_monsters + + " " + + level.found_goals + + "/" + + level.total_goals + + " " + + level.found_secrets + + "/" + + level.total_secrets + + "\" "; + + gi.WriteByte(svc_layout); + gi.WriteString(string); + gi.unicast(ent, true); + } + + 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 = DAMAGE_YES; + self.movetype = 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 |= SVF_DEADMONSTER; + + if (self.deadflag == 0) { + self.client.respawn_time = level.time + 1.0f; + LookAtKiller(self, inflictor, attacker); + self.client.ps.pmove.pm_type = PM_DEAD; + ClientObituary(self, inflictor, attacker); + TossClientWeapon(self); + if (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 < game.num_items; n++) { + if (coop.value != 0 && (itemlist[n].flags & 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 &= ~FL_POWER_ARMOR; + + if (self.health < -40) { // gib + gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + for (n = 0; n < 4; n++) + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowClientHead(self, damage); + + self.takedamage = 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 = ANIM_DEATH; + if ((self.client.ps.pmove.pm_flags & 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; + } + + gi.sound(self, CHAN_VOICE, gi.soundindex("*death" + ((Lib.rand() % 4) + 1) + ".wav"), 1, ATTN_NORM, 0); + } + } + + self.deadflag = DEAD_DEAD; + + 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 = game.clients[anum].ps.stats[STAT_FRAGS]; + int bnum1 = game.clients[bnum].ps.stats[STAT_FRAGS]; + + if (anum1 < bnum1) + return -1; + if (anum1 > bnum1) + return 1; + return 0; + } + }; + + /** + * Processes the commands the player enters in the quake console. + * + */ + public static void ClientCommand(edict_t ent) { + String cmd; + + if (ent.client == null) + return; // not fully in game yet + + cmd = gi.argv(0); + + if (Lib.Q_stricmp(cmd, "players") == 0) { + Cmd.Players_f(ent); + return; + } + if (Lib.Q_stricmp(cmd, "say") == 0) { + Cmd.Say_f(ent, false, false); + return; + } + if (Lib.Q_stricmp(cmd, "say_team") == 0) { + Cmd.Say_f(ent, true, false); + return; + } + if (Lib.Q_stricmp(cmd, "score") == 0) { + Cmd.Score_f(ent); + return; + } + if (Lib.Q_stricmp(cmd, "help") == 0) { + Cmd.Help_f(ent); + return; + } + + if (level.intermissiontime != 0) + return; + + if (Lib.Q_stricmp(cmd, "use") == 0) + Cmd.Use_f(ent); + else if (Lib.Q_stricmp(cmd, "drop") == 0) + Cmd.Drop_f(ent); + else if (Lib.Q_stricmp(cmd, "give") == 0) + Cmd.Give_f(ent); + else if (Lib.Q_stricmp(cmd, "god") == 0) + Cmd.God_f(ent); + else if (Lib.Q_stricmp(cmd, "notarget") == 0) + Cmd.Notarget_f(ent); + else if (Lib.Q_stricmp(cmd, "noclip") == 0) + Cmd.Noclip_f(ent); + else if (Lib.Q_stricmp(cmd, "inven") == 0) + Cmd.Inven_f(ent); + else if (Lib.Q_stricmp(cmd, "invnext") == 0) + SelectNextItem(ent, -1); + else if (Lib.Q_stricmp(cmd, "invprev") == 0) + SelectPrevItem(ent, -1); + else if (Lib.Q_stricmp(cmd, "invnextw") == 0) + SelectNextItem(ent, IT_WEAPON); + else if (Lib.Q_stricmp(cmd, "invprevw") == 0) + SelectPrevItem(ent, IT_WEAPON); + else if (Lib.Q_stricmp(cmd, "invnextp") == 0) + SelectNextItem(ent, IT_POWERUP); + else if (Lib.Q_stricmp(cmd, "invprevp") == 0) + SelectPrevItem(ent, IT_POWERUP); + else if (Lib.Q_stricmp(cmd, "invuse") == 0) + Cmd.InvUse_f(ent); + else if (Lib.Q_stricmp(cmd, "invdrop") == 0) + Cmd.InvDrop_f(ent); + else if (Lib.Q_stricmp(cmd, "weapprev") == 0) + Cmd.WeapPrev_f(ent); + else if (Lib.Q_stricmp(cmd, "weapnext") == 0) + Cmd.WeapNext_f(ent); + else if (Lib.Q_stricmp(cmd, "weaplast") == 0) + Cmd.WeapLast_f(ent); + else if (Lib.Q_stricmp(cmd, "kill") == 0) + Cmd.Kill_f(ent); + else if (Lib.Q_stricmp(cmd, "putaway") == 0) + Cmd.PutAway_f(ent); + else if (Lib.Q_stricmp(cmd, "wave") == 0) + Cmd.Wave_f(ent); + else if (Lib.Q_stricmp(cmd, "playerlist") == 0) + Cmd.PlayerList_f(ent); + else // anything that doesn't match a command will be a chat + Cmd.Say_f(ent, false, true); + } + + public static ItemUseAdapter Use_PowerArmor = new ItemUseAdapter() { + public void use(edict_t ent, gitem_t item) { + int index; + + if ((ent.flags & FL_POWER_ARMOR) != 0) { + ent.flags &= ~FL_POWER_ARMOR; + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0); + } + else { + index = ITEM_INDEX(FindItem("cells")); + if (0 == ent.client.pers.inventory[index]) { + gi.cprintf(ent, PRINT_HIGH, "No cells for power armor.\n"); + return; + } + ent.flags |= FL_POWER_ARMOR; + gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0); + } + } + }; + + public static boolean Pickup_PowerArmor(edict_t ent, edict_t other) { + int quantity; + + quantity = other.client.pers.inventory[ITEM_INDEX(ent.item)]; + + other.client.pers.inventory[ITEM_INDEX(ent.item)]++; + + if (deathmatch.value != 0) { + if (0 == (ent.spawnflags & DROPPED_ITEM)) + 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 ItemDropAdapter Drop_Ammo = new ItemDropAdapter() { + public void drop(edict_t ent, gitem_t item) { + edict_t dropped; + int index; + + index = ITEM_INDEX(item); + dropped = 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 == AMMO_GRENADES + && item.tag == AMMO_GRENADES + && ent.client.pers.inventory[index] - dropped.count <= 0) { + gi.cprintf(ent, PRINT_HIGH, "Can't drop current weapon\n"); + G_FreeEdict(dropped); + return; + } + + ent.client.pers.inventory[index] -= dropped.count; + ValidateSelectedItem(ent); + } + }; + + public static ItemDropAdapter Drop_General = new ItemDropAdapter() { + public void drop(edict_t ent, gitem_t item) { + Drop_Item(ent, item); + ent.client.pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem(ent); + } + }; + + public static ItemDropAdapter Drop_PowerArmor = new ItemDropAdapter() { + public void drop(edict_t ent, gitem_t item) { + if (0 != (ent.flags & FL_POWER_ARMOR) && (ent.client.pers.inventory[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, ARMOR_JACKET); + public static gitem_armor_t combatarmor_info = new gitem_armor_t(50, 100, .60f, .30f, ARMOR_COMBAT); + public static gitem_armor_t bodyarmor_info = new gitem_armor_t(100, 200, .80f, .60f, ARMOR_BODY); + + public static gitem_t itemlist[] = { + //leave index 0 alone + null, + + // + // ARMOR + // + new gitem_t( + /*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + + "item_armor_body", Pickup_Armor, null, null, null, "misc/ar1_pkup.wav", "models/items/armor/body/tris.md2", EF_ROTATE, null, + /* icon */ + "i_bodyarmor", + /* pickup */ + "Body Armor", + /* width */ + 3, 0, null, IT_ARMOR, 0, bodyarmor_info, ARMOR_BODY, + /* precache */ + ""), + + /*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "item_armor_combat", + GameAI.Pickup_Armor, + null, + null, + null, + "misc/ar1_pkup.wav", + "models/items/armor/combat/tris.md2", + EF_ROTATE, + null, + /* icon */ + "i_combatarmor", + /* pickup */ + "Combat Armor", + /* width */ + 3, 0, null, IT_ARMOR, 0, GameAI.combatarmor_info, ARMOR_COMBAT, + /* precache */ + ""), + + /*QUAKED item_armor_jacket (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "item_armor_jacket", + GameAI.Pickup_Armor, + null, + null, + null, + "misc/ar1_pkup.wav", + "models/items/armor/jacket/tris.md2", + EF_ROTATE, + null, + /* icon */ + "i_jacketarmor", + /* pickup */ + "Jacket Armor", + /* width */ + 3, 0, null, IT_ARMOR, 0, GameAI.jacketarmor_info, ARMOR_JACKET, + /* precache */ + ""), + + /*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "item_armor_shard", + GameAI.Pickup_Armor, + null, + null, + null, + "misc/ar2_pkup.wav", + "models/items/armor/shard/tris.md2", + EF_ROTATE, + null, + /* icon */ + "i_jacketarmor", + /* pickup */ + "Armor Shard", + /* width */ + 3, 0, null, IT_ARMOR, 0, null, ARMOR_SHARD, + /* precache */ + ""), + + /*QUAKED item_power_screen (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "item_power_screen", + GameAI.Pickup_PowerArmor, + GameAI.Use_PowerArmor, + GameAI.Drop_PowerArmor, + null, + "misc/ar3_pkup.wav", + "models/items/armor/screen/tris.md2", + EF_ROTATE, + null, + /* icon */ + "i_powerscreen", + /* pickup */ + "Power Screen", + /* width */ + 0, 60, null, IT_ARMOR, 0, null, 0, + /* precache */ + ""), + + /*QUAKED item_power_shield (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "item_power_shield", + Pickup_PowerArmor, + Use_PowerArmor, + Drop_PowerArmor, + null, + "misc/ar3_pkup.wav", + "models/items/armor/shield/tris.md2", + EF_ROTATE, + null, + /* icon */ + "i_powershield", + /* pickup */ + "Power Shield", + /* width */ + 0, 60, null, IT_ARMOR, 0, null, 0, + /* precache */ + "misc/power2.wav misc/power1.wav"), + + // + // WEAPONS + // + + /* weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16) + always owned, never in the world + */ + new gitem_t( + "weapon_blaster", + null, + GamePWeapon.Use_Weapon, + null, + GamePWeapon.Weapon_Blaster, + "misc/w_pkup.wav", + null, + 0, + "models/weapons/v_blast/tris.md2", + /* icon */ + "w_blaster", + /* pickup */ + "Blaster", 0, 0, null, IT_WEAPON | IT_STAY_COOP, WEAP_BLASTER, null, 0, + /* precache */ + "weapons/blastf1a.wav misc/lasfly.wav"), + + /*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "weapon_shotgun", + GamePWeapon.Pickup_Weapon, + GamePWeapon.Use_Weapon, + GamePWeapon.Drop_Weapon, + GamePWeapon.Weapon_Shotgun, + "misc/w_pkup.wav", + "models/weapons/g_shotg/tris.md2", + EF_ROTATE, + "models/weapons/v_shotg/tris.md2", + /* icon */ + "w_shotgun", + /* pickup */ + "Shotgun", 0, 1, "Shells", IT_WEAPON | IT_STAY_COOP, WEAP_SHOTGUN, null, 0, + /* precache */ + "weapons/shotgf1b.wav weapons/shotgr1b.wav"), + + /*QUAKED weapon_supershotgun (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "weapon_supershotgun", + GamePWeapon.Pickup_Weapon, + GamePWeapon.Use_Weapon, + GamePWeapon.Drop_Weapon, + GamePWeapon.Weapon_SuperShotgun, + "misc/w_pkup.wav", + "models/weapons/g_shotg2/tris.md2", + EF_ROTATE, + "models/weapons/v_shotg2/tris.md2", + /* icon */ + "w_sshotgun", + /* pickup */ + "Super Shotgun", 0, 2, "Shells", IT_WEAPON | IT_STAY_COOP, WEAP_SUPERSHOTGUN, null, 0, + /* precache */ + "weapons/sshotf1b.wav"), + + /*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "weapon_machinegun", + GamePWeapon.Pickup_Weapon, + GamePWeapon.Use_Weapon, + GamePWeapon.Drop_Weapon, + GamePWeapon.Weapon_Machinegun, + "misc/w_pkup.wav", + "models/weapons/g_machn/tris.md2", + EF_ROTATE, + "models/weapons/v_machn/tris.md2", + /* icon */ + "w_machinegun", + /* pickup */ + "Machinegun", 0, 1, "Bullets", IT_WEAPON | IT_STAY_COOP, WEAP_MACHINEGUN, null, 0, + /* precache */ + "weapons/machgf1b.wav weapons/machgf2b.wav weapons/machgf3b.wav weapons/machgf4b.wav weapons/machgf5b.wav"), + + /*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "weapon_chaingun", + GamePWeapon.Pickup_Weapon, + GamePWeapon.Use_Weapon, + GamePWeapon.Drop_Weapon, + GamePWeapon.Weapon_Chaingun, + "misc/w_pkup.wav", + "models/weapons/g_chain/tris.md2", + EF_ROTATE, + "models/weapons/v_chain/tris.md2", + /* icon */ + "w_chaingun", + /* pickup */ + "Chaingun", 0, 1, "Bullets", IT_WEAPON | IT_STAY_COOP, WEAP_CHAINGUN, null, 0, + /* precache */ + "weapons/chngnu1a.wav weapons/chngnl1a.wav weapons/machgf3b.wav` weapons/chngnd1a.wav"), + + /*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "ammo_grenades", + Pickup_Ammo, + GamePWeapon.Use_Weapon, + Drop_Ammo, + GamePWeapon.Weapon_Grenade, + "misc/am_pkup.wav", + "models/items/ammo/grenades/medium/tris.md2", + 0, + "models/weapons/v_handgr/tris.md2", + /* icon */ + "a_grenades", + /* pickup */ + "Grenades", + /* width */ + 3, 5, "grenades", IT_AMMO | IT_WEAPON, WEAP_GRENADES, null, AMMO_GRENADES, + /* precache */ + "weapons/hgrent1a.wav weapons/hgrena1b.wav weapons/hgrenc1b.wav weapons/hgrenb1a.wav weapons/hgrenb2a.wav "), + + /*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "weapon_grenadelauncher", + GamePWeapon.Pickup_Weapon, + GamePWeapon.Use_Weapon, + GamePWeapon.Drop_Weapon, + GamePWeapon.Weapon_GrenadeLauncher, + "misc/w_pkup.wav", + "models/weapons/g_launch/tris.md2", + EF_ROTATE, + "models/weapons/v_launch/tris.md2", + /* icon */ + "w_glauncher", + /* pickup */ + "Grenade Launcher", 0, 1, "Grenades", IT_WEAPON | IT_STAY_COOP, WEAP_GRENADELAUNCHER, null, 0, + /* precache */ + "models/objects/grenade/tris.md2 weapons/grenlf1a.wav weapons/grenlr1b.wav weapons/grenlb1b.wav"), + + /*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "weapon_rocketlauncher", + GamePWeapon.Pickup_Weapon, + GamePWeapon.Use_Weapon, + GamePWeapon.Drop_Weapon, + GamePWeapon.Weapon_RocketLauncher, + "misc/w_pkup.wav", + "models/weapons/g_rocket/tris.md2", + EF_ROTATE, + "models/weapons/v_rocket/tris.md2", + /* icon */ + "w_rlauncher", + /* pickup */ + "Rocket Launcher", 0, 1, "Rockets", IT_WEAPON | IT_STAY_COOP, WEAP_ROCKETLAUNCHER, null, 0, + /* precache */ + "models/objects/rocket/tris.md2 weapons/rockfly.wav weapons/rocklf1a.wav weapons/rocklr1b.wav models/objects/debris2/tris.md2"), + + /*QUAKED weapon_hyperblaster (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "weapon_hyperblaster", + GamePWeapon.Pickup_Weapon, + GamePWeapon.Use_Weapon, + GamePWeapon.Drop_Weapon, + GamePWeapon.Weapon_HyperBlaster, + "misc/w_pkup.wav", + "models/weapons/g_hyperb/tris.md2", + EF_ROTATE, + "models/weapons/v_hyperb/tris.md2", + /* icon */ + "w_hyperblaster", + /* pickup */ + "HyperBlaster", 0, 1, "Cells", IT_WEAPON | IT_STAY_COOP, WEAP_HYPERBLASTER, null, 0, + /* precache */ + "weapons/hyprbu1a.wav weapons/hyprbl1a.wav weapons/hyprbf1a.wav weapons/hyprbd1a.wav misc/lasfly.wav"), + + /*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "weapon_railgun", + GamePWeapon.Pickup_Weapon, + GamePWeapon.Use_Weapon, + GamePWeapon.Drop_Weapon, + GamePWeapon.Weapon_Railgun, + "misc/w_pkup.wav", + "models/weapons/g_rail/tris.md2", + EF_ROTATE, + "models/weapons/v_rail/tris.md2", + /* icon */ + "w_railgun", + /* pickup */ + "Railgun", 0, 1, "Slugs", IT_WEAPON | IT_STAY_COOP, WEAP_RAILGUN, null, 0, + /* precache */ + "weapons/rg_hum.wav"), + + /*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "weapon_bfg", + GamePWeapon.Pickup_Weapon, + GamePWeapon.Use_Weapon, + GamePWeapon.Drop_Weapon, + GamePWeapon.Weapon_BFG, + "misc/w_pkup.wav", + "models/weapons/g_bfg/tris.md2", + EF_ROTATE, + "models/weapons/v_bfg/tris.md2", + /* icon */ + "w_bfg", + /* pickup */ + "BFG10K", 0, 50, "Cells", IT_WEAPON | IT_STAY_COOP, WEAP_BFG, null, 0, + /* precache */ + "sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2 weapons/bfg__f1y.wav weapons/bfg__l1a.wav weapons/bfg__x1b.wav weapons/bfg_hum.wav"), + + // + // AMMO ITEMS + // + + /*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "ammo_shells", + GamePWeapon.Pickup_Ammo, + null, + GamePWeapon.Drop_Ammo, + null, + "misc/am_pkup.wav", + "models/items/ammo/shells/medium/tris.md2", + 0, + null, + /* icon */ + "a_shells", + /* pickup */ + "Shells", + /* width */ + 3, 10, null, IT_AMMO, 0, null, AMMO_SHELLS, + /* precache */ + ""), + + /*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "ammo_bullets", + GamePWeapon.Pickup_Ammo, + null, + GamePWeapon.Drop_Ammo, + null, + "misc/am_pkup.wav", + "models/items/ammo/bullets/medium/tris.md2", + 0, + null, + /* icon */ + "a_bullets", + /* pickup */ + "Bullets", + /* width */ + 3, 50, null, IT_AMMO, 0, null, AMMO_BULLETS, + /* precache */ + ""), + + /*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "ammo_cells", + GamePWeapon.Pickup_Ammo, + null, + GamePWeapon.Drop_Ammo, + null, + "misc/am_pkup.wav", + "models/items/ammo/cells/medium/tris.md2", + 0, + null, + /* icon */ + "a_cells", + /* pickup */ + "Cells", + /* width */ + 3, 50, null, IT_AMMO, 0, null, AMMO_CELLS, + /* precache */ + ""), + + /*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "ammo_rockets", + GamePWeapon.Pickup_Ammo, + null, + GamePWeapon.Drop_Ammo, + null, + "misc/am_pkup.wav", + "models/items/ammo/rockets/medium/tris.md2", + 0, + null, + /* icon */ + "a_rockets", + /* pickup */ + "Rockets", + /* width */ + 3, 5, null, IT_AMMO, 0, null, AMMO_ROCKETS, + /* precache */ + ""), + + /*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "ammo_slugs", + GamePWeapon.Pickup_Ammo, + null, + GamePWeapon.Drop_Ammo, + null, + "misc/am_pkup.wav", + "models/items/ammo/slugs/medium/tris.md2", + 0, + null, + /* icon */ + "a_slugs", + /* pickup */ + "Slugs", + /* width */ + 3, 10, null, IT_AMMO, 0, null, AMMO_SLUGS, + /* precache */ + ""), + + // + // POWERUP ITEMS + // + /*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "item_quad", + Pickup_Powerup, + Use_Quad, + Drop_General, + null, + "items/pkup.wav", + "models/items/quaddama/tris.md2", + EF_ROTATE, + null, + /* icon */ + "p_quad", + /* pickup */ + "Quad Damage", + /* width */ + 2, 60, null, IT_POWERUP, 0, null, 0, + /* precache */ + "items/damage.wav items/damage2.wav items/damage3.wav"), + + /*QUAKED item_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "item_invulnerability", + Pickup_Powerup, + Use_Invulnerability, + Drop_General, + null, + "items/pkup.wav", + "models/items/invulner/tris.md2", + EF_ROTATE, + null, + /* icon */ + "p_invulnerability", + /* pickup */ + "Invulnerability", + /* width */ + 2, 300, null, IT_POWERUP, 0, null, 0, + /* precache */ + "items/protect.wav items/protect2.wav items/protect4.wav"), + + /*QUAKED item_silencer (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "item_silencer", + Pickup_Powerup, + Use_Silencer, + Drop_General, + null, + "items/pkup.wav", + "models/items/silencer/tris.md2", + EF_ROTATE, + null, + /* icon */ + "p_silencer", + /* pickup */ + "Silencer", + /* width */ + 2, 60, null, IT_POWERUP, 0, null, 0, + /* precache */ + ""), + + /*QUAKED item_breather (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "item_breather", + Pickup_Powerup, + Use_Breather, + Drop_General, + null, + "items/pkup.wav", + "models/items/breather/tris.md2", + EF_ROTATE, + null, + /* icon */ + "p_rebreather", + /* pickup */ + "Rebreather", + /* width */ + 2, 60, null, IT_STAY_COOP | IT_POWERUP, 0, null, 0, + /* precache */ + "items/airout.wav"), + + /*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "item_enviro", + Pickup_Powerup, + Use_Envirosuit, + Drop_General, + null, + "items/pkup.wav", + "models/items/enviro/tris.md2", + EF_ROTATE, + null, + /* icon */ + "p_envirosuit", + /* pickup */ + "Environment Suit", + /* width */ + 2, 60, null, IT_STAY_COOP | IT_POWERUP, 0, null, 0, + /* precache */ + "items/airout.wav"), + + /*QUAKED item_ancient_head (.3 .3 1) (-16 -16 -16) (16 16 16) + Special item that gives +2 to maximum health + */ + new gitem_t( + "item_ancient_head", + Pickup_AncientHead, + null, + null, + null, + "items/pkup.wav", + "models/items/c_head/tris.md2", + EF_ROTATE, + null, + /* icon */ + "i_fixme", + /* pickup */ + "Ancient Head", + /* width */ + 2, 60, null, 0, 0, null, 0, + /* precache */ + ""), + + /*QUAKED item_adrenaline (.3 .3 1) (-16 -16 -16) (16 16 16) + gives +1 to maximum health + */ + new gitem_t( + "item_adrenaline", + Pickup_Adrenaline, + null, + null, + null, + "items/pkup.wav", + "models/items/adrenal/tris.md2", + EF_ROTATE, + null, + /* icon */ + "p_adrenaline", + /* pickup */ + "Adrenaline", + /* width */ + 2, 60, null, 0, 0, null, 0, + /* precache */ + ""), + + /*QUAKED item_bandolier (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "item_bandolier", + Pickup_Bandolier, + null, + null, + null, + "items/pkup.wav", + "models/items/band/tris.md2", + EF_ROTATE, + null, + /* icon */ + "p_bandolier", + /* pickup */ + "Bandolier", + /* width */ + 2, 60, null, 0, 0, null, 0, + /* precache */ + ""), + + /*QUAKED item_pack (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + new gitem_t( + "item_pack", + GamePWeapon.Pickup_Pack, + null, + null, + null, + "items/pkup.wav", + "models/items/pack/tris.md2", + EF_ROTATE, + null, + /* icon */ + "i_pack", + /* pickup */ + "Ammo Pack", + /* width */ + 2, 180, null, 0, 0, null, 0, + /* precache */ + ""), + + // + // KEYS + // + /*QUAKED key_data_cd (0 .5 .8) (-16 -16 -16) (16 16 16) + key for computer centers + */ + new gitem_t( + "key_data_cd", + Pickup_Key, + null, + Drop_General, + null, + "items/pkup.wav", + "models/items/keys/data_cd/tris.md2", + EF_ROTATE, + null, + "k_datacd", + "Data CD", + 2, + 0, + null, + IT_STAY_COOP | IT_KEY, + 0, + null, + 0, + /* precache */ + ""), + + /*QUAKED key_power_cube (0 .5 .8) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN NO_TOUCH + warehouse circuits + */ + new gitem_t( + "key_power_cube", + Pickup_Key, + null, + Drop_General, + null, + "items/pkup.wav", + "models/items/keys/power/tris.md2", + EF_ROTATE, + null, + "k_powercube", + "Power Cube", + 2, + 0, + null, + IT_STAY_COOP | IT_KEY, + 0, + null, + 0, + /* precache */ + ""), + + /*QUAKED key_pyramid (0 .5 .8) (-16 -16 -16) (16 16 16) + key for the entrance of jail3 + */ + new gitem_t( + "key_pyramid", + Pickup_Key, + null, + Drop_General, + null, + "items/pkup.wav", + "models/items/keys/pyramid/tris.md2", + EF_ROTATE, + null, + "k_pyramid", + "Pyramid Key", + 2, + 0, + null, + IT_STAY_COOP | IT_KEY, + 0, + null, + 0, + /* precache */ + ""), + + /*QUAKED key_data_spinner (0 .5 .8) (-16 -16 -16) (16 16 16) + key for the city computer + */ + new gitem_t( + "key_data_spinner", + Pickup_Key, + null, + Drop_General, + null, + "items/pkup.wav", + "models/items/keys/spinner/tris.md2", + EF_ROTATE, + null, + "k_dataspin", + "Data Spinner", + 2, + 0, + null, + IT_STAY_COOP | IT_KEY, + 0, + null, + 0, + /* precache */ + ""), + + /*QUAKED key_pass (0 .5 .8) (-16 -16 -16) (16 16 16) + security pass for the security level + */ + new gitem_t( + "key_pass", + Pickup_Key, + null, + Drop_General, + null, + "items/pkup.wav", + "models/items/keys/pass/tris.md2", + EF_ROTATE, + null, + "k_security", + "Security Pass", + 2, + 0, + null, + IT_STAY_COOP | IT_KEY, + 0, + null, + 0, + /* precache */ + ""), + + /*QUAKED key_blue_key (0 .5 .8) (-16 -16 -16) (16 16 16) + normal door key - blue + */ + new gitem_t( + "key_blue_key", + Pickup_Key, + null, + Drop_General, + null, + "items/pkup.wav", + "models/items/keys/key/tris.md2", + EF_ROTATE, + null, + "k_bluekey", + "Blue Key", + 2, + 0, + null, + IT_STAY_COOP | IT_KEY, + 0, + null, + 0, + /* precache */ + ""), + + /*QUAKED key_red_key (0 .5 .8) (-16 -16 -16) (16 16 16) + normal door key - red + */ + new gitem_t( + "key_red_key", + Pickup_Key, + null, + Drop_General, + null, + "items/pkup.wav", + "models/items/keys/red_key/tris.md2", + EF_ROTATE, + null, + "k_redkey", + "Red Key", + 2, + 0, + null, + IT_STAY_COOP | IT_KEY, + 0, + null, + 0, + /* precache */ + ""), + + /*QUAKED key_commander_head (0 .5 .8) (-16 -16 -16) (16 16 16) + tank commander's head + */ + new gitem_t( + "key_commander_head", + Pickup_Key, + null, + Drop_General, + null, + "items/pkup.wav", + "models/monsters/commandr/head/tris.md2", + EF_GIB, + null, + /* icon */ + "k_comhead", + /* pickup */ + "Commander's Head", + /* width */ + 2, 0, null, IT_STAY_COOP | IT_KEY, 0, null, 0, + /* precache */ + ""), + + /*QUAKED key_airstrike_target (0 .5 .8) (-16 -16 -16) (16 16 16) + tank commander's head + */ + new gitem_t( + "key_airstrike_target", + Pickup_Key, + null, + Drop_General, + null, + "items/pkup.wav", + "models/items/keys/target/tris.md2", + EF_ROTATE, + null, + /* icon */ + "i_airstrike", + /* pickup */ + "Airstrike Marker", + /* width */ + 2, 0, null, IT_STAY_COOP | IT_KEY, 0, null, 0, + /* precache */ + ""), new gitem_t(null, Pickup_Health, null, null, null, "items/pkup.wav", null, 0, null, + /* icon */ + "i_health", + /* pickup */ + "Health", + /* width */ + 3, 0, null, 0, 0, null, 0, + /* precache */ + "items/s_health.wav items/n_health.wav items/l_health.wav items/m_health.wav"), + + // end of list marker + null }; + + public static void InitItems() { + //game.num_items = sizeof(itemlist)/sizeof(itemlist[0]) - 1; + game.num_items = itemlist.length - 1; + } + + + + /*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + public static void SP_item_health (edict_t self) + { + if ( deathmatch.value!=0 && ((int)dmflags.value & DF_NO_HEALTH) !=0) + { + G_FreeEdict (self); + } + + self.model = "models/items/healing/medium/tris.md2"; + self.count = 10; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/n_health.wav"); + } + + /*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + static void SP_item_health_small (edict_t self) + { + if ( deathmatch.value!=0 && ((int)dmflags.value & DF_NO_HEALTH)!=0) + { + G_FreeEdict (self); + return; + } + + self.model = "models/items/healing/stimpack/tris.md2"; + self.count = 2; + SpawnItem (self, FindItem ("Health")); + self.style = HEALTH_IGNORE_MAX; + gi.soundindex ("items/s_health.wav"); + } + + /*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + static void SP_item_health_large (edict_t self) + { + if ( deathmatch.value!=0 && ((int)dmflags.value & DF_NO_HEALTH) !=0) + { + G_FreeEdict (self); + return; + } + + self.model = "models/items/healing/large/tris.md2"; + self.count = 25; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/l_health.wav"); + } + + /* + * QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) + */ + static void SP_item_health_mega (edict_t self) + { + if ( deathmatch.value!=0 && ((int)dmflags.value & DF_NO_HEALTH) !=0) + { + G_FreeEdict (self); + return; + } + + self.model = "models/items/mega_h/tris.md2"; + self.count = 100; + SpawnItem (self, FindItem ("Health")); + gi.soundindex ("items/m_health.wav"); + self.style = HEALTH_IGNORE_MAX | HEALTH_TIMED; + } +} |