const RESPAWN_TRIG_RECHECK_WAIT = 1 const DEFAULT_SP_ARRIVAL_TOLERANCE = 200 const DEFAULT_SP_ASSAULT_TIMEOUT = 0 // move these to auto precache global const TEAM_MIL_GRUNT_MODEL = $"models/humans/grunts/mlt_grunt_rifle.mdl" global const TEAM_MIL_GRUNT_MODEL_LMG = $"models/humans/grunts/mlt_grunt_lmg.mdl" global const TEAM_MIL_GRUNT_MODEL_RIFLE = $"models/humans/grunts/mlt_grunt_rifle.mdl" global const TEAM_MIL_GRUNT_MODEL_ROCKET = $"models/humans/grunts/mlt_grunt_rifle.mdl" global const TEAM_MIL_GRUNT_MODEL_SHOTGUN = $"models/humans/grunts/mlt_grunt_shotgun.mdl" global const TEAM_MIL_GRUNT_MODEL_SMG = $"models/humans/grunts/mlt_grunt_smg.mdl" global function GamemodeSp_Init global function GiveBatteryChargeTool global function HasBatteryChargeTool global function FriendlyFire_MissionFailure global function DamageAlwaysLethal global function ServerRestartMission global function DisableFriendlyHighlight global function EnableFriendlyHighlight global function SP_PlayerLastSlideTime //global function CodeCallback_Ping global function SetGameState global function GetGameModeAnnouncement global function CodeCallback_GamerulesThink global struct SvGlobalsSP { int gruntCombatState = eGruntCombatState.IDLE array onLoadSaveGameCallbacks } global SvGlobalsSP svGlobalSP ////////////////////////////// global function DEV_ToggleFriendlyHighlight ////////////////////////////// struct { LevelTransitionStruct ornull storedLevelTransitionStruct entity spPetTitanStart bool friendlyHighlightEnabled = true float playerLastSlideTime } file void function GamemodeSp_Init() { FlagInit( "FriendlyFireStrict" ) FlagInit( "TitanCanSavePlayer" ) FlagInit( "TitanDeathPenalityDisabled" ) FlagInit( "SaveGame_Enabled", true ) FlagInit( "MissionFailed" ) RegisterSignal( "TitanSavesPlayer" ) FlagSet( "PilotBot" ) AddSoulDeathCallback( SoulDeath_ReloadOnPetTitanDeath ) AddClientCommandCallback( "RestartMission", ClientCommand_RestartMission ) SetRoundBased( false ) AddCallback_OnClientConnecting( SpPlayerConnecting ) AddCallback_OnClientConnected( SpPlayerConnected ) AddSpawnCallback( "npc_soldier", SpNpcCommon ) AddSpawnCallback( "npc_spectre", SpNpcCommon ) AddSpawnCallback( "npc_titan", SpNpcCommon ) AddSpawnCallback( "npc_soldier", SpNpcCommonGrunt ) AddSpawnCallbackEditorClass( "info_target", "info_pet_titan_start", PetTitanStartSpawnInit ) AddCallback_OnPlayerRespawned( GameStateSP_OnPlayerRespawn ) AddDamageCallbackSourceID( eDamageSourceId.damagedef_titan_fall, PreventFriendlyTitanfallDamage ) AddDamageCallbackSourceID( eDamageSourceId.damagedef_reaper_fall, PreventFriendlyTitanfallDamage ) shGlobal.proto_soldierShieldRegenDelay = 3000.0 svGlobal.cloakBreaksOnMelee = true svGlobal.defaultPilotLeechTime = 2.0 //AddDamageCallback( "player", DiminishPlayerComboDamage ) SPTitanLoadout_SetupForLevelStart() SpSharedInit() AddCallback_EntitiesDidLoad( EntitiesDidLoad_SpGameState ) AddDeathCallback( "npc_soldier", SoldierFriendlyFireCheck_OnKilled ) AddCallback_OnPilotBecomesTitan( PilotBecomesTitanStoreWeaponVar ) // SAVE THIS LEVEL IF IT'S IN THE LEVEL LIST var dataTable = GetDataTable( $"datatable/sp_levels.rpak" ) string mapName = GetMapName() string startPoint = GetStartPoint() int numRows = GetDatatableRowCount( dataTable ) int lastLevelNum = 0 int bspCol = GetDataTableColumnByName( dataTable, "level" ) int levelCol = GetDataTableColumnByName( dataTable, "levelNum" ) for ( int i=0; i 1 ) { CodeWarning( "Can't play SP with more or less than 1 player" ) return } SetTeam( player, TEAM_MILITIA ) InitPassives( player ) player.SetInventoryChangedCallbackEnabled( true ) entity ornull spawningOn = GetPlayerToSpawnOn() if ( spawningOn == null ) { // vanilla spawning logic - spawn first player at the level's spawnpoint entity start = GetEnt( "info_player_start" ) player.SetOrigin( start.GetOrigin() ) player.SetAngles( start.GetAngles() ) } else { expect entity( spawningOn ) // use safe func otherwise we can get stuck in the ceiling and shit thread TeleportToEntitySafe( player, spawningOn ) } if ( IsValid( file.spPetTitanStart ) ) { CreatePetTitan( player ) entity titan = player.GetPetTitan() if ( titan != null ) titan.kv.alwaysAlert = false } } void function GameStateSP_OnPlayerRespawn( entity player ) { UpdateSpDifficulty( player ) thread TrackPlayerLastSlideTime( player ) if ( !IsTestMap() ) { CheckPoint_ForcedSilent() } } void function SpPlayerConnected( entity player ) { thread SpPlayerConnected_Thread( player ) } void function SpPlayerConnected_Thread( entity player ) { player.EndSignal( "OnDestroy" ) if ( IsTestMap() ) return string levelName = GetMapName() int startIndex = GetStartPointIndexFromName( levelName, GetCurrentStartPoint() ) string startPointEnum = GetStartPointNameFromIndex( levelName, startIndex ) if ( !StartPointHasDetente( levelName, startPointEnum ) ) return var dataTable = GetDataTable( $"datatable/sp_introscreen.rpak" ) int row = GetDataTableRowMatchingStringValue( dataTable, GetDataTableColumnByName( dataTable, "level" ), levelName ) if ( row == -1 ) return float delay = GetDataTableFloat( dataTable, row, GetDataTableColumnByName( dataTable, "bgFadeDelay" ) ) float fadeTime = GetDataTableFloat( dataTable, row, GetDataTableColumnByName( dataTable, "bgFadeTime" ) ) printt( "Detent fade in " + delay + " blend " + fadeTime ) WaitFrame() WaitFrame() // wait two frames to fix ScreenFade bug ScreenFade( player, 0, 0, 0, 255, fadeTime, delay, FFADE_IN | FFADE_PURGE ) //printt( "sv SCREENFADE: " + fadeTime + " " + delay ) } float function SP_PlayerLastSlideTime() { return file.playerLastSlideTime } void function PetTitanStartSpawnInit( entity spawn ) { file.spPetTitanStart = spawn } void function TrackPlayerLastSlideTime( entity player ) { player.EndSignal( "OnDeath" ) for ( ;; ) { if ( player.IsSliding() ) file.playerLastSlideTime = Time() wait 0.5 } } void function CreatePetTitan( entity player ) { Assert( IsValid( file.spPetTitanStart ) ) // Player is in his Titan, don't create a titan if ( player.IsTitan() ) return // Player already has a pet titan, don't create one entity petTitan = player.GetPetTitan() if ( IsValid( petTitan ) ) return // Make a pet titan at the spawn point TitanLoadoutDef loadout = GetTitanLoadoutForCurrentMap() entity titanStart = file.spPetTitanStart entity titan = CreateAutoTitanForPlayer_FromTitanLoadout( player, loadout, titanStart.GetOrigin(), titanStart.GetAngles() ) DispatchSpawn( titan ) player.SetPetTitan( titan ) titan.DisableHibernation() } /************************************************************************************************\ ######## ### ## ## ### ###### ######## ## ## ## ## ### ### ## ## ## ## ## ## ## ## ## #### #### ## ## ## ## ## ## ## ## ## #### ## ## ## ## ### ###### ## ## ######### ## ## ## ######### ## ## ## ## ## ## ## ## ## ## ## ## ## ## ######## ## ## ## ## ## ## ###### ######## \************************************************************************************************/ // putting this stuff here for now since it's only for SP at this point bool function TryTitanSavesPlayer( entity player ) { if ( !Flag( "TitanCanSavePlayer" ) ) return false if ( !IsAlive( player.GetPetTitan() ) ) return false entity titan = player.GetPetTitan() if ( TitanIsCurrentlyEmbarkableForPlayer( player, titan ) ) { thread TitanSavesPlayer( player, titan ) return true } return false } void function TitanSavesPlayer( entity player, entity titan ) { player.EndSignal( "OnDestroy" ) titan.EndSignal( "OnDeath" ) player.SetNoTarget( true ) player.SetInvulnerable() titan.SetInvulnerable() player.DisableWeapon() player.FreezeControlsOnServer() player.ForceCrouch() OnThreadEnd( function() : ( player, titan ) { if ( IsValid( player ) ) { if ( !IsPlayerEmbarking( player ) ) { player.EnableWeapon() } player.UnforceCrouch() player.UnfreezeControlsOnServer() player.ClearInvulnerable() player.SetNoTarget( false ) } if ( IsValid( titan ) ) titan.ClearInvulnerable() } ) wait 0.25 float fadeTime = 2 float blackTime_pre = 4.0 float blackTime_post = 1.0 thread ScreenFadeToBlack( player, 2.0, blackTime_pre ) wait blackTime_pre * 0.5 string titanAlias = GenerateTitanOSAlias( player, "briefCriticalDamage" ) thread EmitSoundOnEntity( player, titanAlias ) wait blackTime_pre * 0.5 vector ornull clampedPos = NavMesh_ClampPointForAI( player.GetOrigin(), titan ) if ( clampedPos != null ) titan.SetOrigin( expect vector( clampedPos ) ) titan.SetAngles( Vector( 0, player.GetAngles().y, 0 ) ) entity soul = titan.GetTitanSoul() SetStanceKneel( soul ) table criteria = { embark = "front", titanCanStandRequired = false } var embarkAction embarkAction = FindEmbarkActionForCriteria( criteria ) if ( embarkAction == null ) embarkAction = GetRandomEmbarkAction() table action = expect table( GenerateEmbarkActionTable( player, titan, embarkAction ) ) thread PlayerEmbarksTitan( player, titan, action ) thread ScreenFadeFromBlack( player, 12.0, blackTime_post ) player.Signal( "TitanSavesPlayer" ) } bool function DamageAlwaysLethal( var damageInfo ) { const damageMask = DF_INSTANT | DF_IMPACT | DF_TITAN_STEP | DF_KILLSHOT | DF_MELEE // | DF_EXPLOSION if ( DamageInfo_GetCustomDamageType( damageInfo ) & damageMask ) return true // not quite sure what this returns. It doesn't seem to be the flags listed in death_package.nut // if ( DamageInfo_GetDamageType( damageInfo ) & damageMask ) // return false // damage type doesn't seem to be correct for most things so I'm forced to check GetDamageSourceIdentifier -Roger switch ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) ) { case damagedef_crush: case damagedef_nuclear_core: case damagedef_suicide: case damagedef_titan_fall: case damagedef_titan_hotdrop: case eDamageSourceId.bubble_shield: case eDamageSourceId.burn: case eDamageSourceId.droppod_impact: case eDamageSourceId.evac_dropship_explosion: case eDamageSourceId.fall: case eDamageSourceId.flash_surge: case eDamageSourceId.floor_is_lava: case eDamageSourceId.human_execution: case eDamageSourceId.human_melee: case eDamageSourceId.indoor_inferno: case eDamageSourceId.outOfBounds: case eDamageSourceId.round_end: case eDamageSourceId.splat: case eDamageSourceId.stuck: case eDamageSourceId.submerged: case eDamageSourceId.switchback_trap: case eDamageSourceId.team_switch: case eDamageSourceId.titan_execution: case eDamageSourceId.titan_explosion: case eDamageSourceId.wall_smash: // case eDamageSourceId.grunt_melee: // case eDamageSourceId.prowler_melee: // case eDamageSourceId.spectre_melee: return true default: break } return false } /************************************************************************************************\ ## ## ######## ###### ### ## ## ## ## ## #### ## ## ## ## ## ## ## ######## ## ## #### ## ## ## ### ## ## ## ## ## ## ###### \************************************************************************************************/ void function SpNpcCommon( entity npc ) { if ( npc.GetTeam() == TEAM_MILITIA ) { if ( !npc.IsTitan() ) HideName( npc ) // this is a temporary stop gap until we get skins if ( file.friendlyHighlightEnabled ) Highlight_SetFriendlyHighlight( npc, "sp_friendly_pilot" ) } } void function SpNpcCommonGrunt( entity npc ) { // heros clear this setting so they should keep their names if ( npc.GetTeam() == TEAM_MILITIA ) { string title if ( npc.Dev_GetAISettingByKeyField( "IsGenericGrunt" ) == 0 ) { title = npc.GetSettingTitle() } else { title = GetMilitiaTitle() } npc.SetTitle( title ) ShowName( npc ) } } void function DiminishPlayerComboDamage( entity player, var damageInfo ) { if ( !IsPilot( player ) ) return // printt( "damage " + DamageInfo_GetDamage( damageInfo ) ) // printt( "shield health " + player.GetShieldHealth() ) if ( player.GetShieldHealth() > 0 ) return // blunt damage from combos float damage = DamageInfo_GetDamage( damageInfo ) float recentDamage = TotalDamageOverTime_BlendedOut( player, 0.5, 1.5 ) // damage is ramped down based on how much damage was taken recently float damageMod = GraphCapped( recentDamage, 70, 140, 1.0, 0.1 ) DamageInfo_ScaleDamage( damageInfo, damageMod ) } void function SoulDeath_ReloadOnPetTitanDeath( entity soul, var damageInfo ) { if ( Flag( "TitanDeathPenalityDisabled" ) ) return if ( IsTestMap() ) return thread SoulDeath_ReloadOnPetTitanDeath_Thread( soul, damageInfo ) } void function SoldierFriendlyFireCheck_OnKilled( entity npc, var damageInfo ) { if ( !Flag( "FriendlyFireStrict" ) ) return entity attacker = DamageInfo_GetAttacker( damageInfo ) if ( !attacker.IsPlayer() ) return if ( npc.GetTeam() != attacker.GetTeam() ) return thread SoldierFriendlyFireCheck_OnKilledDelayed() } void function SoldierFriendlyFireCheck_OnKilledDelayed() { FlagClear( "SaveGame_Enabled" ) // no more saving, you have lost wait 0.75 FriendlyFire_MissionFailure() } void function FriendlyFire_MissionFailure() { if ( Flag( "MissionFailed" ) ) return foreach ( player in GetPlayerArray() ) { Remote_CallFunction_NonReplay( player, "ServerCallback_FriendlyFire_MissionFailure" ) } ReloadForMissionFailure() } void function SoulDeath_ReloadOnPetTitanDeath_Thread( entity soul, var damageInfo ) { entity player = soul.GetBossPlayer() if ( !IsValid( player ) ) return if ( !level.nv.replayDisabled ) { if ( player.p.watchingPetTitanKillReplay ) return player.p.watchingPetTitanKillReplay = true } if ( damageInfo == null ) { printt( "ServerCallback_TitanDied with null sourceid" ) ReloadForMissionFailure() return } int source = DamageInfo_GetDamageSourceIdentifier( damageInfo ) Remote_CallFunction_NonReplay( player, "ServerCallback_TitanDied", source ) #if DEV printt( "ServerCallback_TitanDied with sourceid " + source + " and source string " + GetObitFromDamageSourceID( source ) ) #endif if ( IsInstantDeath( damageInfo ) ) { ReloadForMissionFailure() return } entity attacker = DamageInfo_GetAttacker( damageInfo ) if ( !IsValid( attacker ) ) { ReloadForMissionFailure() return } int index = attacker.GetIndexForEntity() entity titan = soul.GetTitan() FlagClear( "SaveGame_Enabled" ) // no more saving, you have lost if ( !level.nv.replayDisabled ) { /* wait 3.8 float replayDelay = 6.5 player.SetKillReplayDelay( replayDelay ) player.SetViewIndex( index ) if ( IsValid( titan ) ) player.SetKillReplayVictim( titan ) ReloadForMissionFailure( replayDelay + 2.5 ) */ } else { ReloadForMissionFailure() // replayDelay + 2.5 } } bool function HasBatteryChargeTool( entity player ) { switch ( CHARGE_TOOL ) { case "sp_weapon_proto_battery_charger_offhand": return HasOffhandWeapon( player, CHARGE_TOOL ) case "sp_weapon_arc_tool": return HasWeapon( player, CHARGE_TOOL ) } unreachable } void function GiveBatteryChargeTool( entity player ) { switch ( CHARGE_TOOL ) { case "sp_weapon_proto_battery_charger_offhand": player.GiveOffhandWeapon( CHARGE_TOOL, OFFHAND_SPECIAL ) break case "sp_weapon_arc_tool": player.GiveWeapon( CHARGE_TOOL ) break } UpdateArcConnectorHints() } /* void function CodeCallback_Ping( entity player ) { } */ /* ---------------------------------------------------------------------------------------------- STUFF FORM MP THAT WE WILL SLOWLY GET RID OF -------------------------------------------------------------------------------------------------*/ string function GetGameModeAnnouncement() { return "" } void function SetGameState( int newState ) { level.nv.gameStartTime = Time() level.nv.gameStateChangeTime = Time() level.nv.gameState = newState svGlobal.levelEnt.Signal( "GameStateChanged" ) foreach ( callbackFunc in svGlobal.gameStateEnterCallbacks[ newState ] ) { callbackFunc() } } void function CodeCallback_GamerulesThink() { } void function DEV_ToggleFriendlyHighlight() { file.friendlyHighlightEnabled = !file.friendlyHighlightEnabled UpdateFriendlyHighlight() } void function DisableFriendlyHighlight() { file.friendlyHighlightEnabled = false UpdateFriendlyHighlight() } void function EnableFriendlyHighlight() { file.friendlyHighlightEnabled = true UpdateFriendlyHighlight() } void function UpdateFriendlyHighlight() { if ( file.friendlyHighlightEnabled ) { foreach ( npc in GetNPCArrayOfTeam( TEAM_MILITIA ) ) Highlight_SetFriendlyHighlight( npc, "sp_friendly_pilot" ) } else { foreach ( npc in GetNPCArrayOfTeam( TEAM_MILITIA ) ) Highlight_ClearFriendlyHighlight( npc ) } } bool function ClientCommand_RestartMission( entity player, array args ) { ServerRestartMission( player ) return true } void function ServerRestartMission( entity player ) { if ( IsTestMap() ) { ServerCommand( "reload" ) return } string mapName = GetMapName() LevelTransitionStruct ornull trans = file.storedLevelTransitionStruct if ( trans == null ) { int startIndex = 0 #if DEV string startName = Dev_GetStartCommandLine( mapName ) if ( startName != "" ) startIndex = GetStartPointIndexFromName( mapName, startName ) #endif // loaded a level manually in dev or are in sp_training ExecuteLoadingClientCommands_SetStartPoint( mapName, startIndex ) ClientCommand( player, "map " + mapName ) return } expect LevelTransitionStruct( trans ) ExecuteLoadingClientCommands_SetStartPoint( mapName, trans.startPointIndex ) ChangeLevel( mapName, trans ) } void function PilotBecomesTitanStoreWeaponVar( entity pilot, entity npc_titan ) { entity weapon = pilot.GetMainWeapons()[0] int index = GetTitanLoadoutIndex( weapon.GetWeaponClassName() ) SetConVarInt( "sp_titanLoadoutCurrent", index ) } void function PreventFriendlyTitanfallDamage( entity ent, var damageInfo ) { entity attacker = DamageInfo_GetAttacker( damageInfo ) if ( attacker.GetTeam() == ent.GetTeam() ) { DamageInfo_SetDamage( damageInfo, 0 ) } }