diff options
Diffstat (limited to 'Northstar.CustomServers/mod/scripts/vscripts')
16 files changed, 261 insertions, 2194 deletions
diff --git a/Northstar.CustomServers/mod/scripts/vscripts/_misc.gnut b/Northstar.CustomServers/mod/scripts/vscripts/_misc.gnut deleted file mode 100644 index 20b53c50..00000000 --- a/Northstar.CustomServers/mod/scripts/vscripts/_misc.gnut +++ /dev/null @@ -1,40 +0,0 @@ -//todo figure out where these stub functions should be and move them to those places -global function Spotting_Init -global function FW_Border_GlobalInit -global function IsVDUTitan -global function PIN_PlayerRodeoedEnemyTitanToCompletion -global function PlayerProgressionAllowed - -void function Spotting_Init() -{ - -} - -void function FW_Border_GlobalInit() -{ - AddSpawnCallbackEditorClass( "func_brush", "func_brush_fw_territory_border", RemoveFWBorder ) -} - -void function RemoveFWBorder( entity border ) -{ - if ( GameModeRemove( border ) ) - return - - if ( !border.HasKey( "gamemode_" + GAMETYPE ) ) - border.Destroy() -} - -bool function IsVDUTitan(entity titan) -{ - return false -} - -void function PIN_PlayerRodeoedEnemyTitanToCompletion( entity player, entity titan, bool playerHadBattery ) -{ - -} - -bool function PlayerProgressionAllowed( entity player ) -{ - return true -}
\ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_boss_titan.gnut b/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_boss_titan.gnut deleted file mode 100644 index da3058d7..00000000 --- a/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_boss_titan.gnut +++ /dev/null @@ -1,794 +0,0 @@ -global function PlayerParentTest - -global function AIBossTitan_Init -global function OnBossTitanPrimaryFire -global function IsVDUTitan -global function IsBossTitan -global function GetBossTitanCharacterModel - -global function BossTitanRetreat -global function BossTitanAdvance -global function IsMercTitan -global function GetMercCharacterID -global function BossTitanIntro -global function BossTitanVDUEnabled -global function BossTitanPlayerView - -global function MakeMidHealthTitan - -global const float SLAMZOOM_TIME = 1.0 -global const float BOSS_TITAN_CORE_DAMAGE_SCALER_LOW = 0.6 -global const float BOSS_TITAN_CORE_DAMAGE_SCALER = 0.5 - -void function AIBossTitan_Init() -{ - if ( IsMultiplayer() ) - return - - FlagInit( "BossTitanViewFollow" ) - - AddSpawnCallback( "npc_titan", NPCTitanSpawned ) - AddDeathCallback( "npc_titan", OnBossTitanDeath ) - AddCallback_OnTitanDoomed( OnBossTitanDoomed ) - AddCallback_OnTitanHealthSegmentLost( OnTitanLostSegment ) - - AddSyncedMeleeServerCallback( GetSyncedMeleeChooser( "titan", "titan" ), OnBossTitanExecuted ) - - PrecacheParticleSystem( $"P_VDU_mflash" ) - - RegisterSignal( "BossTitanStartAnim" ) - RegisterSignal( "BossTitanIntroEnded" ) -} - -void function OnBossTitanExecuted( SyncedMeleeChooser actions, SyncedMelee action, entity attacker, entity victim ) -{ - if ( victim.IsNPC() && IsVDUTitan( victim ) && BossTitanVDUEnabled( victim ) ) - { - string name = victim.ai.bossCharacterName == "" ? "Generic1" : victim.ai.bossCharacterName - int bossID = GetBossTitanID( name ) - foreach ( player in GetPlayerArray() ) - { - if ( player == attacker || IsMercTitan( victim ) ) - { - Remote_CallFunction_NonReplay( player, "ServerCallback_BossTitanDeath", victim.GetEncodedEHandle(), bossID ) - } - } - } -} - -void function OnBossTitanDeath( entity titan, var damageInfo ) -{ - int damageSourceId = DamageInfo_GetDamageSourceIdentifier( damageInfo ) - if ( damageSourceId == eDamageSourceId.titan_execution ) - return - - entity soul = titan.GetTitanSoul() - if ( soul.IsEjecting() ) - return - - entity attacker = DamageInfo_GetAttacker( damageInfo ) - - if ( IsVDUTitan( titan ) && BossTitanVDUEnabled( titan ) ) - { - foreach ( player in GetPlayerArray() ) - { - if ( player == attacker || IsMercTitan( titan ) ) - { - string name = titan.ai.bossCharacterName == "" ? "Generic1" : titan.ai.bossCharacterName - Remote_CallFunction_NonReplay( player, "ServerCallback_BossTitanDeath", titan.GetEncodedEHandle(), GetBossTitanID( name ) ) - } - } - } -} - -void function OnBossTitanDoomed( entity titan, var damageInfo ) -{ - entity attacker = DamageInfo_GetAttacker( damageInfo ) - - if ( IsVDUTitan( titan ) && BossTitanVDUEnabled( titan ) ) - { - foreach ( player in GetPlayerArray() ) - { - if ( player == attacker || IsMercTitan( titan ) ) - Remote_CallFunction_NonReplay( player, "ServerCallback_BossTitanDoomed", titan.GetEncodedEHandle() ) - } - } -} - -void function OnBossTitanCoreMitigation( entity titan, var damageInfo ) -{ - int damageSourceID = DamageInfo_GetDamageSourceIdentifier( damageInfo ) - switch ( damageSourceID ) - { - case eDamageSourceId.mp_titancore_salvo_core: - DamageInfo_ScaleDamage( damageInfo, BOSS_TITAN_CORE_DAMAGE_SCALER_LOW ) - return - - // case eDamageSourceId.mp_titancore_laser_cannon: laser core handles this in mp_titanweapon_lasercannon.nut - case eDamageSourceId.mp_titancore_flame_wave: - case eDamageSourceId.mp_titancore_flame_wave_secondary: - case eDamageSourceId.mp_titancore_shift_core: - case eDamageSourceId.mp_titanweapon_flightcore_rockets: - case eDamageSourceId.mp_titancore_amp_core: - case damagedef_nuclear_core: - DamageInfo_ScaleDamage( damageInfo, BOSS_TITAN_CORE_DAMAGE_SCALER ) - return - } - - // SMART CORE - array<string> weaponMods = GetWeaponModsFromDamageInfo( damageInfo ) - if ( weaponMods.contains( "Smart_Core" ) ) - { - DamageInfo_ScaleDamage( damageInfo, BOSS_TITAN_CORE_DAMAGE_SCALER ) - // DamageInfo_ScaleDamage( damageInfo, BOSS_TITAN_CORE_DAMAGE_SCALER_LOW ) - return - } -} - -void function NPCTitanSpawned( entity titan ) -{ - Assert( !IsMultiplayer() ) - - if ( titan.GetTeam() == TEAM_IMC ) - { - switch ( titan.ai.bossTitanType ) - { - case TITAN_WEAK: - case TITAN_HENCH: - MakeMidHealthTitan( titan ) - - case TITAN_BOSS: - RegisterBossTitan( titan ) - ApplyTitanDamageState( titan ) - - if ( titan.ai.bossTitanType == TITAN_BOSS ) - AddEntityCallback_OnDamaged( titan, OnBossTitanCoreMitigation ) - - if ( titan.HasKey( "skip_boss_intro" ) && titan.GetValueForKey( "skip_boss_intro" ) == "1" ) - return - thread BossTitanNoIntro( titan ) - break; - - - case TITAN_MERC: - // TODO: This SetSkin() call should move to RegisterBossTitan() when the above TITAN_BOSS stuff is cleaned up/removed. - titan.SetSkin( 1 ) // all titan models have a boss titan version of the skin at index 1 - RegisterBossTitan( titan ) - ApplyTitanDamageState( titan ) - - AddEntityCallback_OnDamaged( titan, OnBossTitanCoreMitigation ) - - if ( titan.HasKey( "skip_boss_intro" ) && titan.GetValueForKey( "skip_boss_intro" ) == "1" ) - return - - if ( !titan.ai.bossTitanPlayIntro ) - return - - foreach ( player in GetPlayerArray() ) - { - thread BossTitanIntro( player, titan ) - } - break - - // case TITAN_WEAK: - // MakeLowHealthTitan( titan ) - // break - - case TITAN_AUTO: - if ( !IsMultiplayer() && GetMapName() == "sp_hub_timeshift" || GetMapName() == "sp_timeshift_spoke02" ) - MakeLowHealthTitan( titan ) - break - default: - return - } - } -} - -void function BossTitanNoIntro( entity titan ) -{ - FlagWait( "PlayerDidSpawn" ) - - entity player = GetPlayerArray()[0] - - player.EndSignal( "OnDeath" ) - titan.EndSignal( "OnDeath" ) - - // Wait until player sees the boss titan - waitthread WaitForHotdropToEnd( titan ) - - while ( 1 ) - { - waitthread WaitTillLookingAt( player, titan, true, 60, 5100 ) - if ( titan.GetEnemy() == null ) - titan.WaitSignal( "OnSeeEnemy" ) - else - break - } - - if ( BossTitanVDUEnabled( titan ) ) - Remote_CallFunction_NonReplay( player, "ServerCallback_BossTitanNoIntro", titan.GetEncodedEHandle() ) - AddEntityCallback_OnDamaged( titan, OnBossTitanDamaged ) - AddTitanCallback_OnHealthSegmentLost( titan, OnBossTitanLostSegment ) -} - -void function BossTitanIntro( entity player, entity titan, BossTitanIntroData ornull introdata = null ) -{ - Assert( titan.IsNPC() ) - Assert( titan.ai.bossCharacterName != "" ) - - if ( introdata == null ) - { - BossTitanIntroData defaultData = GetBossTitanIntroData( titan.ai.bossCharacterName ) - introdata = defaultData - } - - expect BossTitanIntroData( introdata ) - - player.EndSignal( "OnDeath" ) - titan.EndSignal( "OnDeath" ) - - HideCrit( titan ) - titan.SetValidHealthBarTarget( false ) - titan.SetInvulnerable() - - // Wait until player sees the boss titan - - while ( titan.e.isHotDropping ) - { - WaitFrame() - } - - HideName( titan ) - titan.kv.allowshoot = 0 - - if ( introdata.waitToStartFlag != "" ) - FlagWait( introdata.waitToStartFlag ) - - if ( introdata.waitForLookat ) - waitthread WaitTillLookingAt( player, titan, introdata.lookatDoTrace, introdata.lookatDegrees, introdata.lookatMinDist ) - - while ( IsPlayerDisembarking( player ) || IsPlayerEmbarking( player ) ) - { - WaitFrame() - } - - BossTitanData bossTitanData = GetBossTitanData( titan.ai.bossCharacterName ) - - // Create a ref node to animate on - vector refPos - vector refAngles - - if ( bossTitanData.introAnimTitanRef != "" ) - { - entity titanAnimRef = GetEntByScriptName( bossTitanData.introAnimTitanRef ) - refPos = titanAnimRef.GetOrigin() - refAngles = titanAnimRef.GetAngles() - } - else - { - refPos = titan.GetOrigin() - - vector vecToPlayer = Normalize( player.GetOrigin() - titan.GetOrigin() ) - refAngles = VectorToAngles( vecToPlayer ) - refAngles = FlattenAngles( refAngles ) - } - - entity ref - if ( introdata.parentRef != null ) - { - ref = introdata.parentRef - } - else - ref = CreateScriptRef( refPos, refAngles ) - - entity soul = titan.GetTitanSoul() - if ( IsValid( soul.soul.bubbleShield ) ) - { - soul.soul.bubbleShield.Destroy() - } - - // Freeze player and clear up the screen - StartBossIntro( player, titan, introdata ) - player.Hide() - player.SetVelocity( <0,0,0> ) - player.FreezeControlsOnServer() - player.SetNoTarget( true ) - player.SetInvulnerable() - - // Do special player view movement - FlagSet( "BossTitanViewFollow" ) - - // Animate the boss titan - entity pilot = CreatePropDynamic( GetBossTitanCharacterModel( titan ) ) - if ( introdata.parentRef != null ) - { - if ( introdata.parentAttach != "" ) - { - pilot.SetParent( introdata.parentRef, introdata.parentAttach ) - } - else - { - pilot.SetParent( introdata.parentRef ) - } - } - SetTeam( pilot, TEAM_IMC ) - - string pilotAnimName = bossTitanData.introAnimPilot - string titanAnimName = bossTitanData.introAnimTitan - - float introDuration = 6.0 - - Assert( titan.Anim_HasSequence( titanAnimName ), "Your boss titan does not have an intro animation set, or it is missing." ) - - introDuration = titan.GetSequenceDuration( titanAnimName ) - - svGlobal.levelEnt.Signal( "BossTitanStartAnim" ) - - if ( introdata.parentAttach != "" ) - { - thread PlayAnim( pilot, pilotAnimName, ref, introdata.parentAttach, 0.0 ) - thread PlayAnim( titan, titanAnimName, ref, introdata.parentAttach, 0.0 ) - } - else - { - thread PlayAnim( pilot, pilotAnimName, ref, 0.0 ) - thread PlayAnim( titan, titanAnimName, ref, 0.0 ) - } - - Objective_Hide( player ) - - thread BossTitanPlayerView( player, titan, ref, bossTitanData.titanCameraAttachment ) - - wait introDuration - SLAMZOOM_TIME - - // Player view returns to normal - FlagClear( "BossTitanViewFollow" ) - EndBossIntro( player, titan ) - - wait SLAMZOOM_TIME - - // Return the player screen and movement back to normal - player.UnfreezeControlsOnServer() - player.SetNoTarget( false ) - player.ClearInvulnerable() - player.Show() - pilot.Destroy() - - if ( IsValid( titan ) ) - { - titan.ClearInvulnerable() - titan.Solid() - AddEntityCallback_OnDamaged( titan, OnBossTitanDamaged ) - AddTitanCallback_OnHealthSegmentLost( titan, OnBossTitanLostSegment ) - ShowName( titan ) - titan.SetValidHealthBarTarget( true ) - ShowCrit( titan ) - Signal( titan, "BossTitanIntroEnded" ) - } - - wait 0.5 - - if ( Flag( "AutomaticCheckpointsEnabled" ) ) - { - if ( introdata.checkpointOnlyIfPlayerTitan ) - { - if ( player.IsTitan() ) - CheckPoint_Forced() - } - else - CheckPoint_Forced() - } - - wait 1.0 - - titan.kv.allowshoot = 1 - Remote_CallFunction_NonReplay( player, "ServerCallback_BossTitanPostIntro", titan.GetEncodedEHandle(), BossTitanVDUEnabled( titan ) ) -} - -void function PlayerParentTest() -{ - entity player = GetPlayerArray()[0] - - vector moverStartPos = player.EyePosition() - vector moverStartAng = FlattenAngles( player.GetAngles() ) - entity mover = CreateScriptMover( moverStartPos, moverStartAng ) - - player.SnapEyeAngles( moverStartAng ) - player.SetParent( mover, "", true ) -} - -void function BossTitanPlayerView( entity player, entity titan, entity ref, string titanCameraAttachment ) -{ - bool hasTitanCameraAttachment = titanCameraAttachment != "" - - EndSignal( player, "OnDeath" ) - EndSignal( titan, "OnDeath" ) - - vector moverStartPos = player.CameraPosition() - - vector camFeetDiff = < 0,0,-185 >//player.GetOrigin() - player.CameraPosition() - - vector moverStartAng = player.CameraAngles() - entity mover = CreateScriptMover( moverStartPos, moverStartAng ) - - // player.SnapEyeAngles( moverStartAng ) - // player.SetParent( mover, "", true ) - // ViewConeZero( player ) - - entity camera = CreateEntity( "point_viewcontrol" ) - camera.kv.spawnflags = 56 // infinite hold time, snap to goal angles, make player non-solid - - camera.SetOrigin( player.CameraPosition() ) - camera.SetAngles( player.CameraAngles() ) - DispatchSpawn( camera ) - - camera.SetParent( mover, "", false ) - - OnThreadEnd( - function() : ( player, titan, mover, camera ) - { - if ( IsValid( camera ) ) - { - camera.Destroy() - } - - mover.Destroy() - - if ( IsValid( player ) ) - { - player.ClearParent() - player.ClearViewEntity() - RemoveCinematicFlag( player, CE_FLAG_HIDE_MAIN_HUD ) - RemoveCinematicFlag( player, CE_FLAG_TITAN_3P_CAM ) - } - - if ( IsAlive( titan ) && titan.IsNPC() ) - { - titan.SetNoTarget( false ) - titan.DisableNPCFlag( NPC_IGNORE_ALL ) - } - } - ) - - // Slam Zoom In - float slamZoomTime = SLAMZOOM_TIME - float slamZoomTimeAccel = 0.3 - float slamZoomTimeDecel = 0.3 - vector viewOffset = < 200, 100, 160 > - - vector viewPos = ref.GetOrigin() + ( AnglesToForward( ref.GetAngles() ) * viewOffset.x ) + ( AnglesToRight( ref.GetAngles() ) * viewOffset.y ) + ( AnglesToUp( ref.GetAngles() ) * viewOffset.z ) - vector viewAngles = ref.GetAngles() + <0,180,0> - if ( hasTitanCameraAttachment ) - { - WaitFrame() - int titanCameraAttachmentID = titan.LookupAttachment( titanCameraAttachment ) - viewPos = titan.GetAttachmentOrigin( titanCameraAttachmentID ) - viewAngles = titan.GetAttachmentAngles( titanCameraAttachmentID ) - } - - float blendTime = 0.5 - float waittime = 0.3 - float moveTime = slamZoomTime - blendTime - waittime - - float startTime = Time() - - player.SetVelocity( < 0,0,0 > ) - player.MakeInvisible() - HolsterAndDisableWeapons( player ) - - wait waittime // wait for the AI to blend into the anim - - if ( titan.IsNPC() ) - { - titan.SetNoTarget( true ) - titan.EnableNPCFlag( NPC_IGNORE_ALL ) - } - - AddCinematicFlag( player, CE_FLAG_HIDE_MAIN_HUD ) - AddCinematicFlag( player, CE_FLAG_TITAN_3P_CAM ) - - mover.SetOrigin( player.CameraPosition() ) - mover.SetAngles( player.CameraAngles() ) - player.SetViewEntity( camera, true ) - - player.SetPredictionEnabled( false ) - OnThreadEnd( - function() : ( player ) - { - if ( IsValid( player ) ) - player.SetPredictionEnabled( true ) - } - ) - - while ( Time() - startTime < moveTime ) - { - if ( hasTitanCameraAttachment ) - { - int titanCameraAttachmentID = titan.LookupAttachment( titanCameraAttachment ) - viewPos = titan.GetAttachmentOrigin( titanCameraAttachmentID ) - viewAngles = titan.GetAttachmentAngles( titanCameraAttachmentID ) - } - mover.NonPhysicsMoveTo( viewPos, moveTime - (Time() - startTime), 0, 0 ) - mover.NonPhysicsRotateTo( viewAngles, moveTime - (Time() - startTime), 0, 0 ) - wait 0.1 - } - - if ( hasTitanCameraAttachment ) - { - mover.SetParent( titan, titanCameraAttachment, false, blendTime ) - } - - wait 0.5 - - int tagID = titan.LookupAttachment( "CHESTFOCUS" ) - while ( Flag( "BossTitanViewFollow" ) ) - { - vector lookVec = Normalize( titan.GetAttachmentOrigin( tagID ) - mover.GetOrigin() ) - vector angles = VectorToAngles( lookVec ) - if ( !hasTitanCameraAttachment ) - mover.NonPhysicsRotateTo( angles, 0.2, 0.0, 0.0 ) - WaitFrame() - } - - // Slam Zoom Out - - mover.ClearParent() - - startTime = Time() - while ( Time() - startTime < slamZoomTime ) - { - moverStartPos = player.GetOrigin() - camFeetDiff - moverStartAng = FlattenAngles( player.GetAngles() ) - mover.NonPhysicsMoveTo( moverStartPos, slamZoomTime - (Time() - startTime), 0, 0 ) - mover.NonPhysicsRotateTo( moverStartAng, slamZoomTime - (Time() - startTime), 0, 0 ) - wait 0.1 - } - - // mover.NonPhysicsMoveTo( moverStartPos, slamZoomTime, slamZoomTimeDecel, slamZoomTimeAccel ) - // mover.NonPhysicsRotateTo( moverStartAng, slamZoomTime, slamZoomTimeDecel, slamZoomTimeAccel ) - // wait slamZoomTime - - ClearPlayerAnimViewEntity( player ) - player.SnapEyeAngles( moverStartAng ) - DeployAndEnableWeapons( player ) - player.MakeVisible() - - EmitSoundOnEntity( player, "UI_Lobby_RankChip_Disable" ) -} - -void function OnBossTitanDamaged( entity titan, var damageInfo ) -{ -} - -void function OnBossTitanLostSegment( entity titan, entity attacker ) -{ - if ( !titan.IsNPC() || !BossTitanVDUEnabled( titan ) ) - return - - foreach ( player in GetPlayerArray() ) - { - if ( player == attacker || IsMercTitan( titan ) ) - Remote_CallFunction_NonReplay( player, "ServerCallback_BossTitanLostSegment", titan.GetEncodedEHandle(), GetTitanCurrentRegenTab( titan ) ) - } -} - -void function OnBossTitanPrimaryFire( entity titan ) -{ -} - -bool function IsVDUTitan( entity titan ) -{ - Assert( IsSingleplayer() ) - - if ( titan.GetTeam() != TEAM_IMC ) - return false - - switch ( titan.ai.bossTitanType ) - { - case TITAN_AUTO: - case TITAN_WEAK: - return false - - case TITAN_HENCH: - case TITAN_MERC: - case TITAN_BOSS: - return true - } - - Assert( 0, "Unknown boss titan type " + titan.ai.bossTitanType ) - unreachable -} - -bool function IsBossTitan( entity titan ) -{ - Assert( IsSingleplayer() ) - - if ( titan.GetTeam() != TEAM_IMC ) - return false - - switch ( titan.ai.bossTitanType ) - { - case TITAN_MERC: - case TITAN_BOSS: - return true - } - - return false -} - -int function GetMercCharacterID( entity titan ) -{ - return titan.ai.mercCharacterID -} - -asset function GetBossTitanCharacterModel( entity titan ) -{ - int mercCharacterID = GetMercCharacterID( titan ) - return GetMercCharacterModel( mercCharacterID ) -} - -void function OnTitanLostSegment( entity titan, entity attacker ) -{ - entity player - - if ( !titan.IsPlayer() ) - player = titan.GetBossPlayer() - else - player = titan - - if ( !IsValid( player ) ) - return - - if ( !IsValid( attacker ) ) - return - - if ( !attacker.IsNPC() || !IsVDUTitan( attacker ) || !BossTitanVDUEnabled( attacker ) ) - return - - Remote_CallFunction_NonReplay( player, "BossTitanPlayerLostHealthSegment", GetSegmentHealthForTitan( titan ) ) -} - -void function BossTitanRetreat( entity titan ) -{ - if ( !IsVDUTitan( titan ) || !BossTitanVDUEnabled( titan ) ) - return - - foreach ( player in GetPlayerArray() ) - { - Remote_CallFunction_NonReplay( player, "ServerCallback_BossTitanRetreat", titan.GetEncodedEHandle() ) - } -} - -void function BossTitanAdvance( entity titan ) -{ - if ( !IsVDUTitan( titan ) || !BossTitanVDUEnabled( titan ) ) - return - - foreach ( player in GetPlayerArray() ) - { - Remote_CallFunction_NonReplay( player, "ServerCallback_BossTitanAdvance", titan.GetEncodedEHandle() ) - } -} - -/* ------------------------------------------------------------- -Low Health Titans ------------------------------------------------------------- -*/ - -void function MakeLowHealthTitan( entity ent ) -{ - entity soul = ent.GetTitanSoul() - soul.soul.regensHealth = false - thread SetHealthValuesForLowHealth( soul ) - //ent.SetValidHealthBarTarget( false ) - - ent.TakeOffhandWeapon( OFFHAND_ORDNANCE ) - ent.TakeOffhandWeapon( OFFHAND_ANTIRODEO ) - ent.TakeOffhandWeapon( OFFHAND_EQUIPMENT ) - ent.TakeOffhandWeapon( OFFHAND_SPECIAL ) -} - -void function MakeMidHealthTitan( entity ent ) -{ - entity soul = ent.GetTitanSoul() - soul.soul.regensHealth = false - thread SetHealthValuesForMidHealth( soul ) -} - -void function SetHealthValuesForMidHealth( entity soul ) -{ - soul.EndSignal( "OnDestroy" ) - WaitEndFrame() // wait for a bunch of variables to start up - soul.Signal( SIGNAL_TITAN_HEALTH_REGEN ) - soul.Signal( "StopShieldRegen" ) - soul.SetShieldHealth( 0 ) - - entity titan = soul.GetTitan() - int numSegments = ( titan.GetMaxHealth() / GetSegmentHealthForTitan( titan ) ) - 2 - Assert( numSegments > 0 ) - SetSoulBatteryCount( soul, numSegments ) - if ( IsAlive( titan ) ) - { - soul.soul.skipDoomState = true - int segmentHealth = GetSegmentHealthForTitan( titan ) * numSegments - titan.SetMaxHealth( segmentHealth ) - titan.SetHealth( segmentHealth ) - titan.kv.healthEvalMultiplier = 2 - } - - titan.Signal( "WeakTitanHealthInitialized" ) - - ApplyTitanDamageState( titan ) -} - -void function SetHealthValuesForLowHealth( entity soul ) -{ - soul.EndSignal( "OnDestroy" ) - WaitEndFrame() // wait for a bunch of variables to start up - soul.Signal( SIGNAL_TITAN_HEALTH_REGEN ) - soul.Signal( "StopShieldRegen" ) - soul.SetShieldHealth( 0 ) - - int numSegments = 2 - - SetSoulBatteryCount( soul, numSegments ) - entity titan = soul.GetTitan() - if ( IsAlive( titan ) ) - { - soul.soul.skipDoomState = true - int segmentHealth = GetSegmentHealthForTitan( titan ) * numSegments - titan.SetMaxHealth( segmentHealth ) - titan.SetHealth( segmentHealth ) - titan.kv.healthEvalMultiplier = 2 - } - - titan.Signal( "WeakTitanHealthInitialized" ) - - ApplyTitanDamageState( titan ) -} - -void function ApplyTitanDamageState( entity titan ) -{ - array<float> healthScale = [ - 1.0, - 0.6, - 0.3, - 0.1 - ] - - int state = 0 - - if ( titan.HasKey( "DamageState" ) ) - { - state = int( titan.GetValueForKey( "DamageState" ) ) - } - - titan.SetHealth( titan.GetMaxHealth() * healthScale[state] ) - - if ( state >= 1 ) - { - string part = [ - "left_arm", - "right_arm" - ].getrandom() - GibBodyPart( titan, part ) - } - - if ( state >= 2 ) - GibBodyPart( titan, "torso" ) -} - -bool function IsMercTitan( entity titan ) -{ - if ( IsMultiplayer() ) - return false - if ( titan.GetTeam() != TEAM_IMC ) - return false - return titan.ai.bossTitanType == TITAN_MERC -} - -bool function BossTitanVDUEnabled( entity titan ) -{ - return titan.ai.bossTitanVDUEnabled -}
\ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_mortar_spectres.gnut b/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_mortar_spectres.gnut index 4aa3ac30..080e2f68 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_mortar_spectres.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_mortar_spectres.gnut @@ -1,6 +1,6 @@ global function MortarSpectreGetSquadFiringPositions -array<vector> function MortarSpectreGetSquadFiringPositions(vector origin, vector testTarget) +array<vector> function MortarSpectreGetSquadFiringPositions( vector origin, vector testTarget ) { array< vector > ret return ret diff --git a/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_soldiers_sp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_soldiers_sp.gnut deleted file mode 100644 index 6faf6649..00000000 --- a/Northstar.CustomServers/mod/scripts/vscripts/ai/_ai_soldiers_sp.gnut +++ /dev/null @@ -1,17 +0,0 @@ -global function IsAutoPopulateEnabled - -bool function IsAutoPopulateEnabled( var team = null ) -{ - if ( IsNPCSpawningEnabled() == false ) - return false - - if ( Flag( "disable_npcs" ) ) - return false - - if ( team == TEAM_MILITIA && Flag( "Disable_MILITIA" ) ) - return false - if ( team == TEAM_IMC && Flag( "Disable_IMC" ) ) - return false - - return true -}
\ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut b/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut index 712fa9de..d84ab873 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/evac/_evac.gnut @@ -67,6 +67,7 @@ struct { void function Evac_Init() { EvacShared_Init() + RegisterSignal( "EvacShipLeaves" ) } void function AddEvacNode( entity evacNode ) @@ -95,7 +96,7 @@ void function EvacEpilogue() { // make sure we don't run this on ffa modes if no epilogue was specified, since evac is default epilogue if ( GetCurrentPlaylistVarInt( "max_teams", 2 ) == 2 ) - thread Evac( GetPlayerArray()[0].GetTeam(), EVAC_INITIAL_WAIT, EVAC_ARRIVAL_TIME, EVAC_WAIT_TIME, EvacEpiloguePlayerCanBoard, EvacEpilogueShouldLeaveEarly, EvacEpilogueCompleted ) + thread Evac( GetOtherTeam( GetWinningTeam() ), EVAC_INITIAL_WAIT, EVAC_ARRIVAL_TIME, EVAC_WAIT_TIME, EvacEpiloguePlayerCanBoard, EvacEpilogueShouldLeaveEarly, EvacEpilogueCompleted ) else thread EvacEpilogueCompleted( null ) // this is hacky but like, this also shouldn't really be hit in normal gameplay } @@ -146,12 +147,12 @@ void function EvacEpilogueCompleted( entity dropship ) } // global evac func, anything can call this, it's not necessarily an epilogue thing -void function Evac( int evacTeam, float initialWait, float arrivalTime, float waitTime, bool functionref( entity, entity ) canBoardCallback, bool functionref( entity ) shouldLeaveEarlyCallback, void functionref( entity ) completionCallback ) +void function Evac( int evacTeam, float initialWait, float arrivalTime, float waitTime, bool functionref( entity, entity ) canBoardCallback, bool functionref( entity ) shouldLeaveEarlyCallback, void functionref( entity ) completionCallback, entity customEvacNode = null ) { wait initialWait // setup evac nodes if not manually registered - if ( file.evacNodes.len() == 0 ) + if ( file.evacNodes.len() == 0 && !IsValid( customEvacNode ) ) { for ( int i = 1; ; i++ ) { @@ -167,7 +168,9 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa if ( !IsValid( file.spaceNode ) ) file.spaceNode = GetEnt( "spaceNode" ) - entity evacNode = file.evacNodes.getrandom() + entity evacNode + if ( !IsValid( customEvacNode ) ) + evacNode = file.evacNodes.getrandom() // setup client evac position file.evacIcon = CreateEntity( "info_target" ) @@ -261,6 +264,7 @@ void function Evac( int evacTeam, float initialWait, float arrivalTime, float wa player.HolsterWeapon() // fly away + dropship.Signal( "EvacShipLeaves" ) thread PlayAnim( dropship, "cd_dropship_rescue_side_end", evacNode ) SetTeamActiveObjective( evacTeam, "EG_DropshipExtractDropshipFlyingAway" ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_featured_mode_settings.gnut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_featured_mode_settings.gnut index 090814cb..62fd52c9 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_featured_mode_settings.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_featured_mode_settings.gnut @@ -113,13 +113,10 @@ void function FeaturedModeSettingsSetupPilotLoadouts( entity player ) void function FeaturedModeSettingsSetupTitanLoadouts( entity player, entity titan ) { - // this doesn't work atm, figure out how it should work and fix at some point - entity soul = player.GetTitanSoul() if ( IsFeaturedMode( "turbo_titans" ) ) { - if ( GetSoulTitanSubClass( soul ) == "stryder" || GetSoulTitanSubClass( soul ) == "atlas" ) - GivePassive( player, ePassives.PAS_MOBILITY_DASH_CAPACITY ) - else - GivePassive( player, ePassives.PAS_DASH_RECHARGE ) + array<string> settingsMods = player.GetPlayerSettingsMods() + settingsMods.append( "turbo_titan" ) + player.SetPlayerSettingsWithMods( player.GetPlayerSettings(), settingsMods ) } }
\ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut index a30944cf..a8089679 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_aitdm.nut @@ -1,12 +1,6 @@ global function GamemodeAITdm_Init -global function RateSpawnpoints_Frontline void function GamemodeAITdm_Init() { -} - -void function RateSpawnpoints_Frontline(int _0, array<entity> _1, int _2, entity _3) -{ - }
\ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut index 8d0545cb..326f3302 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_gamemode_mfd.nut @@ -1,9 +1,11 @@ untyped global function GamemodeMfd_Init +global function RateSpawnpoints_Frontline struct { entity imcLastMark entity militiaLastMark + bool isMfdPro } file void function GamemodeMfd_Init() @@ -11,6 +13,16 @@ void function GamemodeMfd_Init() GamemodeMfdShared_Init() RegisterSignal( "MarkKilled" ) + + // todo + if ( GAMETYPE == MARKED_FOR_DEATH_PRO ) + { + file.isMfdPro = true + SetRespawnsEnabled( true ) + SetRoundBased( true ) + SetShouldUseRoundWinningKillReplay( true ) + Riff_ForceSetEliminationMode( eEliminationMode.Pilots ) + } AddCallback_OnPlayerKilled( UpdateMarksForKill ) AddCallback_GameStateEnter( eGameState.Playing, CreateInitialMarks ) @@ -170,63 +182,57 @@ void function UpdateMarksForKill( entity victim, entity attacker, var damageInfo } } -/* -void function MarkPlayers() +// could probably put this in spawn.nut? only here because i believe it's the main mode that uses this func +void function RateSpawnpoints_Frontline( int checkClass, array<entity> spawnpoints, int team, entity player ) { - // todo: need to handle disconnecting marks - if ( !TargetsMarkedImmediately() ) - wait MFD_BETWEEN_MARKS_TIME - + Frontline frontline = GetFrontline( player.GetTeam() ) - // wait until we actually have 2 valid players - array<entity> imcPlayers - array<entity> militiaPlayers - while ( imcPlayers.len() == 0 || militiaPlayers.len() == 0 ) - { - imcPlayers = GetPlayerArrayOfTeam( TEAM_IMC ) - militiaPlayers = GetPlayerArrayOfTeam( TEAM_MILITIA ) - - WaitFrame() - } + // heavily based on ctf spawn algo iteration 4, only changes it at the end + array<entity> startSpawns = SpawnPoints_GetPilotStart( team ) + array<entity> enemyStartSpawns = SpawnPoints_GetPilotStart( GetOtherTeam( team ) ) + array<entity> enemyPlayers = GetPlayerArrayOfTeam_Alive( team ) - // decide marks - entity imcMark = imcPlayers[ RandomInt( imcPlayers.len() ) ] - level.mfdPendingMarkedPlayerEnt[ TEAM_IMC ].SetOwner( imcMark ) + // get average startspawn position and max dist between spawns + // could probably cache this, tbh, not like it should change outside of halftimes + vector averageFriendlySpawns + float maxFriendlySpawnDist - entity militiaMark = militiaPlayers[ RandomInt( militiaPlayers.len() ) ] - level.mfdPendingMarkedPlayerEnt[ TEAM_MILITIA ].SetOwner( militiaMark ) - - foreach ( entity player in GetPlayerArray() ) + foreach ( entity spawn in startSpawns ) { - Remote_CallFunction_NonReplay( player, "SCB_MarkedChanged" ) - Remote_CallFunction_NonReplay( player, "ServerCallback_MFD_StartNewMarkCountdown", Time() + MFD_COUNTDOWN_TIME ) - } + foreach ( entity otherSpawn in startSpawns ) + { + float dist = Distance2D( spawn.GetOrigin(), otherSpawn.GetOrigin() ) + if ( dist > maxFriendlySpawnDist ) + maxFriendlySpawnDist = dist + } - wait MFD_COUNTDOWN_TIME + averageFriendlySpawns += spawn.GetOrigin() + } - while ( !IsAlive( imcMark ) || !IsAlive( militiaMark ) ) - WaitFrame() + averageFriendlySpawns /= startSpawns.len() - // clear pending marks - level.mfdPendingMarkedPlayerEnt[ TEAM_IMC ].SetOwner( null ) - level.mfdPendingMarkedPlayerEnt[ TEAM_MILITIA ].SetOwner( null ) + // get average enemy startspawn position + vector averageEnemySpawns - // set marks - level.mfdActiveMarkedPlayerEnt[ TEAM_IMC ].SetOwner( imcMark ) - level.mfdActiveMarkedPlayerEnt[ TEAM_MILITIA ].SetOwner( militiaMark ) + foreach ( entity spawn in enemyStartSpawns ) + averageEnemySpawns += spawn.GetOrigin() - foreach ( entity player in GetPlayerArray() ) - Remote_CallFunction_NonReplay( player, "SCB_MarkedChanged" ) + averageEnemySpawns /= enemyStartSpawns.len() + + // from here, rate spawns + float baseDistance = Distance2D( averageFriendlySpawns, averageEnemySpawns ) + float spawnIterations = ( baseDistance / maxFriendlySpawnDist ) / 2 - while ( IsAlive( imcMark ) && IsAlive( militiaMark ) ) - WaitFrame() + foreach ( entity spawn in spawnpoints ) + { + // ratings should max/min out at 100 / -100 + // start by prioritizing closer spawns, but not so much that enemies won't really affect them + float rating = 10 * ( 1.0 - Distance2D( averageFriendlySpawns, spawn.GetOrigin() ) / baseDistance ) - // clear marks - level.mfdActiveMarkedPlayerEnt[ TEAM_IMC ].SetOwner( null ) - level.mfdActiveMarkedPlayerEnt[ TEAM_MILITIA ].SetOwner( null ) + // rate based on distance to frontline, and then prefer spawns in the same dir from the frontline as the combatdir + rating += rating * ( 1.0 - ( Distance2D( spawn.GetOrigin(), frontline.friendlyCenter ) / baseDistance ) ) + rating *= fabs( frontline.combatDir.y - Normalize( spawn.GetOrigin() - averageFriendlySpawns ).y ) - foreach ( entity player in GetPlayerArray() ) - Remote_CallFunction_NonReplay( player, "SCB_MarkedChanged" ) - - thread MarkPlayers() -}*/
\ No newline at end of file + spawn.CalculateRating( checkClass, player.GetTeam(), rating, rating ) + } +}
\ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_riff_floor_is_lava.nut b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_riff_floor_is_lava.nut index b660e89f..f8aa7799 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_riff_floor_is_lava.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/gamemodes/_riff_floor_is_lava.nut @@ -1,5 +1,10 @@ global function RiffFloorIsLava_Init +struct { + float pilotDamageMultiplier + float titanDamageMultiplier +} file + void function RiffFloorIsLava_Init() { AddCallback_OnPlayerRespawned( FloorIsLava_PlayerRespawned ) @@ -83,20 +88,28 @@ void function FloorIsLava_ThinkForPlayer( entity player ) player.EndSignal( "OnDestroy" ) player.EndSignal( "OnDeath" ) + float lastHeight + while ( true ) { WaitFrame() - - if ( player.GetOrigin().z < GetLethalFogTop() ) + + float height = player.GetOrigin().z + if ( height < GetLethalFogTop() ) { // do damage float damageMultiplier = 0.08 if ( player.IsTitan() ) - damageMultiplier *= 0.05 - + damageMultiplier = 0.04 + + // scale damage by time spent in fog and depth + damageMultiplier *= 1 - ( height / GetLethalFogTop() ) + player.TakeDamage( player.GetMaxHealth() * damageMultiplier, null, null, { damageSourceId = eDamageSourceId.floor_is_lava } ) wait 0.1 } + + lastHeight = height } }
\ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_ai_mp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_ai_mp.gnut index 4cbab84c..87e3650f 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_ai_mp.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_ai_mp.gnut @@ -1,15 +1,22 @@ +untyped global function MpInitAILoadouts global function SetProficiency -global function IsAutoPopulateEnabled global function SPMP_UpdateNPCProficiency global function SPMP_Callback_ForceAIMissPlayer +global function IsAutoPopulateEnabled void function MpInitAILoadouts() { } -void function SetProficiency( entity soldier ) +void function SetProficiency( entity npc ) +{ + // unsure what the logic for deciding this should be so just going to default good + npc.kv.WeaponProfiency = eWeaponProficiency.VERYGOOD +} + +void function SPMP_UpdateNPCProficiency( entity npc ) { } @@ -30,12 +37,33 @@ bool function IsAutoPopulateEnabled( var team = null ) return true } -void function SPMP_UpdateNPCProficiency( entity ent ) -{ - -} - bool function SPMP_Callback_ForceAIMissPlayer( entity npc, entity player ) { - return true + if ( GetGameState() >= eGameState.Postmatch ) + return true + + if ( player.IsTitan() ) + return false + + int lethality = Riff_AILethality() + if ( lethality <= eAILethality.Medium ) + if ( player.GetTitanSoulBeingRodeoed() != null ) + return true + + if ( Bleedout_IsBleedoutLogicActive() && Bleedout_ShouldAIMissBleedingPlayer( player ) ) + return true + + if ( player.ContextAction_IsActive() ) + return RandomFloat( 1 ) >= 0.25 + + if ( IsFastPilot( player ) ) + { + float chance = ( lethality + 1 ) * 0.125 + if ( lethality <= eAILethality.Medium && npc.IsMechanical() ) + chance /= 1.25 + + return RandomFloat( 1 ) >= chance + } + + return false }
\ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut index cdefd8c8..70fa148e 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_base_gametype_mp.gnut @@ -423,9 +423,7 @@ void function RespawnAsTitan( entity player, bool manualPosition = false ) asset model = GetPlayerSettingsAssetForClassName( titanLoadout.setFile, "bodymodel" ) Attachment warpAttach = GetAttachmentAtTimeFromModel( model, "at_hotdrop_01", "offset", spawnpoint.GetOrigin(), spawnpoint.GetAngles(), 0 ) PlayFX( TURBO_WARP_FX, warpAttach.position, warpAttach.angle ) - - player.RespawnPlayer( null ) // spawn player as pilot so they get their pilot loadout on embark - + entity titan = CreateAutoTitanForPlayer_FromTitanLoadout( player, titanLoadout, spawnpoint.GetOrigin(), spawnpoint.GetAngles() ) DispatchSpawn( titan ) player.SetPetTitan( null ) // prevent embark prompt from showing up @@ -459,6 +457,10 @@ void function RespawnAsTitan( entity player, bool manualPosition = false ) player.DeployWeapon() // let them use weapons again player.isSpawning = false + player.RespawnPlayer( null ) // spawn player as pilot so they get their pilot loadout on embark + player.SetOrigin( titan.GetOrigin() ) + WaitFrame() + PilotBecomesTitan( player, titan ) // make player titan titan.Destroy() // pilotbecomestitan leaves an npc titan that we need to delete } diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_changemap.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_changemap.nut index 94fda4d7..a2e95e08 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_changemap.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_changemap.nut @@ -46,8 +46,8 @@ void function PopulatePostgameData() player.SetPersistentVar( "postGameData.myTeam", player.GetTeam() ) player.SetPersistentVar( "postGameData.myXuid", player.GetUID() ) - player.SetPersistentVar( "postGameData.gameMode", PersistenceGetEnumIndexForItemName( "gamemodes", GAMETYPE ) ) - player.SetPersistentVar( "postGameData.map", PersistenceGetEnumIndexForItemName( "maps", GetMapName() ) ) + player.SetPersistentVar( "postGameData.gameMode", enumModeIndex ) + player.SetPersistentVar( "postGameData.map", enumMapIndex ) player.SetPersistentVar( "postGameData.teams", standardTeams ) player.SetPersistentVar( "postGameData.maxTeamSize", teams ) player.SetPersistentVar( "postGameData.privateMatch", true ) diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut index e3f7e0b0..4263eb1a 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/_gamestate_mp.nut @@ -314,6 +314,20 @@ void function GameStateEnter_WinnerDetermined_Threaded() if ( killcamsWereEnabled ) SetKillcamsEnabled( true ) } + else + { + // these numbers are temp and should really be based on consts of some kind + foreach( entity player in GetPlayerArray() ) + { + player.FreezeControlsOnServer() + ScreenFadeToBlackForever( player, 2.0 ) + } + + wait 2.5 + + foreach( entity player in GetPlayerArray() ) + player.UnfreezeControlsOnServer() + } if ( IsRoundBased() ) { diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/_pickups.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/_pickups.gnut deleted file mode 100644 index ecf9b3e5..00000000 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/_pickups.gnut +++ /dev/null @@ -1,1195 +0,0 @@ -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) -} - - - - - - - - - diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/_lf_maps_shared.gnut b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/_lf_maps_shared.gnut index 178b6560..695f096d 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/_lf_maps_shared.gnut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/levels/_lf_maps_shared.gnut @@ -5,4 +5,5 @@ void function SetupLiveFireMaps() { Riff_ForceTitanAvailability( eTitanAvailability.Never ) ClassicMP_SetLevelIntro( ClassicMP_DefaultNoIntro_Setup, ClassicMP_DefaultNoIntro_GetLength() ) + ClassicMP_ForceDisableEpilogue( true ) }
\ No newline at end of file diff --git a/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut b/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut index 61fabb3c..b72e660e 100644 --- a/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut +++ b/Northstar.CustomServers/mod/scripts/vscripts/mp/spawn.nut @@ -3,7 +3,6 @@ untyped global function InitRatings // temp for testing global function Spawn_Init -global function SetSpawnsUseFrontline global function SetRespawnsEnabled global function RespawnsEnabled global function SetSpawnpointGamemodeOverride @@ -11,7 +10,6 @@ global function GetSpawnpointGamemodeOverride global function CreateNoSpawnArea global function DeleteNoSpawnArea -global function GetCurrentFrontline global function FindSpawnPoint global function RateSpawnpoints_Generic @@ -32,11 +30,7 @@ struct { table<string, NoSpawnArea> noSpawnAreas - bool frontlineBased = false - float lastImcFrontlineRatingTime - float lastMilitiaFrontlineRatingTime - Frontline& lastImcFrontline - Frontline& lastMilitiaFrontline + array<vector> preferSpawnNodes } file void function Spawn_Init() @@ -45,6 +39,25 @@ void function Spawn_Init() AddSpawnCallback( "info_spawnpoint_human_start", InitSpawnpoint ) AddSpawnCallback( "info_spawnpoint_titan", InitSpawnpoint ) AddSpawnCallback( "info_spawnpoint_titan_start", InitSpawnpoint ) + + AddCallback_EntitiesDidLoad( InitPreferSpawnNodes ) +} + +void function InitPreferSpawnNodes() +{ + foreach ( entity hardpoint in GetEntArrayByClass_Expensive( "info_hardpoint" ) ) + { + if ( !hardpoint.HasKey( "hardpointGroup" ) ) + continue + + if ( hardpoint.kv.hardpointGroup != "A" && hardpoint.kv.hardpointGroup != "B" && hardpoint.kv.hardpointGroup != "C" ) + continue + + file.preferSpawnNodes.append( hardpoint.GetOrigin() ) + } + + //foreach ( entity frontline in GetEntArrayByClass_Expensive( "info_frontline" ) ) + // file.preferSpawnNodes.append( frontline.GetOrigin() ) } void function InitSpawnpoint( entity spawnpoint ) @@ -106,63 +119,10 @@ string function GetSpawnpointGamemodeOverride() unreachable } -void function SetSpawnsUseFrontline( bool useFrontline ) -{ - file.frontlineBased = useFrontline -} - -bool function InitRatings( entity player, int team ) +void function InitRatings( entity player, int team ) { - Frontline frontline = GetCurrentFrontline( team ) - print( team ) - print( frontline.friendlyCenter ) - - vector offsetOrigin = frontline.friendlyCenter + frontline.combatDir * 256 - SpawnPoints_InitFrontlineData( offsetOrigin, frontline.combatDir, frontline.line, frontline.friendlyCenter, 2.0 ) // temp - if ( player != null ) SpawnPoints_InitRatings( player, team ) // no idea what the second arg supposed to be lol - - return frontline.friendlyCenter == < 0, 0, 0 > && file.frontlineBased // if true, use startspawns -} - -// this sucks -Frontline function GetCurrentFrontline( int team ) -{ - float lastFrontlineRatingTime - Frontline lastFrontline - if ( team == TEAM_IMC ) - { - lastFrontline = file.lastImcFrontline - lastFrontlineRatingTime = file.lastImcFrontlineRatingTime - } - else - { - lastFrontline = file.lastMilitiaFrontline - lastFrontlineRatingTime = file.lastMilitiaFrontlineRatingTime - } - - // current frontline is old, get a new one - if ( lastFrontlineRatingTime + 20.0 < Time() || lastFrontline.friendlyCenter == < 0, 0, 0 > ) - { - print( "rerating frontline..." ) - Frontline frontline = GetFrontline( team ) - - if ( team == TEAM_IMC ) - { - file.lastImcFrontlineRatingTime = Time() - file.lastImcFrontline = frontline - } - else - { - file.lastMilitiaFrontlineRatingTime = Time() - file.lastMilitiaFrontline = frontline - } - - lastFrontline = frontline - } - - return lastFrontline } entity function FindSpawnPoint( entity player, bool isTitan, bool useStartSpawnpoint ) @@ -171,15 +131,14 @@ entity function FindSpawnPoint( entity player, bool isTitan, bool useStartSpawnp if ( HasSwitchedSides() ) team = GetOtherTeam( team ) - useStartSpawnpoint = InitRatings( player, player.GetTeam() ) || useStartSpawnpoint // force startspawns if no frontline - print( "useStartSpawnpoint: " + useStartSpawnpoint ) - array<entity> spawnpoints if ( useStartSpawnpoint ) spawnpoints = isTitan ? SpawnPoints_GetTitanStart( team ) : SpawnPoints_GetPilotStart( team ) else spawnpoints = isTitan ? SpawnPoints_GetTitan() : SpawnPoints_GetPilot() + InitRatings( player, team ) + void functionref( int, array<entity>, int, entity ) ratingFunc = isTitan ? GameMode_GetTitanSpawnpointsRatingFunc( GAMETYPE ) : GameMode_GetPilotSpawnpointsRatingFunc( GAMETYPE ) ratingFunc( isTitan ? TD_TITAN : TD_PILOT, spawnpoints, team, player ) @@ -238,6 +197,19 @@ entity function GetBestSpawnpoint( entity player, array<entity> spawnpoints ) } } + // last resort + if ( validSpawns.len() == 0 ) + { + print( "map has literally 0 spawnpoints, as such everything is fucked probably, attempting to use info_player_start if present" ) + entity start = GetEnt( "info_player_start" ) + + if ( IsValid( start ) ) + { + start.s.lastUsedTime <- -999 + validSpawns.append( start ) + } + } + return validSpawns[ RandomInt( validSpawns.len() ) ] // slightly randomize it } @@ -268,6 +240,9 @@ bool function IsSpawnpointValid( entity spawnpoint, int team ) if ( spawnpoint.IsOccupied() ) return false + if ( Time() - spawnpoint.s.lastUsedTime <= 1.0 ) + return false + foreach ( k, NoSpawnArea noSpawnArea in file.noSpawnAreas ) { if ( Distance( noSpawnArea.position, spawnpoint.GetOrigin() ) > noSpawnArea.radius ) @@ -309,9 +284,6 @@ bool function IsSpawnpointValid( entity spawnpoint, int team ) if ( TraceLineSimple( enemyPlayer.EyePosition(), spawnpoint.GetOrigin() + < 0, 0, 48 >, enemyPlayer ) == 1.0 ) return false } - - if ( Time() - spawnpoint.s.lastUsedTime <= 1.0 ) - return false return true } @@ -323,4 +295,86 @@ void function RateSpawnpoints_Generic( int checkClass, array<entity> spawnpoints // realistically, this should spawn people fairly spread out across the map, preferring being closer to their startspawns // should spawn like, near fights, but not in them + + + // using old func for tests rq + // calculate ratings for preferred nodes + // this tries to prefer nodes with more teammates, then activity on them + // todo: in the future it might be good to have this prefer nodes with enemies up to a limit of some sort + // especially in ffa modes i could deffo see this falling apart a bit rn + // perhaps dead players could be used to calculate some sort of activity rating? so high-activity points with an even balance of friendly/unfriendly players are preferred + array<float> preferSpawnNodeRatings + foreach ( vector preferSpawnNode in file.preferSpawnNodes ) + { + float currentRating + + // this seems weird, not using rn + //Frontline currentFrontline = GetCurrentFrontline( team ) + //if ( !IsFFAGame() || currentFrontline.friendlyCenter != < 0, 0, 0 > ) + // currentRating += max( 0.0, ( 1000.0 - Distance2D( currentFrontline.origin, preferSpawnNode ) ) / 200 ) + + foreach ( entity nodePlayer in GetPlayerArray() ) + { + float currentChange = 0.0 + + // the closer a player is to a node the more they matter + float dist = Distance2D( preferSpawnNode, nodePlayer.GetOrigin() ) + if ( dist > 600.0 ) + continue + + currentChange = ( 600.0 - dist ) / 5 + if ( player == nodePlayer ) + currentChange *= -3 // always try to stay away from places we've already spawned + else if ( !IsAlive( nodePlayer ) ) // dead players mean activity which is good, but they're also dead so they don't matter as much as living ones + currentChange *= 0.6 + if ( nodePlayer.GetTeam() != player.GetTeam() ) // if someone isn't on our team and alive they're probably bad + { + if ( IsFFAGame() ) // in ffa everyone is on different teams, so this isn't such a big deal + currentChange *= -0.2 + else + currentChange *= -0.6 + } + + currentRating += currentChange + } + + preferSpawnNodeRatings.append( currentRating ) + } + + foreach ( entity spawnpoint in spawnpoints ) + { + float currentRating + float petTitanModifier + // scale how much a given spawnpoint matters to us based on how far it is from each node + bool spawnHasRecievedInitialBonus = false + for ( int i = 0; i < file.preferSpawnNodes.len(); i++ ) + { + // bonus if autotitan is nearish + if ( IsAlive( player.GetPetTitan() ) && Distance( player.GetPetTitan().GetOrigin(), file.preferSpawnNodes[ i ] ) < 1200.0 ) + petTitanModifier += 10.0 + + float dist = Distance2D( spawnpoint.GetOrigin(), file.preferSpawnNodes[ i ] ) + if ( dist > 750.0 ) + continue + + if ( dist < 600.0 && !spawnHasRecievedInitialBonus ) + { + currentRating += 10.0 + spawnHasRecievedInitialBonus = true // should only get a bonus for simply being by a node once to avoid over-rating + } + + currentRating += ( preferSpawnNodeRatings[ i ] * ( ( 750.0 - dist ) / 75 ) ) + max( RandomFloat( 1.25 ), 0.9 ) + if ( dist < 250.0 ) // shouldn't get TOO close to an active node + currentRating *= 0.7 + + if ( spawnpoint.s.lastUsedTime < 10.0 ) + currentRating *= 0.7 + } + + float rating = spawnpoint.CalculateRating( checkClass, team, currentRating, currentRating + petTitanModifier ) + //print( "spawnpoint at " + spawnpoint.GetOrigin() + " has rating: " + ) + + if ( rating != 0.0 || currentRating != 0.0 ) + print( "rating = " + rating + ", internal rating = " + currentRating ) + } }
\ No newline at end of file |