aboutsummaryrefslogtreecommitdiff
path: root/Northstar.CustomServers/scripts/vscripts/mp/_pickups.gnut
diff options
context:
space:
mode:
Diffstat (limited to 'Northstar.CustomServers/scripts/vscripts/mp/_pickups.gnut')
-rw-r--r--Northstar.CustomServers/scripts/vscripts/mp/_pickups.gnut1195
1 files changed, 1195 insertions, 0 deletions
diff --git a/Northstar.CustomServers/scripts/vscripts/mp/_pickups.gnut b/Northstar.CustomServers/scripts/vscripts/mp/_pickups.gnut
new file mode 100644
index 000000000..ecf9b3e51
--- /dev/null
+++ b/Northstar.CustomServers/scripts/vscripts/mp/_pickups.gnut
@@ -0,0 +1,1195 @@
+untyped
+
+global function Pickups_Init
+global function AddCollectible
+global function UpdateCollectiblesAfterLoadSaveGame
+global function CreateWeaponPickup
+global function CreatePickup
+global function WaitUntilPlayerPicksUp
+global function AddClipToWeapon
+global function AddRoundsToWeapon
+global function AddRoundToWeapon
+global function AddClipToMainWeapons
+global function AddTwoClipToMainWeapons
+global function AddRoundToOrdnance
+global function AddRoundsToTactical
+global function CreateScriptWeapon
+global function GetAllLeveledScriptWeapons
+global function TitanLoadoutWaitsForPickup // Needed only to be global to spawn pickups from dev menu.
+
+#if DEV
+ global function Dev_ResetCollectiblesProgress_Level
+#endif
+
+const HEALTH_PICKUP_AMOUNT = 3000
+global const PICKUP_GLOW_FX = $"P_ar_titan_droppoint"
+//const HEALTH_MODEL = $"models/vehicle/droppod_fireteam/droppod_fireteam_collision.mdl"
+//const HEALTH_MODEL = $"models/domestic/trash_can_green_closed.mdl"
+//const HEALTH_MODEL = $"models/weapons/bullets/projectile_rocket_large.mdl"
+const HEALTH_MODEL = $"models/gameplay/health_pickup_small.mdl"
+const HEALTH_MODEL_LARGE = $"models/gameplay/health_pickup_large.mdl"
+
+const GRENADE_AMMO_MODEL = $"models/Weapons/ammoboxes/ammobox_01.mdl"
+const LION_MODEL = $"models/statues/lion_statue_bronze_green_small.mdl"
+const HELMET_COLLECTIBLE_MODEL = $"models/humans/heroes/mlt_hero_jack_helmet_static.mdl"
+const COLLECTIBLE_GLOW_EFFECT = $"P_item_bluelion"
+
+
+global struct LeveledScriptedWeapons
+{
+ table<string, bool> foundScriptWeapons
+ array<entity> infoTargets
+}
+
+struct HealthPickup
+{
+ float healAmount
+ float healTime
+ string pickupSound
+ string healSound
+ string endSound
+ asset model
+}
+
+struct Collectible
+{
+ entity ent
+ int id
+ vector pos
+}
+
+struct
+{
+ int nextHealthDropSmall
+ int nextHealthDropLarge
+ float lastHealthDropTime
+ table<string, HealthPickup> healthPickups
+ array<Collectible> collectibles
+ int testMapCollectibleValue
+} file
+
+function Pickups_Init()
+{
+ HealthPickup small
+ small.healAmount = 0.4
+ small.healTime = 1.0
+ small.pickupSound = "Pilot_HealthPack_Small_Pickup"
+ small.healSound = "Pilot_HealthPack_Small_Healing"
+ small.endSound = "Pilot_HealthPack_Small_Healing_End"
+ small.model = HEALTH_MODEL
+ file.healthPickups[ "health_pickup_small" ] <- small
+
+ HealthPickup large
+ large.healAmount = 0.8
+ large.healTime = 2.0
+ large.pickupSound = "Pilot_HealthPack_Large_Pickup"
+ large.healSound = "Pilot_HealthPack_Large_Healing"
+ large.endSound = "Pilot_HealthPack_Large_Healing_End"
+ large.model = HEALTH_MODEL_LARGE
+ file.healthPickups[ "health_pickup_large" ] <- large
+
+
+ //AddSpawnCallbackEditorClass( "script_ref", "script_pickup_health", HealthPickup_OnSpawned )
+ //AddSpawnCallbackEditorClass( "script_ref", "script_pickup_health_large", HealthPickupLarge_OnSpawned )
+ AddSpawnCallbackEditorClass( "script_mover_lightweight", "script_collectible", AddCollectible )
+ AddSpawnCallbackEditorClass( "script_ref", "script_pickup_weapon", CreateWeaponPickup )
+ //AddSpawnCallbackEditorClass( "script_ref", "script_pickup_grenades", CreateGrenadeAmmoPickup )
+ //AddSpawnCallbackEditorClass( "script_ref", "script_pickup_ammo", CreateGrenadeAmmoPickup )
+ AddSpawnCallbackEditorClass( "script_ref", "script_pickup_titan", CreateTitanPickup )
+
+ PrecacheModel( HEALTH_MODEL )
+ PrecacheModel( HEALTH_MODEL_LARGE )
+ PrecacheModel( GRENADE_AMMO_MODEL )
+ PrecacheModel( LION_MODEL )
+ PrecacheModel( HELMET_COLLECTIBLE_MODEL )
+ PrecacheParticleSystem( COLLECTIBLE_PICKUP_EFFECT )
+ PrecacheParticleSystem( COLLECTIBLE_GLOW_EFFECT )
+
+ RegisterSignal( "NewHealthPickup" )
+ RegisterSignal( "CollectibleEndThink" )
+
+ SetNextHealthDropSmall()
+ SetNextHealthDropLarge()
+
+ AddCallback_EntitiesDidLoad( EntitiesDidLoad_Pickups )
+}
+
+void function CreateScriptWeapon( entity point )
+{
+ CreateWeaponPickup( point )
+ array<entity> linkParents = point.GetLinkParentArray()
+ foreach ( entity linkParent in linkParents )
+ {
+ linkParent.UnlinkFromEnt( point )
+ }
+ //point.Destroy()
+}
+
+void function EntitiesDidLoad_Pickups()
+{
+ if ( shGlobal.proto_pilotHealthPickupsEnabled )
+ {
+ AddDeathCallback( "npc_soldier", OnNPCKilled_DropHealth )
+ AddDeathCallback( "npc_spectre", OnNPCKilled_DropHealth )
+ }
+
+ SetupCollectibles()
+}
+
+void function SetNextHealthDropSmall()
+{
+ if ( shGlobal.proto_pilotHealthRegenDisabled )
+ file.nextHealthDropSmall = RandomInt( 6 ) + 6
+ else
+ file.nextHealthDropSmall = RandomInt( 4 ) + 4
+
+ file.lastHealthDropTime = Time()
+}
+
+void function SetNextHealthDropLarge()
+{
+ file.nextHealthDropLarge = RandomIntRange( 3, 6 )
+}
+
+void function OnNPCKilled_DropHealth( entity npc, var damageInfo )
+{
+ entity attacker = DamageInfo_GetAttacker( damageInfo )
+ if ( !IsValid( attacker ) )
+ return
+
+ switch ( npc.GetClassName() )
+ {
+ case "npc_soldier":
+ case "npc_spectre":
+ case "npc_stalker":
+ break
+
+ default:
+ return
+ }
+
+ OnNPCKilled_DropHealth_Internal( npc, attacker, damageInfo )
+}
+
+void function OnNPCKilled_DropHealth_Internal( entity npc, entity attacker, var damageInfo )
+{
+ if ( npc.GetTeam() != TEAM_IMC )
+ return
+
+ file.nextHealthDropSmall--
+
+ if ( !DropHealthFromDeath( npc, attacker ) )
+ return
+
+ SetNextHealthDropSmall()
+
+ vector angles = npc.GetAngles()
+ angles.x = 0
+
+ vector forward = AnglesToForward( angles )
+
+ vector origin = npc.GetWorldSpaceCenter() + forward * 16
+
+ entity health
+ file.nextHealthDropLarge--
+ if ( file.nextHealthDropLarge < 0 && shGlobal.proto_pilotHealthRegenDisabled )
+ {
+ SetNextHealthDropLarge()
+ health = CreateHealthPickupSized( origin, angles, "health_pickup_large" )
+ }
+ else
+ {
+ health = CreateHealthPickupSized( origin, angles, "health_pickup_small" )
+ }
+
+ EmitSoundOnEntity( health, "Pilot_HealthPack_Drop" )
+ health.Fire( "Kill", "", 180 )
+}
+
+bool function DropHealthFromDeath( entity npc, entity attacker )
+{
+ if ( !attacker.IsPlayer() )
+ return file.nextHealthDropSmall < 0
+
+ if ( Time() - file.lastHealthDropTime < 3.0 )
+ return false
+
+ float healthRatio = float( attacker.GetHealth() ) / attacker.GetMaxHealth()
+
+ float chanceFromNextHealth = GraphCapped( file.nextHealthDropSmall, 0, 5, 0.4, 1.1 )
+ float chanceFromLowHealth = GraphCapped( healthRatio, 0.25, 0.80, 0.4, 1.1 )
+ float dropChance = chanceFromNextHealth * chanceFromLowHealth
+
+ return RandomFloat( 1.0 ) > dropChance
+
+ /*
+ if ( file.nextHealthDropSmall <= 1 )
+ {
+ float ratio = float( attacker.GetHealth() ) / attacker.GetMaxHealth() - 0.15
+ float random = RandomFloat( 1.0 )
+ if ( random > ratio )
+ return true
+ }
+
+ if ( Time() - file.lastHealthDropTime < 5.0 )
+ return false
+
+ if ( float( attacker.GetHealth() ) / attacker.GetMaxHealth() > 0.25 )
+ return false
+
+ return Distance( attacker.GetOrigin(), npc.GetOrigin() ) < 2000
+ */
+}
+
+void function CreateTitanPickup( entity ent )
+{
+ entity mover = CreatePickup( ent, LION_MODEL, DropTitanPickedUp )
+
+ mover.EndSignal( "OnDestroy" )
+ wait 0.2 // for some buggy reason!?
+ EmitSoundOnEntity( mover, "health_pickup_loopsound_far" )
+ EmitSoundOnEntity( mover, "health_pickup_loopsound_near" )
+ return
+}
+
+bool function DropTitanPickedUp( entity player )
+{
+ if ( player.IsTitan() )
+ return false
+
+ AddPlayerScore( player, "PilotHealthPickup" )
+ EmitSoundOnEntity( player, "titan_energyshield_up" )
+ player.SetNextTitanRespawnAvailable( 0 )
+
+ return true
+}
+
+function DisplayTempNameText( entity ent, string text )
+{
+ ent.EndSignal( "OnDestroy" )
+ for ( ;; )
+ {
+ array<entity> players = GetPlayerArray()
+ if ( players.len() )
+ {
+ entity nearestPlayer = GetClosest( players, ent.GetOrigin(), 2300 )
+ if ( nearestPlayer != null )
+ DebugDrawText( ent.GetWorldSpaceCenter(), text, true, 1 )
+ }
+ wait 0.9
+ }
+}
+
+entity function CreateHealthPickupSized( vector origin, vector angles, string healthType )
+{
+ HealthPickup pickup = file.healthPickups[ healthType ]
+ entity ent = CreatePropPhysics( pickup.model, origin, angles )
+ ent.NotSolid()
+
+
+ angles = AnglesCompose( angles, < -45,0,0> )
+ vector forward = AnglesToForward( angles )
+ ent.SetVelocity( forward * 200 )
+
+ ent.SetAngularVelocity( RandomFloatRange( 300, 500 ), RandomFloatRange( -100, 100 ), 0 )
+
+ thread HealthPickupWaitsForPickup( ent, pickup )
+ Highlight_SetNeutralHighlight( ent, "health_pickup" )
+ return ent
+}
+
+void function HealthPickup_OnSpawned( entity ent )
+{
+ //if ( shGlobal.proto_pilotHealthRegenDisabled )
+ {
+ CreateHealthPickupSized( ent.GetOrigin(), ent.GetAngles(), "health_pickup_small" )
+ ent.Destroy()
+ return
+ }
+
+}
+
+void function HealthPickupLarge_OnSpawned( entity ent )
+{
+ if ( shGlobal.proto_pilotHealthRegenDisabled )
+ {
+ CreateHealthPickupSized( ent.GetOrigin(), ent.GetAngles(), "health_pickup_small" )
+ ent.Destroy()
+ //HealthPickup_OnSpawned( ent )
+ return
+ }
+
+ CreateHealthPickupSized( ent.GetOrigin(), ent.GetAngles(), "health_pickup_large" )
+ ent.Destroy()
+}
+
+void function CreateHealthRegenField( entity ent )
+{
+ ent.EndSignal( "OnDestroy" )
+ OnThreadEnd(
+ function() : ( ent )
+ {
+ if ( IsValid( ent ) )
+ ent.Destroy()
+ }
+ )
+
+ PickupGlow pickupGlow = CreatePickupGlow( ent, 0, 255, 0 )
+
+ float healthRemainingMax = 1000
+ float healthRemainingCurrent = healthRemainingMax
+ float useIncrement = 9
+ float refillRate = 0
+ float nextRegenTime = 0
+
+ bool available
+ entity player
+ entity lastPlayer
+
+ for ( ;; )
+ {
+ WaitFrame()
+
+ float ratio = healthRemainingCurrent / healthRemainingMax
+ int green = int( Graph( ratio, 0, 1, 0, 255 ) )
+ PickupGlow_SetColor( pickupGlow, 0, green, 0 )
+
+ if ( Time() > nextRegenTime )
+ {
+ healthRemainingCurrent = min( healthRemainingCurrent + refillRate, healthRemainingMax )
+ nextRegenTime = Time() + 1.2
+ }
+
+ if ( healthRemainingCurrent < useIncrement )
+ continue
+
+ player = GetHealthPickupPlayer( ent )
+
+ if ( lastPlayer != player )
+ {
+ if ( IsValid( lastPlayer ) )
+ {
+ //EmitSoundOnEntity( lastPlayer, "Pilot_Stimpack_Deactivate" )
+ StopSoundOnEntity( lastPlayer, "Pilot_Stimpack_Loop" )
+ }
+
+ if ( player != null )
+ {
+ // new player powers up
+ EmitSoundOnEntity( player, "Pilot_Stimpack_Activate" )
+ EmitSoundOnEntity( player, "Pilot_Stimpack_Loop" )
+ }
+
+ lastPlayer = player
+ }
+
+ if ( player == null )
+ continue
+
+ // recent damage reduces healing effect, so you cant abuse it
+ float recentDamage = TotalDamageOverTime_BlendedOut( player, 0.5, 5.0 )
+
+ // damage is ramped down based on how much damage was taken recently
+ float damageMod = GraphCapped( recentDamage, 0, 40, 1.0, 0.35 )
+ float healthGain = useIncrement * damageMod
+
+ healthRemainingCurrent -= healthGain
+ int newHealth = int( min( player.GetHealth() + healthGain, player.GetMaxHealth() ) )
+ player.SetHealth( newHealth )
+
+ nextRegenTime = Time() + 10
+ }
+}
+
+entity function GetHealthPickupPlayer( entity ent )
+{
+ // try to heal the player
+ entity player = GetPickupPlayer( ent )
+ if ( player == null )
+ return null
+ if ( !IsPilot( player ) )
+ return null
+ if ( player.GetHealth() >= player.GetMaxHealth() )
+ return null
+
+ return player
+}
+
+void function CreateGrenadeAmmoPickup( entity ent )
+{
+ thread DisplayTempNameText( ent, "Ammo" )
+ CreatePickup( ent, GRENADE_AMMO_MODEL, GenericAmmoPickup )
+ CreatePickupGlow( ent, 13, 104, 255 )
+}
+
+entity function CreatePickup( entity ent, asset model, bool functionref( entity ) pickupFunc )
+{
+ entity mover = CreateEntity( "script_mover" )
+ mover.kv.solid = 0
+ mover.SetValueForModelKey( model )
+ mover.SetFadeDistance( 5000 )
+ mover.kv.SpawnAsPhysicsMover = 0
+ mover.SetOrigin( ent.GetOrigin() )
+ mover.SetAngles( ent.GetAngles() )
+ DispatchSpawn( mover )
+
+ ent.EndSignal( "OnDestroy" )
+
+ mover.SetOwner( ent )
+
+ ent.SetParent( mover )
+ thread PickupWaitsForPickup( ent, mover, pickupFunc )
+
+ return mover
+}
+
+void function PickupWaitsForPickup( entity ent, entity mover, bool functionref( entity ) pickupFunc )
+{
+ ent.EndSignal( "OnDestroy" )
+ OnThreadEnd(
+ function() : ( mover, ent )
+ {
+ if ( IsValid( mover ) )
+ mover.Destroy()
+ if ( IsValid( ent ) )
+ ent.Destroy()
+ }
+ )
+
+// local colorVec = Vector(r,g,b)
+// local cpoint = CreateEntity( "info_placement_helper" )
+// SetTargetName( cpoint, UniqueString( "pickup_controlpoint" ) )
+// DispatchSpawn( cpoint )
+// cpoint.SetOrigin( colorVec )
+// local glowFX = PlayFXWithControlPoint( PICKUP_GLOW_FX, mover.GetOrigin(), cpoint, null, null, null, C_PLAYFX_LOOP )
+//
+// OnThreadEnd(
+// function() : ( ent, mover, glowFX, cpoint )
+// {
+// cpoint.Fire( "Kill", "", 1.0 )
+// if ( IsValid(glowFX) )
+// {
+// glowFX.Fire( "StopPlayEndCap" )
+// glowFX.Fire( "Kill", "", 1.0 )
+// }
+// mover.Destroy()
+// ent.Destroy()
+// }
+// )
+
+ for ( ;; )
+ {
+ entity player = WaitUntilPlayerPicksUp( ent )
+ if ( pickupFunc( player ) )
+ return
+ WaitFrame()
+ }
+}
+
+void function HealthPickupWaitsForPickup( entity ent, HealthPickup pickup )
+{
+ ent.EndSignal( "OnDestroy" )
+ OnThreadEnd(
+ function() : ( ent )
+ {
+ if ( IsValid( ent ) )
+ ent.Destroy()
+ }
+ )
+
+ for ( ;; )
+ {
+ WaitFrame()
+
+ entity player = WaitUntilPlayerPicksUp( ent )
+ if ( player.IsTitan() )
+ continue
+ if ( player.GetHealth() >= player.GetMaxHealth() )
+ continue
+
+ thread HealPlayerOverTime( player, pickup )
+ return
+ }
+}
+
+
+void function TitanLoadoutWaitsForPickup( entity ent, bool functionref( entity, entity ) pickupFunc )
+{
+ ent.EndSignal( "OnDestroy" )
+ OnThreadEnd(
+ function() : ( ent )
+ {
+ if ( IsValid( ent ) )
+ ent.Destroy()
+ }
+ )
+
+ for ( ;; )
+ {
+ entity player = WaitUntilPlayerPicksUp( ent )
+ if ( pickupFunc( player, ent ) )
+ return
+ WaitFrame()
+ }
+}
+
+
+entity function GetPickupPlayer( entity ent )
+{
+ array<entity> players = GetPlayerArray()
+
+ vector entOrigin = ent.GetCenter()
+
+ foreach ( player in players )
+ {
+ if ( !IsAlive( player ) )
+ continue
+
+ int pickupDist
+ if ( player.IsTitan() )
+ pickupDist = 256 * 256
+ else
+ pickupDist = 96 * 96
+
+ if ( GetEditorClass( ent ) == "script_collectible" )
+ pickupDist = 72 * 72
+
+ vector playerOrigin = player.GetOrigin()
+ if ( DistanceSqr( playerOrigin, entOrigin ) < pickupDist )
+ {
+ TraceResults trace
+ trace = TraceLine( entOrigin, playerOrigin, [ player, ent ], TRACE_MASK_SOLID, TRACE_COLLISION_GROUP_NONE )
+ if ( trace.fraction >= 0.99 || trace.hitEnt == ent )
+ return player
+ }
+ }
+
+ return null
+}
+
+entity function WaitUntilPlayerPicksUp( entity ent )
+{
+ while ( true )
+ {
+ entity player = GetPickupPlayer( ent )
+ if ( player != null )
+ return player
+ WaitFrame()
+ }
+
+ unreachable
+}
+
+function PickupHover( mover )
+{
+ mover.EndSignal( "OnDestroy" )
+
+ int direction = 1
+
+ while ( 1 )
+ {
+ mover.MoveTo( mover.GetOrigin() + Vector( 0, 0, 20*direction ), 1, 0.4, 0.4 )
+ mover.RotateTo( mover.GetAngles() + Vector( 0, 90, 0 ), 1, 0, 0 )
+ direction *= -1
+ wait 1
+ }
+}
+
+int function AddClipToMainWeapons( entity player )
+{
+ int gainedAmmo
+ foreach ( weapon in player.GetMainWeapons() )
+ {
+ gainedAmmo += AddClipToWeapon( player, weapon )
+ }
+ return gainedAmmo
+}
+
+int function AddTwoClipToMainWeapons( entity player )
+{
+ int gainedAmmo
+ for ( int i = 0; i < 2; i++ )
+ {
+ foreach ( weapon in player.GetMainWeapons() )
+ {
+ gainedAmmo += AddClipToWeapon( player, weapon )
+ }
+ }
+ return gainedAmmo
+}
+
+int function AddRoundToOrdnance( entity player )
+{
+ int gainedAmmo
+ entity ordnance = player.GetOffhandWeapon( OFFHAND_ORDNANCE )
+ if ( IsValid( ordnance ) )
+ gainedAmmo += AddRoundToWeapon( player, ordnance )
+ return gainedAmmo
+}
+
+int function AddRoundsToTactical( entity player, int count = 1 )
+{
+ int gainedAmmo
+ entity ordnance = player.GetOffhandWeapon( OFFHAND_SPECIAL )
+ if ( IsValid( ordnance ) )
+ gainedAmmo += AddRoundsToWeapon( player, ordnance, count )
+ return gainedAmmo
+}
+
+int function AddClipToWeapon( entity player, entity weapon )
+{
+ int ammoPerClip = weapon.GetWeaponPrimaryClipCountMax()
+ int gainedAmmo = 0
+
+ switch ( weapon.GetWeaponInfoFileKeyField( "fire_mode" ) )
+ {
+ case "offhand_hybrid":
+ case "offhand":
+ case "offhand_instant":
+
+ // offhand weapons typically cant store ammo, so refill the current clip
+ if ( ammoPerClip > 0 )
+ {
+ int primaryClipCount = weapon.GetWeaponPrimaryClipCount()
+ weapon.SetWeaponPrimaryClipCount( ammoPerClip )
+ gainedAmmo = weapon.GetWeaponPrimaryClipCount() - primaryClipCount
+ }
+ break
+
+ default:
+ int primaryAmmoCount = weapon.GetWeaponPrimaryAmmoCount()
+ // this weapon has off-clip ammo storage, so add ammo to storage
+ int stockpile = player.GetWeaponAmmoStockpile( weapon )
+ weapon.SetWeaponPrimaryAmmoCount( primaryAmmoCount + ammoPerClip )
+ gainedAmmo = player.GetWeaponAmmoStockpile( weapon ) - stockpile
+ break
+ }
+
+ return gainedAmmo
+}
+
+int function AddRoundToWeapon( entity player, entity weapon )
+{
+ return AddRoundsToWeapon( player, weapon, 1 )
+}
+
+int function AddRoundsToWeapon( entity player, entity weapon, int rounds )
+{
+ int ammoPerClip = weapon.GetWeaponPrimaryClipCountMax()
+ int gainedAmmo = 0
+
+ switch ( weapon.GetWeaponInfoFileKeyField( "fire_mode" ) )
+ {
+ case "offhand_hybrid":
+ case "offhand":
+ case "offhand_instant":
+
+ // offhand weapons typically cant store ammo, so refill the current clip
+ if ( ammoPerClip > 0 )
+ {
+ int primaryAmmoInClipCount = weapon.GetWeaponPrimaryClipCount()
+ int newAmmo = minint( ammoPerClip, primaryAmmoInClipCount + rounds )
+ if ( newAmmo > primaryAmmoInClipCount )
+ {
+ weapon.SetWeaponPrimaryClipCount( newAmmo )
+ gainedAmmo = weapon.GetWeaponPrimaryClipCount() - primaryAmmoInClipCount
+ }
+ }
+ break
+
+
+ default:
+ int primaryAmmoCount = weapon.GetWeaponPrimaryAmmoCount()
+ // this weapon has off-clip ammo storage, so add ammo to storage
+ int stockpile = player.GetWeaponAmmoStockpile( weapon )
+ weapon.SetWeaponPrimaryAmmoCount( primaryAmmoCount + rounds )
+ gainedAmmo = player.GetWeaponAmmoStockpile( weapon ) - stockpile
+ break
+ }
+
+ return gainedAmmo
+}
+
+bool function GenericAmmoPickup( entity player )
+{
+ if ( player.IsTitan() )
+ return false
+
+ Assert( player.IsPlayer() )
+
+ int gainedAmmo = 0
+ foreach ( weapon in player.GetMainWeapons() )
+ {
+ gainedAmmo += AddClipToWeapon( player, weapon )
+ gainedAmmo += AddClipToWeapon( player, weapon )
+ gainedAmmo += AddClipToWeapon( player, weapon )
+ gainedAmmo += AddClipToWeapon( player, weapon )
+ }
+ foreach ( weapon in player.GetOffhandWeapons() )
+ {
+ gainedAmmo += AddClipToWeapon( player, weapon )
+ }
+
+ if ( gainedAmmo > 0 )
+ {
+ AddPlayerScore( player, "PilotAmmoPickup" )
+ EmitSoundOnEntity( player, "BurnCard_GrenadeRefill_Refill" )
+ EmitSoundOnEntity( player, "titan_energyshield_up" )
+ return true
+ }
+
+ return false
+}
+
+
+bool function GrenadeAmmoPickedUp( entity player )
+{
+ if ( player.IsTitan() )
+ return false
+
+ Assert( player.IsPlayer() )
+
+ entity weapon = player.GetOffhandWeapon( 0 )
+ if ( !IsValid( weapon ) )
+ return false
+
+ int ammo = weapon.GetWeaponPrimaryClipCount()
+ int newAmmo = minint( player.GetWeaponAmmoMaxLoaded( weapon ), ammo + 2 )
+ weapon.SetWeaponPrimaryClipCount( newAmmo )
+
+ bool pickup = newAmmo > ammo
+
+ if ( pickup )
+ {
+ AddPlayerScore( player, "PilotAmmoPickup" )
+ EmitSoundOnEntity( player, "BurnCard_GrenadeRefill_Refill" )
+ EmitSoundOnEntity( player, "titan_energyshield_up" )
+ return true
+ }
+
+ return false
+}
+
+void function HealPlayerOverTime( entity player, HealthPickup pickup )
+{
+ //StimPlayer( player, pickup.healTime * 2.0 )
+
+ Assert( IsNewThread(), "Must be threaded off" )
+ Assert( player.IsPlayer() )
+ //AddPlayerScore( player, "PilotHealthPickup" )
+ //EmitSoundOnEntity( player, "titan_energyshield_up" )
+
+ // cycle old effects
+ //player.Signal( "NewHealthPickup" )
+ //player.EndSignal( "NewHealthPickup" )
+
+ Assert( IsAlive( player ) )
+ player.EndSignal( "OnDeath" )
+
+ EmitSoundOnEntity( player, pickup.pickupSound )
+ EmitSoundOnEntity( player, pickup.healSound )
+
+
+ int frames = 10 * int( pickup.healTime )
+ float amount = player.GetMaxHealth() * pickup.healAmount
+ float healthPerFrame = amount / frames
+ float midTime = Time() + pickup.healTime * 0.5
+ float healthRemainder = 0
+
+ for ( int i = 0; i < frames; i++ )
+ {
+ WaitFrame()
+
+ float healthThisFrame
+ if ( Time() < midTime )
+ healthThisFrame = healthPerFrame * 0.6
+ else
+ healthThisFrame = healthPerFrame * 1.4
+
+ healthRemainder += healthThisFrame % 1
+ healthThisFrame -= healthThisFrame % 1
+ healthThisFrame += ( healthRemainder - healthRemainder % 1 )
+ healthRemainder %= 1
+// printt( "healththisframe is " + healthThisFrame + " healthRemainder is " + healthRemainder )
+
+ float newHealth = min( player.GetHealth() + healthThisFrame, player.GetMaxHealth() )
+ player.SetHealth( newHealth )
+ }
+
+ EmitSoundOnEntity( player, pickup.endSound )
+}
+
+LeveledScriptedWeapons function GetAllLeveledScriptWeapons()
+{
+ LeveledScriptedWeapons leveledScriptedWeapons
+
+ table<string, bool> tableAllWeapons
+
+ foreach ( weaponName in GetAllSPWeapons() )
+ {
+ tableAllWeapons[ weaponName ] <- true
+ }
+
+ foreach ( ent in GetEntArrayByClass_Expensive( "info_target" ) )
+ {
+ if ( !ent.HasKey( "editorclass" ) )
+ continue
+
+ string editorclass = expect string( ent.kv.editorclass )
+ if ( !( editorclass in tableAllWeapons ) )
+ continue
+
+ leveledScriptedWeapons.infoTargets.append( ent )
+ leveledScriptedWeapons.foundScriptWeapons[ editorclass ] <- true
+ }
+
+ // legacy support
+ foreach ( ent in GetEntArrayByClass_Expensive( "script_ref" ) )
+ {
+ if ( !ent.HasKey( "editorclass" ) )
+ continue
+ if ( ent.kv.editorclass != "script_pickup_weapon" )
+ continue
+
+ Assert( ent.HasKey( "script_weapon" ) )
+ string weapon = expect string( ent.kv.script_weapon )
+ if ( !( weapon in tableAllWeapons ) )
+ continue
+
+ //leveledScriptedWeapons.infoTargets.append( ent )
+ leveledScriptedWeapons.foundScriptWeapons[ weapon ] <- true
+ }
+
+ AddSpawnCallbackEditorClass( "script_ref", "script_pickup_weapon", CreateWeaponPickup )
+
+ return leveledScriptedWeapons
+}
+
+void function CreateWeaponPickup( entity ent )
+{
+ Assert( ent.HasKey( "script_weapon" ) )
+ string weaponClass = ent.GetValueForKey( "script_weapon" )
+
+ #if DEV
+ if ( !IsTestMap() )
+ {
+ if ( !WeaponIsPrecached( weaponClass ) )
+ {
+ CodeWarning( "Weapon " + weaponClass + " is not precached, re-export auto precache script" )
+ return
+ }
+
+ if ( !GetWeaponInfoFileKeyField_Global( weaponClass, "leveled_pickup" ) )
+ {
+ CodeWarning( "Tried to place illegal " + weaponClass + " in leveled at " + ent.GetOrigin() )
+ return
+ }
+ }
+
+ VerifyWeaponPickupModel( ent, weaponClass )
+
+ if ( GetWeaponInfoFileKeyField_Global( weaponClass, "offhand_default_inventory_slot" ) == OFFHAND_LEFT )
+ {
+ CodeWarning( "Illegal pickup " + weaponClass + " at " + ent.GetOrigin() )
+ return
+ }
+
+ #endif
+
+
+ bool doMarkAsLoadoutPickup = false
+ int loadoutIndex = GetSPTitanLoadoutIndexForWeapon( weaponClass )
+ if ( loadoutIndex >= 0 )
+ {
+ if ( IsBTLoadoutUnlocked( loadoutIndex ) )
+ return
+ else
+ doMarkAsLoadoutPickup = true
+ }
+
+ bool constrain = !ent.HasKey( "start_constrained" ) || ( ent.HasKey( "start_constrained" ) && ent.GetValueForKey( "start_constrained" ) == "1" )
+ entity weapon
+
+ if ( constrain ) // make all weapons constrained for now
+ {
+ weapon = CreateWeaponEntityByNameConstrained( weaponClass, ent.GetOrigin(), ent.GetAngles() )
+ }
+ else
+ {
+ weapon = CreateWeaponEntityByNameWithPhysics( weaponClass, ent.GetOrigin(), ent.GetAngles() )
+ weapon.SetVelocity( <0,0,0> )
+ }
+
+ SetTargetName( weapon, "leveled_" + weaponClass )
+ if ( ent.HasKey( "fadedist" ) )
+ {
+ weapon.kv.fadedist = ent.kv.fadedist
+ }
+ else
+ {
+ weapon.kv.fadedist = -1
+ }
+
+ ApplyWeaponModsFromEnt( ent, weapon )
+
+ if ( ent.HasKey( "script_name" ) )
+ {
+ weapon.kv.script_name = ent.kv.script_name
+ }
+
+ if ( doMarkAsLoadoutPickup )
+ {
+ weapon.MarkAsLoadoutPickup()
+ thread CreateTitanWeaponPickupHintTrigger( weapon )
+ thread TitanLoadoutWaitsForPickup( weapon, SPTitanLoadoutPickup )
+ }
+
+ HighlightWeapon( weapon )
+
+ // for s2s -mo
+ // for sp_training (pickups travel with moving gun racks) -sean
+ if ( ent.GetParent() )
+ {
+ weapon.SetParent( ent.GetParent(), "", true )
+ }
+
+ // for sp_training, to replenish the weapon when it's picked up -sean
+ ent.e.attachedEnts.append( weapon )
+}
+
+void function VerifyWeaponPickupModel( entity ent, string weaponClass )
+{
+ var playermodel
+ var playermodel1 = GetWeaponInfoFileKeyFieldAsset_Global( weaponClass, "droppedmodel" )
+ var playermodel2 = GetWeaponInfoFileKeyFieldAsset_Global( weaponClass, "playermodel" )
+ if ( playermodel1 != $"" )
+ playermodel = playermodel1
+ else
+ playermodel = playermodel2
+ playermodel = playermodel.tolower()
+ expect asset( playermodel )
+
+ asset modelName = ent.GetModelName().tolower()
+ if ( modelName != $"" && playermodel != modelName )
+ CodeWarning( "Incorrect Model on weapon " + weaponClass + " at " + ent.GetOrigin() + ", replace with real weapon for auto fix. ( " + modelName + " != " + playermodel + ")" )
+}
+
+void function ApplyWeaponModsFromEnt( entity ent, entity weapon )
+{
+ if ( ent.HasKey( "script_mods" ) )
+ {
+ array<string> mods = split( ent.GetValueForKey( "script_mods" ), " " )
+ if ( mods.len() > 0 )
+ {
+ weapon.SetMods( mods )
+ return
+ }
+ }
+
+ array<string> mods = GetWeaponModsForCurrentLevel( weapon.GetWeaponClassName() )
+ if ( mods.len() )
+ {
+ weapon.SetMods( [ mods.getrandom() ] )
+ }
+}
+
+
+void function AddCollectible( entity ent )
+{
+ Assert( ent.GetClassName() == "script_mover_lightweight" )
+
+ if ( ent.GetModelName() != HELMET_COLLECTIBLE_MODEL )
+ ent.SetModel( HELMET_COLLECTIBLE_MODEL )
+
+ ent.DisableFastPathRendering() // Workaround for glow effect not drawing (bug #177177)
+
+ Collectible collectible
+ collectible.ent = ent
+ collectible.pos = ent.GetOrigin()
+ file.collectibles.append( collectible )
+
+ // Drop to ground
+ if ( !collectible.ent.HasKey( "hover" ) || collectible.ent.kv.hover == "0" )
+ {
+ vector groundPos = OriginToGround( collectible.ent.GetOrigin() + <0,0,1> )
+ collectible.ent.SetOrigin( groundPos + < 0, 0, 32 > )
+ collectible.pos = collectible.ent.GetOrigin()
+ }
+
+ // Effect and not solid
+ collectible.ent.DisableHibernation()
+ collectible.ent.NotSolid()
+ collectible.ent.EnableRenderAlways()
+ collectible.ent.kv.fadedist = 100000
+}
+
+void function SetupCollectibles()
+{
+ // Make sure that the number of collectibles in the level matches the hardcoded value in sh_consts so that SP menus know total number per level.
+ string mapName = GetMapName()
+ int saveIndex = GetCollectibleLevelIndex( mapName )
+ Assert( saveIndex < 0 || file.collectibles.len() == GetMaxLionsInLevel( mapName ), "Collectibles count mismatch. Update LEVEL_UNLOCKS_COUNT in sh_consts.gnut to " + file.collectibles.len() )
+
+ // Index the collectibles so each is unique and it's status can be stored in a cvar. They are sorted by distance from map center to keep consistent on each map load
+ file.collectibles.sort( SortCollectiblesFunc )
+
+ foreach ( int i, Collectible collectible in file.collectibles )
+ {
+ collectible.id = 1 << i
+ thread CollectibleThink( collectible )
+ }
+}
+
+int function SortCollectiblesFunc( Collectible a, Collectible b )
+{
+ float distA = DistanceSqr( a.ent.GetOrigin(), <0,0,0> )
+ float distB = DistanceSqr( b.ent.GetOrigin(), <0,0,0> )
+ if ( distA > distB )
+ return 1
+ else if ( distA < distB )
+ return -1
+ return 0
+}
+
+void function UpdateCollectiblesAfterLoadSaveGame()
+{
+ // This has to run when a save game is loaded because the collectible may be there in the save game, but it was picked up after the save, so we need to delete the ones the player picked up
+ foreach ( Collectible collectible in file.collectibles )
+ {
+ if ( HasCollectible( collectible ) )
+ {
+ //DebugDrawSphere( collectible.pos, 40.0, 255, 0, 0, true, 600.0 )
+ if ( IsValid( collectible.ent ) )
+ collectible.ent.Destroy()
+ Signal( collectible, "CollectibleEndThink" )
+ }
+ }
+
+ // Delete all the weapons already unlocked in the level
+ array<string> unlockedLoadouts = GetSPTitanLoadoutsUnlocked()
+ SPTitanLoadout_RemoveOwnedLoadoutPickupsInLevel( unlockedLoadouts )
+}
+
+#if DEV
+ void function Dev_ResetCollectiblesProgress_Level()
+ {
+ printt( "RESETTING COLLECTIBLE PROGRESS (LEVEL)" )
+ string mapName = GetMapName()
+ int saveIndex = GetCollectibleLevelIndex( mapName )
+ if ( saveIndex == -1 )
+ return
+ printt( " sp_unlocks_level_" + saveIndex, 0 )
+ SetConVarInt( "sp_unlocks_level_" + saveIndex, 0 )
+ }
+#endif
+
+void function CollectibleThink( Collectible collectible )
+{
+ EndSignal( collectible, "CollectibleEndThink" )
+
+ if ( HasCollectible( collectible ) )
+ {
+ //printt( "Player already has collectible", collectible.id )
+ //DebugDrawSphere( collectible.ent.GetOrigin(), 25.0, 150, 0, 0, true, 600.0 )
+ collectible.ent.Destroy()
+ return
+ }
+
+ entity glowEffect = StartParticleEffectOnEntity_ReturnEntity( collectible.ent, GetParticleSystemIndex( COLLECTIBLE_GLOW_EFFECT ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 )
+
+ // Rotate the collectible
+ collectible.ent.NonPhysicsRotate( < 0, 0, 1 >, 35.0 )
+
+ WaitFrame() // emit sound doesn't work on first frame so we have to wait a frame so sound will play. Player can't pickup collectible in frame 1 anyways
+
+ EmitSoundOnEntity( collectible.ent, "Emit_PilotHelmet_Collectible" )
+
+ //wait 1.0
+ //DebugDrawText( collectible.ent.GetOrigin(), string(collectible.id), true, 600.0 )
+ //DebugDrawSphere( collectible.ent.GetOrigin(), 25.0, 255, 0, 0, true, 600.0 )
+
+ // Wait until it's touched by a player
+ entity player = WaitUntilPlayerPicksUp( collectible.ent )
+
+ // Remove collectible
+ EmitSoundOnEntity( player, "Pilot_Collectible_Pickup" )
+ if ( IsValid( glowEffect ) )
+ EffectStop( glowEffect )
+ collectible.ent.Destroy()
+
+ // Save to player profile
+ string mapName = GetMapName()
+ int saveIndex = GetCollectibleLevelIndex( mapName )
+ int bitMask
+ if ( saveIndex >= 0 )
+ {
+ // If it's a real map we store it to player profile
+ string unlockVar = "sp_unlocks_level_" + saveIndex
+ bitMask = GetConVarInt( unlockVar )
+ bitMask = bitMask | collectible.id
+ //printt( "Saving collectible state", unlockVar, bitMask )
+ SetConVarInt( unlockVar, bitMask )
+ }
+ else
+ {
+ // Not a real map, we store it to a file var that wont persist, just so we can pick them up and have kind of working collectibles in test maps
+ CodeWarning( "Collectible state not being saved because this map is not shipping" )
+ file.testMapCollectibleValue = file.testMapCollectibleValue | collectible.id
+ bitMask = file.testMapCollectibleValue
+ }
+
+ // See how many collectibles are found now to pass to the RUI
+ int numCollectiblesFound = GetCollectiblesFoundForLevel( mapName )
+ int maxCollectibles = GetMaxLionsInLevel( mapName )
+
+ // Show message on HUD
+ Remote_CallFunction_NonReplay( player, "ServerCallback_CollectibleFoundMessage", numCollectiblesFound, maxCollectibles )
+
+ CollectiblePickupRumble( player )
+
+ UpdateHeroStatsForPlayer( player )
+
+ int totalLionsCollectedForGame = GetTotalLionsCollected()
+
+ if ( totalLionsCollectedForGame >= GetTotalLionsInGame() )
+ UnlockAchievement( player, achievements.COLLECTIBLES_3 )
+
+ if ( totalLionsCollectedForGame >= ACHIEVEMENT_COLLECTIBLES_2_COUNT )
+ UnlockAchievement( player, achievements.COLLECTIBLES_2 )
+
+ if ( totalLionsCollectedForGame >= ACHIEVEMENT_COLLECTIBLES_1_COUNT )
+ UnlockAchievement( player, achievements.COLLECTIBLES_1 )
+}
+
+void function CollectiblePickupRumble( entity player )
+{
+ float rumbleAmplitude = 200.0
+ float rumbleFrequency = 90.0
+ float rumbleDuration = 2.2
+
+ CreateAirShakeRumbleOnly( player.GetOrigin(), rumbleAmplitude, rumbleFrequency, rumbleDuration )
+}
+
+bool function HasCollectible( Collectible collectible )
+{
+ string mapName = GetMapName()
+ int saveIndex = GetCollectibleLevelIndex( mapName )
+
+ // Not a shipping map, so there is no saved var for this level. Just always make it available
+ if ( saveIndex == -1 )
+ return false
+
+ string unlockVar = "sp_unlocks_level_" + saveIndex
+ int bitMask = GetConVarInt( unlockVar )
+
+ return bool(bitMask & collectible.id)
+}
+
+
+
+
+
+
+
+
+