/*
* 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.
*
*/
/* Modifications
Copyright 2003-2004 Bytonic Software
Copyright 2010 Google Inc.
*/
package com.googlecode.gwtquake.shared.game;
import com.googlecode.gwtquake.*;
import com.googlecode.gwtquake.shared.client.*;
import com.googlecode.gwtquake.shared.common.*;
import com.googlecode.gwtquake.shared.game.adapters.EntityDieAdapter;
import com.googlecode.gwtquake.shared.game.adapters.EntityThinkAdapter;
import com.googlecode.gwtquake.shared.game.adapters.EntityBlockedAdapter;
import com.googlecode.gwtquake.shared.game.monsters.MonsterInfantry;
import com.googlecode.gwtquake.shared.render.*;
import com.googlecode.gwtquake.shared.server.*;
import com.googlecode.gwtquake.shared.util.Lib;
import com.googlecode.gwtquake.shared.util.Math3D;
public class GameTurret {
public static void AnglesNormalize(float[] vec) {
while (vec[0] > 360)
vec[0] -= 360;
while (vec[0] < 0)
vec[0] += 360;
while (vec[1] > 360)
vec[1] -= 360;
while (vec[1] < 0)
vec[1] += 360;
}
public static float SnapToEights(float x) {
x *= 8.0;
if (x > 0.0)
x += 0.5;
else
x -= 0.5;
return 0.125f * (int) x;
}
/**
* QUAKED turret_breach (0 0 0) ? This portion of the turret can change both
* pitch and yaw. The model should be made with a flat pitch. It (and the
* associated base) need to be oriented towards 0. Use "angle" to set the
* starting angle.
*
* "speed" default 50 "dmg" default 10 "angle" point this forward "target"
* point this at an info_notnull at the muzzle tip "minpitch" min acceptable
* pitch angle : default -30 "maxpitch" max acceptable pitch angle : default
* 30 "minyaw" min acceptable yaw angle : default 0 "maxyaw" max acceptable
* yaw angle : default 360
*/
public static void turret_breach_fire(Entity self) {
float[] f = { 0, 0, 0 }, r = { 0, 0, 0 }, u = { 0, 0, 0 };
float[] start = { 0, 0, 0 };
int damage;
int speed;
Math3D.AngleVectors(self.s.angles, f, r, u);
Math3D.VectorMA(self.s.origin, self.move_origin[0], f, start);
Math3D.VectorMA(start, self.move_origin[1], r, start);
Math3D.VectorMA(start, self.move_origin[2], u, start);
damage = (int) (100 + Lib.random() * 50);
speed = (int) (550 + 50 * GameBase.skill.value);
GameWeapon.fire_rocket(self.teammaster.owner, start, f, damage, speed, 150,
damage);
ServerSend.SV_StartSound(start, self, Constants.CHAN_WEAPON, ServerInit.SV_SoundIndex("weapons/rocklf1a.wav"), (float) 1,
(float) Constants.ATTN_NORM, (float) 0);
}
public static void SP_turret_breach(Entity self) {
self.solid = Constants.SOLID_BSP;
self.movetype = Constants.MOVETYPE_PUSH;
ServerGame.PF_setmodel(self, self.model);
if (self.speed == 0)
self.speed = 50;
if (self.dmg == 0)
self.dmg = 10;
if (GameBase.st.minpitch == 0)
GameBase.st.minpitch = -30;
if (GameBase.st.maxpitch == 0)
GameBase.st.maxpitch = 30;
if (GameBase.st.maxyaw == 0)
GameBase.st.maxyaw = 360;
self.pos1[Constants.PITCH] = -1 * GameBase.st.minpitch;
self.pos1[Constants.YAW] = GameBase.st.minyaw;
self.pos2[Constants.PITCH] = -1 * GameBase.st.maxpitch;
self.pos2[Constants.YAW] = GameBase.st.maxyaw;
self.ideal_yaw = self.s.angles[Constants.YAW];
self.move_angles[Constants.YAW] = self.ideal_yaw;
self.blocked = turret_blocked;
self.think = turret_breach_finish_init;
self.nextthink = GameBase.level.time + Constants.FRAMETIME;
World.SV_LinkEdict(self);
}
/**
* QUAKED turret_base (0 0 0) ? This portion of the turret changes yaw only.
* MUST be teamed with a turret_breach.
*/
public static void SP_turret_base(Entity self) {
self.solid = Constants.SOLID_BSP;
self.movetype = Constants.MOVETYPE_PUSH;
ServerGame.PF_setmodel(self, self.model);
self.blocked = turret_blocked;
World.SV_LinkEdict(self);
}
public static void SP_turret_driver(Entity self) {
if (GameBase.deathmatch.value != 0) {
GameUtil.G_FreeEdict(self);
return;
}
self.movetype = Constants.MOVETYPE_PUSH;
self.solid = Constants.SOLID_BBOX;
self.s.modelindex = ServerInit.SV_ModelIndex("models/monsters/infantry/tris.md2");
Math3D.VectorSet(self.mins, -16, -16, -24);
Math3D.VectorSet(self.maxs, 16, 16, 32);
self.health = 100;
self.gib_health = 0;
self.mass = 200;
self.viewheight = 24;
self.die = turret_driver_die;
self.monsterinfo.stand = MonsterInfantry.infantry_stand;
self.flags |= Constants.FL_NO_KNOCKBACK;
GameBase.level.total_monsters++;
self.svflags |= Constants.SVF_MONSTER;
self.s.renderfx |= Constants.RF_FRAMELERP;
self.takedamage = Constants.DAMAGE_AIM;
self.use = GameUtil.monster_use;
self.clipmask = Constants.MASK_MONSTERSOLID;
Math3D.VectorCopy(self.s.origin, self.s.old_origin);
self.monsterinfo.aiflags |= Constants.AI_STAND_GROUND | Constants.AI_DUCKED;
if (GameBase.st.item != null) {
self.item = GameItems.FindItemByClassname(GameBase.st.item);
if (self.item == null)
ServerGame.PF_dprintf(self.classname + " at "
+ Lib.vtos(self.s.origin) + " has bad item: "
+ GameBase.st.item + "\n");
}
self.think = turret_driver_link;
self.nextthink = GameBase.level.time + Constants.FRAMETIME;
World.SV_LinkEdict(self);
}
static EntityBlockedAdapter turret_blocked = new EntityBlockedAdapter() {
public String getID() { return "turret_blocked"; }
public void blocked(Entity self, Entity other) {
Entity attacker;
if (other.takedamage != 0) {
if (self.teammaster.owner != null)
attacker = self.teammaster.owner;
else
attacker = self.teammaster;
GameCombat.T_Damage(other, self, attacker, Globals.vec3_origin,
other.s.origin, Globals.vec3_origin,
self.teammaster.dmg, 10, 0, Constants.MOD_CRUSH);
}
}
};
static EntityThinkAdapter turret_breach_think = new EntityThinkAdapter() {
public String getID() { return "turret_breach_think"; }
public boolean think(Entity self) {
Entity ent;
float[] current_angles = { 0, 0, 0 };
float[] delta = { 0, 0, 0 };
Math3D.VectorCopy(self.s.angles, current_angles);
AnglesNormalize(current_angles);
AnglesNormalize(self.move_angles);
if (self.move_angles[Constants.PITCH] > 180)
self.move_angles[Constants.PITCH] -= 360;
// clamp angles to mins & maxs
if (self.move_angles[Constants.PITCH] > self.pos1[Constants.PITCH])
self.move_angles[Constants.PITCH] = self.pos1[Constants.PITCH];
else if (self.move_angles[Constants.PITCH] < self.pos2[Constants.PITCH])
self.move_angles[Constants.PITCH] = self.pos2[Constants.PITCH];
if ((self.move_angles[Constants.YAW] < self.pos1[Constants.YAW])
|| (self.move_angles[Constants.YAW] > self.pos2[Constants.YAW])) {
float dmin, dmax;
dmin = Math.abs(self.pos1[Constants.YAW]
- self.move_angles[Constants.YAW]);
if (dmin < -180)
dmin += 360;
else if (dmin > 180)
dmin -= 360;
dmax = Math.abs(self.pos2[Constants.YAW]
- self.move_angles[Constants.YAW]);
if (dmax < -180)
dmax += 360;
else if (dmax > 180)
dmax -= 360;
if (Math.abs(dmin) < Math.abs(dmax))
self.move_angles[Constants.YAW] = self.pos1[Constants.YAW];
else
self.move_angles[Constants.YAW] = self.pos2[Constants.YAW];
}
Math3D.VectorSubtract(self.move_angles, current_angles, delta);
if (delta[0] < -180)
delta[0] += 360;
else if (delta[0] > 180)
delta[0] -= 360;
if (delta[1] < -180)
delta[1] += 360;
else if (delta[1] > 180)
delta[1] -= 360;
delta[2] = 0;
if (delta[0] > self.speed * Constants.FRAMETIME)
delta[0] = self.speed * Constants.FRAMETIME;
if (delta[0] < -1 * self.speed * Constants.FRAMETIME)
delta[0] = -1 * self.speed * Constants.FRAMETIME;
if (delta[1] > self.speed * Constants.FRAMETIME)
delta[1] = self.speed * Constants.FRAMETIME;
if (delta[1] < -1 * self.speed * Constants.FRAMETIME)
delta[1] = -1 * self.speed * Constants.FRAMETIME;
Math3D.VectorScale(delta, 1.0f / Constants.FRAMETIME, self.avelocity);
self.nextthink = GameBase.level.time + Constants.FRAMETIME;
for (ent = self.teammaster; ent != null; ent = ent.teamchain)
ent.avelocity[1] = self.avelocity[1];
// if we have adriver, adjust his velocities
if (self.owner != null) {
float angle;
float target_z;
float diff;
float[] target = { 0, 0, 0 };
float[] dir = { 0, 0, 0 };
// angular is easy, just copy ours
self.owner.avelocity[0] = self.avelocity[0];
self.owner.avelocity[1] = self.avelocity[1];
// x & y
angle = self.s.angles[1] + self.owner.move_origin[1];
angle *= (Math.PI * 2 / 360);
target[0] = GameTurret.SnapToEights((float) (self.s.origin[0] +
Math.cos(angle) * self.owner.move_origin[0]));
target[1] = GameTurret.SnapToEights((float) (self.s.origin[1] +
Math.sin(angle) * self.owner.move_origin[0]));
target[2] = self.owner.s.origin[2];
Math3D.VectorSubtract(target, self.owner.s.origin, dir);
self.owner.velocity[0] = dir[0] * 1.0f / Constants.FRAMETIME;
self.owner.velocity[1] = dir[1] * 1.0f / Constants.FRAMETIME;
// z
angle = self.s.angles[Constants.PITCH] * (float) (Math.PI * 2f / 360f);
target_z = GameTurret.SnapToEights((float) (self.s.origin[2]
+ self.owner.move_origin[0] * Math.tan(angle) + self.owner.move_origin[2]));
diff = target_z - self.owner.s.origin[2];
self.owner.velocity[2] = diff * 1.0f / Constants.FRAMETIME;
if ((self.spawnflags & 65536) != 0) {
turret_breach_fire(self);
self.spawnflags &= ~65536;
}
}
return true;
}
};
static EntityThinkAdapter turret_breach_finish_init = new EntityThinkAdapter() {
public String getID() { return "turret_breach_finish_init"; }
public boolean think(Entity self) {
// get and save info for muzzle location
if (self.target == null) {
ServerGame.PF_dprintf(self.classname + " at "
+ Lib.vtos(self.s.origin) + " needs a target\n");
} else {
self.target_ent = GameBase.G_PickTarget(self.target);
Math3D.VectorSubtract(self.target_ent.s.origin, self.s.origin,
self.move_origin);
GameUtil.G_FreeEdict(self.target_ent);
}
self.teammaster.dmg = self.dmg;
self.think = turret_breach_think;
self.think.think(self);
return true;
}
};
/*
* QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32) Must NOT be on the
* team with the rest of the turret parts. Instead it must target the
* turret_breach.
*/
static EntityDieAdapter turret_driver_die = new EntityDieAdapter() {
public String getID() { return "turret_driver_die"; }
public void die(Entity self, Entity inflictor, Entity attacker,
int damage, float[] point) {
Entity ent;
// level the gun
self.target_ent.move_angles[0] = 0;
// remove the driver from the end of them team chain
for (ent = self.target_ent.teammaster; ent.teamchain != self; ent = ent.teamchain)
;
ent.teamchain = null;
self.teammaster = null;
self.flags &= ~Constants.FL_TEAMSLAVE;
self.target_ent.owner = null;
self.target_ent.teammaster.owner = null;
MonsterInfantry.infantry_die.die(self, inflictor, attacker, damage, null);
}
};
static EntityThinkAdapter turret_driver_think = new EntityThinkAdapter() {
public String getID() { return "turret_driver_think"; }
public boolean think(Entity self) {
float[] target = { 0, 0, 0 };
float[] dir = { 0, 0, 0 };
float reaction_time;
self.nextthink = GameBase.level.time + Constants.FRAMETIME;
if (self.enemy != null
&& (!self.enemy.inuse || self.enemy.health <= 0))
self.enemy = null;
if (null == self.enemy) {
if (!GameUtil.FindTarget(self))
return true;
self.monsterinfo.trail_time = GameBase.level.time;
self.monsterinfo.aiflags &= ~Constants.AI_LOST_SIGHT;
} else {
if (GameUtil.visible(self, self.enemy)) {
if ((self.monsterinfo.aiflags & Constants.AI_LOST_SIGHT) != 0) {
self.monsterinfo.trail_time = GameBase.level.time;
self.monsterinfo.aiflags &= ~Constants.AI_LOST_SIGHT;
}
} else {
self.monsterinfo.aiflags |= Constants.AI_LOST_SIGHT;
return true;
}
}
// let the turret know where we want it to aim
Math3D.VectorCopy(self.enemy.s.origin, target);
target[2] += self.enemy.viewheight;
Math3D.VectorSubtract(target, self.target_ent.s.origin, dir);
Math3D.vectoangles(dir, self.target_ent.move_angles);
// decide if we should shoot
if (GameBase.level.time < self.monsterinfo.attack_finished)
return true;
reaction_time = (3 - GameBase.skill.value) * 1.0f;
if ((GameBase.level.time - self.monsterinfo.trail_time) < reaction_time)
return true;
self.monsterinfo.attack_finished = GameBase.level.time
+ reaction_time + 1.0f;
//FIXME how do we really want to pass this along?
self.target_ent.spawnflags |= 65536;
return true;
}
};
public static EntityThinkAdapter turret_driver_link = new EntityThinkAdapter() {
public String getID() { return "turret_driver_link"; }
public boolean think(Entity self) {
float[] vec = { 0, 0, 0 };
Entity ent;
self.think = turret_driver_think;
self.nextthink = GameBase.level.time + Constants.FRAMETIME;
self.target_ent = GameBase.G_PickTarget(self.target);
self.target_ent.owner = self;
self.target_ent.teammaster.owner = self;
Math3D.VectorCopy(self.target_ent.s.angles, self.s.angles);
vec[0] = self.target_ent.s.origin[0] - self.s.origin[0];
vec[1] = self.target_ent.s.origin[1] - self.s.origin[1];
vec[2] = 0;
self.move_origin[0] = Math3D.VectorLength(vec);
Math3D.VectorSubtract(self.s.origin, self.target_ent.s.origin, vec);
Math3D.vectoangles(vec, vec);
AnglesNormalize(vec);
self.move_origin[1] = vec[1];
self.move_origin[2] = self.s.origin[2] - self.target_ent.s.origin[2];
// add the driver to the end of them team chain
for (ent = self.target_ent.teammaster; ent.teamchain != null; ent = ent.teamchain)
;
ent.teamchain = self;
self.teammaster = self.target_ent.teammaster;
self.flags |= Constants.FL_TEAMSLAVE;
return true;
}
};
}