Restore weapon precaching. Add precaching for gesture items. Fix broken physics of light actors. Add light actor physics to confetti. Fix composite items not keeping their tracers on travel. Fix ground distance reporting meters when it should be showing kilometers. Reattach player shadow on travel. Fix tip 37 erroneously listing slots from 6 and up as FOILINVUL when it should be 7 and up.
1264 lines
29 KiB
Text
1264 lines
29 KiB
Text
// collectible items that may drop sometimes
|
|
|
|
Class SWWMCollectible : Inventory abstract
|
|
{
|
|
Mixin SWWMUseToPickup;
|
|
|
|
int avail;
|
|
bool propagated;
|
|
|
|
Property Availability : avail;
|
|
|
|
// minimum gametype requirements
|
|
enum EAvailability
|
|
{
|
|
AVAIL_Strife = GAME_Strife,
|
|
AVAIL_Hexen = AVAIL_Strife|GAME_Hexen,
|
|
AVAIL_Heretic = AVAIL_Hexen|GAME_Heretic,
|
|
AVAIL_All = AVAIL_Heretic|GAME_DoomChex
|
|
};
|
|
|
|
Default
|
|
{
|
|
Inventory.PickupSound "menu/buyinv";
|
|
Inventory.Amount 1;
|
|
Inventory.MaxAmount 1;
|
|
Inventory.PickupFlash "SWWMCyanPickupFlash";
|
|
SWWMCollectible.Availability AVAIL_All;
|
|
+INVENTORY.UNTOSSABLE;
|
|
+INVENTORY.UNDROPPABLE;
|
|
+INVENTORY.UNCLEARABLE;
|
|
+INVENTORY.AUTOACTIVATE;
|
|
+FLOATBOB;
|
|
FloatBobStrength 0.25;
|
|
Radius 8;
|
|
Height 24;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
// delet ourselves if wrong iwad
|
|
if ( !(gameinfo.gametype&avail) )
|
|
Destroy();
|
|
}
|
|
override bool CanPickup( Actor toucher )
|
|
{
|
|
// no pickup if wrong iwad
|
|
if ( !(gameinfo.gametype&avail) ) return false;
|
|
return Super.CanPickup(toucher);
|
|
}
|
|
override string PickupMessage()
|
|
{
|
|
if ( Stamina > 0 )
|
|
return StringTable.Localize(PickupMsg)..String.Format(" \cj(\cg¥\cf%d\cj)\c-",Stamina);
|
|
return Super.PickupMessage();
|
|
}
|
|
override bool Use( bool pickup )
|
|
{
|
|
if ( Owner.player && !propagated )
|
|
{
|
|
if ( pickup && CVar.GetCVar('swwm_collectanim',Owner.player) )
|
|
SWWMGesture.SetSpecialGesture(Owner.player.mo,self,true);
|
|
else if ( !pickup )
|
|
SWWMGesture.SetSpecialGesture(Owner.player.mo,self);
|
|
}
|
|
// clean up the flag
|
|
propagated = false;
|
|
return false;
|
|
}
|
|
|
|
override void AttachToOwner( Actor other )
|
|
{
|
|
Super.AttachToOwner(other);
|
|
// we're only attaching to the other players
|
|
if ( propagated )
|
|
return;
|
|
// give credit
|
|
if ( other.player && (Stamina > 0) )
|
|
{
|
|
SWWMScoreObj.Spawn(Stamina,other.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+other.Height/2),Font.CR_GOLD);
|
|
SWWMCredits.Give(other.player,Stamina);
|
|
}
|
|
// send to all other players
|
|
for ( int i=0; i<MAXPLAYERS; i++ )
|
|
{
|
|
if ( !playeringame[i] || !players[i].mo || (i == other.PlayerNumber()) )
|
|
continue;
|
|
let c = SWWMCollectible(Spawn(GetClass(),pos));
|
|
c.propagated = true;
|
|
if ( !c.CallTryPickup(players[i].mo) )
|
|
c.Destroy();
|
|
}
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1;
|
|
Stop;
|
|
}
|
|
|
|
// helper
|
|
action void A_FinishGesture()
|
|
{
|
|
let gest = SWWMGesture(FindInventory("SWWMGesture"));
|
|
if ( !gest )
|
|
{
|
|
ThrowAbortException("Call to A_FinishGesture() without owned SWWMGesture");
|
|
return;
|
|
}
|
|
if ( gest.sstate.Size() > 0 )
|
|
{
|
|
gest.whichgesture = GS_Null;
|
|
gest.whichstate = gest.sstate[0];
|
|
gest.whichcaller = gest.scaller[0];
|
|
// push back
|
|
gest.sstate.Delete(0);
|
|
gest.scaller.Delete(0);
|
|
let psp = player.FindPSprite(PSP_WEAPON);
|
|
psp.caller = gest;
|
|
psp.SetState(gest.ResolveState("Ready"));
|
|
return;
|
|
}
|
|
if ( gest.queued )
|
|
{
|
|
gest.whichstate = null;
|
|
gest.whichgesture = gest.nextgesture;
|
|
gest.queued = false;
|
|
let psp = player.FindPSprite(PSP_WEAPON);
|
|
psp.caller = gest;
|
|
psp.SetState(gest.ResolveState("Ready"));
|
|
return;
|
|
}
|
|
player.PendingWeapon = gest.formerweapon;
|
|
let psp = player.FindPSprite(PSP_WEAPON);
|
|
psp.caller = gest;
|
|
psp.SetState(gest.ResolveState("Deselect"));
|
|
}
|
|
}
|
|
|
|
// April Fools 2020
|
|
Class FroggyChair : Actor
|
|
{
|
|
int cdown;
|
|
bool carried;
|
|
|
|
Default
|
|
{
|
|
Tag "$T_FROGGY";
|
|
Radius 12;
|
|
Height 16;
|
|
+SOLID;
|
|
+SHOOTABLE;
|
|
+NODAMAGE;
|
|
+NOBLOOD;
|
|
+INTERPOLATEANGLES;
|
|
}
|
|
private void BeginCarry( Actor carrier )
|
|
{
|
|
if ( !carrier ) return;
|
|
carrier.A_StartSound("demolitionist/handsup",CHAN_ITEM,CHANF_OVERLAP);
|
|
A_SetRenderStyle(.5,STYLE_Translucent);
|
|
A_SetSize(32,32); // easier to deselect
|
|
carried = true;
|
|
bSOLID = false;
|
|
bSHOOTABLE = false;
|
|
bNOGRAVITY = true;
|
|
vel *= 0;
|
|
master = carrier;
|
|
}
|
|
private void EndCarry()
|
|
{
|
|
if ( master ) master.A_StartSound("demolitionist/handsdown",CHAN_ITEM,CHANF_OVERLAP);
|
|
A_SetRenderStyle(1.,STYLE_Normal);
|
|
A_SetSize(default.radius,default.height);
|
|
carried = false;
|
|
bSOLID = true;
|
|
bSHOOTABLE = true;
|
|
bNOGRAVITY = false;
|
|
master = null;
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( cdown < 80 )
|
|
{
|
|
cdown++;
|
|
if ( cdown == 10 )
|
|
Console.MidPrint(newsmallfont,"$D_FROGGY1");
|
|
else if ( cdown == 80 )
|
|
Console.MidPrint(newsmallfont,"$D_FROGGY2");
|
|
}
|
|
if ( carried )
|
|
{
|
|
if ( !master || (master.Health <= 0) ) EndCarry();
|
|
else
|
|
{
|
|
Vector3 tomove = master.Vec2OffsetZ(cos(master.angle)*40.,sin(master.angle)*40.,master.player.viewz-32.);
|
|
Vector3 dirto = level.Vec3Diff(pos,tomove);
|
|
SetOrigin(level.Vec3Offset(pos,dirto*.3),true);
|
|
double magvel = dirto.length();
|
|
dirto /= magvel;
|
|
vel = dirto*min(20,magvel);
|
|
double angleto = deltaangle(angle,AngleTo(master));
|
|
A_SetAngle(angle+angleto*.3,SPF_INTERPOLATE);
|
|
}
|
|
return;
|
|
}
|
|
Super.Tick();
|
|
if ( pos.z <= floorz ) vel.xy *= .9; // fast friction
|
|
}
|
|
override bool Used( Actor user )
|
|
{
|
|
if ( carried )
|
|
{
|
|
if ( user != master ) return false;
|
|
if ( TestMobjLocation() && level.IsPointInLevel(pos) )
|
|
EndCarry();
|
|
}
|
|
else BeginCarry(user);
|
|
return true;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
// The collectibles
|
|
Class MothPlushy : SWWMCollectible
|
|
{
|
|
// TODO post-release :: 1/64 chance after 5th use, set countdown to 175 to activate contract
|
|
int usetimes;
|
|
int cdown;
|
|
bool mashiro; // contract with white lady is active
|
|
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( cdown > 0 )
|
|
{
|
|
if ( Owner && Owner.CheckLocalView() )
|
|
{
|
|
if ( cdown == 105 )
|
|
A_StartSound("mashiro/appear",CHAN_ITEMEXTRA,CHANF_OVERLAP|CHANF_UI,1.,0.);
|
|
else if ( cdown == 35 )
|
|
Console.MidPrint(newsmallfont,"$D_MASHIRO");
|
|
}
|
|
cdown--;
|
|
if ( cdown <= 0 ) mashiro = true;
|
|
}
|
|
}
|
|
|
|
Default
|
|
{
|
|
Tag "$T_MOTHPLUSH";
|
|
Inventory.PickupMessage "$T_MOTHPLUSH";
|
|
Stamina 4000;
|
|
}
|
|
}
|
|
Class AkariProject : SWWMCollectible
|
|
{
|
|
Default
|
|
{
|
|
Tag "$T_AKARIPROJECT";
|
|
Inventory.PickupMessage "$T_AKARIPROJECT";
|
|
Stamina 2000;
|
|
Radius 4;
|
|
Height 22;
|
|
}
|
|
}
|
|
Class LoveSignalsCD : SWWMCollectible
|
|
{
|
|
Default
|
|
{
|
|
Tag "$T_LOVESIGNALS";
|
|
Inventory.PickupMessage "$T_LOVESIGNALS";
|
|
Stamina 3000;
|
|
}
|
|
}
|
|
Class NutatcoBar : SWWMCollectible
|
|
{
|
|
Default
|
|
{
|
|
Tag "$T_NUTATCO";
|
|
Inventory.PickupMessage "$T_NUTATCO";
|
|
Stamina 200;
|
|
Radius 3;
|
|
Height 22;
|
|
}
|
|
}
|
|
Class FrispyCorn : SWWMCollectible
|
|
{
|
|
Default
|
|
{
|
|
Tag "$T_FRISPYCORN";
|
|
Inventory.PickupMessage "$T_FRISPYCORN";
|
|
Stamina 400;
|
|
Radius 5;
|
|
Height 23;
|
|
}
|
|
}
|
|
// Heretic
|
|
Class DemoPlush : SWWMCollectible
|
|
{
|
|
Default
|
|
{
|
|
Tag "$T_DEMOPLUSH";
|
|
Inventory.PickupMessage "$T_DEMOPLUSH";
|
|
SWWMCollectible.Availability AVAIL_Heretic;
|
|
Stamina 6000;
|
|
Radius 12;
|
|
Height 36;
|
|
}
|
|
}
|
|
Class SayaBean : SWWMCollectible
|
|
{
|
|
Default
|
|
{
|
|
Tag "$T_SAYABEAN";
|
|
Inventory.PickupMessage "$T_SAYABEAN";
|
|
SWWMCollectible.Availability AVAIL_Heretic;
|
|
Stamina 5000;
|
|
}
|
|
}
|
|
// Hexen
|
|
Class KirinCummies : SWWMCollectible
|
|
{
|
|
Default
|
|
{
|
|
Tag "$T_PEACH";
|
|
Inventory.PickupMessage "$T_PEACH";
|
|
SWWMCollectible.Availability AVAIL_Hexen;
|
|
Stamina 300;
|
|
Radius 3;
|
|
Height 21;
|
|
}
|
|
}
|
|
Class MilkBreads : SWWMCollectible
|
|
{
|
|
Default
|
|
{
|
|
Tag "$T_MILKBREAD";
|
|
Inventory.PickupMessage "$T_MILKBREAD";
|
|
SWWMCollectible.Availability AVAIL_Hexen;
|
|
Stamina 900;
|
|
Radius 4;
|
|
Height 21;
|
|
}
|
|
}
|
|
Class KirinManga : SWWMCollectible
|
|
{
|
|
Default
|
|
{
|
|
Tag "$T_KIRINMANGA";
|
|
Inventory.PickupMessage "$T_KIRINMANGA";
|
|
SWWMCollectible.Availability AVAIL_Hexen;
|
|
Stamina 1600;
|
|
Radius 4;
|
|
Height 22;
|
|
}
|
|
}
|
|
Class KirinPlush : SWWMCollectible
|
|
{
|
|
Default
|
|
{
|
|
Tag "$T_KIRINPLUSH";
|
|
Inventory.PickupMessage "$T_KIRINPLUSH";
|
|
SWWMCollectible.Availability AVAIL_Hexen;
|
|
Stamina 8000;
|
|
Radius 14;
|
|
Height 37;
|
|
}
|
|
}
|
|
|
|
// yay!
|
|
Class FancyConfetti : Actor
|
|
{
|
|
int deadtimer;
|
|
bool dead;
|
|
double anglevel, pitchvel, rollvel;
|
|
Sector tracksector;
|
|
int trackplane;
|
|
|
|
Default
|
|
{
|
|
Radius 2;
|
|
Height 2;
|
|
+NOBLOCKMAP;
|
|
+DROPOFF;
|
|
+THRUACTORS;
|
|
+NOTELEPORT;
|
|
+DONTSPLASH;
|
|
+INTERPOLATEANGLES;
|
|
+ROLLSPRITE;
|
|
+ROLLCENTER;
|
|
Gravity 0.05;
|
|
FloatBobPhase 0;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
deadtimer = Random[Junk](-30,30);
|
|
anglevel = FRandom[Junk](3,12)*RandomPick[Junk](-1,1);
|
|
pitchvel = FRandom[Junk](3,12)*RandomPick[Junk](-1,1);
|
|
rollvel = FRandom[Junk](3,12)*RandomPick[Junk](-1,1);
|
|
if ( bAMBUSH ) frame = 0;
|
|
else frame = Random[Junk](0,9);
|
|
scale *= Frandom[Junk](0.8,1.2);
|
|
}
|
|
override void Tick()
|
|
{
|
|
prev = pos; // for interpolation
|
|
if ( isFrozen() ) return;
|
|
if ( dead )
|
|
{
|
|
// do nothing but follow floor movement
|
|
if ( tracksector )
|
|
{
|
|
double trackz;
|
|
if ( trackplane ) trackz = tracksector.ceilingplane.ZAtPoint(pos.xy);
|
|
else trackz = tracksector.floorplane.ZAtPoint(pos.xy);
|
|
if ( trackz != pos.z )
|
|
{
|
|
SetZ(trackz);
|
|
UpdateWaterLevel(false);
|
|
}
|
|
}
|
|
deadtimer++;
|
|
if ( deadtimer > 300 ) A_FadeOut(0.05);
|
|
return;
|
|
}
|
|
if ( pos.z > floorz ) vel.z -= GetGravity();
|
|
vel.xy *= .98;
|
|
if ( vel.z > 0 ) vel.z *= .98;
|
|
// linetrace-based movement (hopefully more reliable than traditional methods)
|
|
Vector3 dir = vel;
|
|
double spd = vel.length();
|
|
dir /= spd;
|
|
double dist = spd;
|
|
FLineTraceData d;
|
|
Vector3 newpos = pos;
|
|
int nstep = 0;
|
|
while ( dist > 0 )
|
|
{
|
|
// safeguard, too many bounces
|
|
if ( nstep > MAXBOUNCEPERTIC )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
double ang = atan2(dir.y,dir.x);
|
|
double pt = asin(-dir.z);
|
|
LineTrace(ang,dist,pt,TRF_THRUACTORS|TRF_THRUHITSCAN|TRF_ABSPOSITION,newpos.z,newpos.x,newpos.y,d);
|
|
Vector3 hitnormal = -d.HitDir;
|
|
if ( d.HitType == TRACE_HitFloor )
|
|
{
|
|
if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.top.Normal;
|
|
else hitnormal = d.HitSector.floorplane.Normal;
|
|
}
|
|
else if ( d.HitType == TRACE_HitCeiling )
|
|
{
|
|
if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.bottom.Normal;
|
|
else hitnormal = d.HitSector.ceilingplane.Normal;
|
|
}
|
|
else if ( d.HitType == TRACE_HitWall )
|
|
{
|
|
hitnormal = (-d.HitLine.delta.y,d.HitLine.delta.x,0).unit();
|
|
if ( !d.LineSide ) hitnormal *= -1;
|
|
}
|
|
dist -= d.Distance;
|
|
if ( (d.HitType != TRACE_HitNone) && (d.HitType != TRACE_HitFloor) )
|
|
{
|
|
// should only happen if we bounced
|
|
dir = d.HitDir-(1.2*hitnormal*(d.HitDir dot hitnormal));
|
|
vel = dir*spd;
|
|
newpos = d.HitLocation+hitnormal;
|
|
}
|
|
else newpos = level.Vec3Offset(newpos,dir*spd);
|
|
if ( (d.HitType == TRACE_HitFloor) || (newpos.z <= floorz) )
|
|
{
|
|
// lose speed and die
|
|
if ( d.Hit3DFloor )
|
|
{
|
|
newpos.z = d.Hit3DFloor.top.ZAtPoint(newpos.xy);
|
|
tracksector = d.Hit3DFloor.model;
|
|
trackplane = 1;
|
|
}
|
|
else
|
|
{
|
|
// hacky workaround
|
|
if ( !d.HitSector ) d.HitSector = floorsector;
|
|
newpos.z = d.HitSector.floorplane.ZAtPoint(newpos.xy);
|
|
tracksector = d.HitSector;
|
|
trackplane = 0;
|
|
}
|
|
vel = (0,0,0);
|
|
pitch = 0;
|
|
roll = 0;
|
|
dead = true;
|
|
SetStateLabel("Death");
|
|
break;
|
|
}
|
|
nstep++;
|
|
}
|
|
SetOrigin(newpos,true);
|
|
UpdateWaterLevel();
|
|
if ( waterlevel > 0 )
|
|
{
|
|
anglevel *= .98;
|
|
pitchvel *= .98;
|
|
rollvel *= .98;
|
|
}
|
|
if ( !CheckNoDelay() || (tics == -1) ) return;
|
|
if ( tics > 0 ) tics--;
|
|
while ( !tics )
|
|
{
|
|
if ( !SetState(CurState.NextState) )
|
|
return;
|
|
}
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 # 1
|
|
{
|
|
angle += anglevel;
|
|
pitch += pitchvel;
|
|
roll += rollvel;
|
|
}
|
|
Wait;
|
|
Death:
|
|
XZW1 # -1;
|
|
Stop;
|
|
Dummy:
|
|
XZW1 ABCDEFGHIJ -1;
|
|
Stop;
|
|
}
|
|
}
|
|
Class SuperFancySparkle : Actor
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
Radius 0.1;
|
|
Height 0;
|
|
Scale .2;
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+DONTSPLASH;
|
|
+ROLLSPRITE;
|
|
+ROLLCENTER;
|
|
+FORCEXYBILLBOARD;
|
|
+NOINTERACTION;
|
|
FloatBobPhase 0;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Scale *= FRandom[ExploS](.75,1.5);
|
|
specialf1 = FRandom[ExploS](.95,.98);
|
|
specialf2 = FRandom[ExploS](.005,.015);
|
|
double ang = FRandom[ExploS](0,360);
|
|
double pt = FRandom[ExploS](-90,30);
|
|
vel = (cos(ang)*cos(pt),sin(ang)*cos(pt),sin(-pt))*FRandom[ExploS](4,20);
|
|
frame = Random[ExploS](0,7);
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( isFrozen() ) return;
|
|
A_SetScale(scale.x*specialf1);
|
|
A_FadeOut(specialf2);
|
|
Vector3 dir = vel;
|
|
double magvel = dir.length();
|
|
magvel *= .99;
|
|
if ( magvel > 0. )
|
|
{
|
|
dir /= magvel;
|
|
dir += .2*(FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1));
|
|
vel = dir.unit()*magvel;
|
|
}
|
|
SetOrigin(level.Vec3Offset(pos,vel),true);
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
BLPF # -1 Bright;
|
|
Stop;
|
|
}
|
|
}
|
|
Class SuperPartyLight : PaletteLight
|
|
{
|
|
Default
|
|
{
|
|
Tag "SpRainbow";
|
|
Args 0,0,0,100;
|
|
ReactionTime 40;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
SetTag(String.Format("SpRainbow,%d",Random[ExploS](0,7)));
|
|
ReactionTime = Random[ExploS](30,50);
|
|
double ang = FRandom[ExploS](0,360);
|
|
double pt = FRandom[ExploS](-90,30);
|
|
vel = (cos(ang)*cos(pt),sin(ang)*cos(pt),sin(-pt))*FRandom[ExploS](2,10);
|
|
Super.PostBeginPlay();
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( isFrozen() ) return;
|
|
SetOrigin(level.Vec3Offset(pos,vel),true);
|
|
}
|
|
}
|
|
Class PartyTime : Actor
|
|
{
|
|
bool ignite;
|
|
|
|
Default
|
|
{
|
|
Radius .1;
|
|
Height 0.;
|
|
+NOBLOCKMAP;
|
|
+NOTELEPORT;
|
|
+DONTSPLASH;
|
|
+NOINTERACTION;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
if ( target ) specialf1 = target.Height/2.;
|
|
else specialf1 = 16.;
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( ignite )
|
|
{
|
|
// wait for the sound to stop
|
|
if ( !IsActorPlayingSound(CHAN_ITEM) )
|
|
Destroy();
|
|
return;
|
|
}
|
|
if ( !target || (target.tics == -1) )
|
|
{
|
|
// burst into treats!
|
|
ignite = true;
|
|
A_Confetti();
|
|
return;
|
|
}
|
|
SetOrigin(target.pos,false);
|
|
}
|
|
action void A_Confetti()
|
|
{
|
|
if ( !bAMBUSH ) A_StartSound("misc/tada",CHAN_ITEM);
|
|
double ang, pt;
|
|
int numpt = Random[ExploS](100,120);
|
|
if ( bAMBUSH ) numpt *= 2;
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
ang = FRandom[ExploS](0,360);
|
|
pt = FRandom[ExploS](-90,30);
|
|
let c = Spawn("FancyConfetti",Vec3Offset(0,0,specialf1));
|
|
c.vel = (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[ExploS](2,8);
|
|
if ( bAMBUSH ) c.vel *= 2;
|
|
}
|
|
if ( !bAMBUSH ) return;
|
|
numpt = Random[ExploS](60,80);
|
|
for ( int i=0; i<numpt; i++ )
|
|
Spawn("SuperFancySparkle",Vec3Offset(0,0,specialf1));
|
|
numpt = Random[ExploS](6,9);
|
|
for ( int i=0; i<numpt; i++ )
|
|
Spawn("SuperPartyLight",Vec3Offset(0,0,specialf1));
|
|
}
|
|
}
|
|
|
|
Class ChanceboxSpawner : Actor
|
|
{
|
|
override void PostBeginPlay()
|
|
{
|
|
int numbox = 0;
|
|
let ti = ThinkerIterator.Create("ChanceboxSpawner");
|
|
while ( ti.Next() ) numbox++;
|
|
ti = ThinkerIterator.Create("Chancebox");
|
|
while ( ti.Next() ) numbox++;
|
|
if ( numbox > 3 )
|
|
{
|
|
// there's three boxes in the map already (plus ourselves)
|
|
Destroy();
|
|
return;
|
|
}
|
|
let b = Spawn("Chancebox",pos);
|
|
// copy all our stuff
|
|
b.spawnangle = spawnangle;
|
|
b.angle = angle;
|
|
b.pitch = pitch;
|
|
b.roll = roll;
|
|
b.spawnpoint = spawnpoint;
|
|
b.special = special;
|
|
for ( int i=0; i<5; i++ )
|
|
b.Args[i] = Args[i];
|
|
b.spawnflags = spawnflags&~MTF_SECRET;
|
|
b.HandleSpawnFlags();
|
|
b.bCOUNTSECRET = spawnflags&MTF_SECRET;
|
|
b.ChangeTid(tid);
|
|
Destroy();
|
|
}
|
|
|
|
Default
|
|
{
|
|
Radius 12;
|
|
Height 20;
|
|
}
|
|
}
|
|
|
|
Class BoxSpawnSpot
|
|
{
|
|
Vector3 pos;
|
|
double angle;
|
|
}
|
|
|
|
Class CBoxLight : SpotLightAttenuated
|
|
{
|
|
Default
|
|
{
|
|
Args 112,64,224,100;
|
|
DynamicLight.SpotInnerAngle 20;
|
|
DynamicLight.SpotOuterAngle 80;
|
|
+INTERPOLATEANGLES;
|
|
}
|
|
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( !target || target.InStateSequence(target.CurState,target.FindState("BlowUp")) )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
Vector2 ofs = ((special1<2)?8:-8,(special1%2)?12:-12);
|
|
double ang = (special1<2)?0:180;
|
|
angle = target.angle+ang;
|
|
SetOrigin(target.Vec3Offset(ofs.x*cos(target.angle)-ofs.y*sin(target.angle),ofs.y*cos(target.angle)+ofs.x*sin(target.angle),10),true);
|
|
}
|
|
}
|
|
|
|
// Collectible box (recycling of discarded "chance box" item)
|
|
Class Chancebox : Actor
|
|
{
|
|
bool dud;
|
|
|
|
static void SpawnChanceboxes()
|
|
{
|
|
// find all secret sectors, find potential spawn spots within them
|
|
// after that, spawn up to 3 boxes total within them
|
|
int tboxes = 0;
|
|
for ( int i=0; i<level.Sectors.Size(); i++ )
|
|
{
|
|
if ( !(level.Sectors[i].flags&Sector.SECF_SECRET) ) continue;
|
|
Sector s = level.Sectors[i];
|
|
// find any spots in the sector that are within it and have no linedefs or blocking actors within a 32 unit box
|
|
// start from the center, expand in rings
|
|
Vector2 origin = s.centerspot;
|
|
double maxradius = 0;
|
|
for ( int j=0; j<s.lines.Size(); j++ )
|
|
{
|
|
Line l = s.lines[j];
|
|
double v1len = (l.v1.p-origin).length(),
|
|
v2len = (l.v2.p-origin).length();
|
|
if ( v1len > maxradius ) maxradius = v1len;
|
|
if ( v2len > maxradius ) maxradius = v2len;
|
|
}
|
|
int rings = 1;
|
|
bool spawned = false;
|
|
Array<BoxSpawnSpot> spots;
|
|
spots.Clear();
|
|
for ( double j=0.; j<maxradius; j+=16. )
|
|
{
|
|
for ( double k=0.; k<360.; k+=(360./rings) )
|
|
{
|
|
// check spot
|
|
Vector3 testpos = (origin.x+j*cos(k),origin.y+j*sin(k),0);
|
|
testpos.z = s.floorplane.ZAtPoint(testpos.xy);
|
|
if ( (level.PointInSector(testpos.xy) != s) || !level.IsPointInLevel(testpos) ) continue;
|
|
double ceil = s.ceilingplane.ZAtPoint(testpos.xy);
|
|
bool blocked = false;
|
|
BlockLinesIterator bl = BlockLinesIterator.CreateFromPos(testpos,32,32,s);
|
|
double tbox[4];
|
|
// top, bottom, left, right
|
|
tbox[0] = testpos.y+32;
|
|
tbox[1] = testpos.y-32;
|
|
tbox[2] = testpos.x-32;
|
|
tbox[3] = testpos.x+32;
|
|
while ( bl.Next() )
|
|
{
|
|
Line l = bl.CurLine;
|
|
if ( !l ) continue;
|
|
if ( tbox[2] > l.bbox[3] ) continue;
|
|
if ( tbox[3] < l.bbox[2] ) continue;
|
|
if ( tbox[0] < l.bbox[1] ) continue;
|
|
if ( tbox[1] > l.bbox[0] ) continue;
|
|
if ( SWWMUtility.BoxOnLineSide(tbox[0],tbox[1],tbox[2],tbox[3],l) != -1 ) continue;
|
|
blocked = true;
|
|
break;
|
|
}
|
|
if ( blocked ) continue;
|
|
// check for 3D floors first
|
|
int nffloor = s.Get3DFloorCount();
|
|
double bceil = ceil;
|
|
for ( int l=0; l<nffloor; l++ )
|
|
{
|
|
if ( !(s.Get3DFloor(l).flags&F3DFloor.FF_SOLID) ) continue;
|
|
double fz = s.Get3DFloor(l).top.ZAtPoint(testpos.xy);
|
|
if ( fz < testpos.z ) continue;
|
|
double cz = ceil;
|
|
for ( int l2=0; l2<nffloor; l2++ )
|
|
{
|
|
if ( !(s.Get3DFloor(l2).flags&F3DFloor.FF_SOLID) ) continue;
|
|
if ( (s.Get3DFloor(l2).top.ZAtPoint(testpos.xy) < fz) ) continue;
|
|
cz = s.Get3DFloor(l2).bottom.ZAtPoint(testpos.xy);
|
|
break;
|
|
}
|
|
if ( cz < bceil ) bceil = cz;
|
|
if ( cz-fz < 56 ) continue; // too short
|
|
bool blockedff = false;
|
|
BlockThingsIterator bt = BlockThingsIterator.CreateFromPos(testpos.x,testpos.y,fz,56,256,false);
|
|
while ( bt.Next() )
|
|
{
|
|
if ( !bt.Thing ) continue;
|
|
if ( abs(bt.Thing.pos.x-testpos.x) > 32+bt.Thing.Radius ) continue;
|
|
if ( abs(bt.Thing.pos.y-testpos.y) > 32+bt.Thing.Radius ) continue;
|
|
blockedff = true;
|
|
break;
|
|
}
|
|
if ( blockedff ) continue;
|
|
let sp = new("BoxSpawnSpot");
|
|
sp.pos = (testpos.x,testpos.y,fz);
|
|
sp.angle = k+180;
|
|
spots.Push(sp);
|
|
}
|
|
// spawn at the real floor
|
|
if ( bceil-testpos.z < 56 ) continue; // too short
|
|
// don't spawn on sky or hurtfloors if there are 3D floors
|
|
if ( (nffloor > 0) && ((s.GetTexture(0) == skyflatnum) || (s.damageamount > 0)) ) continue;
|
|
BlockThingsIterator bt = BlockThingsIterator.CreateFromPos(testpos.x,testpos.y,testpos.z,56,256,false);
|
|
while ( bt.Next() )
|
|
{
|
|
if ( !bt.Thing ) continue;
|
|
if ( abs(bt.Thing.pos.x-testpos.x) > 32+bt.Thing.Radius ) continue;
|
|
if ( abs(bt.Thing.pos.y-testpos.y) > 32+bt.Thing.Radius ) continue;
|
|
blocked = true;
|
|
break;
|
|
}
|
|
if ( blocked ) continue;
|
|
let sp = new("BoxSpawnSpot");
|
|
sp.pos = testpos;
|
|
sp.angle = k+180;
|
|
spots.Push(sp);
|
|
}
|
|
rings += 3;
|
|
}
|
|
if ( spots.Size() < 10 ) continue;
|
|
int ws = Random[Chancebox](0,spots.Size()-1);
|
|
let c = Spawn("ChanceboxSpawner",spots[ws].pos);
|
|
c.angle = spots[ws].angle;
|
|
tboxes++;
|
|
if ( tboxes >= 3 ) break; // already spawned 3 boxes in one map (which is a lot)
|
|
}
|
|
}
|
|
|
|
action void A_DropSomething()
|
|
{
|
|
Array<Class <SWWMCollectible> > candidates;
|
|
candidates.Clear();
|
|
for ( int i=0; i<AllActorClasses.Size(); i++ )
|
|
{
|
|
let c = (Class<SWWMCollectible>)(AllActorClasses[i]);
|
|
if ( !c || (c == 'SWWMCollectible') ) continue;
|
|
let def = GetDefaultByType(c);
|
|
// check that we can collect it in this IWAD
|
|
if ( !(gameinfo.gametype&def.avail) ) continue;
|
|
candidates.Push(c);
|
|
}
|
|
let ti = ThinkerIterator.Create("SWWMCollectible");
|
|
SWWMCollectible c;
|
|
while ( c = SWWMCollectible(ti.Next()) )
|
|
{
|
|
int f = candidates.Find(c.GetClass());
|
|
if ( f < candidates.Size() )
|
|
candidates.Delete(f);
|
|
}
|
|
if ( (candidates.Size() <= 0) || invoker.dud )
|
|
{
|
|
// no candidates? just burst into treats
|
|
if ( Random[Chancebox](0,1) )
|
|
{
|
|
let a = Spawn(!Random[Chancebox](0,2)?"GoldShell":Random[Chancebox](0,1)?"GrilledCheeseSandwich":"YnykronAmmo",pos);
|
|
a.bDROPPED = true;
|
|
a.bNOGRAVITY = false;
|
|
a.vel.z = FRandom[Chancebox](2,4);
|
|
}
|
|
else if ( !Random[Chancebox](0,2) )
|
|
{
|
|
for ( int i=0; i<=12; i++ )
|
|
{
|
|
let a = Spawn((i==0)?"CandyGun":"CandyGunBullets",pos);
|
|
a.bDROPPED = true;
|
|
a.bNOGRAVITY = false;
|
|
a.vel.z = FRandom[Chancebox](2,4);
|
|
if ( i > 0 ) a.vel.xy = (cos(i*30),sin(i*30))*FRandom[Chancebox](1,2);
|
|
}
|
|
}
|
|
else if ( !Random[Chancebox](0,2) )
|
|
{
|
|
for ( int i=0; i<3; i++ )
|
|
{
|
|
let a = Spawn("SilverBullets2",pos);
|
|
a.bDROPPED = true;
|
|
a.bNOGRAVITY = false;
|
|
a.vel.z = FRandom[Chancebox](2,4);
|
|
a.vel.xy = (cos(i*120),sin(i*120))*FRandom[Chancebox](1,2);
|
|
}
|
|
for ( int i=0; i<6; i++ )
|
|
{
|
|
let a = Spawn("SilverBullets",pos);
|
|
a.bDROPPED = true;
|
|
a.bNOGRAVITY = false;
|
|
a.vel.z = FRandom[Chancebox](2,4);
|
|
a.vel.xy = (cos(i*60),sin(i*60))*FRandom[Chancebox](3,4);
|
|
}
|
|
}
|
|
else if ( Random[Chancebox](0,1) )
|
|
{
|
|
let a = Spawn("HellblazerWarheads",pos);
|
|
a.bDROPPED = true;
|
|
a.bNOGRAVITY = false;
|
|
a.vel.z = FRandom[Chancebox](2,4);
|
|
for ( int i=0; i<3; i++ )
|
|
{
|
|
a = Spawn("HellblazerRavagers",pos);
|
|
a.bDROPPED = true;
|
|
a.bNOGRAVITY = false;
|
|
a.vel.z = FRandom[Chancebox](2,4);
|
|
a.vel.xy = (cos(i*120),sin(i*120))*FRandom[Chancebox](1,2);
|
|
}
|
|
for ( int i=0; i<5; i++ )
|
|
{
|
|
a = Spawn("HellblazerCrackshots",pos);
|
|
a.bDROPPED = true;
|
|
a.bNOGRAVITY = false;
|
|
a.vel.z = FRandom[Chancebox](2,4);
|
|
a.vel.xy = (cos(i*72),sin(i*72))*FRandom[Chancebox](3,4);
|
|
}
|
|
for ( int i=0; i<8; i++ )
|
|
{
|
|
a = Spawn("HellblazerMissiles",pos);
|
|
a.bDROPPED = true;
|
|
a.bNOGRAVITY = false;
|
|
a.vel.z = FRandom[Chancebox](2,4);
|
|
a.vel.xy = (cos(i*45),sin(i*45))*FRandom[Chancebox](5,6);
|
|
}
|
|
}
|
|
else if ( Random[Chancebox](0,1) )
|
|
{
|
|
let a = Spawn("BlackShell",pos);
|
|
a.bDROPPED = true;
|
|
a.bNOGRAVITY = false;
|
|
a.vel.z = FRandom[Chancebox](2,4);
|
|
for ( int i=0; i<3; i++ )
|
|
{
|
|
a = Spawn("BlueShell",pos);
|
|
a.bDROPPED = true;
|
|
a.bNOGRAVITY = false;
|
|
a.vel.z = FRandom[Chancebox](2,4);
|
|
a.vel.xy = (cos(i*120),sin(i*120))*FRandom[Chancebox](1,2);
|
|
}
|
|
for ( int i=0; i<8; i++ )
|
|
{
|
|
a = Spawn((i%2)?"WhiteShell":"PurpleShell",pos);
|
|
a.bDROPPED = true;
|
|
a.bNOGRAVITY = false;
|
|
a.vel.z = FRandom[Chancebox](2,4);
|
|
a.vel.xy = (cos(i*72),sin(i*72))*FRandom[Chancebox](3,4);
|
|
}
|
|
for ( int i=0; i<12; i++ )
|
|
{
|
|
a = Spawn((i%2)?"RedShell":"GreenShell",pos);
|
|
a.bDROPPED = true;
|
|
a.bNOGRAVITY = false;
|
|
a.vel.z = FRandom[Chancebox](2,4);
|
|
a.vel.xy = (cos(i*30),sin(i*30))*FRandom[Chancebox](5,6);
|
|
}
|
|
}
|
|
else if ( Random[Chancebox](0,1) )
|
|
{
|
|
for ( int i=0; i<100; i++ )
|
|
{
|
|
let a = Spawn("HealthNuggetItem",pos);
|
|
a.bDROPPED = true;
|
|
a.bNOGRAVITY = false;
|
|
a.vel.z = FRandom[Chancebox](2,8);
|
|
a.vel.xy = (cos(i*3.6),sin(i*3.6))*FRandom[Chancebox](1,8);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for ( int i=0; i<=15; i++ )
|
|
{
|
|
let a = Spawn((i==0)?"RefresherItem":(i%3)?"HealthNuggetItem":"ArmorNuggetItem",pos);
|
|
a.bDROPPED = true;
|
|
a.bNOGRAVITY = false;
|
|
a.vel.z = FRandom[Chancebox](2,4);
|
|
if ( i > 0 ) a.vel.xy = (cos(i*24),sin(i*24))*FRandom[Chancebox](1,2);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// pop one at random
|
|
let a = Spawn(candidates[Random[Chancebox](0,candidates.Size()-1)],pos);
|
|
a.bDROPPED = true;
|
|
a.bNOGRAVITY = false;
|
|
a.vel.z = FRandom[Chancebox](2,4);
|
|
}
|
|
int numpt = Random[ExploS](16,32);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](.3,8);
|
|
let s = Spawn("SWWMHalfSmoke",Vec3Offset(0,0,16));
|
|
s.vel = pvel;
|
|
s.SetShade(Color(2,1,3)*Random[ExploS](64,85));
|
|
s.special1 = Random[ExploS](1,4);
|
|
s.alpha *= .4;
|
|
s.scale *= 2.4;
|
|
}
|
|
}
|
|
action void A_Confetti()
|
|
{
|
|
A_StartSound("misc/tada",CHAN_ITEM);
|
|
double ang, pt;
|
|
int numpt = Random[ExploS](100,120);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
ang = FRandom[ExploS](0,360);
|
|
pt = FRandom[ExploS](-90,30);
|
|
let c = Spawn("FancyConfetti",Vec3Offset(0,0,16));
|
|
c.vel = (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[ExploS](2,8);
|
|
}
|
|
}
|
|
override bool Used( Actor user )
|
|
{
|
|
// test vertical range
|
|
Vector3 diff = level.Vec3Diff(user.Vec3Offset(0,0,user.Height/2),Vec3Offset(0,0,Height/2));
|
|
double rang = user.player?PlayerPawn(user.player.mo).UseRange:(user.Height/2);
|
|
if ( abs(diff.z) > rang ) return false;
|
|
if ( CurState != FindState("Spawn") ) return false;
|
|
if ( !dud )
|
|
{
|
|
if ( !Random[Chancebox](0,2) )
|
|
{
|
|
// all boxes are duds
|
|
let ti = ThinkerIterator.Create("Chancebox");
|
|
Chancebox c;
|
|
while ( c = Chancebox(ti.Next()) )
|
|
c.dud = true;
|
|
}
|
|
else if ( Random[Chancebox](0,2) )
|
|
{
|
|
int nbox = 0, ndud = 0;
|
|
// this one's a dud (unless all the others are)
|
|
let ti = ThinkerIterator.Create("Chancebox");
|
|
Chancebox c;
|
|
while ( c = Chancebox(ti.Next()) )
|
|
{
|
|
if ( c == self ) continue;
|
|
nbox++;
|
|
if ( c.dud ) ndud++;
|
|
}
|
|
if ( ndud < nbox ) dud = true;
|
|
}
|
|
else
|
|
{
|
|
// the others are duds
|
|
let ti = ThinkerIterator.Create("Chancebox");
|
|
Chancebox c;
|
|
while ( c = Chancebox(ti.Next()) )
|
|
{
|
|
if ( c == self ) continue;
|
|
c.dud = true; // we found one, the others just drop goodies
|
|
}
|
|
}
|
|
}
|
|
if ( bCOUNTITEM )
|
|
{
|
|
user.player.itemcount++;
|
|
level.found_items++;
|
|
bCOUNTITEM = false;
|
|
}
|
|
if ( bCOUNTSECRET )
|
|
{
|
|
user.GiveSecret();
|
|
bCOUNTSECRET = false;
|
|
}
|
|
if ( special )
|
|
{
|
|
user.A_CallSpecial(special,args[0],args[1],args[2],args[3],args[4]);
|
|
special = 0;
|
|
}
|
|
SWWMLoreLibrary.Add(user.player,"Chancebox");
|
|
specialf2 = AngleTo(user);
|
|
SetStateLabel("PreActive");
|
|
return true;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
for ( int i=0; i<4; i++ )
|
|
{
|
|
let l = Spawn("CBoxLight",pos);
|
|
l.special1 = i;
|
|
l.target = self;
|
|
}
|
|
let ti = ThinkerIterator.Create("Chancebox");
|
|
Chancebox c;
|
|
while ( c = Chancebox(ti.Next()) )
|
|
{
|
|
if ( c.dud || (c.CurState == c.FindState("Spawn")) )
|
|
continue;
|
|
// automatically become a dud if collectible has been found
|
|
dud = true;
|
|
break;
|
|
}
|
|
}
|
|
Default
|
|
{
|
|
Tag "$T_CHANCEBOX";
|
|
Radius 12;
|
|
Height 20;
|
|
+MOVEWITHSECTOR;
|
|
+ROLLSPRITE;
|
|
+SOLID;
|
|
+INTERPOLATEANGLES;
|
|
+COUNTITEM;
|
|
Species "Chancebox";
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1;
|
|
Stop;
|
|
PreActive:
|
|
XZW1 A 1
|
|
{
|
|
double delta = deltaangle(angle,specialf2);
|
|
int sign = (delta>=0.)?1:-1;
|
|
delta = clamp(abs(delta)*.15,.1,10.)*sign;
|
|
angle += delta;
|
|
return A_JumpIf(abs(deltaangle(angle,specialf2))<1.,"Active");
|
|
}
|
|
Wait;
|
|
Active:
|
|
XZW1 A 1
|
|
{
|
|
angle = specialf2;
|
|
specialf1 = angle;
|
|
A_StartSound("misc/drumroll",CHAN_WEAPON);
|
|
}
|
|
XZW1 A 1
|
|
{
|
|
bINTERPOLATEANGLES = false;
|
|
angle = specialf1+FRandom[Chancebox](-5,5);
|
|
pitch = FRandom[Chancebox](-5,5);
|
|
roll = FRandom[Chancebox](-5,5);
|
|
special1++;
|
|
return A_JumpIf(special1>40,"BlowUp");
|
|
}
|
|
Wait;
|
|
BlowUp:
|
|
XZW2 A 1
|
|
{
|
|
A_SetSize(12,3);
|
|
A_QuakeEx(2,2,2,9,0,500,"",QF_RELATIVE|QF_SCALEDOWN,falloff:200,rollIntensity:.2);
|
|
A_StartSound("chancebox/explode",CHAN_VOICE);
|
|
angle = specialf1;
|
|
pitch = roll = 0;
|
|
let t = Spawn("ChanceboxTop",Vec3Offset(0,0,20));
|
|
t.angle = angle;
|
|
let s1 = Spawn("ChanceboxSide",level.Vec3Offset(pos,(RotateVector((12,0),angle+90),0)));
|
|
s1.angle = angle+90;
|
|
let s2 = Spawn("ChanceboxSide",level.Vec3Offset(pos,(RotateVector((12,0),angle-90),0)));
|
|
s2.angle = angle-90;
|
|
A_DropSomething();
|
|
}
|
|
XZW2 BCDEFGHIJKLMNO 1;
|
|
XZW2 P -1 A_Confetti();
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
// top side of chancebox, shoots upwards
|
|
Class ChanceboxTop : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 12;
|
|
Height 3;
|
|
Species "Chancebox";
|
|
PROJECTILE;
|
|
+THRUSPECIES;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
vel = (0,0,20);
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A 1
|
|
{
|
|
double magvel = vel.length();
|
|
if ( magvel > 0. )
|
|
{
|
|
magvel = min(60,magvel*1.2);
|
|
vel = vel.unit()*magvel;
|
|
}
|
|
}
|
|
Wait;
|
|
Death:
|
|
TNT1 A 1 A_SpawnItemEx("ExplodiumBulletImpact");
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
// left/right side of chancebox, shoots forward
|
|
Class ChanceboxSide : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 12;
|
|
Height 24;
|
|
Species "Chancebox";
|
|
PROJECTILE;
|
|
+THRUSPECIES;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
vel = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch))*20;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A 1
|
|
{
|
|
double magvel = vel.length();
|
|
if ( magvel > 0. )
|
|
{
|
|
magvel = min(60,magvel*1.2);
|
|
vel = vel.unit()*magvel;
|
|
}
|
|
}
|
|
Wait;
|
|
Death:
|
|
TNT1 A 1 A_SpawnItemEx("ExplodiumBulletImpact");
|
|
Stop;
|
|
}
|
|
}
|