Class UBioAmmo : Ammo { Default { Tag "$T_SLUDGE"; Inventory.Icon "I_Sludge"; Inventory.PickupMessage ""; Inventory.Amount 25; Inventory.MaxAmount 100; Ammo.BackpackAmount 50; Ammo.BackpackMaxAmount 200; Ammo.DropAmount 10; } override String PickupMessage() { if ( PickupMsg.Length() > 0 ) return Super.PickupMessage(); return String.Format("%s%d%s",StringTable.Localize("$I_SLUDGEL"),Amount,StringTable.Localize("$I_SLUDGER")); } States { Spawn: BIOA ABCDEFGHIJKLMNOPQRSTUVWYZ 1; BIA2 ABCD 1; Loop; } } Class UBioAmmo2 : UBioAmmo { Default { Tag "$T_SLUDGE2"; Inventory.Amount 10; Ammo.DropAmount 5; } States { Spawn: BIOA ABCDEFGHIJKL 2; Loop; } } Class UBioGel : Actor { Actor l, b; enum EHitType { HIT_NONE, HIT_WALL, HIT_CEILING, HIT_FLOOR }; int hittype; int deadtimer, dttimer; Line atline; int atside; int atpart; int atplane; Sector atsector; double atz; double rollvel, pitchvel, yawvel; Vector3 normal; Actor atbridge; bool onbridge; Vector3 atbridgeofs; override void PostBeginPlay() { Super.PostBeginPlay(); vel.z += 3; deadtimer = 105; l = Spawn("BioLight",pos); l.target = self; rollvel = FRandom[GES](10,30)*RandomPick[GES](-1,1); pitchvel = FRandom[GES](10,30)*RandomPick[GES](-1,1); yawvel = FRandom[GES](10,30)*RandomPick[GES](-1,1); } override int SpecialMissileHit( Actor victim ) { if ( victim == b ) return 1; if ( (victim is 'BioHitbox') && ((victim.target == master) || (victim.target.master == master)) ) return 1; return -1; } override void Tick() { Super.Tick(); if ( isFrozen() ) return; if ( !bNOGRAVITY ) { roll += rollvel; pitch += pitchvel; pitch += yawvel; if ( waterlevel > 0 ) { vel.xy *= 0.98; rollvel *= 0.98; pitchvel *= 0.98; yawvel *= 0.98; } } if ( deadtimer > -2 ) { if ( onbridge ) // attempt to follow the movement of the bridge (if it's moving) { if ( atbridge ) { if ( !Warp(atbridge,atbridgeofs.x,atbridgeofs.y,atbridgeofs.z,0,WARPF_ABSOLUTEOFFSET|WARPF_USECALLERANGLE|WARPF_COPYINTERPOLATION) ) deadtimer = min(deadtimer,0); } else deadtimer = min(deadtimer,0); } if ( atline ) // attempt to follow the movement of the line { if ( atpart == 1 ) { if ( atline.flags&Line.ML_DONTPEGTOP ) SetOrigin(Vec2OffsetZ(0,0,atz+atline.sidedef[atside].sector.GetPlaneTexZ(1)),true); else SetOrigin(Vec2OffsetZ(0,0,atz+atline.sidedef[atside?0:1].sector.GetPlaneTexZ(1)),true); } else if ( atpart == -1 ) { if ( atline.flags&Line.ML_DONTPEGBOTTOM ) SetOrigin(Vec2OffsetZ(0,0,atz+atline.sidedef[atside].sector.GetPlaneTexZ(0)),true); else SetOrigin(Vec2OffsetZ(0,0,atz+atline.sidedef[atside?0:1].sector.GetPlaneTexZ(0)),true); } else if ( atline.flags&Line.ML_DONTPEGBOTTOM ) SetOrigin(Vec2OffsetZ(0,0,atz+atline.sidedef[atside].sector.GetPlaneTexZ(0)),true); else SetOrigin(Vec2OffsetZ(0,0,atz+atline.sidedef[atside].sector.GetPlaneTexZ(1)),true); if ( (pos.z > ceilingz) || (pos.z < floorz) ) deadtimer = min(deadtimer,0); } else if ( atsector ) // attempt to follow the movement of the plane { SetOrigin(Vec2OffsetZ(0,0,atz+atsector.GetPlaneTexZ(atplane)),true); if ( ceilingz-floorz <= 2 ) deadtimer = min(deadtimer,0); } } if ( !InStateSequence(CurState,FindState("XDeath")) ) { int numpt; if ( (!bNOGRAVITY && !Random[GES](0,2)) || !Random[GES](0,10) ) { numpt = Min(20,int(Scale.x*2))+Random[GES](-1,1); for ( int i=0; i= pos.z-4*Scale.x)) ) deadtimer = 0; } if ( deadtimer-- <= 0 ) { deadtimer = -1; SetStateLabel("XDeath"); } } // align self to what surface was hit // TODO handle plane collision within the very border between two // sectors (most noticeable with moving 3d floors) virtual void AlignSelf() { F3DFloor ff; bINTERPOLATEANGLES = false; bHITOWNER = true; A_NoGravity(); A_Stop(); if ( bAMBUSH ) { SetStateLabel("XDeath"); return; } A_SetSize(0.1,0); if ( tracer && tracer.bACTLIKEBRIDGE ) { atbridge = tracer; onbridge = true; if ( (pos.x+radius) <= (atbridge.pos.x-atbridge.radius) ) { // west side normal = (-1,0,0); SetOrigin((atbridge.pos.x-atbridge.radius,pos.y,pos.z),false); atbridgeofs = pos-atbridge.pos; angle = 180; pitch = 0; roll = 180; // otherwise it slides upwards (UT changes roll like this too) if ( waterlevel > 0 ) hittype = HIT_FLOOR; else hittype = HIT_WALL; } else if ( (pos.x-radius) >= (atbridge.pos.x+atbridge.radius) ) { // east side normal = (1,0,0); SetOrigin((atbridge.pos.x+atbridge.radius,pos.y,pos.z),false); atbridgeofs = pos-atbridge.pos; angle = 0; pitch = 0; roll = 180; // otherwise it slides upwards (UT changes roll like this too) if ( waterlevel > 0 ) hittype = HIT_FLOOR; else hittype = HIT_WALL; } else if ( (pos.y+radius) <= (atbridge.pos.y-atbridge.radius) ) { // north side normal = (0,-1,0); SetOrigin((pos.x,atbridge.pos.y-atbridge.radius,pos.z),false); atbridgeofs = pos-atbridge.pos; angle = 270; pitch = 0; roll = 180; // otherwise it slides upwards (UT changes roll like this too) if ( waterlevel > 0 ) hittype = HIT_FLOOR; else hittype = HIT_WALL; } else if ( (pos.y-radius) >= (atbridge.pos.y+atbridge.radius) ) { // south side normal = (0,1,0); SetOrigin((pos.x,atbridge.pos.y+atbridge.radius,pos.z),false); atbridgeofs = pos-atbridge.pos; angle = 90; pitch = 0; roll = 180; // otherwise it slides upwards (UT changes roll like this too) if ( waterlevel > 0 ) hittype = HIT_FLOOR; else hittype = HIT_WALL; } else if ( pos.z >= (atbridge.pos.z+atbridge.height) ) { // top of actor normal = (0,0,1); SetOrigin((pos.x,pos.y,atbridge.pos.z+atbridge.height),false); atbridgeofs = pos-atbridge.pos; pitch = -90; angle = 0; roll = FRandom[GES](0,360); hittype = HIT_FLOOR; } else if ( (pos.z+height) <= atbridge.pos.z ) { // bottom of actor normal = (0,0,-1); SetOrigin((pos.x,pos.y,atbridge.pos.z),false); pitch = 90; angle = 0; roll = FRandom[GES](0,360); if ( waterlevel > 0 ) hittype = HIT_FLOOR; else hittype = HIT_CEILING; } else { // inside of actor, just shove to the top or bottom based on our Z velocity if ( vel.z <= 0 ) { normal = (0,0,1); SetOrigin((pos.x,pos.y,atbridge.pos.z+atbridge.height),false); atbridgeofs = pos-atbridge.pos; pitch = -90; angle = 0; roll = FRandom[GES](0,360); hittype = HIT_FLOOR; } else { normal = (0,0,-1); SetOrigin((pos.x,pos.y,atbridge.pos.z),false); pitch = 90; angle = 0; roll = FRandom[GES](0,360); if ( waterlevel > 0 ) hittype = HIT_FLOOR; else hittype = HIT_CEILING; } } } else if ( BlockingFloor ) { // find closest 3d floor for its normal for ( int i=0; i 0 ) hittype = HIT_FLOOR; else if ( normal dot (0,0,-1) > 0.7 ) hittype = HIT_CEILING; else hittype = HIT_FLOOR; } else if ( BlockingLine ) { atline = BlockingLine; normal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit(); atside = 1; if ( !BlockingLine.sidedef[1] || (CurSector == BlockingLine.frontsector) ) { atside = 0; normal *= -1; } Vector3 orig = (BlockingLine.v1.p.x,BlockingLine.v1.p.y,0); Vector3 onwall = pos-(normal dot (pos-orig))*normal; SetOrigin(onwall+normal*0.5,false); // attempt to guess line part (upper/mid/lower) if ( !atline.sidedef[1] ) atpart = 0; // mid else if ( atline.sidedef[atside?0:1].sector.ceilingplane.ZAtPoint(pos.xy) < pos.z ) atpart = 1; // upper else if ( atline.sidedef[atside?0:1].sector.floorplane.ZAtPoint(pos.xy) > (pos.z+height) ) atpart = -1; // lower else { atpart = 0; // check if we're touching a 3d floor line Sector backsector = atline.sidedef[atside?0:1].sector; for ( int i=0; i (pos.z+height) ) continue; if ( backsector.Get3DFloor(i).top.ZAtPoint(pos.xy) < pos.z ) continue; ff = backsector.Get3DFloor(i); break; } // attach to it if ( ff ) { atline = ff.master; atside = 0; } } if ( atpart == 1 ) { if ( atline.flags&Line.ML_DONTPEGTOP ) atz = pos.z-atline.sidedef[atside].sector.GetPlaneTexZ(1); else atz = pos.z-atline.sidedef[atside?0:1].sector.GetPlaneTexZ(1); } else if ( atpart == -1 ) { if ( atline.flags&Line.ML_DONTPEGBOTTOM ) atz = pos.z-atline.sidedef[atside].sector.GetPlaneTexZ(0); else atz = pos.z-atline.sidedef[atside?0:1].sector.GetPlaneTexZ(0); } else if ( atline.flags&Line.ML_DONTPEGBOTTOM ) atz = pos.z-atline.sidedef[atside].sector.GetPlaneTexZ(0); else atz = pos.z-atline.sidedef[atside].sector.GetPlaneTexZ(1); angle = atan2(normal.y,normal.x); pitch = 0; roll = 180; // otherwise it slides upwards (UT changes roll like this too) if ( waterlevel > 0 ) hittype = HIT_FLOOR; else hittype = HIT_WALL; } else { SetStateLabel("XDeath"); return; } b = Spawn("BioHitbox",pos); b.target = self; A_PlaySound("ges/hit"); A_SprayDecal("BioSplat",-172); int numpt = Min(100,int(Scale.x*10))+Random[GES](-5,5); for ( int i=0; i 1) ) numsplash = int(4*Scale.x)-1; } override void Tick() { Super.Tick(); if ( isFrozen() || !sting_biosplash ) return; Vector3 ofs = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch)); for ( int i=0; i<2; i++ ) { if ( numsplash-- <= 0 ) return; Vector3 dir = (ofs+(FRandom[GES](-.8,.8),FRandom[GES](-.8,.8),FRandom[GES](-.8,.8))).unit(); A_SetScale(scale.x-0.15); let d = Spawn("UBioSplash",pos+ofs*4); d.target = target; d.master = self; d.scale *= FRandom[GES](0.5,0.7); d.angle = atan2(dir.y,dir.x); d.pitch = -asin(dir.z); d.vel = (cos(d.angle)*cos(d.pitch),sin(d.angle)*cos(d.pitch),-sin(d.pitch))*d.speed*FRandom[GES](0.4,0.6); d.vel.z -= 2; } } } Class UBioSplash : UBioGel { override void AlignSelf() { Super.AlignSelf(); if ( hittype == HIT_CEILING ) hittype = HIT_FLOOR; } } Class UBioRifle : UnrealWeapon { double chargesize, count; bool bCharging; override int, int, bool, bool GetClipAmount() { return bCharging?min(5,int(chargesize+0.1)):-1, -1, false, false; } action void A_BioFire( bool bAlt = false ) { Weapon weap = Weapon(invoker); if ( !weap ) return; if ( !bAlt ) { if ( weap.Ammo1.Amount <= 0 ) return; if ( !weap.DepleteAmmo(weap.bAltFire,true,1) ) return; } invoker.bCharging = false; invoker.chargesize = min(invoker.chargesize,4.9); if ( bAlt ) A_PlaySound("ges/fire",CHAN_WEAPON,Dampener.Active(self)?.17:1.,pitch:max(.5,1.35-invoker.chargesize/8.)); else A_PlaySound("ges/fire",CHAN_WEAPON,Dampener.Active(self)?.17:1.); invoker.FireEffect(); UTMainHandler.DoFlash(self,Color(48,0,255,0),1); if ( !Dampener.Active(self) ) A_AlertMonsters(); if ( bAlt ) A_QuakeEx(1+int(0.5*invoker.chargesize),1+int(0.5*invoker.chargesize),1+int(0.5*invoker.chargesize),5+int(1.2*invoker.chargesize),0,64,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.05+0.01*invoker.chargesize); else A_QuakeEx(1,1,1,5,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.05); Vector3 x, y, z; double a, s; [x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll); Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x+4*y-5*z); Actor p; if ( bAlt ) { p = Spawn("UBioGlob",origin); p.A_SetScale(0.5+invoker.chargesize/3.5); UTMainHandler.DoSwing(self,(FRandom[GES](-0.6,-1.3),FRandom[GES](-0.9,-0.2)),1+invoker.chargesize*0.3,-0.1,3,SWING_Spring,3,2); } else { p = Spawn("UBioGel",origin); UTMainHandler.DoSwing(self,(FRandom[GES](-0.6,-1.3),FRandom[GES](-0.9,-0.2)),2,-0.5,2,SWING_Spring,2,2); } p.angle = angle; p.pitch = BulletSlope(); p.vel = (cos(p.angle)*cos(p.pitch),sin(p.angle)*cos(p.pitch),-sin(p.pitch))*p.speed; p.target = self; for ( int i=0; i<12; i++ ) { let s = Spawn("UTViewSmoke",origin); UTViewSmoke(s).ofs = (10,4,-5); s.scale *= 2.0; s.target = self; if ( Random[GES](0,1) ) s.SetShade("40FF60"); else s.SetShade("60FF40"); s.A_SetRenderStyle(0.5,STYLE_AddShaded); UTViewSmoke(s).vvel += (FRandom[GES](0.8,1.6),FRandom[GES](-0.5,0.5),FRandom[GES](-0.5,0.5)); } } action void A_ChargeUp() { let weap = Weapon(invoker); if ( (invoker.chargesize < 4.9) && (weap.Ammo1.Amount > 0) ) { invoker.chargesize += 1./TICRATE; invoker.count += 1./TICRATE; if ( invoker.count > 1. ) { weap.DepleteAmmo(weap.bAltFire,true,1); invoker.count = 0; } } if ( !(player.cmd.buttons&BT_ALTATTACK) ) player.SetPSPrite(PSP_WEAPON,invoker.FindState("AltRelease")); } action void A_BeginCharge() { let weap = Weapon(invoker); invoker.bCharging = true; weap.DepleteAmmo(weap.bAltFire,true,1); invoker.count = invoker.chargesize = 0; A_PlaySound("ges/load",CHAN_WEAPON,Dampener.Active(self)?.13:1.,true); A_Overlay(-9999,"Dummy2"); } override bool CheckAmmo( int fireMode, bool autoSwitch, bool requireAmmo, int ammocount ) { if ( bCharging ) return true; return Super.CheckAmmo(fireMode,autoSwitch,requireAmmo,ammocount); } Default { Tag "$T_BIORIFLE"; Inventory.PickupMessage "$I_BIORIFLE"; Weapon.UpSound "ges/select"; Weapon.SlotNumber 8; Weapon.SelectionOrder 1300; Weapon.SlotPriority 1; Weapon.AmmoType "UBioAmmo"; Weapon.AmmoUse 1; Weapon.AmmoType2 "UBioAmmo"; Weapon.AmmoUse2 1; Weapon.AmmoGive 25; UTWeapon.DropAmmo 5; } States { Spawn: BIOP A -1; Stop; BIOP B -1; Stop; Select: BIOS A 1 A_Raise(int.max); Wait; Ready: BIOS ABCDEFGHIJKLMNOPQRST 1 A_WeaponReady(WRF_NOFIRE); Goto Idle; Dummy: TNT1 A 1 { A_CheckReload(); A_WeaponReady(); // that's a long-ass if if ( player.FindPSprite(PSP_WEAPON).CurState.InStateSequence(invoker.FindState("Idle")) && (player.cmd.forwardmove || player.cmd.sidemove) && (player.vel.length() > 0.5) ) player.SetPSPrite(PSP_WEAPON,invoker.FindState("Sway")); } Wait; Idle: #### # 4 A_Overlay(-9999,"Dummy"); // little hackeroo to make this more responsive BIOI A 1 A_Jump(80,"Drip"); BIOI A -1; Stop; Sway: #### # 3; BIOW ABCDEFGHIJKLMNOPQRS 3 A_JumpIf((!player.cmd.forwardmove && !player.cmd.sidemove) || (player.vel.length() < 0.5),"Idle"); Goto Sway+1; Drip: #### # 4; BIOT ABC 6; BIOT D 6 A_PlaySound("ges/drip",CHAN_ITEM,Dampener.Active(self)?.05:.5); BIOT EFG 6; BIOI A 4; Goto Idle; Fire: BIOF A 0 { A_Overlay(-9999,"Null"); A_BioFire(); } BIOF ABCDEFGHIJ 1; Goto Idle; Dummy2: TNT1 A 1 A_ChargeUp(); Wait; AltFire: #### # 2 A_BeginCharge(); BIOA ABCDEFGHIJKLMNOPQRSTUVWXYZ 6; BIA2 ABC 6; BIA2 D 0 A_JumpIf(sting_bhold,1); Goto AltRelease; BIA2 D 3; BIA2 CBA 6; BIOA ZYXWVUT 6; BIOA S 0; Goto AltRelease; AltRelease: #### # 1 { A_Overlay(-9999,"Null"); if ( self is 'UTPlayer' ) UTPlayer(self).PlayAttacking3(); A_BioFire(true); } BIOF ABCDEFGHIJ 2; Goto Idle; Deselect: BIOD A 0 A_Overlay(-9999,"Null"); BIOD ABCDEF 1; BIOD G 1 A_Lower(int.max); Wait; } }