474 lines
11 KiB
Plaintext
474 lines
11 KiB
Plaintext
/*
|
|
* Copyright (c) 2016-2022 Vera Visions LLC.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
|
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
|
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
/*QUAKED func_tankmortar (0 .5 .8) ?
|
|
A mountable tank mortar turret type entity. A player (or NPC) can interact with
|
|
it and shoot it. It's in the same family as the func_tank entity, the
|
|
difference being that this shoots mortar blasts and not bullets.
|
|
|
|
-------- KEYS --------
|
|
"targetname" : Name
|
|
"yawrate" : The speed of the left/right movement of the mortar.
|
|
"yawrange" : Range of left/right movement in degrees.
|
|
"pitchrate" : The speed of the up/down movement of the mortar.
|
|
"pitchrange" : Range of up/down movement in degrees.
|
|
"barrel" : Distance from origin to barrel tip in units.
|
|
"barrely" : Horizontal distance origin to the center of the barrel tip.
|
|
"barrelz" : Vertical distance origin to the center of the barrel tip.
|
|
"firerate" : Number of bullets fired per second.
|
|
"iMagnitude" : Power of each explosion.
|
|
"firespread" : Accuracy of the mortar. 0 is best, 4 is worst.
|
|
"persistance" : Time in seconds for how long an NPC might continue shooting.
|
|
"minRange" : Minimum range the target can be at for an NPC to fire.
|
|
"maxRange" : Maximum range the target can be at for an NPC to fire.
|
|
"spritesmoke" : Sprite to spawn for 'smoke' when the entity is fired.
|
|
"spriteflash" : Sprite to spawn for a 'muzzleflash' when the entity is fired.
|
|
"spritescale" : Scale multiplier for both smoke and flash sprites.
|
|
"rotatesound" : Sound file to play in a loop while barrel is rotating.
|
|
|
|
-------- NOTES --------
|
|
I don't like the sprite stuff tacked on at all because of the extra networking
|
|
involved and because it's so awfully GoldSrc specific.
|
|
Eventually I need to design a more generic routine that allows people to just
|
|
refer to materials with the appropriate blend-modes instead of hardcoding that
|
|
some random sprites needs to be treated additive.
|
|
|
|
-------- TRIVIA --------
|
|
This entity was introduced in Half-Life (1998).
|
|
*/
|
|
|
|
/* TODO: Implement these */
|
|
enumflags
|
|
{
|
|
FNCTANK_ACTIVE,
|
|
FNCTANK_UNUSED1,
|
|
FNCTANK_UNUSED2,
|
|
FNCTANK_UNUSED3,
|
|
FNCTANK_DIRECTONLY,
|
|
FNCTANK_CONTROLLABLE
|
|
};
|
|
|
|
class func_tankmortar:NSVehicle
|
|
{
|
|
/* attributes */
|
|
float m_flYawRate;
|
|
float m_flPitchRate;
|
|
vector m_vecTipPos;
|
|
float m_flFireRate;
|
|
int m_iDamage;
|
|
string m_strSpriteSmoke;
|
|
string m_strSpriteFlash;
|
|
float m_flSpriteScale;
|
|
vector m_vecSpread; /* TODO: Needs checking */
|
|
string m_strSndRotate; /* TODO */
|
|
float m_flPersistance; /* TODO */
|
|
float m_flMinRange; /* TODO */
|
|
float m_flMaxRange; /* TODO */
|
|
float m_flYawRange; /* TODO */
|
|
float m_flPitchRange; /* TODO */
|
|
|
|
float m_flFireTime;
|
|
void(void) func_tankmortar;
|
|
|
|
virtual void(void) Spawned;
|
|
virtual void(void) PlayerInput;
|
|
virtual void(vector) SpriteSmoke;
|
|
virtual void(vector) SpriteFlash;
|
|
|
|
#ifdef CLIENT
|
|
virtual void(void) PredictPreFrame;
|
|
virtual void(void) PredictPostFrame;
|
|
virtual void(float, float) ReceiveEntity;
|
|
virtual void(void) UpdateView;
|
|
#else
|
|
virtual void(void) EvaluateEntity;
|
|
virtual float(entity, float) SendEntity;
|
|
virtual void(void) Respawn;
|
|
virtual void(string, string) SpawnKey;
|
|
#endif
|
|
};
|
|
|
|
|
|
#ifdef CLIENT
|
|
void
|
|
func_tankmortar::UpdateView(void)
|
|
{
|
|
}
|
|
|
|
void
|
|
func_tankmortar::PredictPreFrame(void)
|
|
{
|
|
SAVE_STATE(angles)
|
|
SAVE_STATE(origin)
|
|
SAVE_STATE(velocity)
|
|
}
|
|
|
|
void
|
|
func_tankmortar::PredictPostFrame(void)
|
|
{
|
|
ROLL_BACK(angles)
|
|
ROLL_BACK(origin)
|
|
ROLL_BACK(velocity)
|
|
}
|
|
|
|
void
|
|
func_tankmortar::ReceiveEntity(float fChanged, float new)
|
|
{
|
|
if (fChanged & VEHFL_CHANGED_ORIGIN) {
|
|
origin[0] = readcoord();
|
|
origin[1] = readcoord();
|
|
origin[2] = readcoord();
|
|
}
|
|
|
|
if (fChanged & VEHFL_CHANGED_ANGLES) {
|
|
angles[0] = readshort() / (32767 / 360);
|
|
angles[1] = readshort() / (32767 / 360);
|
|
angles[2] = readshort() / (32767 / 360);
|
|
}
|
|
|
|
if (fChanged & VEHFL_CHANGED_MODELINDEX) {
|
|
setmodelindex(this, readshort());
|
|
}
|
|
|
|
if (fChanged & VEHFL_CHANGED_SOLID) {
|
|
solid = readbyte();
|
|
}
|
|
|
|
if (fChanged & VEHFL_CHANGED_MOVETYPE) {
|
|
movetype = readbyte();
|
|
}
|
|
|
|
if (fChanged & VEHFL_CHANGED_SIZE) {
|
|
mins[0] = readcoord();
|
|
mins[1] = readcoord();
|
|
mins[2] = readcoord();
|
|
maxs[0] = readcoord();
|
|
maxs[1] = readcoord();
|
|
maxs[2] = readcoord();
|
|
}
|
|
|
|
if (fChanged & VEHFL_CHANGED_VELOCITY) {
|
|
velocity[0] = readfloat();
|
|
velocity[1] = readfloat();
|
|
velocity[2] = readfloat();
|
|
}
|
|
|
|
if (fChanged & VEHFL_CHANGED_DRIVER) {
|
|
m_eDriver = findfloat(world, ::entnum, readentitynum());
|
|
}
|
|
|
|
if (new)
|
|
drawmask = MASK_ENGINE;
|
|
}
|
|
#else
|
|
void
|
|
func_tankmortar::EvaluateEntity(void)
|
|
{
|
|
/* while the engine is still handling physics for these, we can't
|
|
* predict when origin/angle might change */
|
|
if (ATTR_CHANGED(origin)) {
|
|
SetSendFlags(VEHFL_CHANGED_ORIGIN);
|
|
}
|
|
if (ATTR_CHANGED(angles)) {
|
|
angles[0] = Math_FixDelta(angles[0]);
|
|
angles[1] = Math_FixDelta(angles[1]);
|
|
angles[2] = Math_FixDelta(angles[2]);
|
|
|
|
SetSendFlags(VEHFL_CHANGED_ANGLES);
|
|
}
|
|
if (ATTR_CHANGED(velocity)) {
|
|
SetSendFlags(VEHFL_CHANGED_VELOCITY);
|
|
}
|
|
|
|
SAVE_STATE(origin)
|
|
SAVE_STATE(angles)
|
|
SAVE_STATE(velocity)
|
|
}
|
|
|
|
float
|
|
func_tankmortar::SendEntity(entity ePEnt, float fChanged)
|
|
{
|
|
WriteByte(MSG_ENTITY, ENT_VEH_TANKMORTAR);
|
|
WriteFloat(MSG_ENTITY, fChanged);
|
|
|
|
/* really trying to get our moneys worth with 23 bits of mantissa */
|
|
if (fChanged & VEHFL_CHANGED_ORIGIN) {
|
|
WriteCoord(MSG_ENTITY, origin[0]);
|
|
WriteCoord(MSG_ENTITY, origin[1]);
|
|
WriteCoord(MSG_ENTITY, origin[2]);
|
|
}
|
|
|
|
if (fChanged & VEHFL_CHANGED_ANGLES) {
|
|
WriteShort(MSG_ENTITY, angles[0] * 32767 / 360);
|
|
WriteShort(MSG_ENTITY, angles[1] * 32767 / 360);
|
|
WriteShort(MSG_ENTITY, angles[2] * 32767 / 360);
|
|
}
|
|
|
|
if (fChanged & VEHFL_CHANGED_MODELINDEX) {
|
|
WriteShort(MSG_ENTITY, modelindex);
|
|
}
|
|
|
|
if (fChanged & VEHFL_CHANGED_SOLID) {
|
|
WriteByte(MSG_ENTITY, solid);
|
|
}
|
|
|
|
if (fChanged & VEHFL_CHANGED_MOVETYPE) {
|
|
WriteByte(MSG_ENTITY, movetype);
|
|
}
|
|
|
|
if (fChanged & VEHFL_CHANGED_SIZE) {
|
|
WriteCoord(MSG_ENTITY, mins[0]);
|
|
WriteCoord(MSG_ENTITY, mins[1]);
|
|
WriteCoord(MSG_ENTITY, mins[2]);
|
|
WriteCoord(MSG_ENTITY, maxs[0]);
|
|
WriteCoord(MSG_ENTITY, maxs[1]);
|
|
WriteCoord(MSG_ENTITY, maxs[2]);
|
|
}
|
|
|
|
if (fChanged & VEHFL_CHANGED_VELOCITY) {
|
|
WriteFloat(MSG_ENTITY, velocity[0]);
|
|
WriteFloat(MSG_ENTITY, velocity[1]);
|
|
WriteFloat(MSG_ENTITY, velocity[2]);
|
|
}
|
|
|
|
if (fChanged & VEHFL_CHANGED_DRIVER) {
|
|
WriteEntity(MSG_ENTITY, m_eDriver);
|
|
}
|
|
|
|
return (1);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
func_tankmortar::SpriteSmoke(vector org)
|
|
{
|
|
#ifdef SERVER
|
|
static void Die(void) {
|
|
remove(self);
|
|
}
|
|
|
|
if (!m_strSpriteSmoke)
|
|
return;
|
|
|
|
NSRenderableEntity smoke = spawn(NSRenderableEntity);
|
|
smoke.SetModel(m_strSpriteSmoke);
|
|
smoke.SetOrigin(org);
|
|
smoke.think = Die;
|
|
smoke.nextthink = time + 0.1f;
|
|
smoke.scale = m_flSpriteScale;
|
|
smoke.SetRenderMode(RM_ADDITIVE);
|
|
smoke.SetRenderColor([1,1,1]);
|
|
smoke.SetRenderAmt(1.0f);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
func_tankmortar::SpriteFlash(vector org)
|
|
{
|
|
#ifdef SERVER
|
|
static void Die(void) {
|
|
remove(self);
|
|
}
|
|
|
|
if (!m_strSpriteFlash)
|
|
return;
|
|
|
|
NSRenderableEntity flash = spawn(NSRenderableEntity);
|
|
flash.SetModel(m_strSpriteFlash);
|
|
flash.SetOrigin(org);
|
|
flash.think = Die;
|
|
flash.nextthink = time + 0.1f;
|
|
flash.scale = m_flSpriteScale;
|
|
flash.SetRenderMode(RM_ADDITIVE);
|
|
flash.SetRenderColor([1,1,1]);
|
|
flash.SetRenderAmt(1.0f);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
func_tankmortar::PlayerInput(void)
|
|
{
|
|
#ifdef SERVER
|
|
if (m_eDriver && m_eDriver.health <= 0)
|
|
PlayerLeave((NSClientPlayer)m_eDriver);
|
|
#else
|
|
//print("foooo\n");
|
|
#endif
|
|
|
|
if (m_eDriver) {
|
|
vector wantang, endang;
|
|
vector aimorg;
|
|
|
|
makevectors(input_angles);
|
|
aimorg = m_eDriver.origin + v_forward * 4086;
|
|
|
|
/* lerp */
|
|
makevectors(vectoangles(aimorg - origin));
|
|
wantang = v_forward;
|
|
makevectors(angles);
|
|
|
|
endang[0] = Math_Lerp(v_forward[0], wantang[0], input_timelength);
|
|
endang[1] = Math_Lerp(v_forward[1], wantang[1], input_timelength);
|
|
endang[2] = Math_Lerp(v_forward[2], wantang[2], input_timelength);
|
|
angles = vectoangles(endang);
|
|
PlayerUpdateFlags();
|
|
}
|
|
|
|
#ifdef SERVER
|
|
if (m_flFireTime < time)
|
|
if (input_buttons & INPUT_BUTTON0) {
|
|
vector spos;
|
|
vector dir;
|
|
float dmg;
|
|
|
|
/* barrel tip offset */
|
|
makevectors(angles);
|
|
spos = origin + (v_forward * m_vecTipPos[0]);
|
|
spos += v_right * m_vecTipPos[1];
|
|
spos += v_up * m_vecTipPos[2];
|
|
|
|
/* spread */
|
|
dir = v_forward;
|
|
dir += random(-1,1) * m_vecSpread[0] * v_right;
|
|
dir += random(-1,1) * m_vecSpread[1] * v_up;
|
|
traceline(spos, spos + (dir * 4096), MOVE_NORMAL, this);
|
|
|
|
dmg = (float)m_iDamage;
|
|
Damage_Radius(trace_endpos, m_eDriver, dmg, dmg * 2.5f, TRUE, 0);
|
|
|
|
/* fx */
|
|
FX_Explosion(trace_endpos);
|
|
SpriteSmoke(spos);
|
|
SpriteFlash(spos);
|
|
|
|
UseTargets(this, TRIG_ON, m_flDelay);
|
|
|
|
m_flFireTime = time + m_flFireRate;
|
|
}
|
|
|
|
input_buttons &= ~INPUT_BUTTON0;
|
|
#endif
|
|
}
|
|
|
|
#ifdef SERVER
|
|
void
|
|
func_tankmortar::Respawn(void)
|
|
{
|
|
SetMovetype(MOVETYPE_PUSH);
|
|
SetSolid(SOLID_BSP);
|
|
SetModel(GetSpawnModel());
|
|
SetOrigin(GetSpawnOrigin());
|
|
SetAngles(GetSpawnAngles());
|
|
|
|
if (m_eDriver)
|
|
PlayerLeave((NSClientPlayer)m_eDriver);
|
|
}
|
|
|
|
void
|
|
func_tankmortar::SpawnKey(string strKey, string strValue)
|
|
{
|
|
switch (strKey) {
|
|
case "yawrate":
|
|
m_flYawRate = stof(strValue) * 0.01f;
|
|
break;
|
|
case "yawrange":
|
|
m_flYawRange = stof(strValue);
|
|
break;
|
|
case "pitchrate":
|
|
m_flPitchRate = stof(strValue) * 0.01f;
|
|
break;
|
|
case "pitchrange":
|
|
m_flPitchRange = stof(strValue);
|
|
break;
|
|
case "barrel":
|
|
m_vecTipPos[0] = stof(strValue);
|
|
break;
|
|
case "barrely":
|
|
m_vecTipPos[1] = stof(strValue);
|
|
break;
|
|
case "barrelz":
|
|
m_vecTipPos[2] = stof(strValue);
|
|
break;
|
|
case "firerate":
|
|
m_flFireRate = 1.0f / stof(strValue);
|
|
break;
|
|
case "iMagnitude":
|
|
m_iDamage = stoi(strValue);
|
|
break;
|
|
case "firespread":
|
|
m_vecSpread = [0.10, 0.10, 0] * stof(strValue);
|
|
break;
|
|
case "persistance":
|
|
m_flPersistance = stof(strValue);
|
|
break;
|
|
case "minRange":
|
|
m_flMinRange = stof(strValue);
|
|
break;
|
|
case "maxRange":
|
|
m_flMaxRange = stof(strValue);
|
|
break;
|
|
case "spritesmoke":
|
|
m_strSpriteSmoke = strValue;
|
|
break;
|
|
case "spriteflash":
|
|
m_strSpriteFlash = strValue;
|
|
break;
|
|
case "spritescale":
|
|
m_flSpriteScale = stof(strValue);
|
|
break;
|
|
case "rotatesound":
|
|
m_strSndRotate = strValue;
|
|
break;
|
|
default:
|
|
super::SpawnKey(strValue, strKey);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void
|
|
func_tankmortar::Spawned(void)
|
|
{
|
|
super::Spawned();
|
|
|
|
#ifdef SERVER
|
|
if (m_strSpriteFlash)
|
|
precache_model(m_strSpriteFlash);
|
|
if (m_strSpriteSmoke)
|
|
precache_model(m_strSpriteSmoke);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
func_tankmortar::func_tankmortar(void)
|
|
{
|
|
m_iVehicleFlags |= VHF_FROZEN | VHF_NOATTACK;
|
|
|
|
}
|
|
|
|
#ifdef CLIENT
|
|
void
|
|
func_tankmortar_readentity(float isnew)
|
|
{
|
|
func_tankmortar veh = (func_tankmortar)self;
|
|
float flags = readfloat();
|
|
|
|
if (isnew)
|
|
spawnfunc_func_tankmortar();
|
|
|
|
veh.ReceiveEntity(flags, isnew);
|
|
}
|
|
#endif
|