WEAPON_GAUSSPISTOL: Get some more work done towards this weapon.

WEAPON_MINIGUN: Ditto
Fix up the crosshair loading and some other misc work...
This commit is contained in:
Marco Cawthorne 2022-02-01 23:09:24 -08:00
parent 196282c16c
commit 1ee8be9c9f
Signed by: eukara
GPG key ID: C196CD8BA993248A
12 changed files with 436 additions and 43 deletions

View file

@ -44,6 +44,7 @@ ClientGame_RendererRestart(string rstr)
FX_GibHuman_Init();
FX_Spark_Init();
FX_Impact_Init();
FX_GaussBeam_Init();
BEAM_TRIPMINE = particleeffectnum("weapon_tripmine.beam");
@ -51,4 +52,5 @@ ClientGame_RendererRestart(string rstr)
MUZZLE_RIFLE = (int)getmodelindex("sprites/muzzleflash1.spr");
MUZZLE_SMALL = (int)getmodelindex("sprites/muzzleflash2.spr");
MUZZLE_WEIRD = (int)getmodelindex("sprites/muzzleflash3.spr");
g_cross_spr = spriteframe("sprites/crosshairs.spr", 0, 0.0f);
}

View file

@ -0,0 +1,178 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* 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.
*/
void
HLMultiplayerRules::FrameStart(void)
{
if (cvar("timelimit"))
if (time >= (cvar("timelimit") * 60)) {
IntermissionStart();
}
IntermissionCycle();
}
void
HLMultiplayerRules::CheckRules(void)
{
/* last person who killed somebody has hit the limit */
if (cvar("fraglimit"))
if (g_dmg_eAttacker.frags >= cvar("fraglimit"))
IntermissionStart();
}
void
HLMultiplayerRules::PlayerDeath(base_player pl)
{
/* obituary networking */
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET);
WriteByte(MSG_MULTICAST, EV_OBITUARY);
WriteString(MSG_MULTICAST, (g_dmg_eAttacker.netname) ? g_dmg_eAttacker.netname : g_dmg_eAttacker.classname);
WriteString(MSG_MULTICAST, pl.netname);
WriteByte(MSG_MULTICAST, g_dmg_iWeapon);
WriteByte(MSG_MULTICAST, 0);
msg_entity = world;
multicast([0,0,0], MULTICAST_ALL);
Plugin_PlayerObituary(g_dmg_eAttacker, g_dmg_eTarget, g_dmg_iWeapon, g_dmg_iHitBody, g_dmg_iDamage);
/* death-counter */
pl.deaths++;
forceinfokey(pl, "*deaths", ftos(pl.deaths));
/* update score-counter */
if (pl.flags & FL_CLIENT || pl.flags & FL_MONSTER)
if (g_dmg_eAttacker.flags & FL_CLIENT) {
if (pl == g_dmg_eAttacker)
g_dmg_eAttacker.frags--;
else
g_dmg_eAttacker.frags++;
}
#ifdef VALVE
/* explode all satchels */
s_satchel_detonate((entity)pl);
/* drop their posessions into a weaponbox item */
weaponbox_spawn((player)pl);
#endif
/* either gib, or make a corpse */
if (pl.health < -50) {
FX_GibHuman(pl.origin);
} else {
FX_Corpse_Spawn((player)pl, ANIM_DIESIMPLE);
}
/* now let's make the real client invisible */
pl.SetModelindex(0);
pl.SetMovetype(MOVETYPE_NONE);
pl.SetSolid(SOLID_NOT);
pl.takedamage = DAMAGE_NO;
pl.gflags &= ~GF_FLASHLIGHT;
pl.gflags &= ~GF_EGONBEAM;
pl.armor = pl.activeweapon = pl.g_items = 0;
pl.health = 0;
Sound_Play(pl, CHAN_AUTO, "player.die");
/* force respawn */
pl.think = PutClientInServer;
pl.nextthink = time + 4.0f;
/* have we gone over the fraglimit? */
CheckRules();
}
void
HLMultiplayerRules::PlayerSpawn(base_player pp)
{
player pl = (player)pp;
/* this is where the mods want to deviate */
entity spot;
pl.classname = "player";
pl.health = pl.max_health = 100;
pl.takedamage = DAMAGE_YES;
pl.solid = SOLID_SLIDEBOX;
pl.movetype = MOVETYPE_WALK;
pl.flags = FL_CLIENT;
pl.viewzoom = 1.0;
pl.model = "models/player.mdl";
string mymodel = infokey(pl, "model");
if (mymodel) {
mymodel = sprintf("models/player/%s/%s.mdl", mymodel, mymodel);
if (whichpack(mymodel)) {
pl.model = mymodel;
}
}
setmodel(pl, pl.model);
setsize(pl, VEC_HULL_MIN, VEC_HULL_MAX);
pl.velocity = [0,0,0];
pl.gravity = __NULL__;
pl.frame = 1;
//pl.SendEntity = Player_SendEntity;
pl.SendFlags = UPDATE_ALL;
pl.customphysics = Empty;
pl.iBleeds = TRUE;
forceinfokey(pl, "*spec", "0");
forceinfokey(pl, "*deaths", ftos(pl.deaths));
LevelNewParms();
LevelDecodeParms(pl);
pl.g_items = ITEM_FISTS | ITEM_GAUSSPISTOL | ITEM_SUIT;
pl.activeweapon = WEAPON_GAUSSPISTOL;
pl.ammo_gauss = 35;
spot = Spawn_SelectRandom("info_player_deathmatch");
setorigin(pl, spot.origin);
pl.angles = spot.angles;
Weapons_RefreshAmmo(pl);
Client_FixAngle(pl, pl.angles);
}
float
HLMultiplayerRules::ConsoleCommand(base_player pp, string cmd)
{
tokenize(cmd);
switch (argv(0)) {
case "bot_add":
Bot_AddQuick();
break;
default:
return (0);
}
return (1);
}
int
HLMultiplayerRules::MonstersSpawn(void)
{
return (FALSE);
}
void
HLMultiplayerRules::HLMultiplayerRules(void)
{
/* these lines do nothing but tell the server to register those cvars */
autocvar(timelimit, 15, "Timelimit for multiplayer rounds");
autocvar(fraglimit, 15, "Points limit for multiplayer rounds");
}

View file

@ -36,7 +36,7 @@ monster_trainingbot.qc
gamerules.qc
../../../valve/src/server/gamerules_singleplayer.qc
../../../valve/src/server/gamerules_multiplayer.qc
gamerules_multiplayer.qc
../../../valve/src/server/client.qc
../../../valve/src/server/server.qc

112
src/shared/fx_gaussbeam.qc Normal file
View file

@ -0,0 +1,112 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* 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.
*/
#ifdef CLIENT
var string TRAIL_GAUSSPISTOLBEAM;
void FX_GaussBeam(vector, vector, entity);
void
FX_GaussBeam_Init(void)
{
TRAIL_GAUSSPISTOLBEAM = spriteframe("sprites/gaussbeam2.spr", 0, 0.0f);
}
void
FX_GaussBeam_Parse(void)
{
vector pos, angle;
entity eowner;
pos[0] = readcoord();
pos[1] = readcoord();
pos[2] = readcoord();
angle[0] = readcoord();
angle[1] = readcoord();
angle[2] = readcoord();
eowner = findfloat(world, ::entnum, readentitynum());
FX_GaussBeam(pos, angle, eowner);
}
#endif
#ifdef CLIENT
float foopredraw(void)
{
vector vecPlayer;
player pl = (player)self.owner;
CSQC_UpdateSeat();
vecPlayer = pSeat->m_vecPredictedOrigin + pSeat->m_ePlayer.view_ofs;
/* continously update origin */
makevectors(pl.v_angle);
self.origin = self.owner.origin + self.owner.view_ofs + (v_right * 6) + (v_up * -6) + (v_forward * 16);
makevectors(getproperty(VF_CL_VIEWANGLES));
setproperty(VF_ORIGIN, vecPlayer);
R_BeginPolygon(TRAIL_GAUSSPISTOLBEAM, 1, 0);
R_PolygonVertex(self.angles, [0,0], [1,1,1], self.alpha);
R_PolygonVertex(self.origin, [0,1], [1,1,1], self.alpha);
R_EndPolygonRibbon(2, [1,0]);
self.alpha -= clframetime * 5.0f;
if (self.alpha <= 0.0f)
remove(self);
return PREDRAW_NEXT;
}
#endif
void
FX_GaussBeam(vector vecPos, vector vecAngle, entity eOwner)
{
#ifdef SERVER
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET);
WriteByte(MSG_MULTICAST, EV_GAUSSBEAM);
WriteCoord(MSG_MULTICAST, vecPos[0]);
WriteCoord(MSG_MULTICAST, vecPos[1]);
WriteCoord(MSG_MULTICAST, vecPos[2]);
WriteFloat(MSG_MULTICAST, vecAngle[0]);
WriteFloat(MSG_MULTICAST, vecAngle[1]);
WriteFloat(MSG_MULTICAST, vecAngle[2]);
WriteEntity(MSG_MULTICAST, eOwner);
msg_entity = self;
multicast(vecPos, MULTICAST_PVS);
#else
vector vecSrc;
vector vecEndPos;
vector vecDir;
vector vecFirstImpact = [0,0,0];
makevectors(vecAngle);
vecDir = v_forward;
vecSrc = vecPos;
vecEndPos = vecSrc + v_forward * 1024;
traceline(vecSrc, vecEndPos, FALSE, eOwner);
entity foo = spawn();
foo.alpha = 1.0f;
foo.predraw = foopredraw;
foo.angles = trace_endpos;
foo.drawmask = MASK_ENGINE;
foo.owner = eOwner;
#endif
}

View file

@ -9,7 +9,7 @@ player.qc
../../../valve/src/shared/pmove_water.qc
../../../valve/src/shared/fx_blood.qc
../../../valve/src/shared/fx_gaussbeam.qc
fx_gaussbeam.qc
../../../valve/src/shared/fx_breakmodel.qc
../../../valve/src/shared/fx_explosion.qc
../../../valve/src/shared/fx_gibhuman.qc

View file

@ -213,7 +213,7 @@ w_beamgun_hud(void)
drawsubpic(
pos,
[31,31],
"sprites/crosshairs.spr_0.tga",
g_cross_spr,
[116/256,1/128],
[31/256, 31/128],
[1,1,1],

View file

@ -329,7 +329,7 @@ w_dml_hud(void)
drawsubpic(
pos,
[63,31],
"sprites/crosshairs.spr_0.tga",
g_cross_spr,
[149/256,1/128],
[63/256, 31/128],
[1,1,1],

View file

@ -149,6 +149,17 @@ w_fists_primary(void)
} else {
Weapons_ViewAnimation(KNIFE_ATTACK2);
}
#ifdef SERVER
traceline(Weapons_GetCameraPos(), Weapons_GetCameraPos() + (v_forward * 96),\
FALSE, pl);
if (trace_fraction <= 1.0) {
if (trace_ent.takedamage = DAMAGE_YES) {
Damage_Apply(trace_ent, pl, Skill_GetValue("sk_knife1_d", 25), WEAPON_GAUSSPISTOL, DMG_GENERIC);
}
}
#endif
pl.w_attack_next = 0.5f;
pl.w_idle_next = pl.w_attack_next;
} else {
@ -163,6 +174,17 @@ w_fists_primary(void)
Sound_Play(pl, CHAN_WEAPON, "weapon_fists.hitleft");
#endif
}
#ifdef SERVER
traceline(Weapons_GetCameraPos(), Weapons_GetCameraPos() + (v_forward * 96),\
FALSE, pl);
if (trace_fraction <= 1.0) {
if (trace_ent.takedamage = DAMAGE_YES) {
Damage_Apply(trace_ent, pl, Skill_GetValue("sk_twohandpunch_d", 10), WEAPON_GAUSSPISTOL, DMG_GENERIC);
}
}
#endif
pl.w_attack_next = 0.25f;
pl.w_idle_next = pl.w_attack_next;
}

View file

@ -62,7 +62,7 @@ w_gausspistol_release(void)
return;
}
int r = (float)input_sequence % 5;
int r = floor(pseudorandom() * 5.0f);
switch (r) {
case 0:
case 1:
@ -77,6 +77,45 @@ w_gausspistol_release(void)
}
}
#ifdef SERVER
void
w_gausspistol_firecharge(vector pos, player pl)
{
static void w_gausspistol_firecharge_impact(entity target, entity source) {
Damage_Apply(target, source.owner, Skill_GetValue("sk_gaussslow_d", 50), WEAPON_GAUSSPISTOL, DMG_GENERIC);
}
NSProjectile ball = spawn(NSProjectile);
ball.SetModel("sprites/gausswomp.spr");
ball.SetRenderMode(RM_ADDITIVE);
ball.SetOwner(pl);
ball.SetImpact(w_gausspistol_firecharge_impact);
makevectors(pl.v_angle);
ball.SetOrigin(pos + (v_forward * 8));
ball.SetVelocity(v_forward * 500);
}
void
w_gausspistol_firerapid(vector pos, player pl)
{
static void w_gausspistol_firerapid_impact(entity target, entity source) {
Damage_Apply(target, source.owner, Skill_GetValue("sk_gaussfast_d", 13), WEAPON_GAUSSPISTOL, DMG_GENERIC);
}
NSProjectile ball = spawn(NSProjectile);
ball.SetModel("sprites/gausswomp.spr");
ball.SetScale(0.25f);
ball.SetRenderMode(RM_ADDITIVE);
ball.SetOwner(pl);
ball.SetImpact(w_gausspistol_firerapid_impact);
makevectors(pl.v_angle);
ball.SetOrigin(pos + (v_forward * 8));
ball.SetVelocity(v_forward * 500);
}
#endif
void
w_gausspistol_primary(void)
{
@ -99,27 +138,29 @@ w_gausspistol_primary(void)
}
/* ammo check */
if (pl.a_ammo2 <= 0) {
if (pl.ammo_gauss <= 0) {
return;
}
if (pl.a_ammo3 == GM_CHARGE && pl.a_ammo2 < 10) {
if (pl.gauss_mode == GM_CHARGE && pl.ammo_gauss < 10) {
return;
}
switch (pl.a_ammo3) {
switch (pl.gauss_mode) {
case GM_FAST:
Weapons_ViewAnimation(GP_FIREFAST);
#ifdef SERVER
w_gausspistol_firerapid(Weapons_GetCameraPos(), pl);
Sound_Play(pl, CHAN_WEAPON, "weapon_gausspistol.firefast");
#endif
pl.w_attack_next = 0.15f;
pl.w_attack_next = 0.1f;
pl.w_idle_next = 2.5f;
break;
case GM_CHARGE:
take = 10;
Weapons_ViewAnimation(GP_FIRECHARGE);
#ifdef SERVER
w_gausspistol_firecharge(Weapons_GetCameraPos(), pl);
Sound_Play(pl, CHAN_WEAPON, "weapon_gausspistol.firecharge");
Sound_Play(pl, 8, "weapon_gausspistol.charge");
#endif
@ -130,6 +171,16 @@ w_gausspistol_primary(void)
pl.gflags |= GF_SEMI_TOGGLED;
Weapons_ViewAnimation(GP_FIRESINGLE);
#ifdef SERVER
traceline(Weapons_GetCameraPos(), Weapons_GetCameraPos() + (v_forward * 1024),\
FALSE, pl);
if (trace_fraction <= 1.0) {
if (trace_ent.takedamage == DAMAGE_YES) {
Damage_Apply(trace_ent, pl, Skill_GetValue("sk_gaussshot_d", 10), WEAPON_GAUSSPISTOL, DMG_GENERIC);
}
}
FX_GaussBeam(Weapons_GetCameraPos(), input_angles, pl);
Sound_Play(pl, CHAN_WEAPON, "weapon_gausspistol.firesingle");
#endif
pl.w_attack_next = 0.15f;
@ -139,13 +190,12 @@ w_gausspistol_primary(void)
src = Weapons_GetCameraPos();
#ifdef SERVER
pl.ammo_gauss -= take;
#else
pl.a_ammo2 -= take;
View_SetMuzzleflash(MUZZLE_SMALL);
Weapons_ViewPunchAngle([-5,0,0]);
#ifdef CLIENT
View_SetMuzzleflash(MUZZLE_WEIRD);
#endif
pl.ammo_gauss -= take;
Weapons_ViewPunchAngle([-5,0,0]);
}
void
@ -167,9 +217,7 @@ w_gausspistol_secondary(void)
void
w_gausspistol_updateammo(player pl)
{
#ifdef SERVER
Weapons_UpdateAmmo(pl, -1, pl.ammo_gauss, pl.gauss_mode);
#endif
}
string
@ -238,7 +286,7 @@ w_gausspistol_hud(void)
pos = g_hudmins + (g_hudres / 2) + [-96,-72];
/* far left */
if (pl.a_ammo3 == GM_SINGLE) {
if (pl.gauss_mode == GM_SINGLE) {
drawsubpic(
pos,
[64,144],
@ -260,7 +308,7 @@ w_gausspistol_hud(void)
DRAWFLAG_ADDITIVE
);
}
if (pl.a_ammo3 == GM_CHARGE) {
if (pl.gauss_mode == GM_CHARGE) {
drawsubpic(
pos,
[64,144],
@ -292,7 +340,7 @@ w_gausspistol_hud(void)
DRAWFLAG_ADDITIVE
);
}
if (pl.a_ammo3 == GM_FAST) {
if (pl.gauss_mode == GM_FAST) {
drawsubpic(
pos,
[128,144],
@ -321,7 +369,7 @@ w_gausspistol_hud(void)
drawsubpic(
pos,
[31,31],
"sprites/crosshairs.spr_0.tga",
g_cross_spr,
[1/256,1/128],
[31/256, 31/128],
[1,1,1],
@ -359,6 +407,7 @@ w_gausspistol_precache(void)
precache_model("models/v_guasspistol.mdl");
precache_model("sprites/gausshud1.spr");
precache_model("sprites/gausshud2.spr");
precache_model("sprites/gausswomp.spr");
}
@ -402,8 +451,8 @@ w_gausspistol_hudforward(player pl)
return (1);
}
pl.a_ammo3 = bound(GM_SINGLE, pl.a_ammo3 - 1, GM_FAST);
sendevent("w_gp_setmode", "i", pl.a_ammo3);
pl.gauss_mode = bound(GM_SINGLE, pl.gauss_mode - 1, GM_FAST);
sendevent("w_gp_setmode", "i", pl.gauss_mode);
return (0);
}
@ -414,8 +463,8 @@ w_gausspistol_hudback(player pl)
return (1);
}
pl.a_ammo3 = bound(GM_SINGLE, pl.a_ammo3 + 1, GM_FAST);
sendevent("w_gp_setmode", "i", pl.a_ammo3);
pl.gauss_mode = bound(GM_SINGLE, pl.gauss_mode + 1, GM_FAST);
sendevent("w_gp_setmode", "i", pl.gauss_mode);
return (0);
}
#else
@ -423,7 +472,6 @@ void
CSEv_w_gp_setmode_i(int f)
{
player pl = (player)self;
pl.a_ammo3 = f;
pl.gauss_mode = f;
}
#endif

View file

@ -89,25 +89,24 @@ w_minigun_primary(void)
Sound_Play(pl, CHAN_WEAPON, "weapon_minigun.fire");
#else
View_SetMuzzleflash(MUZZLE_RIFLE);
Weapons_ViewPunchAngle([-2,0,0]);
#endif
Weapons_ViewPunchAngle([-2,0,0]);
pl.ammo_minigun--;
if (pl.menu_active == 1) {
#ifdef CLIENT
Weapons_ViewAnimation(MG_FIRELOOP);
#else
TraceAttack_FireBullets(1, src, 8, [0.1,0.1], WEAPON_MINIGUN);
#ifdef SERVER
TraceAttack_FireBullets(1, src, Skill_GetValue("sk_9mmAR_bullet", 10), [0.1,0.1], WEAPON_MINIGUN);
#endif
pl.w_attack_next = 0.1f;
pl.w_idle_next = 0.1f;
} else {
#ifdef CLIENT
Weapons_ViewAnimation(MG_FIRE);
#else
TraceAttack_FireBullets(1, src, 8, [0.05,0.05], WEAPON_MINIGUN);
#ifdef SERVER
TraceAttack_FireBullets(1, src, Skill_GetValue("sk_9mmAR_bullet", 10), [0.05,0.05], WEAPON_MINIGUN);
#endif
pl.w_attack_next = 0.25f;
pl.w_idle_next = 2.5f;
@ -128,13 +127,14 @@ w_minigun_secondary(void)
} else {
Sound_Play(pl, 8, "weapon_minigun.spindown");
}
#else
#endif
if (pl.menu_active == 0) {
Weapons_ViewAnimation(MG_SPINUP);
} else {
Weapons_ViewAnimation(MG_SPINDOWN);
}
#endif
pl.menu_active = 1 - pl.menu_active;
pl.w_attack_next = 2.0f;
@ -196,7 +196,7 @@ w_minigun_hud(void)
drawsubpic(
pos,
[47,31],
"sprites/crosshairs.spr_0.tga",
g_cross_spr,
[67/256,1/128],
[47/256, 31/128],
[1,1,1],

View file

@ -110,9 +110,11 @@ w_shotgun_primary(void)
Sound_Play(pl, CHAN_WEAPON, "weapon_shotgun.fire");
#else
View_SetMuzzleflash(MUZZLE_SMALL);
#endif
Weapons_ViewPunchAngle([-5,0,0]);
int r = (float)input_sequence % 4;
int r = floor(pseudorandom() * 4.0);
switch (r) {
case 0:
Weapons_ViewAnimation(SHOTGUN_SHOOT1);
@ -126,7 +128,6 @@ w_shotgun_primary(void)
default:
Weapons_ViewAnimation(SHOTGUN_SHOOT4);
}
#endif
pl.w_attack_next = 0.846154f;
pl.w_idle_next = 2.5f;
@ -287,7 +288,7 @@ w_shotgun_hud(void)
drawsubpic(
pos,
[31,31],
"sprites/crosshairs.spr_0.tga",
g_cross_spr,
[34/256,1/128],
[31/256, 31/128],
[1,1,1],

View file

@ -0,0 +1,30 @@
r_part beam
{
texture "sprites/gaussbeam2.spr_0.tga"
scale 2
scaledelta 0.25
rgbf 1 1 1
alpha 1.0
blend add
step 4
randomvel 0
type beam
die 0.25
rgbf 0.25 0.75 1.0
}
r_part trail
{
texture "particles/fteparticlefont.tga"
tcoords 97 97 191 191 256
scale 2
scaledelta 0.25
rgbf 0.25 0.75 1.0
alpha 0.5
blend add
step 4
die 2
randomvel 0
type beam
}