// Backpack that only gives ammo for valid weapons Class UnrealBackpack : BackpackItem replaces Backpack { override Inventory CreateCopy( Actor other ) { // Find every unique type of ammoitem. Give it to the player if // he doesn't have it already, and double its maximum capacity. for ( int i=0; i)(AllActorClasses[i]); if ( !type || (type.GetParentClass() != 'Ammo') ) continue; // check that it's for a valid weapon bool isvalid = false; for ( int j=0; j)(AllActorClasses[j]); if ( !type2 ) continue; let rep = GetReplacement(type2); if ( (rep != type2) && !(rep is "DehackedPickup") ) continue; readonly weap = GetDefaultByType(type2); if ( !other.player || !other.player.weapons.LocateWeapon(type2) || weap.bCheatNotWeapon ) continue; if ( (weap.AmmoType1 == type) || (weap.AmmoType2 == type) ) { isvalid = true; break; } } if ( !isvalid ) continue; let ammoitem = Ammo(other.FindInventory(type)); int amount = GetDefaultByType(type).BackpackAmount; // extra ammo in baby mode and nightmare mode if ( !bIgnoreSkill ) amount = int(amount*G_SkillPropertyFloat(SKILLP_AmmoFactor)); if ( amount < 0 ) amount = 0; if ( !ammoitem ) { // The player did not have the ammoitem. Add it. ammoitem = Ammo(Spawn(type)); ammoitem.Amount = bDepleted?0:amount; if ( ammoitem.BackpackMaxAmount > ammoitem.MaxAmount ) ammoitem.MaxAmount = ammoitem.BackpackMaxAmount; if ( ammoitem.Amount > ammoitem.MaxAmount ) ammoitem.Amount = ammoitem.MaxAmount; ammoitem.AttachToOwner(other); } else { // The player had the ammoitem. Give some more. if ( ammoitem.MaxAmount < ammoitem.BackpackMaxAmount ) ammoitem.MaxAmount = ammoitem.BackpackMaxAmount; if ( !bDepleted && (ammoitem.Amount < ammoitem.MaxAmount) ) { ammoitem.Amount += amount; if ( ammoitem.Amount > ammoitem.MaxAmount ) ammoitem.Amount = ammoitem.MaxAmount; } } } return Inventory.CreateCopy(other); } override bool HandlePickup( Inventory item ) { // Since you already have a backpack, that means you already have every // kind of ammo in your inventory, so we don't need to look at the // entire PClass list to discover what kinds of ammo exist, and we don't // have to alter the MaxAmount either. if ( item is 'BackpackItem' ) { for ( let probe = Owner.Inv; probe; probe = probe.Inv ) { if ( probe.GetParentClass() != 'Ammo' ) continue; if ( probe.Amount >= probe.MaxAmount && !sv_unlimited_pickup ) continue; int amount = Ammo(probe).Default.BackpackAmount; // extra ammo in baby mode and nightmare mode if ( !bIgnoreSkill ) amount = int(amount*G_SkillPropertyFloat(SKILLP_AmmoFactor)); probe.Amount += amount; if ( (probe.Amount > probe.MaxAmount) && !sv_unlimited_pickup ) probe.Amount = probe.MaxAmount; } // The pickup always succeeds, even if you didn't get anything item.bPickupGood = true; return true; } return false; } override void DoPickupSpecial( Actor toucher ) { Super.DoPickupSpecial(toucher); if ( gameinfo.gametype&GAME_DOOMCHEX ) { static const Class xitems[] = {"Flare", "Seeds", "SentryItem", "VoiceBox", "ForceField", "Dampener", "Peacemaker"}; int xitemn[7]; xitemn[0] = max(0,Random[BackpackExtra](-1,3)); xitemn[1] = max(0,Random[BackpackExtra](-1,3)); xitemn[2] = max(0,Random[BackpackExtra](-2,1)); xitemn[3] = max(0,Random[BackpackExtra](-2,1)); xitemn[4] = max(0,Random[BackpackExtra](-2,1)); xitemn[5] = max(0,Random[BackpackExtra](-1,1)); xitemn[6] = max(0,Random[BackpackExtra](-2,1)); // random doubling if ( !Random[BackpackExtra](0,4) ) xitemn[0] *= 2; if ( !Random[BackpackExtra](0,4) ) xitemn[1] *= 2; if ( !Random[BackpackExtra](0,9) ) xitemn[2] *= 2; if ( !Random[BackpackExtra](0,7) ) xitemn[3] *= 2; if ( !Random[BackpackExtra](0,6) ) xitemn[4] *= 2; if ( !Random[BackpackExtra](0,5) ) xitemn[5] *= 2; if ( !Random[BackpackExtra](0,9) ) xitemn[6] *= 2; for ( int i=0; i<7; i++ ) { if ( xitemn[i] <= 0 ) continue; toucher.GiveInventory(xitems[i],xitemn[i]); } } } Default { Tag "$T_BACKPACK"; Inventory.PickupMessage "$I_BACKPACK"; Inventory.RespawnTics 2100; } States { Spawn: BPAK A -1; Stop; } } Class UTranslator : UnrealInventory { bool bNewMessage, bNotNewMessage; string NewMessage, Hint; Array OldMessages, OldHints; override void Travelled() { Super.Travelled(); NewMessage = Hint = ""; OldMessages.Clear(); OldHints.Clear(); } override bool Use( bool pickup ) { if ( pickup ) return false; if ( Owner.player == players[consoleplayer] ) Menu.SetMenu('TranslatorMenu'); bNewMessage = bNotNewmessage = false; return false; } void AddMessage( String msg, String hnt ) { int found = -1; for ( int i=0; i 0) ) { OldMessages.Push(NewMessage); OldHints.Push(Hint); } NewMessage = msg; Hint = hnt; } Default { Tag "$T_TRANSLATOR"; Inventory.PickupMessage "$I_TRANSLATOR"; Inventory.Icon "I_Tran"; Inventory.MaxAmount 1; } States { Spawn: TRNS A -1; Stop; } } // To be placed by mappers // Uses two UDMF-settable user strings // AMBUSH: only triggered by script activation, not touch Class TranslatorEvent : Actor { String user_message, user_hint; Array Touchers; Default { Radius 40; Height 80; +SPECIAL; +NOGRAVITY; +INVISIBLE; } virtual void TriggerMessage( Actor who ) { if ( special1 > gametic ) return; let Translator = UTranslator(who.FindInventory("UTranslator")); if ( !Translator ) return; special1 = gametic+8; if ( who.CheckLocalView() ) { if ( special2 ) Console.Printf(StringTable.Localize("$TR_MSG")); else Console.Printf(StringTable.Localize("$TR_NEWMSG")); S_Sound("translator/event",CHAN_VOICE|CHAN_UI); } Translator.AddMessage(user_message,user_hint); if ( special2 ) Translator.bNotNewMessage = true; else Translator.bNewMessage = true; special2 = 1; } // GZDoom doesn't have Touch/UnTouch like UE1 so I have to improvise override void Tick() { Super.Tick(); for ( int i=0; i pos.x+radius ) deletthis = true; if ( Touchers[i].pos.y+Touchers[i].radius < pos.y-radius ) deletthis = true; if ( Touchers[i].pos.y-Touchers[i].radius > pos.y+radius ) deletthis = true; if ( Touchers[i].pos.z+Touchers[i].height < pos.z ) deletthis = true; if ( Touchers[i].pos.z > pos.z+height ) deletthis = true; if ( !deletthis ) continue; Touchers.Delete(i); i--; } } override void Touch( Actor toucher ) { if ( bAMBUSH || !toucher || !toucher.player ) return; for ( int i=0; i 0 ) { if ( tracer ) tracer.Destroy(); if ( b ) b.Destroy(); A_StopSound(CHAN_VOICE); ReactionTime = 0; return; } if ( !b ) { A_PlaySound("flare/on"); A_PlaySound("flare/loop",CHAN_VOICE,.5,true); tracer = Spawn("FlareThrownX",pos); tracer.angle = angle; tracer.pitch = pitch; tracer.target = self; b = Spawn("FlareHitbox",pos); b.master = self; } b.A_AlertMonsters(0,AMF_TARGETEMITTER); ReactionTime--; } States { Spawn: FLAR A 1 A_JumpIf(ReactionTime<=0,"Death"); Wait; Bounce: FLAR A 0 { pitch = lastpitch; angle = lastangle; roll = lastroll; rotatetodesired = true; desiredangle = FRandom[Junk](0,360); pitchvel = abs(pitchvel)*0.75; anglevel = abs(anglevel)*0.75; rollvel = abs(rollvel)*0.75; } Goto Spawn; Death: FLAR A 0 { anglevel *= 0; } FLAR A 1 { if ( waterlevel > 0 ) { if ( tracer ) tracer.Destroy(); if ( b ) b.Destroy(); A_StopSound(CHAN_VOICE); return ResolveState("Fizz"); } return A_JumpIf(ReactionTime<=0,1); } Wait; FLAR A 0 { if ( tracer ) tracer.Destroy(); if ( b ) b.Destroy(); if ( waterlevel > 0 ) { return ResolveState("Fizz"); } A_StopSound(CHAN_VOICE); A_PlaySound("flare/explode"); A_Explode(50,50); A_NoGravity(); A_Stop(); A_SetRenderStyle(1.,STYLE_Add); SetZ(pos.z+9); Spawn("FlareXLight",pos); bMOVEWITHSECTOR = false; bFORCEXYBILLBOARD = true; A_SetScale(FRandomPick[ExploS](-1.5,1.5),FRandomPick[ExploS](-1.5,1.5)); return A_Jump(256,"Explo1","Explo2","Explo3","Explo4","Explo5"); } Explo1: EXP1 ABCDEFGH 3 Bright; Stop; Explo2: EXP2 ABCDEFGH 3 Bright; Stop; Explo3: EXP3 ABCDEFGH 3 Bright; Stop; Explo4: EXP4 ABCDEFGH 3 Bright; Stop; Explo5: EXP5 ABCDEFGH 3 Bright; Stop; Fizz: FLAR A 1 { special1++; if ( special1 > 150 ) A_FadeOut(); } Wait; } } Class FlareXLight : PaletteLight { Default { ReactionTime 25; Args 0,0,0,80; } } Class FlareHitbox : VoiceBoxHitbox { Default { Tag "$T_FLARES"; Radius 4; Height 6; Health 1; } } Class FlareThrownX : Actor { Default { RenderStyle "Add"; +NOGRAVITY; +NOCLIP; +DONTSPLASH; +INTERPOLATEANGLES; Radius 0.1; Height 0; } override void Tick() { Super.Tick(); if ( !target ) { Destroy(); return; } SetOrigin(target.pos,true); angle = target.angle; pitch = target.pitch; } States { Spawn: FLAR A -1 Bright; Stop; } } Class BetaFlare : UnrealInventory { Class ThrownClass; Property ThrownClass : ThrownClass; override bool TryPickup( in out Actor toucher ) { if ( !sting_flares ) return false; // not allowed return Super.TryPickup(toucher); } override void Tick() { Super.Tick(); if ( sting_flares ) return; if ( Owner ) Owner.RemoveInventory(self); Destroy(); } Default { Inventory.MaxAmount 1; UnrealInventory.Charge 100; +INVENTORY.UNDROPPABLE; +INVENTORY.UNTOSSABLE; } } Class LightFlare : BetaFlare { Default { Tag "$T_LFLARES"; Inventory.Icon "I_FlarBL"; } } Class DarkFlare : BetaFlare { Default { Tag "$T_DFLARES"; Inventory.Icon "I_FlarBD"; } } Class Dampener : UnrealInventory { static bool Active( Actor Owner ) { let d = Dampener(Owner.FindInventory("Dampener")); if ( d && d.bActive ) return true; return false; } override bool Use( bool pickup ) { if ( pickup ) return false; bActive = !bActive; Owner.A_PlaySound(bActive?"dampener/on":"dampener/off",CHAN_ITEM); return false; } override void Tick() { Super.Tick(); if ( !bActive ) return; if ( DrainCharge(1) ) { Owner.A_PlaySound("dampener/off",CHAN_ITEM); if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_DAMPENER")); if ( Amount <= 0 ) DepleteOrDestroy(); } } Default { Tag "$T_DAMPENER"; Inventory.PickupMessage "$I_DAMPENER"; Inventory.Icon "I_Dampen"; Inventory.MaxAmount 3; UnrealInventory.Charge 1000; } States { Spawn: DAMP A 8 A_CheckProximity(1,"PlayerPawn",80,1,CPXF_ANCESTOR|CPXF_CHECKSIGHT); Wait; DAMP ABC 8; DAMP DEFGHIJ 3; DAMP D 0 A_CheckProximity(1,"PlayerPawn",80,0,CPXF_ANCESTOR|CPXF_CHECKSIGHT|CPXF_EXACT); Goto Spawn+4; DAMP CBA 8; Goto Spawn; } } Class Forcefield : UnrealInventory { Default { Tag "$T_FORCEFIELD"; Inventory.PickupMessage "$I_FORCEFIELD"; Inventory.Icon "I_ForceF"; Inventory.MaxAmount 5; } override bool Use( bool pickup ) { if ( pickup ) return false; Vector3 x, y, z; [x, y, z] = dt_CoordUtil.GetAxes(Owner.pitch,Owner.angle,Owner.roll); Vector3 origin = level.Vec3Offset(Owner.Vec2OffsetZ(0,0,Owner.player.viewz),x*90.); origin = level.Vec3Offset(origin,(0,0,-GetDefaultByType("ForceFieldEffect").Height*.5)); if ( !level.IsPointInLevel(origin) ) { if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$M_FFNOROOM")); return false; } let a = Spawn("ForceFieldEffect",origin); if ( !a.TestMobjLocation() ) { if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$M_FFNOROOM")); a.Destroy(); return false; } a.target = Owner; a.angle = Owner.angle; return true; } override void PostBeginPlay() { Super.PostBeginPlay(); tracer = Spawn("ForcefieldX",pos); tracer.angle = angle; tracer.target = self; } States { Spawn: FFPK A -1; Stop; } } Class ForcefieldX : AsmdAmmoX { States { Spawn: FFPK A -1 Bright; Stop; } } Class ForceFieldLight : DynamicLight { double cdown; Default { DynamicLight.Type "Point"; +DYNAMICLIGHT.ATTENUATE; Args 0,0,0,100; } override void Tick() { Super.Tick(); if ( !target ) { Destroy(); return; } SetOrigin(target.Vec3Offset(0,0,target.height/2.),true); if ( isFrozen() ) return; args[LIGHT_RED] = int(255*cdown); args[LIGHT_BLUE] = int(255*cdown); if ( target.bSOLID ) { cdown = min(1.,cdown+1./27); return; } cdown = max(0.,cdown-1./35); if ( cdown <= 0. ) Destroy(); } } Class ForcefieldEffect : Actor { double nvol; int lct; Default { Tag "$T_FORCEFIELD"; RenderStyle "Add"; +NOGRAVITY; +NOTELEPORT; +DONTSPLASH; +CANNOTPUSH; +SHOOTABLE; +SOLID; +NODAMAGE; +NOBLOOD; Mass int.max; Health int.max; Radius 15; Height 45; } override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle ) { A_PlaySound("ffield/hit",CHAN_BODY); return Super.DamageMobj(inflictor,source,damage,mod,flags,angle); } override void PostBeginPlay() { Super.PostBeginPlay(); A_PlaySound("ffield/on",CHAN_ITEM); A_PlaySound("ffield/active",CHAN_VOICE,0.6,true); let tracer = Spawn("ForceFieldLight",pos); tracer.target = self; lct = 24; } States { Spawn: FFLD ABCDEFGHIJ 3 Bright; #### # 700 Bright; #### # 35 Bright { A_UnsetShootable(); A_UnsetSolid(); } FFLD A 1 Bright A_PlaySound("ffield/hit",CHAN_VOICE); Stop; } } Class UFlashlight : UnrealInventory { Actor lt[3]; Default { Tag "$T_FLASHLIGHT"; Inventory.Icon "I_Flashl"; Inventory.MaxAmount 3; Inventory.PickupMessage "$I_FLASHLIGHT"; UnrealInventory.Charge 2800; } States { Spawn: SLIT A -1; Stop; } } Class USearchlight : UFlashlight { Default { Tag "$T_SEARCHLIGHT"; Inventory.Icon "I_BigFl"; Inventory.MaxAmount 1; Inventory.PickupMessage "$I_SEARCHLIGHT"; Inventory.RespawnTics 1050; UnrealInventory.Charge 70000; } } Class SentryItem : UnrealInventory { Default { Tag "$T_SENTRY"; Inventory.Icon "I_Sentry"; Inventory.MaxAmount 1; Inventory.PickupMessage "$I_SENTRY"; Inventory.RespawnTics 1050; UnrealInventory.Charge 300; } States { Spawn: SENT A -1; Stop; } } // The "head" of the sentry, attaches to the body Class MinigunSentry : Actor { States { Spawn: SENT A 15; SENT A 0 A_PlaySound("sentry/raise"); SENR ABCDEFGHIJKLMNO 3; Goto Idle; Idle: SENI A 1; Wait; Fire: SENW ABCDEFGHIJKLMNOPQR 1; Goto FireLoop; FireLoop: SENF ABCDEFGHIJKLMNOPQR 1; Loop; FireEnd: SENU ABCDEFGHIJKLMNOPQR 1; Goto Idle; } } // The body of the sentry Class MinigunSentryBase : Actor { States { Spawn: SENT A 15; SENR ABCDEFGHIJKLMNO 3; Goto Idle; Idle: SENI A 1; Wait; Fire: SENW ABCDEFGHIJKLMNOPQR 1; Goto FireLoop; FireLoop: SENF ABCDEFGHIJKLMNOPQR 1; Loop; FireEnd: SENU ABCDEFGHIJKLMNOPQR 1; Goto Idle; } }