Compare commits

...

6 Commits

10 changed files with 260 additions and 85 deletions

View File

@ -21,7 +21,7 @@ typedef enumflags
BUTTA_TEXON /**< Button texture starts in the **ON** state. */
} sf_button_target_t;
/*!QUAKED button_target (0 .5 .8) ? BUTTA_USE BUTTA_TEXON
/*!QUAKED button_target (0 .5 .8) ? USE TEXTURE_ON
# OVERVIEW
Non-moving button that can either be used by hand, or shot.
@ -31,8 +31,8 @@ Non-moving button that can either be used by hand, or shot.
- "delay" : Time in seconds until target is triggered.
# SPAWNFLAGS
- BUTTA_USE (1) : Button becomes 'use' only, clients have to interact with it manually instead of shooting it.
- BUTTA_TEXON (2) : Texture choices will be inverted in case multiple frames exist.
- USE (1) : Button becomes 'use' only, clients have to interact with it manually instead of shooting it.
- TEXTURE_ON (2) : Texture choices will be inverted in case multiple frames exist.
# TRIVIA
This entity was introduced in Half-Life (1998).

View File

@ -35,6 +35,9 @@ Shoots model entities from its location.
- "m_flGibLife" : Life of the individual model piece.
- "scale" : Scale modifier of the model pieces.
# INPUTS
- "Shoot" : Causes the shooter to shoot.
# TRIVIA
This entity was introduced in Half-Life (1998).
*/
@ -50,7 +53,9 @@ public:
virtual void SpawnKey(string,string);
virtual void Respawn(void);
virtual void Trigger(entity, triggermode_t);
virtual void Input(entity, string, string);
nonvirtual void ShootGib(void);
nonvirtual void ShooterLoop(void);
private:
int m_iGibs;
@ -64,6 +69,7 @@ private:
float m_flShootSounds;
float m_flScale;
float m_flSkin;
bool m_bCanScale;
};
void
@ -79,6 +85,7 @@ env_shooter::env_shooter(void)
m_flShootSounds = 0;
m_flScale = 1.0;
m_flSkin = 0;
m_bCanScale = false;
}
void
@ -86,8 +93,9 @@ env_shooter::Spawned(void)
{
super::Spawned();
if (m_strShootModel)
if (m_strShootModel) {
precache_model(m_strShootModel);
}
/* There isn't a much more portable to do this, maybe this can be abstracted
through separate soundDef entries but I don't know if that'll be less annoying. */
@ -111,6 +119,11 @@ env_shooter::Spawned(void)
default:
break;
}
/* figure out if we're a sprite... */
if (Util_ExtensionFromString(m_strShootModel) == "spr") {
m_bCanScale = true;
}
}
void
@ -127,6 +140,7 @@ env_shooter::Save(float handle)
SaveFloat(handle, "m_flShootSounds", m_flShootSounds);
SaveFloat(handle, "m_flScale", m_flScale);
SaveFloat(handle, "m_flSkin", m_flSkin);
SaveBool(handle, "m_bCanScale", m_bCanScale);
}
void
@ -163,6 +177,9 @@ env_shooter::Restore(string strKey, string strValue)
case "m_flSkin":
m_flSkin = ReadFloat(strValue);
break;
case "m_bCanScale":
m_bCanScale = ReadBool(strValue);
break;
default:
super::Restore(strKey, strValue);
}
@ -173,34 +190,34 @@ env_shooter::SpawnKey(string strKey, string strValue)
{
switch (strKey) {
case "angle":
angles = [stof(strValue) * 90, 0, 0];
angles = [ReadFloat(strValue) * 90, 0, 0];
break;
case "m_iGibs":
m_iGibs = stoi(strValue);
m_iGibs = ReadInt(strValue);
break;
case "delay":
m_flDelay = stof(strValue);
m_flDelay = ReadFloat(strValue);
break;
case "m_flVelocity":
m_flVelocity = stof(strValue);
m_flVelocity = ReadFloat(strValue);
break;
case "m_flVariance":
m_flVariance = stof(strValue);
m_flVariance = ReadFloat(strValue);
break;
case "m_flGibLife":
m_flGibLife = stof(strValue);
m_flGibLife = ReadFloat(strValue);
break;
case "shootmodel":
m_strShootModel = strValue;
break;
case "shootsounds":
m_flShootSounds = stof(strValue);
m_flShootSounds = ReadFloat(strValue);
break;
case "scale":
m_flScale = stof(strValue);
m_flScale = ReadFloat(strValue);
break;
case "skin":
m_flSkin = stof(strValue);
m_flSkin = ReadFloat(strValue);
break;
default:
super::SpawnKey(strKey, strValue);
@ -228,13 +245,23 @@ env_shooter::ShootGib(void)
NSRenderableEntity eGib = spawn(NSRenderableEntity);
eGib.SetMovetype(MOVETYPE_BOUNCE);
eGib.SetModel(m_strShootModel);
eGib.SetSize([-8,-8,-8],[8,8,8]);
eGib.SetOrigin(GetOrigin());
eGib.SetAngles(GetAngles());
eGib.SetRenderColor(m_vecRenderColor);
eGib.SetRenderMode(m_iRenderMode);
eGib.SetRenderFX(m_iRenderFX);
eGib.SetRenderAmt(m_flRenderAmt);
eGib.SetScale(m_flScale);
/* scale multiplier only works on sprites.
the env_shooter entities in lambda_bunker.bsp rely
on this exact behaviour. if Source added support
for this you need to differentiate between the two. */
if (m_bCanScale == true) {
eGib.SetScale(m_flScale);
}
eGib.SetSize([-8,-8,-8],[8,8,8]);
eGib.SetSkin(m_flSkin);
switch (m_flShootSounds) {
@ -273,12 +300,17 @@ env_shooter::ShootGib(void)
eGib.SetVelocity(vecThrowVel);
eGib.SetAngularVelocity(vecSpinVel);
eGib.ScheduleThink(Destroy, m_flGibLife);
}
void
env_shooter::ShooterLoop(void)
{
ShootGib();
m_iGibsLeft--;
/* keep shooting til we're done */
if (m_iGibsLeft) {
ScheduleThink(ShootGib, m_flDelay);
ScheduleThink(ShooterLoop, m_flDelay);
} else {
/* no more gibs left, destroy if wanted */
if (HasSpawnFlags(EVSHOOTER_REPEATABLE) == false) {
@ -300,7 +332,7 @@ env_shooter::Trigger(entity act, triggermode_t state)
m_iGibsLeft = m_iGibs;
}
ScheduleThink(ShootGib, m_flDelay);
ScheduleThink(ShooterLoop, m_flDelay);
break;
default:
if (IsThinking() == false)
@ -309,3 +341,14 @@ env_shooter::Trigger(entity act, triggermode_t state)
Trigger(act, TRIG_OFF);
}
}
void
env_shooter::Input(entity entityActivator, string inputName, string dataField)
{
switch (inputName) {
case "Shoot":
ShootGib();
default:
super::Input(entityActivator, inputName, dataField);
}
}

View File

@ -22,10 +22,13 @@ enumflags
EVSPARK_UNUSED4,
EVSPARK_UNUSED5,
EVSPARK_TOGGLE,
EVSPARK_STARTON
EVSPARK_STARTON,
EVSPARK_UNUSED6,
EVSPARK_SILENT,
EVSPARK_DIRECTIONAL
};
/*!QUAKED env_spark (1 .5 0) (-8 -8 -8) (8 8 8) x x x x x EVSPARK_TOGGLE EVSPARK_STARTON
/*!QUAKED env_spark (1 .5 0) (-8 -8 -8) (8 8 8) x x x x x TOGGLE START_ON
# OVERVIEW
Creates a series (or just one) spark effect with sound when triggered.
@ -36,13 +39,21 @@ Creates a series (or just one) spark effect with sound when triggered.
- "angles" : Sets the pitch, yaw and roll angles of the spark.
- "MaxDelay" : Delay between sparks when start-on (or toggle) is set
# INPUTS
- "StartSpark" : Enables a continous spark emitter.
- "StopSpark" : Stops the ongoing spark emitter.
- "ToggleSpark" : Toggles the state of the spark emitter.
- "SparkOnce" : Creates a single spark effect, once.
# SPAWNFLAGS
- EVSPARK_TOGGLE (32) : When triggered, it'll spark continously with "MaxDelay" dictating the interval.
- EVSPARK_STARTON (64) : Start sparking upon spawning, at least waiting til "MaxDelay" seconds has passed.
- TOGGLE (32) : When triggered, it'll spark continously with "MaxDelay" dictating the interval.
- START_ON (64) : Start sparking upon spawning, at least waiting til "MaxDelay" seconds has passed.
- SILENT (256) : Do not play a sound.
- DIRECTIONAL (512) : Angles are respected to direct the spark.
# NOTES
The spawnflags EVSPARK_TOGGLE and EVSPARK_STARTON are often used together.
Without them set, it'll of course only spark once whenever it's triggered.
The spawnflag START_ON (32) automatically enables the TOGGLE (64) flag
as well.
# TRIVIA
This entity was introduced in Half-Life (1998).
@ -59,17 +70,24 @@ public:
virtual void Spawned(void);
virtual void Respawn(void);
virtual void Trigger(entity, triggermode_t);
nonvirtual void CreateSpark(void);
nonvirtual void TimedSpark(void);
virtual void Input(entity, string, string);
nonvirtual void _TimedSpark(void);
nonvirtual void StartSpark(void);
nonvirtual void StopSpark(void);
nonvirtual void ToggleSpark(void);
nonvirtual void SparkOnce(void);
private:
float m_flMaxDelay;
int _m_iSparkParticle;
};
void
env_spark::env_spark(void)
{
m_flMaxDelay = 0.0f;
_m_iSparkParticle = 0i;
}
void
@ -77,6 +95,7 @@ env_spark::Save(float handle)
{
super::Save(handle);
SaveFloat(handle, "m_flMaxDelay", m_flMaxDelay);
SaveInt(handle, "_m_iSparkParticle", _m_iSparkParticle);
}
void
@ -86,6 +105,9 @@ env_spark::Restore(string strKey, string strValue)
case "m_flMaxDelay":
m_flMaxDelay = ReadFloat(strValue);
break;
case "_m_iSparkParticle":
_m_iSparkParticle = ReadInt(strValue);
break;
default:
super::Restore(strKey, strValue);
}
@ -108,55 +130,104 @@ env_spark::Spawned(void)
{
super::Spawned();
Sound_Precache("env_spark.sfx");
Sound_Precache("fx.spark");
_m_iSparkParticle = particleeffectnum("fx_spark.main");
}
void
env_spark::Respawn(void)
{
InitPointTrigger();
if (m_flMaxDelay <= 0) {
m_flMaxDelay = 1.0f;
}
if (HasSpawnFlags(EVSPARK_STARTON)) {
Trigger(this, TRIG_ON);
StartSpark();
}
if (HasSpawnFlags(EVSPARK_DIRECTIONAL) == false) {
SetAngles([0,0,0]);
}
}
void
env_spark::StartSpark(void)
{
ScheduleThink(_TimedSpark, (random() * m_flMaxDelay));
}
void
env_spark::StopSpark(void)
{
ReleaseThink();
}
void
env_spark::ToggleSpark(void)
{
if (IsThinking() == true) {
StopSpark();
} else {
StartSpark();
}
}
void
env_spark::SparkOnce(void)
{
if (HasSpawnFlags(EVSPARK_SILENT) == false) {
StartSoundDef("fx.spark", CHAN_AUTO, true);
}
pointparticles(_m_iSparkParticle, origin, angles, 1);
}
void
env_spark::Trigger(entity act, triggermode_t state)
{
if (!HasSpawnFlags(EVSPARK_TOGGLE)) {
CreateSpark();
if (!HasSpawnFlags(EVSPARK_TOGGLE) && !HasSpawnFlags(EVSPARK_STARTON)) {
SparkOnce();
return;
}
switch (state) {
case TRIG_OFF:
ReleaseThink();
StopSpark();
break;
case TRIG_ON:
ScheduleThink(CreateSpark, (random() * m_flMaxDelay));
StartSpark();
break;
default:
if (IsThinking() == true) {
Trigger(act, TRIG_OFF);
} else {
Trigger(act, TRIG_ON);
}
ToggleSpark();
}
}
void
env_spark::CreateSpark(void)
env_spark::Input(entity theActivator, string inputName, string dataField)
{
Sound_Play(this, CHAN_AUTO, "env_spark.sfx");
pointparticles(particleeffectnum("platform.spark"), origin, angles, 1);
switch (inputName) {
case "StartSpark":
StartSpark();
break;
case "StopSpark":
StopSpark();
break;
case "ToggleSpark":
ToggleSpark();
break;
case "SparkOnce":
SparkOnce();
break;
default:
super::Input(theActivator, inputName, dataField);
}
}
void
env_spark::TimedSpark(void)
env_spark::_TimedSpark(void)
{
CreateSpark();
ScheduleThink(CreateSpark, (random() * m_flMaxDelay));
SparkOnce();
ScheduleThink(_TimedSpark, (random() * m_flMaxDelay));
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016-2022 Vera Visions LLC.
* Copyright (c) 2016-2023 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
@ -22,7 +22,7 @@ enumflags
TRAIN_NOTSOLID
};
/*!QUAKED func_train (0 .5 .8) ? TRAIN_WAIT x x TRAIN_NOTSOLID
/*!QUAKED func_train (0 .5 .8) ? WAIT x x NOT_SOLID
# OVERVIEW
Moving platform following along path_corner entities, aka nodes.
Most of its behaviour is controlled by the path_corner entities it passes over.
@ -37,8 +37,8 @@ See the entity definition for path_corner to find out more.
- "snd_stop" : Path to sound sample which plays when it stops moving.
# SPAWNFLAGS
- TRAIN_WAIT (1) : Stop at each path_corner until we're triggered again.
- TRAIN_NOTSOLID (8) : Don't do collision testing against this entity.
- WAIT (1) : Stop at each path_corner until we're triggered again.
- NOT_SOLID (8) : Don't do collision testing against this entity.
# NOTES
Upon level entry, the func_train will spawn right where its first path_corner
@ -47,7 +47,7 @@ box somewhere outside the playable area.
If no targetname is specified, the train will move on its own at map-launch.
Marking the func_train with the flag TRAIN_NOTSOLID will make entities not
Marking the func_train with the spawnflag NOT_SOLID will make entities not
collide with the train. This is best used for things in the distance or for
when lasers are following this train as a sort of guide.
@ -257,38 +257,48 @@ func_train::PathMove(void)
void
func_train::PathDone(void)
{
path_corner eNode;
path_corner eNode = __NULL__;
eNode = (path_corner)find(world, ::targetname, target);
if (!eNode) {
return;
}
NSLog("func_train (%s): Touched base with path_corner %S", targetname, target);
if (HasTargetname()) {
NSLog("func_train (id %d, name %S): Touched base with path_corner %S", num_for_edict(this), targetname, target);
} else {
NSLog("func_train (id %d): Touched base with path_corner %S", num_for_edict(this), target);
}
/* fire the path_corners' target */
if (eNode.m_strMessage) {
eNode.Trigger(this, TRIG_TOGGLE);
}
eNode.PathPassTrigger(this, TRIG_TOGGLE);
SoundStop();
}
void
func_train::PathNext(void)
{
path_corner eNode;
path_corner eNode = __NULL__;
if (!target)
/* it's not as critical here to not have a target anymore */
if (HasTriggerTarget() == false) {
return;
}
eNode = (path_corner)find(world, ::targetname, target);
if (!eNode) {
/* a little more serious, but we don't want to break the map. */
if (eNode == __NULL__) {
print(sprintf("func_tracktrain (id %d) target %S does not exist.\n", num_for_edict(this), target));
return;
}
SetOrigin(eNode.origin - (mins + maxs) * 0.5);
PathDone();
m_flWait = eNode.m_flWait;
SetTriggerTarget(eNode.target);
ClearVelocity();
if (eNode.m_flSpeed > 0.0f) {
m_flCurrentSpeed = eNode.m_flSpeed;
@ -296,14 +306,6 @@ func_train::PathNext(void)
m_flCurrentSpeed = m_flSpeed;
}
/* if speed is 0, retain current speed */
//if (eNode.m_flSpeed > 0.0f)
//m_flSpeed = eNode.m_flSpeed;
m_flWait = eNode.m_flWait;
SetTriggerTarget(eNode.target);
ClearVelocity();
/* warp */
if (eNode.HasSpawnFlags(PC_TELEPORT)) {
SetOrigin(eNode.origin - (mins + maxs) * 0.5);
@ -333,7 +335,34 @@ func_train::Trigger(entity act, triggermode_t state)
void
func_train::AfterSpawn(void)
{
PathNext();
path_corner eNode = __NULL__;
if (HasTriggerTarget() == false) {
print(sprintf("func_tracktrain (id %d) has no target.\n", num_for_edict(this)));
Destroy();
return;
}
eNode = (path_corner)find(world, ::targetname, target);
if (eNode == __NULL__) {
print(sprintf("func_tracktrain (id %d) target %S does not exist.\n", num_for_edict(this), target));
Destroy();
return;
}
/* place us to the first node. */
SetOrigin(eNode.origin - (mins + maxs) * 0.5);
SetTriggerTarget(eNode.target);
ClearVelocity();
PathDone();
/* FIXME: Is this authentic? */
if (eNode.m_flSpeed > 0.0f) {
m_flCurrentSpeed = eNode.m_flSpeed;
} else {
m_flCurrentSpeed = m_flSpeed;
}
/* if we're unable to be triggered by anything, begin moving */
if (HasTargetname() == false) {

View File

@ -14,28 +14,19 @@
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*!QUAKED trigger_changelevel (.5 .5 .5) ? LC_NOINTERMISSION LC_USEONLY
vector g_landmarkpos;
/*!QUAKED info_landmark (1 0 0) (-8 -8 -8) (8 8 8)
# OVERVIEW
When a Landmark is specified, you will have to position two info_landmark
entities across your two levels with the same name. They'll mark a translation
point for the coordinates in your levels.
Defines a shared point between two levels. Used for level transitions, such
as the ones produced by a trigger_changelevel.
# KEYS
- "targetname" : Name
- "map" : Next .bsp file name to transition to.
- "landmark" : Landmark name to target.
- "changedelay" : Time in seconds until the transition happens.
# SPAWNFLAGS
- LC_NOINTERMISSION (1) : Don't show intermission cam (unimplemented).
- LC_USEONLY (2) : Can't activate through touching, only via triggers.
# TRIVIA
This entity was introduced in Quake (1996).
This entity was introduced in Half-Life (1998).
*/
vector g_landmarkpos;
class
info_landmark:NSPointTrigger
{
@ -80,6 +71,36 @@ ChangeTarget_Activate(void)
foo.TemporaryTimer(self, Finalize, cvar("_bsp_changedelay"), false);
}
/*!QUAKED trigger_changelevel (.5 .5 .5) ? NO_INTERMISSION TRIGGER_ONLY
# OVERVIEW
A trigger volume that initiates a level change, from one map to the next.
It can be used in combination with info_landmark and trigger_transition
to define a shared point and a transition area for entities respectively.
# KEYS
- "targetname" : Name
- "map" : Next .bsp file name to transition to.
- "landmark" : Landmark name to target.
- "changedelay" : Time in seconds until the transition happens.
# INPUTS
- "ChangeLevel" : Triggers the level to change.
# OUTPUTS
- "OnChangeLevel" : Fired when the level changes.
# SPAWNFLAGS
- NO_INTERMISSION (1) : Don't show intermission cam (unimplemented).
- TRIGGER_ONLY (2) : Can't activate through touching, only via triggers.
# NOTES
When a Landmark is specified, you will have to position two info_landmark
entities across your two levels with the same name. They'll mark a translation
point for the coordinates in your levels.
# TRIVIA
This entity was introduced in Quake (1996).
*/
class
trigger_changelevel:NSBrushTrigger
{

View File

@ -17,16 +17,18 @@
/*!QUAKED trigger_transition (.5 .5 .5) ?
# OVERVIEW
Defines level transition regions.
All entities touching this volume would carry across to the next level.
All entities touching this volume will carry across to the next level.
# KEYS
- "targetname" : Name
# NOTES
In order for this entity to work, you have to assign a working info_landmark entity to your trigger_changelevel, and give this entity the same targetname as said landmark.
In order for this entity to work, one has to assign a working info_landmark entity to a trigger_changelevel, and give this entity the same targetname as said landmark.
If you don't assign a transition, no entities will carry over currently. This is not accurate to vanilla behaviour in Half-Life but should mirror what Source does.
In Half-Life, everything as part of the current PVS seems to carry over. This is probably not what you want to ever do, especially in large outdoor maps.
In GoldSrc games, it appears that every point-entity that's part of the current PVS (mostly the 'room' and attached path-ways you're currently in) carries over to the next level - if a trigger_transition is not defined.
This is probably not what you want to ever do, especially in large outdoor maps where the 'room' is the entire map.
You can also have more than one trigger_transition with the same name, so if any entity is in any of them they will move with the player across the desired level.
# TRIVIA
This entity was introduced in Half-Life (1998).

View File

@ -394,14 +394,14 @@ env_laser::predraw(void)
if (m_iBeamFlags & (LASER_STARTSPARKS)) {
vector dir = vectoangles(origin - m_vecEndPos);
makevectors(dir);
pointparticles(particleeffectnum("platform.spark"), origin, -v_forward, 1);
pointparticles(particleeffectnum("fx.spark"), origin, -v_forward, 1);
}
if (m_iBeamFlags & (LASER_ENDSPARKS)) {
vector dir2 = vectoangles(m_vecEndPos - origin);
makevectors(dir2);
pointparticles(particleeffectnum("platform.spark"), m_vecEndPos, -v_forward, 1);
pointparticles(particleeffectnum("fx.spark"), m_vecEndPos, -v_forward, 1);
}
m_flSparkTime = time + 0.25f;

View File

@ -369,6 +369,7 @@ init(float prevprogs)
PropData_Init();
SurfData_Init();
DecalGroups_Init();
Skill_Init();
/* DO NOT EVER CHANGE THESE. */
cvar_set("r_meshpitch", "1");

View File

@ -1 +1,2 @@
void Util_ChangeClass(entity, string);
void Util_ChangeClass(entity, string);
string Util_ExtensionFromString(string inputString);

View File

@ -69,4 +69,11 @@ Util_ChangeClass(entity objectID, string className)
self = objectID;
callfunction(strcat("spawnfunc_", className));
self = oldSelf;
}
string
Util_ExtensionFromString(string inputString)
{
int modelNameLength = strlen(inputString);
return substring(inputString, modelNameLength - 3, modelNameLength);
}