nuclide/src/gs-entbase/shared/func_tankmortar.qc

456 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.
*/
/* TODO: Implement these */
enumflags
{
FNCTANK_ACTIVE,
FNCTANK_UNUSED1,
FNCTANK_UNUSED2,
FNCTANK_UNUSED3,
FNCTANK_DIRECTONLY,
FNCTANK_CONTROLLABLE
};
/*!QUAKED func_tankmortar (0 .5 .8) ?
# OVERVIEW
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).
*/
class func_tankmortar:NSVehicle
{
public:
void func_tankmortar(void);
virtual void Spawned(void);
virtual void PlayerInput(void);
virtual void SpriteSmoke(vector);
virtual void SpriteFlash(vector);
#ifdef CLIENT
virtual void PredictPreFrame(void);
virtual void PredictPostFrame(void);
virtual void ReceiveEntity(float,float);
virtual void UpdateView(void);
#else
virtual void EvaluateEntity(void);
virtual float SendEntity(entity,float);
virtual void Respawn(void);
virtual void SpawnKey(string,string);
#endif
private:
/* 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;
};
#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 new, float fChanged)
{
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 = (NSEntity)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 = fixAngle(angles);
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 = vectorLerp(v_forward, wantang, input_timelength);
angles = vectorToAngles(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 */
pointparticles(particleeffectnum("fx_explosion.main"), trace_endpos, [0,0,0], 1);
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;
}