From 9a73ace1a1e5273fda96be7f8b643489295e3940 Mon Sep 17 00:00:00 2001 From: NachosChipeados <103285866+NachosChipeados@users.noreply.github.com> Date: Sat, 12 Oct 2024 21:14:35 -0400 Subject: [PATCH 1/6] Add files via upload --- .../scripts/vscripts/client/cl_player.gnut | 2742 +++++++++++++++++ .../scripts/vscripts/client/cl_sp_player.gnut | 1863 +++++++++++ 2 files changed, 4605 insertions(+) create mode 100644 Northstar.Client/mod/scripts/vscripts/client/cl_player.gnut create mode 100644 Northstar.Client/mod/scripts/vscripts/client/cl_sp_player.gnut diff --git a/Northstar.Client/mod/scripts/vscripts/client/cl_player.gnut b/Northstar.Client/mod/scripts/vscripts/client/cl_player.gnut new file mode 100644 index 000000000..6ebe65628 --- /dev/null +++ b/Northstar.Client/mod/scripts/vscripts/client/cl_player.gnut @@ -0,0 +1,2742 @@ +untyped + +global function ClPlayer_Init + +global function PlayIt +global function JumpRandomlyForever + +global function ClientCodeCallback_PlayerDidDamage +global function ClientCodeCallback_PlayerSpawned +//global function ClientCodeCallback_OnHudReloadScheme +global function ClientCodeCallback_HUDThink +global function Player_AddPlayer +global function Player_AddClient +global function PlayerConnectedOrDisconnected +global function ServerCallback_GameModeAnnouncement +global function MainHud_InitScoreBars + +global function ServerCallback_PlayerConnectedOrDisconnected +global function ClientCodeCallback_PlayerDisconnected +global function ServerCallback_PlayerChangedTeams +global function ClientCodeCallback_OnModelChanged +global function ServerCallback_RodeoerEjectWarning +global function ServerCallback_PlayScreenFXWarpJump +global function PlayShieldBreakEffect +global function PlayShieldActivateEffect +global function HandleDoomedState +global function RewardReadyMessage +global function TitanReadyMessage +global function CoreReadyMessage + +global function ServerCallback_RewardReadyMessage +global function ServerCallback_TitanReadyMessage + +global function OnClientPlayerAlive +global function OnClientPlayerDying +global function PlayPlayerDeathSound +global function StopPlayerDeathSound + +global function ServerCallback_ShowNextSpawnMessage +global function GetWaveSpawnTime +global function ServerCallback_HideNextSpawnMessage + +global function ClientCodeCallback_OnHealthChanged +global function ClientCodeCallback_OnCrosshairCurrentTargetChanged +global function Pressed_TitanNextMode +global function ClientCodeCallback_OnGib +global function ClientPilotSpawned +global function AddCallback_OnPlayerDisconnected + +global function IsPlayerEliminated + +global function ServerCallback_GiveMatchLossProtection +global function ServerCallback_OnEntityKilled +global function ServerCallback_OnTitanKilled + +global function ShouldShowSpawnAsTitanHint +global function ServerCallback_SetAssistInformation + +global function GetShieldEffectCurrentColor +global function ClientPlayerClassChanged + +#if DEV +global function BloodSprayDecals_Toggle +#endif + +const float DEFAULT_GAMEMODE_ANNOUNCEMENT_DURATION = 5.0 + +struct { + var orbitalstrike_tracer = null + var law_missile_tracer = null + float nextSpawnTime = 0.0 + entity lastEarnedReward // primarily used to check if we should still show the reward message after a delay +} file + +struct BloodDecalParams +{ + float traceDist + float secondaryTraceDist + asset fxType + asset secondaryFxType +} + +void function ClPlayer_Init() +{ + ClPilotJumpjet_Init() + ClDamageIndicator_Init() + + ClPlayer_Common_Precache() + + RegisterSignal( "OnAnimationDone" ) + RegisterSignal( "OnAnimationInterrupted" ) + RegisterSignal( "OnBleedingOut" ) + RegisterSignal( "PanelAlphaOverTime" ) + RegisterSignal( "LocalClientPlayerRespawned" ) + RegisterSignal( "OnClientPlayerAlive" ) + RegisterSignal( "OnClientPlayerDying" ) + RegisterSignal( "StopAlertCore" ) + RegisterSignal( "OnSpectatorMode" ) + RegisterSignal( "HealthChanged" ) + + FlagInit( "DamageDistancePrint" ) + FlagInit( "EnableTitanModeChange", true ) + FlagInit( "EnableBloodSprayDecals", true ) + + level.vduOpen <- false + level.canSpawnAsTitan <- false + level.grenadeIndicatorEnabled <- true + level.clientsLastKiller <- null + + AddCreateCallback( "player", SetupPlayerAnimEvents ) + AddCreateCallback( "player", MpClientPlayerInit ) + + AddCreateCallback( "first_person_proxy", SetupFirstPersonProxyEvents ) + AddCreateCallback( "predicted_first_person_proxy", SetupFirstPersonProxyEvents ) + + AddCreateCallback( "player", EnableDoDeathCallback ) + AddCreateCallback( "npc_titan", EnableDoDeathCallback ) + + AddCreateCallback( "titan_soul", CreateCallback_TitanSoul ) + + AddCallback_OnPlayerLifeStateChanged( PlayerADSDof ) + + file.orbitalstrike_tracer = PrecacheParticleSystem( $"Rocket_Smoke_Large" ) + //DEBUG Remove when bug is fixed. + file.law_missile_tracer = PrecacheParticleSystem( $"wpn_orbital_rocket_tracer" ) + + level.menuHideGroups <- {} + + level.spawnAsTitanSelected <- false + + AddPlayerFunc( Player_AddPlayer ) +} + +entity function FindEnemyRodeoParent( entity player ) +{ + entity ent = player.GetParent() + if ( ent == null ) + return null + + if ( !ent.IsTitan() ) + return null + + if ( ent == player.GetPetTitan() ) + return null + + if ( ent.GetTeam() == player.GetTeam() ) + return null + + return ent +} + +void function MpClientPlayerInit( entity player ) +{ + player.ClientCommand( "save_enable 0" ) +} + +void function ClientCodeCallback_PlayerSpawned( entity player ) +{ + if ( !IsValid( player ) ) + return + + if ( IsMenuLevel() ) + return + + ClearCrosshairPriority( crosshairPriorityLevel.ROUND_WINNING_KILL_REPLAY ) + + if ( !level.clientScriptInitialized ) + return + + // exists on server and client. Clear it when you respawn. + ClearRecentDamageHistory( player ) + DamageHistoryStruct blankDamageHistory + clGlobal.lastDamageHistory = blankDamageHistory + + if ( player == GetLocalViewPlayer() ) + { + foreach ( callbackFunc in clGlobal.onLocalViewPlayerSpawnedCallbacks ) + { + callbackFunc( player ) + } + } + + if ( player == GetLocalClientPlayer() ) + { + player.cv.lastSpawnTime = Time() + player.cv.roundSpawnCount++ + + foreach ( callbackFunc in clGlobal.onLocalClientPlayerSpawnedCallbacks ) + { + thread callbackFunc( player ) + } + } + + if ( player.IsTitan() ) + return + + if ( player.GetPlayerClass() == level.pilotClass ) + { + thread ClientPilotSpawned( player ) + } +} + + +void function ServerCallback_TitanReadyMessage() +{ + //thread TitanReadyMessage( 1.5, false ) //Delay was necessary at some point in time according to Brent, might no longer be true? + thread TitanReadyMessage( 0.0, false ) +} + + +void function ServerCallback_RewardReadyMessage( float timeSinceLastRespawn ) +{ + if ( timeSinceLastRespawn < 1.0 ) + thread RewardReadyMessage( 6.0, false ) + else + thread RewardReadyMessage( 0.0, false ) +} + + +void function RewardReadyMessage( float delay = 0.0, bool isQuick = false ) +{ + if ( delay > 0.0 ) + wait delay + + if ( !GamePlayingOrSuddenDeath() ) + return + + entity player = GetLocalClientPlayer() + if ( !IsAlive( player ) || IsSpectating() || IsWatchingKillReplay() ) + return + + if ( player.ContextAction_IsMeleeExecution() ) + return + + entity weapon = player.GetOffhandWeapon( OFFHAND_INVENTORY ) + if ( !IsValid( weapon ) ) + return + + file.lastEarnedReward = weapon + + if ( player.IsTitan() ) + { + EmitSoundOnEntity( player, "HUD_Boost_Card_Earned_1P" ) + + string rewardReadyMessage = expect string( GetWeaponInfoFileKeyField_WithMods_Global( weapon.GetWeaponClassName(), weapon.GetMods(), "readymessage" ) ) + string rewardReadyHint = expect string( GetWeaponInfoFileKeyField_WithMods_Global( weapon.GetWeaponClassName(), weapon.GetMods(), "readyhint" ) ) + asset rewardIcon = GetWeaponInfoFileKeyFieldAsset_WithMods_Global( weapon.GetWeaponClassName(), weapon.GetMods(), "hud_icon" ) + + AnnouncementData announcement = CreateAnnouncementMessageQuick( player, rewardReadyMessage, rewardReadyHint, <1, 0.5, 0>, rewardIcon ) + announcement.displayConditionCallback = LastEarnedRewardStillValid + AnnouncementFromClass( player, announcement ) + } + else + { + EmitSoundOnEntity( player, "HUD_Boost_Card_Earned_1P" ) + + string rewardReadyMessage = expect string( GetWeaponInfoFileKeyField_WithMods_Global( weapon.GetWeaponClassName(), weapon.GetMods(), "readymessage" ) ) + string rewardReadyHint = expect string( GetWeaponInfoFileKeyField_WithMods_Global( weapon.GetWeaponClassName(), weapon.GetMods(), "readyhint" ) ) + asset rewardIcon = GetWeaponInfoFileKeyFieldAsset_WithMods_Global( weapon.GetWeaponClassName(), weapon.GetMods(), "hud_icon" ) + + AnnouncementData announcement = CreateAnnouncementMessageQuick( player, rewardReadyMessage, rewardReadyHint, <1, 0.5, 0>, rewardIcon ) + announcement.displayConditionCallback = LastEarnedRewardStillValid + AnnouncementFromClass( player, announcement ) + } +} + +void function TitanReadyMessage( float delay = 0.0, bool isQuick = false ) +{ + if ( delay > 0.0 ) + wait delay + + if ( !GamePlayingOrSuddenDeath() ) + return + + entity player = GetLocalClientPlayer() + if ( !IsAlive( player ) || IsSpectating() || IsWatchingKillReplay() ) + return + + if ( player.ContextAction_IsMeleeExecution() ) + return + + if ( Riff_TitanAvailability() == eTitanAvailability.Never ) + return + + if ( !IsTitanAvailable( player ) && + (Riff_TitanAvailability() == eTitanAvailability.Custom) + ) + return + + int loadoutIndex = GetPersistentSpawnLoadoutIndex( player, "titan" ) + TitanLoadoutDef loadout = GetTitanLoadoutFromPersistentData( player, loadoutIndex ) + + string titanReadyMessage = GetTitanReadyMessageFromSetFile( loadout.setFile ) + string titanReadyHint = GetTitanReadyHintFromSetFile( loadout.setFile ) + + AnnouncementData announcement = CreateAnnouncementMessageQuick( player, titanReadyMessage, titanReadyHint, TEAM_COLOR_YOU, $"rui/hud/titanfall_marker_arrow_ready" ) + announcement.displayConditionCallback = ConditionNoTitan + AnnouncementFromClass( player, announcement ) + + #if FACTION_DIALOGUE_ENABLED + if ( !isQuick || CoinFlip() ) + PlayFactionDialogueOnLocalClientPlayer( "mp_titanReady" ) //Playing here as opposed to on the server since delay is normally not 0 + #endif + + if ( PlayerEarnMeter_GetMode( player ) == eEarnMeterMode.DEFAULT ) //Help stop spamming "Your Titan is Ready" + { + Cl_EarnMeter_SetLastHintTime( Time() ) + } +} + + +function CoreReadyMessage( entity player, bool isQuick = false ) +{ + if ( !GamePlayingOrSuddenDeath() ) + return + + if ( !IsAlive( player ) ) + return + + if ( GetDoomedState( player ) ) + return + + if ( !player.IsTitan() ) + return + + entity weapon = player.GetOffhandWeapon( OFFHAND_EQUIPMENT ) + + string coreOnlineMessage = expect string( weapon.GetWeaponInfoFileKeyField( "readymessage" ) ) + string coreOnlineHint = expect string( weapon.GetWeaponInfoFileKeyField( "readyhint" ) ) + + if ( isQuick ) + { + AnnouncementData announcement = CreateAnnouncementMessageQuick( player, coreOnlineMessage, coreOnlineHint, TEAM_COLOR_YOU ) + announcement.displayConditionCallback = ConditionPlayerIsTitan + AnnouncementFromClass( player, announcement ) + } + else + { + AnnouncementData announcement = CreateAnnouncementMessage( player, coreOnlineMessage, coreOnlineHint, TEAM_COLOR_YOU ) + announcement.displayConditionCallback = ConditionPlayerIsTitan + announcement.subText = coreOnlineHint + AnnouncementFromClass( player, announcement ) + } +} + + +bool function ConditionPlayerIsTitan() +{ + entity player = GetLocalClientPlayer() + if ( !IsAlive( player ) ) + return false + + return player.IsTitan() +} + + +bool function ConditionPlayerIsNotTitan() +{ + entity player = GetLocalClientPlayer() + if ( !IsAlive( player ) ) + return false + + return !player.IsTitan() +} + +bool function LastEarnedRewardStillValid() +{ + entity player = GetLocalClientPlayer() + if ( !IsAlive( player ) ) + return false + + entity weapon = player.GetOffhandWeapon( OFFHAND_INVENTORY ) + if ( !IsValid( weapon ) ) + return false + + return weapon == file.lastEarnedReward +} + + +bool function ConditionNoTitan() +{ + entity player = GetLocalClientPlayer() + if ( !IsAlive( player ) ) + return false + + if ( IsValid( player.GetPetTitan() ) ) + return false + + return !player.IsTitan() +} + + +function ClientPilotSpawned( entity player ) +{ + player.EndSignal( "SettingsChanged" ) + player.EndSignal( "OnDestroy" ) + player.EndSignal( "OnDeath" ) + + if ( (player != GetLocalViewPlayer()) ) + //Turning off for the time being since the front rodeo spot leaves persistent jumpjets in the face of the TItan + thread ParentedPlayerJets( player ) +} + +void function Player_AddClient( entity player ) +{ + if ( GetCurrentPlaylistVarInt( "titan_mode_change_allowed", 1 ) ) + RegisterConCommandTriggeredCallback( "+scriptCommand2", Pressed_TitanNextMode ) + + RegisterConCommandTriggeredCallback( "+scriptCommand2", Pressed_RequestTitanfall ) + RegisterConCommandTriggeredCallback( "+scriptCommand2", Pressed_ActivateMobilityGhost ) + + RegisterConCommandTriggeredCallback( "+use", Pressed_OfferRodeoBattery ) + RegisterConCommandTriggeredCallback( "+use", Pressed_RequestRodeoBattery ) + RegisterConCommandTriggeredCallback( "+useandreload", Pressed_OfferRodeoBattery ) + RegisterConCommandTriggeredCallback( "+useandreload", Pressed_RequestRodeoBattery ) + + #if MP + RegisterConCommandTriggeredCallback( "+use", Pressed_TryNukeGrenade ) + RegisterConCommandTriggeredCallback( "-use", Released_TryNukeGrenade ) + RegisterConCommandTriggeredCallback( "+useandreload", Pressed_TryNukeGrenade ) + RegisterConCommandTriggeredCallback( "-useandreload", Released_TryNukeGrenade ) + #endif + + Create_DamageIndicatorHUD() + + if ( !IsLobby() ) + { + player.EnableHealthChangedCallback() + + player.cv.deathTime <- 0.0 + player.cv.lastSpawnTime <- 0.0 + player.cv.deathOrigin <- <0.0, 0.0, 0.0> + player.cv.roundSpawnCount <- 0 + + thread CinematicIntroScreen() + } +} + +void function Player_AddPlayer( entity player ) +{ + player.s.weaponUpdateData <- {} + + player.s.trackedAttackers <- {} // for titans + player.classChanged = true +} + +function Pressed_RequestTitanfall( entity player ) +{ + if ( !IsTitanAvailable( player ) ) + return + + #if DEV + printt( player.GetEntIndex(), "Requested replacement Titan from eye pos " + player.EyePosition() + " view angles " + player.EyeAngles() + " player origin " + player.GetOrigin() + " map " + GetMapName() ) + #endif + + player.ClientCommand( "ClientCommand_RequestTitan" ) //Send client command regardless of whether we can call the titan in or not. Server decides + Rumble_Play( "rumble_titanfall_request", {} ) + + // + //if ( player.cv.announcementActive && player.cv.announcementActive.messageText == "#HUD_TITAN_READY" ) + //{ + // clGlobal.levelEnt.Signal( "AnnoucementPurge" ) + //} + // + ////PlayMusic( "Music_FR_Militia_TitanFall1" ) + //EmitSoundOnEntity( player, "titan_callin" ) + //return + //} +} + +function Pressed_TitanNextMode( entity player ) +{ + if ( player.IsTitan() ) + return + + if ( IsWatchingReplay() ) + return + + if ( !IsAlive( player ) ) + return + + if ( player.IsPhaseShifted() ) + return + + if ( !IsAlive( player.GetPetTitan() ) ) + return + + if ( !Flag( "EnableTitanModeChange" ) ) + return + + // cannot change modes while titan is incoming + if ( player.GetHotDropImpactTime() ) + return + + player.ClientCommand( "TitanNextMode" ) + + local newMode = player.GetPetTitanMode() + 1 + if ( newMode == eNPCTitanMode.MODE_COUNT ) + newMode = eNPCTitanMode.FOLLOW + + SetAutoTitanModeHudIndicator( player, newMode ) + + local guardModeAlias = GenerateTitanOSAlias( player, AUTO_TITAN_GUARD_MODE_DIAG_SUFFIX ) + local followModeAlias = GenerateTitanOSAlias( player, AUTO_TITAN_FOLLOW_MODE_DIAG_SUFFIX ) + + // prevent the sounds from stomping each other if button is pressed rapidly + StopSoundOnEntity( player, guardModeAlias ) + StopSoundOnEntity( player, AUTO_TITAN_GUARD_MODE_SOUND ) + StopSoundOnEntity( player, followModeAlias ) + StopSoundOnEntity( player, AUTO_TITAN_FOLLOW_MODE_SOUND ) + + if ( newMode == eNPCTitanMode.FOLLOW ) + { + EmitSoundOnEntity( player, followModeAlias ) + EmitSoundOnEntity( player, AUTO_TITAN_FOLLOW_MODE_SOUND ) + } + else if ( newMode == eNPCTitanMode.STAY ) + { + EmitSoundOnEntity( player, guardModeAlias ) + EmitSoundOnEntity( player, AUTO_TITAN_GUARD_MODE_SOUND ) + } +} + +/* +void function ClientCodeCallback_OnHudReloadScheme() +{ +} +*/ + +void function ClientCodeCallback_HUDThink() +{ + PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink ) + + entity player = GetLocalViewPlayer() + + if ( !player.p.playerScriptsInitialized ) + { + PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink ) + return + } + + if ( !IsMenuLevel() ) + { + PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink_4 ) + + ClGameState_Think() + PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink_4 ) + + PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink_5 ) + UpdateVoiceHUD() + #if PC_PROG + UpdateChatHUDVisibility() + #endif // PC_PROG + PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink_5 ) + + UpdateScreenFade() + + entity clientPlayer = GetLocalClientPlayer() + if ( !IsWatchingKillReplay() && clientPlayer.classChanged ) + { + ClientPlayerClassChanged( clientPlayer, clientPlayer.GetPlayerClass() ) + } + + PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink_6 ) + SmartAmmo_LockedOntoWarningHUD_Update() + WeaponFlyoutThink( player ) + PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink_6 ) + } + + PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink ) +} + +function ClientPlayerClassChanged( entity player, newClass ) +{ + //printl( "ClientPlayerClassChanged to " + player.GetPlayerClass() ) + player.classChanged = false + + level.vduOpen = false // vdu goes away when class changes + + Assert( !IsServer() ) + Assert( newClass, "No class " ) + + switch ( newClass ) + { + case "titan": + SetStandardAbilityBindingsForTitan( player ) + SetAbilityBinding( player, 6, "+offhand4", "-offhand4" ) // "+ability 6" + + LinkButtonPair( -1, -1, -1 ) + break + + case level.pilotClass: + SetStandardAbilityBindingsForPilot( player ) + SetAbilityBinding( player, 6, "+offhand4", "-offhand4" ) // "+ability 6" + + LinkButtonPair( IN_OFFHAND0, IN_OFFHAND1, IN_OFFHAND3 ) + + if ( clGlobal.isAnnouncementActive && (clGlobal.activeAnnouncement.messageText == "#HUD_CORE_ONLINE_STRYDER" || + clGlobal.activeAnnouncement.messageText == "#HUD_CORE_ONLINE_ATLAS" || + clGlobal.activeAnnouncement.messageText == "#HUD_CORE_ONLINE_OGRE" ) ) + { + clGlobal.levelEnt.Signal( "AnnoucementPurge" ) + } + break + + case "spectator": + LinkButtonPair( -1, -1, -1 ) + break + + default: + Assert( 0, "Unknown class " + newClass ) + } + + PlayActionMusic() +} + +function ShouldShowSpawnAsTitanHint( entity player ) +{ + if ( Time() - player.cv.deathTime < GetRespawnButtonCamTime( player ) ) + return false + + if ( GetGameState() < eGameState.Playing ) + return false + + if ( GetGameState() == eGameState.SwitchingSides ) + return false + + return !IsPlayerEliminated( player ) +} + +function ServerCallback_PlayerChangedTeams( player_eHandle, oldTeam, newTeam ) +{ + entity player = GetEntityFromEncodedEHandle( player_eHandle ) + if ( player == null ) + return + Assert( oldTeam != null ) + Assert( newTeam != null ) + + string playerName = player.GetPlayerNameWithClanTag() + vector playerNameColor = OBITUARY_COLOR_ENEMY + string teamString = "ENEMY" + if ( newTeam == GetLocalViewPlayer().GetTeam() ) + { + playerNameColor = OBITUARY_COLOR_FRIENDLY + teamString = "FRIENDLY" + } + + Obituary_Print( playerName, "CHANGED TEAMS TO", teamString, playerNameColor, OBITUARY_COLOR_WEAPON, playerNameColor ) + //"Switching " + player.GetPlayerNameWithClanTag() + " from " + GetTeamStr( team1 ) + " to " + GetTeamStr( team2 ) +} + +function ServerCallback_PlayerConnectedOrDisconnected( player_eHandle, state ) +{ + entity player = GetEntityFromEncodedEHandle( player_eHandle ) + PlayerConnectedOrDisconnected( player, state ) + + if ( !IsLobby() || !IsConnected() ) + UpdatePlayerStatusCounts() +} + +void function AddCallback_OnPlayerDisconnected( void functionref( entity ) callbackFunc ) +{ + Assert( !clGlobal.onPlayerDisconnectedFuncs.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnPlayerDisconnected" ) + + clGlobal.onPlayerDisconnectedFuncs.append( callbackFunc ) +} + +void function ClientCodeCallback_PlayerDisconnected( entity player, string cachedPlayerName ) +{ + PlayerConnectedOrDisconnected( player, 0, cachedPlayerName ) + + if ( ShouldUpdatePlayerStatusCounts() ) + UpdatePlayerStatusCounts() + + // Added via AddCallback_OnPlayerDisconnected + foreach ( callbackFunc in clGlobal.onPlayerDisconnectedFuncs ) + { + callbackFunc( player ) + } +} + +function ShouldUpdatePlayerStatusCounts() +{ + if ( GetGameState() < eGameState.WaitingForPlayers ) + return false + + if ( !IsLobby() ) + return true + + if ( !IsConnected() ) + return true + + return false +} + +function PlayerConnectedOrDisconnected( entity player, state, string disconnectingPlayerName = "" ) +{ + if ( IsLobby() || GetMapName() == "" ) + // HACK: If you are disconnecting GetMapName() in IsLobby() will return "" + return + + if ( !IsValid( player ) ) + return + + Assert( state == 0 || state == 1 ) + + if ( !IsValid( GetLocalViewPlayer() ) ) + return + + string playerName + if ( state == 0 ) + { + if ( disconnectingPlayerName == "" ) + return + + playerName = disconnectingPlayerName + Assert( typeof( playerName ) == "string" ) + } + else + { + playerName = player.GetPlayerNameWithClanTag() + Assert( typeof( playerName ) == "string" ) + } + + vector playerNameColor = player.GetTeam() == GetLocalViewPlayer().GetTeam() ? OBITUARY_COLOR_FRIENDLY : OBITUARY_COLOR_ENEMY + string connectionString = (state == 0) ? "#MP_PLAYER_DISCONNECTED" : "#MP_PLAYER_CONNECTED" + + Obituary_Print_Generic( connectionString, playerName, <255, 255, 255>, playerNameColor ) +} + +void function ClientCodeCallback_PlayerDidDamage( PlayerDidDamageParams params ) +{ + if ( IsWatchingThirdPersonKillReplay() ) + return + + entity attacker = GetLocalViewPlayer() + if ( !IsValid( attacker ) ) + return + + entity victim = params.victim + if ( !IsValid( victim ) ) + return + + vector damagePosition = params.damagePosition + int hitBox = params.hitBox + int damageType = params.damageType + float damageAmount = params.damageAmount + int damageFlags = params.damageFlags + int hitGroup = params.hitGroup + entity weapon = params.weapon + float distanceFromAttackOrigin = params.distanceFromAttackOrigin + + bool playHitSound = true + bool showCrosshairHitIndicator = true + bool hitIneffective = false + bool victimIsHeavyArmor = false + bool isCritShot = (damageType & DF_CRITICAL) ? true : false + bool isHeadShot = (damageType & DF_HEADSHOT) ? true : false + bool isKillShot = (damageType & DF_KILLSHOT) ? true : false + bool isMelee = (damageType & DF_MELEE) ? true : false + bool isExplosion = (damageType & DF_EXPLOSION) ? true : false + bool isBullet = (damageType & DF_BULLET) ? true : false + bool isShotgun = (damageType & DF_SHOTGUN) ? true : false + bool isDoomFatality = (damageType & DF_DOOM_FATALITY) ? true : false + bool isDoomProtected = ((damageType & DF_DOOM_PROTECTED) && !isDoomFatality) ? true : false + victimIsHeavyArmor = victim.GetArmorType() == ARMOR_TYPE_HEAVY + + isDoomFatality = false + isDoomProtected = false + + if ( isDoomProtected ) + RegisterDoomProtectionHintDamage( damageAmount ) + + bool playKillSound = isKillShot + + if ( !attacker.IsTitan() ) + { + if ( victimIsHeavyArmor ) + { + showCrosshairHitIndicator = true + if ( victim.IsTitan() ) + hitIneffective = false //!IsHitEffectiveVsTitan( victim, damageType ) + else + hitIneffective = isCritShot || isHeadShot || !IsHitEffectiveVsNonTitan( victim, damageType ) + } + else + { + switch ( victim.GetSignifierName() ) + { + case "npc_super_spectre": + //if ( !( damageType & DF_CRITICAL ) ) + // hitIneffective = true + + default: + if ( (damageType & DF_BULLET && damageType & DF_MAX_RANGE) ) + hitIneffective = true + break + } + } + } + else + { + if ( victim.IsTitan() && victim.IsPlayer() ) + { + if ( PlayerHasPassive( victim, ePassives.PAS_BERSERKER ) ) + hitIneffective = true + } + } + + if ( damageType & DF_MAX_RANGE && damageType & DF_BULLET ) + // TODO: this is crap; these damage types should just send DF_NO_HITBEEP + playHitSound = false + + if ( damageType & DF_TITAN_STEP ) + // TODO: this is crap; these damage types should just send DF_NO_HITBEEP + { + playHitSound = false + playKillSound = false + } + + if ( damageType & DF_MELEE ) + // TODO: this is crap; these damage types should just send DF_NO_HITBEEP + { + playHitSound = false + playKillSound = false + } + + if ( damageType & DF_NO_HITBEEP ) + { + playHitSound = false + playKillSound = false + } + + if ( damageFlags & DAMAGEFLAG_VICTIM_HAS_VORTEX ) + showCrosshairHitIndicator = false + + if ( damageType & DF_SHIELD_DAMAGE ) + { + PlayShieldHitEffect( params ) + showCrosshairHitIndicator = true + } + else if ( damageAmount <= 0 ) + { + playHitSound = false + playKillSound = false + showCrosshairHitIndicator = false + } + + if ( damageType & DF_NO_INDICATOR ) + { + playHitSound = false + playKillSound = false + showCrosshairHitIndicator = false + } + + if ( isDoomProtected ) + playHitSound = false + + if ( showCrosshairHitIndicator ) + { + Tracker_PlayerAttackedTarget( attacker, victim ) + + //if ( hitIneffective ) + // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_INEFFECTIVE ) + //else + // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_NORMAL ) + // + //if ( (isCritShot || isDoomFatality) && !isDoomProtected ) + // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_CRITICAL ) + // + //if ( isHeadShot ) + // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_HEADSHOT ) + + if ( IsMultiplayer() && !victim.IsTitan() && !victim.IsHologram() ) + PROTO_HitIndicatorEffect( attacker, victim, damagePosition, isHeadShot, isKillShot ) + + if ( isKillShot ) + KillShotBloodSpray( attacker, victim, damagePosition, isExplosion, isBullet, isShotgun ) + + if ( victim.IsTitan() && isKillShot ) + ClientScreenShake( 8, 10, 1, Vector( 0, 0, 0 ) ) + + BloodSprayDecals( attacker, victim, damagePosition, damageAmount, isHeadShot, isKillShot, isMelee, isExplosion, isBullet, isShotgun ) + + DamageFlyout( damageAmount, damagePosition, victim, isHeadShot || isCritShot, hitIneffective ) + } + + bool playedHitSound = false + if ( playHitSound ) + { + if ( isHeadShot ) + playedHitSound = PlayHeadshotConfirmSound( attacker, victim, isKillShot ) + else if ( playKillSound ) + playedHitSound = PlayKillshotConfirmSound( attacker, victim, damageType ) + } + + if ( IsSpectre( victim ) ) + { + if ( isHeadShot ) + victim.Signal( "SpectreGlowEYEGLOW" ) + } + + // Play a hit sound effect if we didn't play a kill shot sound, and other conditions are met + if ( playHitSound && IsAlive( victim ) && !playedHitSound ) + { + PlayHitSound( victim, attacker, damageFlags, isCritShot, victimIsHeavyArmor, isKillShot, hitGroup ) + } + + if ( PlayerHasPassive( attacker, ePassives.PAS_SMART_CORE ) && isKillShot ) + { + attacker.p.smartCoreKills++ + } + + foreach ( callback in clGlobal.onLocalPlayerDidDamageCallback ) + { + callback( attacker, victim, damagePosition, damageType ) + } +} + +void function PlayHitSound( entity victim, entity attacker, int damageFlags, bool isCritShot, bool victimIsHeavyArmor, bool isKillShot, int hitGroup ) +{ + if ( damageFlags & DAMAGEFLAG_VICTIM_INVINCIBLE ) + { + EmitSoundOnEntity( attacker, "Player.HitbeepInvincible" ) + } + else if ( damageFlags & DAMAGEFLAG_VICTIM_HAS_VORTEX ) + { + EmitSoundOnEntity( attacker, "Player.HitbeepVortex" ) + } + else if ( isCritShot && victimIsHeavyArmor ) + { + EmitSoundOnEntity( attacker, "titan_damage_crit" ) + } + else if ( isCritShot ) + { + EmitSoundOnEntity( attacker, "Player.Hitbeep_crit" ) + } + else + { + EmitSoundOnEntity( attacker, "Player.Hitbeep" ) + } +} + +function PROTO_HitIndicatorEffect( entity player, entity victim, vector damagePosition, bool isHeadShot, bool isKillShot ) +{ + int fxId + if ( isKillShot ) + fxId = GetParticleSystemIndex( $"P_ar_impact_pilot_kill" ) + else if ( isHeadShot && !isKillShot ) + return + // fxId = GetParticleSystemIndex( $"P_ar_impact_pilot_headshot" ) + else + return + // fxId = GetParticleSystemIndex( $"P_ar_impact_pilot" ) + + vector victimVelocity = victim.GetVelocity() + damagePosition += (Length( victimVelocity ) * 0.15) * Normalize( victimVelocity ) + vector fxOffset = damagePosition - victim.GetOrigin() + StartParticleEffectOnEntityWithPos( victim, fxId, FX_PATTACH_ABSORIGIN_FOLLOW, -1, damagePosition - victim.GetOrigin(), <0, 0, 0> ) +} + +void function KillShotBloodSpray( entity player, entity victim, vector damagePosition, bool isExplosion, bool isBullet, bool isShotgun ) +{ + if ( IsSoftenedLocale() ) + return + + if ( !victim.IsHuman() && !IsProwler( victim ) ) + return + + if ( victim.IsMechanical() ) + return + + if ( victim.IsHologram() ) + return + + if ( !isExplosion && !isBullet && !isShotgun ) + return + + int fxId = GetParticleSystemIndex( FX_KILLSHOT_BLOODSPRAY ) + + vector victimVelocity = victim.GetVelocity() + damagePosition += (Length( victimVelocity ) * 0.15) * Normalize( victimVelocity ) + StartParticleEffectOnEntityWithPos( victim, fxId, FX_PATTACH_ABSORIGIN_FOLLOW, -1, damagePosition - victim.GetOrigin(), <0, 0, 0> ) +} + +void function BloodSprayDecals( entity player, entity victim, vector damagePosition, float damageAmount, bool isHeadShot, bool isKillShot, bool isMelee, bool isExplosion, bool isBullet, bool isShotgun ) +{ + if ( IsSoftenedLocale() || !Flag( "EnableBloodSprayDecals" ) ) + return + + if ( !victim.IsHuman() && !IsProwler( victim ) ) + return + + if ( victim.IsMechanical() ) + return + + if ( victim.IsHologram() ) + return + + if ( !isMelee && !isExplosion && !isBullet && !isShotgun ) + return + + // in MP, too expensive to do on every shot + if ( IsMultiplayer() && !isKillShot ) + return + + thread BloodSprayDecals_Think( player, victim, damagePosition, damageAmount, isHeadShot, isKillShot, isMelee, isExplosion, isBullet, isShotgun ) +} + +void function BloodSprayDecals_Think( entity player, entity victim, vector damagePosition, float damageAmount, bool isHeadShot, bool isKillShot, bool isMelee, bool isExplosion, bool isBullet, bool isShotgun ) +{ + player.EndSignal( "OnDestroy" ) + victim.EndSignal( "OnDestroy" ) + + BloodDecalParams params = BloodDecal_GetParams( damageAmount, isHeadShot, isKillShot, isMelee, isExplosion, isBullet, isShotgun ) + float traceDist = params.traceDist + float secondaryTraceDist = params.secondaryTraceDist + asset fxType = params.fxType + asset secondaryFxType = params.secondaryFxType + + int fxId = GetParticleSystemIndex( fxType ) + + // PRIMARY TRACES + vector traceStart = damagePosition + vector traceFwd = player.GetViewVector() + + if ( isExplosion || isMelee ) + { + // for explosion/melee damage, use chest instead of actual damage position + int attachID = victim.LookupAttachment( "CHESTFOCUS" ) + traceStart = victim.GetAttachmentOrigin( attachID ) + + if ( isExplosion ) + traceFwd = AnglesToForward( victim.GetAngles() ) * -1 + } + + vector traceEnd = damagePosition + (traceFwd * traceDist) + //TraceResults traceResult = TraceLine( traceStart, traceEnd, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) + + var deferredTrace_primary = DeferredTraceLineHighDetail( traceStart, traceEnd, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) + + while( !IsDeferredTraceFinished( deferredTrace_primary ) ) + WaitFrame() + + TraceResults traceResult = GetDeferredTraceResult( deferredTrace_primary ) + + vector primaryTraceEndPos = traceResult.endPos + vector primaryTraceNormal = traceResult.surfaceNormal + //DebugDrawLine( traceStart, traceEnd, 255, 150, 0, true, 5 ) + //DebugDrawSphere( primaryTraceEndPos, 8.0, 255, 0, 0, true, 5 ) + + bool doGravitySplat = isMelee ? false : true + + if ( traceResult.fraction < 1.0 ) + { + vector normAng = VectorToAngles( traceResult.surfaceNormal ) + vector fxAng = AnglesCompose( normAng, < 90, 0, 0 > ) + + StartParticleEffectInWorld( fxId, primaryTraceEndPos, fxAng ) + //DebugDrawAngles( endPos, fxAng, 5 ) + } + else if ( doGravitySplat ) + { + // trace behind the guy on the ground and put a decal there + float gravitySplatBackTraceDist = 58.0 // how far behind the guy to put the gravity splat + float gravitySplatDownTraceDist = 100.0 // max dist vertically to try to trace and put a gravity splat + vector groundTraceStartPos = damagePosition + (traceFwd * gravitySplatBackTraceDist) + vector groundTraceEndPos = groundTraceStartPos - <0, 0, 100> + + var deferredTrace_gravitySplat = DeferredTraceLineHighDetail( groundTraceStartPos, groundTraceEndPos, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) + + while( !IsDeferredTraceFinished( deferredTrace_gravitySplat ) ) + WaitFrame() + + TraceResults downTraceResult = GetDeferredTraceResult( deferredTrace_gravitySplat ) + + if ( downTraceResult.fraction < 1.0 ) + { + //DebugDrawLine( groundTraceStartPos, downTraceResult.endPos, 255, 150, 0, true, 5 ) + //DebugDrawSphere( downTraceResult.endPos, 4.0, 255, 0, 0, true, 5 ) + + vector normAng = VectorToAngles( downTraceResult.surfaceNormal ) + vector fxAng = AnglesCompose( normAng, < 90, 0, 0 > ) + + //DebugDrawAngles( downTraceResult.endPos, fxAng, 5 ) + + StartParticleEffectInWorld( fxId, downTraceResult.endPos, fxAng ) + } + } + + // MP doesn't want secondaries, too expensive + if ( IsMultiplayer() ) + return + + // SECONDARY TRACES + array testVecs = [] + vector tempAng = VectorToAngles( traceFwd ) + + if ( isExplosion ) + { + // for explosions, different & more angles for secondary splatter + testVecs.append( AnglesToRight( tempAng ) ) + testVecs.append( AnglesToRight( tempAng ) * -1 ) + testVecs.append( traceFwd * -1 ) + testVecs.append( AnglesToUp( tempAng ) ) + testVecs.append( AnglesToUp( tempAng ) * -1 ) + } + else + { + // mostly to cover edge cases involving corners + vector traceRight = AnglesToRight( tempAng ) + vector traceLeft = traceRight * -1 + vector backLeft = (traceFwd + traceLeft) * 0.5 + vector backRight = (traceFwd + traceRight) * 0.5 + testVecs.append( backRight ) + testVecs.append( backLeft ) + + // add blood on the ground for these weapons too + if ( isBullet || isShotgun ) + testVecs.append( AnglesToUp( tempAng ) * -1 ) + } + + if ( !testVecs.len() ) + return + + array secondaryDeferredTraces = [] + foreach ( testVec in testVecs ) + { + vector secondaryTraceEnd = traceStart + (testVec * secondaryTraceDist) + var secondaryDeferredTrace = DeferredTraceLineHighDetail( traceStart, secondaryTraceEnd, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) + secondaryDeferredTraces.append( secondaryDeferredTrace ) + } + + int secondaryFxId = GetParticleSystemIndex( secondaryFxType ) + + float startTime = Time() + array processedResults = [] + while ( processedResults.len() < secondaryDeferredTraces.len() ) + { + WaitFrame() + + foreach ( deferredTrace in secondaryDeferredTraces ) + { + if ( processedResults.contains( deferredTrace ) ) + continue + + if ( !IsDeferredTraceFinished( deferredTrace ) ) + continue + + processedResults.append( deferredTrace ) + + TraceResults traceResult = GetDeferredTraceResult( deferredTrace ) + + if ( traceResult.fraction == 1.0 ) + continue + + // don't put secondaries on the same wall as the primary + vector secondaryTraceNormal = traceResult.surfaceNormal + if ( primaryTraceNormal == secondaryTraceNormal ) + continue + + vector normAng = VectorToAngles( secondaryTraceNormal ) + vector fxAng = AnglesCompose( normAng, < 90, 0, 0 > ) + + vector endPos = traceResult.endPos + //DebugDrawSphere( endPos, 4.0, 255, 0, 0, true, 5 ) + StartParticleEffectInWorld( secondaryFxId, endPos, fxAng ) + } + + // timeout if traces aren't returning + if ( Time() - startTime >= 0.3 ) + return + } +} + +BloodDecalParams function BloodDecal_GetParams( float damageAmount, bool isHeadShot, bool isKillShot, bool isMelee, bool isExplosion, bool isBullet, bool isShotgun ) +{ + // default: bullet damage + float traceDist = 175 + float secondaryTraceDist = 100 + asset fxType = FX_BLOODSPRAY_DECAL_SML + asset secondaryFxType = FX_BLOODSPRAY_DECAL_SML + + if ( isBullet ) + { + // HACK- shotguns report isBullet also + if ( isShotgun ) + { + //if ( isKillShot ) + // fxType = FX_BLOODSPRAY_DECAL_LRG + //else + fxType = FX_BLOODSPRAY_DECAL_MED + } + else + { + if ( isKillShot ) + fxType = FX_BLOODSPRAY_DECAL_MED + else + fxType = FX_BLOODSPRAY_DECAL_SML + + if ( damageAmount >= 200 ) + { + traceDist = 216 + fxType = FX_BLOODSPRAY_DECAL_LRG + secondaryFxType = FX_BLOODSPRAY_DECAL_MED + } + } + } + + else if ( isExplosion ) + { + secondaryTraceDist = traceDist + + float maxDmg = 100 + float medDmg = 75 + + if ( damageAmount >= maxDmg ) + { + fxType = FX_BLOODSPRAY_DECAL_LRG + secondaryFxType = FX_BLOODSPRAY_DECAL_LRG + } + else if ( damageAmount >= medDmg ) + { + fxType = FX_BLOODSPRAY_DECAL_LRG + secondaryFxType = FX_BLOODSPRAY_DECAL_MED + } + else if ( isKillShot ) + { + fxType = FX_BLOODSPRAY_DECAL_MED + secondaryFxType = FX_BLOODSPRAY_DECAL_MED + } + } + + else if ( isMelee ) + { + traceDist = 96 + + if ( isKillShot ) + fxType = FX_BLOODSPRAY_DECAL_MED + } + + // for kills, increase trace distance a bit + if ( isKillShot ) + { + traceDist = traceDist + (traceDist * 0.1) + secondaryTraceDist = secondaryTraceDist + (secondaryTraceDist * 0.1) + } + + BloodDecalParams params + params.traceDist = traceDist + params.secondaryTraceDist = secondaryTraceDist + params.fxType = fxType + params.secondaryFxType = secondaryFxType + return params +} + +#if DEV +string function BloodSprayDecals_Toggle() +{ + string returnStr = "" + + if ( Flag( "EnableBloodSprayDecals" ) ) + { + FlagClear( "EnableBloodSprayDecals" ) + returnStr = "Blood spray decals DISABLED" + } + else + { + FlagSet( "EnableBloodSprayDecals" ) + returnStr = "Blood spray decals ENABLED" + } + + return returnStr +} +#endif + +function ServerCallback_RodeoerEjectWarning( soulHandle, ejectTime ) +{ + entity soul = GetEntityFromEncodedEHandle( soulHandle ) + + if ( !IsValid( soul ) ) + return + + thread TitanEjectHatchSequence( soul, ejectTime ) +} + +function TitanEjectHatchSequence( soul, ejectTime ) +{ + expect entity( soul ) + + soul.EndSignal( "OnSoulTransfer" ) + soul.EndSignal( "OnTitanDeath" ) + soul.EndSignal( "OnDestroy" ) + + local effects = [] + + OnThreadEnd( + function() : ( effects ) + { + foreach ( effect in effects ) + { + if ( !EffectDoesExist( effect ) ) + continue + + EffectStop( effect, true, true ) + } + } + ) + + int boltCount = 6 + int fxID = GetParticleSystemIndex( $"xo_spark_bolt" ) + + for ( int index = 0; index < boltCount; index++ ) + { + entity titan = soul.GetTitan() + + WaitEndFrame() // so OnTitanDeath/Destroy can happen + + if ( !IsAlive( titan ) ) + return + + if ( !titan.IsTitan() ) + { + printt( "WARNING: " + titan + " is not a Titan!" ) + return + } + + int attachID = titan.LookupAttachment( "HATCH_BOLT" + (index + 1) ) + //printt( "attachID is " + attachID ) + vector boltOrgin = titan.GetAttachmentOrigin( attachID ) + vector boltAngles = titan.GetAttachmentAngles( attachID ) + vector launchVec = AnglesToForward( boltAngles ) * 500 + + CreateClientsideGib( $"models/industrial/bolt_tiny01.mdl", boltOrgin, boltAngles, launchVec, < 0, 0, 0 >, 3.0, 1000.0, 200.0 ) + int effect = PlayFXOnTag( titan, fxID, attachID ) + effects.append( effect ) + EmitSoundOnEntity( titan, "titan_bolt_loose" ) + + wait (ejectTime / boltCount) + } +} + +void function ServerCallback_OnEntityKilled( attackerEHandle, victimEHandle, int scriptDamageType, damageSourceId ) +{ + expect int( damageSourceId ) + + bool isHeadShot = (scriptDamageType & DF_HEADSHOT) > 0 + + entity victim = GetEntityFromEncodedEHandle( victimEHandle ) + entity attacker = attackerEHandle ? GetHeavyWeightEntityFromEncodedEHandle( attackerEHandle ) : null + entity localClientPlayer = GetLocalClientPlayer() + + if ( !IsValid( victim ) ) + return + + Signal( victim, "OnDeath" ) + + if ( victim == localClientPlayer ) + { + victim.cv.deathOrigin = victim.GetOrigin() + level.clientsLastKiller = attacker + } + + if ( damageSourceId == eDamageSourceId.indoor_inferno ) + { + if ( victim == localClientPlayer ) + thread PlayerFieryDeath( victim ) + } + + UpdatePlayerStatusCounts() + + if ( IsValid( attacker ) && attacker.IsPlayer() ) + { + PlayTargetEliminatedTitanVO( attacker, victim ) + + if ( attacker == GetLocalViewPlayer() ) + WeaponFlyoutRefresh() // refreshes to display xp gained from kills + } + else if ( victim.IsPlayer() ) + { + if ( ("latestAssistTime" in victim.s) && victim.s.latestAssistTime >= Time() - MAX_NPC_KILL_STEAL_PREVENTION_TIME ) + { + attacker = expect entity( victim.s.latestAssistPlayer ) + damageSourceId = expect int( victim.s.latestAssistDamageSource ) + } + } + + if ( victim.IsPlayer() && victim != attacker ) + { + if ( attacker == localClientPlayer ) + { + thread PlayKillConfirmedSound( "Pilot_Killed_Indicator" ) + } + else if ( IsValid( attacker ) && attacker.IsTitan() ) + { + entity bossPlayer = attacker.GetBossPlayer() + if ( bossPlayer && bossPlayer == localClientPlayer ) + thread PlayKillConfirmedSound( "Pilot_Killed_Indicator" ) + } + } + else if ( (IsGrunt( victim ) || IsSpectre( victim )) && attacker == localClientPlayer ) + { + thread PlayKillConfirmedSound( "HUD_Grunt_Killed_Indicator" ) + } + + //if it's an auto titan, the obit was already printed when doomed + if ( (victim.IsTitan()) && (!victim.IsPlayer()) ) + return + + Obituary( attacker, "", victim, scriptDamageType, damageSourceId, isHeadShot ) +} + + +const float KILL_CONFIRM_DEBOUNCE = 0.025 +void function PlayKillConfirmedSound( string sound ) +{ + while ( true ) + { + if ( Time() - clGlobal.lastKillConfirmTime > KILL_CONFIRM_DEBOUNCE ) + { + clGlobal.lastKillConfirmTime = Time() + EmitSoundOnEntity( GetLocalClientPlayer(), sound ) + return + } + + WaitFrame() + } +} + +void function ServerCallback_OnTitanKilled( int attackerEHandle, int victimEHandle, int scriptDamageType, int damageSourceId ) +{ + //Gets run on every client whenever a titan is doomed by another player + bool isHeadShot = false + entity attacker = attackerEHandle != -1 ? GetEntityFromEncodedEHandle( attackerEHandle ) : null + entity victim = GetEntityFromEncodedEHandle( victimEHandle ) + + if ( (!IsValid( victim )) || (!IsValid( attacker )) ) + return + + //Obit: titans get scored/obits when doomed, so we don't want to just see "Player" in the obit, we want to see "Player's Titan" + bool victimIsOwnedTitan = victim.IsPlayer() + Obituary( attacker, "", victim, scriptDamageType, damageSourceId, isHeadShot, victimIsOwnedTitan ) +} + +function PlayTargetEliminatedTitanVO( attacker, victim ) +{ + entity localPlayer = GetLocalViewPlayer() + + if ( attacker != localPlayer ) + return + + if ( !victim.IsPlayer() ) + return + + if ( victim.IsTitan() ) + { + // a bit more delay for a titan explosion to clear + thread TitanCockpit_PlayDialogDelayed( localPlayer, 1.3, "elimTarget" ) + } + else + { + thread TitanCockpit_PlayDialogDelayed( localPlayer, 0.8, "elimEnemyPilot" ) + } +} + +function ServerCallback_SetAssistInformation( damageSourceId, attackerEHandle, entityEHandle, assistTime ) +{ + local ent = GetHeavyWeightEntityFromEncodedEHandle( entityEHandle ) + if ( !ent ) + return + + local latestAssistPlayer = GetEntityFromEncodedEHandle ( attackerEHandle ) + if ( !("latestAssistPlayer" in ent.s) ) + { + ent.s.latestAssistPlayer <- latestAssistPlayer + ent.s.latestAssistDamageSource <- damageSourceId + ent.s.latestAssistTime <- assistTime + } + else + { + ent.s.latestAssistPlayer = latestAssistPlayer + ent.s.latestAssistDamageSource = damageSourceId + ent.s.latestAssistTime = assistTime + } +} + +void function ClientCodeCallback_OnModelChanged( entity ent ) +{ +/* + // OnModelChanged gets called for each model change, but gets processed after the model has done all switches + + if ( !IsValid( ent ) ) + return; + + if ( !("creationCount" in ent.s) ) + return; + + Assert( ent instanceof C_BaseAnimating ); +*/ +} + + +void function ClientCodeCallback_OnHealthChanged( entity ent, int oldHealth, int newHealth ) +{ + if ( IsLobby() ) + return + + entity player = GetLocalViewPlayer() + if ( !IsValid( player ) ) + return + + if ( !IsValid( ent ) ) + return + + ent.Signal( "HealthChanged", { oldHealth = oldHealth, newHealth = newHealth } ) +} + +void function ClientCodeCallback_OnCrosshairCurrentTargetChanged( entity player, entity newTarget ) +{ + if ( IsLobby() ) + return; + if ( !IsValid( player ) ) + return + + if ( IsValid( newTarget ) ) + TryOfferRodeoBatteryHint( newTarget ) +} + +void function SetupPlayerAnimEvents( entity player ) +{ + SetupPlayerJumpJetAnimEvents( player ) + AddAnimEvent( player, "WallHangAttachDataKnife", WallHangAttachDataKnife ) +} + +void function JumpRandomlyForever() +{ + for (;; ) + { + if ( IsWatchingReplay() ) + { + wait 1 + continue + } + + entity player = GetLocalClientPlayer() + if ( !IsAlive( player ) || player != GetLocalViewPlayer() ) + { + wait 1 + continue + } + + printt( "jump!" ) + player.ClientCommand( "+jump" ) + wait 0 + player.ClientCommand( "-jump" ) + + wait RandomFloatRange( 0.2, 1.1 ) + } +} + +void function RemoteTurretFadeoutAnimEvent( entity ent ) +{ + entity player = GetLocalViewPlayer() + ScreenFade( player, 0, 0, 0, 255, 0.1, 0.25, FFADE_OUT ); +} + +void function SetupFirstPersonProxyEvents( entity firstPersonProxy ) +{ + //printt( "SetupFirstPersonProxyEvents" ) + + AddAnimEvent( firstPersonProxy, "mantle_smallmantle", OnSmallMantle ) + AddAnimEvent( firstPersonProxy, "mantle_mediummantle", OnMediumMantle ) + AddAnimEvent( firstPersonProxy, "mantle_lowmantle", OnLowMantle ) + AddAnimEvent( firstPersonProxy, "mantle_extralowmantle", OnExtraLowMantle ) + AddAnimEvent( firstPersonProxy, "remoteturret_fadeout", RemoteTurretFadeoutAnimEvent ) +} + +void function OnSmallMantle( entity firstPersonProxy ) //Was set up in script instead of anim to be able to play quieter sounds with stealth passive. No longer needed, but more work to move inside of anim +{ + entity player = GetLocalViewPlayer() + EmitSoundOnEntity( firstPersonProxy, "mantle_smallmantle" ) +} + +void function OnMediumMantle( entity firstPersonProxy ) //Was set up in script instead of anim to be able to play quieter sounds with stealth passive. No longer needed, but more work to move inside of anim +{ + entity player = GetLocalViewPlayer() + EmitSoundOnEntity( firstPersonProxy, "mantle_mediummantle" ) +} + +void function OnLowMantle( entity firstPersonProxy ) //Was set up in script instead of anim to be able to play quieter sounds with stealth passive. No longer needed, but more work to move inside of anim +{ + entity player = GetLocalViewPlayer() + EmitSoundOnEntity( firstPersonProxy, "mantle_lowmantle" ) +} + +void function OnExtraLowMantle( entity firstPersonProxy ) //Was set up in script instead of anim to be able to play quieter sounds with stealth passive. No longer needed, but more work to move inside of anim +{ + entity player = GetLocalViewPlayer() + EmitSoundOnEntity( firstPersonProxy, "mantle_extralow" ) +} + +void function CreateCallback_TitanSoul( entity ent ) +{ +} + +bool function ShouldHideRespawnSelectionText( entity player ) +{ + if ( player != GetLocalClientPlayer() ) + return false + if ( player.GetPlayerClass() != "spectator" ) + return false + if ( IsWatchingReplay() ) + return false + + return true +} + + + +void function WallHangAttachDataKnife( entity player ) +{ + int attachIdx = player.LookupAttachment( "l_hand" ) + if ( attachIdx == 0 ) + // hack while i wait for the attachment to be fixed + return + + entity dataknife = CreateClientSidePropDynamic( player.GetAttachmentOrigin( attachIdx ), player.GetAttachmentAngles( attachIdx ), DATA_KNIFE_MODEL ) + dataknife.SetParent( player, "l_hand" ) + + thread DeleteDataKnifeAfterWallHang( player, dataknife ) +} + +void function DeleteDataKnifeAfterWallHang( entity player, entity dataknife ) +{ + OnThreadEnd( + function() : ( dataknife ) + { + if ( IsValid( dataknife ) ) + dataknife.Kill_Deprecated_UseDestroyInstead() + } + ) + + player.EndSignal( "OnDeath" ) + player.EndSignal( "OnDestroy" ) + + for (;; ) + { + Wait( 0.1 ) + if ( !player.IsWallHanging() ) + break + } +} + +bool function ClientCodeCallback_OnGib( entity victim, vector attackDir ) +{ + if ( !victim.IsMechanical() ) + return SpawnFleshGibs( victim, attackDir ) + + return false +} + +bool function SpawnFleshGibs( entity victim, vector attackDir ) +{ + asset modelName = $"models/gibs/human_gibs.mdl" + attackDir = Normalize( attackDir ) + + float cullDist = 2048.0 + if ( "gibDist" in victim.s ) + cullDist = expect float( victim.s.gibDist ) + + vector startOrigin = victim.GetWorldSpaceCenter() + (attackDir * -30) + + vector origin = victim.GetOrigin() + < RandomIntRange( 10, 20 ), RandomIntRange( 10, 20 ), RandomIntRange( 32, 64 ) > + vector angles = < 0, 0, 0 > + vector flingDir = attackDir * RandomIntRange( 80, 200 ) + + int fxID + bool isSoftenedLocale = IsSoftenedLocale() + + if ( isSoftenedLocale ) + { + if ( victim.GetModelName() == FLYER_MODEL ) + fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist_LG_nochunk" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) + else + fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist_nochunk" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) + } + else + { + if ( victim.GetModelName() == FLYER_MODEL ) + fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist_LG" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) + else + fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) + } + + EffectSetControlPointVector( fxID, 1, flingDir ) + + if ( isSoftenedLocale ) + return true + + vector angularVel = < 0, 0, 0 > + float lifeTime = 10.0 + CreateClientsideGibWithBodyGroupGibs( modelName, victim.GetOrigin(), angles, attackDir, angularVel, lifeTime, cullDist, 1024 ) + + return true +} + +function ServerCallback_PlayScreenFXWarpJump() +{ + if ( IsWatchingReplay() ) + return false + + thread PlayScreenFXWarpJump( GetLocalClientPlayer() ) +} + +void function PlayScreenFXWarpJump( entity clientPlayer ) +{ + clientPlayer.EndSignal( "OnDeath" ) + clientPlayer.EndSignal( "OnDestroy" ) + + entity player = GetLocalViewPlayer() + int index = GetParticleSystemIndex( SCREENFX_WARPJUMP ) + int indexD = GetParticleSystemIndex( SCREENFX_WARPJUMPDLIGHT ) + int fxID = StartParticleEffectInWorldWithHandle( index, < 0, 0, 0 >, < 0, 0, 0 > ) + int fxID2 = -1 + if ( IsValid( player.GetCockpit() ) ) + { + fxID2 = StartParticleEffectOnEntity( player, indexD, FX_PATTACH_POINT_FOLLOW, player.GetCockpit().LookupAttachment( "CAMERA" ) ) + EffectSetIsWithCockpit( fxID2, true ) + } + + OnThreadEnd( + function() : ( clientPlayer, fxID, fxID2 ) + { + if ( IsValid( clientPlayer ) && !IsAlive( clientPlayer ) ) + { + EffectStop( fxID, true, false ) + if ( fxID2 > -1 ) + EffectStop( fxID2, true, false ) + } + } + ) + + wait 3.2 + if ( IsValid( player.GetCockpit() ) ) + thread TonemappingUpdateAfterWarpJump() +} + +const EXPOSURE_RAMPDOWN_DURATION = 2 +const EXPOSURE_RAMPDOWN_MAX = 20 +const EXPOSURE_RAMPDOWN_MIN = 0 +const MAX_RAMPDOWN_DURATION = 5 +const MAX_RAMPDOWN_MAX = 3 +const MAX_RAMPDOWN_MIN = 1 + +function TonemappingUpdateAfterWarpJump() +{ + // Turn cubemaps black inside drop ship, since it's pretty dark in there anyway and we don't have a great way to take a valid cubemap shot for that location. + SetConVarFloat( "mat_envmap_scale", 0 ); + + AutoExposureSetMaxExposureMultiplier( 500 ); // allow exposure to actually go bright, even if it's clamped in the level. + + // Start the exposure super bright behind the white FX, and ramp it down quickly to normal. + local startTime = Time() + while( 1 ) + { + local time = Time() - startTime + float factor = GraphCapped( time, 0, EXPOSURE_RAMPDOWN_DURATION, 1, 0 ) + local toneMapScale = EXPOSURE_RAMPDOWN_MIN + (EXPOSURE_RAMPDOWN_MAX - EXPOSURE_RAMPDOWN_MIN) * factor * factor * factor * factor + AutoExposureSetExposureCompensationBias( toneMapScale ) + AutoExposureSnap() + wait 0 + if ( factor == 0 ) + break; + } + + // Ramp the max exposure multiplier back down to 1 gently + startTime = Time() + while( 1 ) + { + local time = Time() - startTime + float factor = GraphCapped( time, 0, MAX_RAMPDOWN_DURATION, 1, 0 ) + local scale = MAX_RAMPDOWN_MIN + (MAX_RAMPDOWN_MAX - MAX_RAMPDOWN_MIN) * factor * factor + AutoExposureSetMaxExposureMultiplier( scale ); + wait 0 + if ( factor == 0 ) + break; + } +} + +function SetPanelAlphaOverTime( panel, alpha, duration ) +{ + // HACK this should be a code command - Mackey + Signal( panel, "PanelAlphaOverTime" ) + EndSignal( panel, "PanelAlphaOverTime" ) + EndSignal( panel, "OnDestroy" ) + + local startTime = Time() + local endTime = startTime + duration + local startAlpha = panel.GetPanelAlpha() + + while( Time() <= endTime ) + { + float a = GraphCapped( Time(), startTime, endTime, startAlpha, alpha ) + panel.SetPanelAlpha( a ) + WaitFrame() + } + + panel.SetPanelAlpha( alpha ) +} + + + +function HandleDoomedState( entity player, entity titan ) +{ + bool isDoomed = GetDoomedState( titan ) + if ( isDoomed ) + { + titan.Signal( "Doomed" ) + + if ( HasSoul( titan ) ) + { + entity soul = titan.GetTitanSoul() + soul.Signal( "Doomed" ) + } + } +} + +const asset SHIELD_BREAK_FX = $"P_xo_armor_break_CP" +function PlayShieldBreakEffect( entity ent ) +{ + entity shieldEnt = ent + if ( IsSoul( ent ) ) + { + shieldEnt = ent.GetTitan() + if ( !shieldEnt ) + return + } + + float shieldHealthFrac = GetShieldHealthFrac( shieldEnt ) + + int shieldBreakFX = GetParticleSystemIndex( SHIELD_BREAK_FX ) + + local attachID + if ( shieldEnt.IsTitan() ) + attachID = shieldEnt.LookupAttachment( "exp_torso_main" ) + else + attachID = shieldEnt.LookupAttachment( "ref" ) // TEMP + + local shieldFXHandle = StartParticleEffectOnEntity( shieldEnt, shieldBreakFX, FX_PATTACH_POINT_FOLLOW, attachID ) + EffectSetControlPointVector( shieldFXHandle, 1, GetShieldEffectCurrentColor( 1 - shieldHealthFrac ) ) +} + +function PlayShieldActivateEffect( entity ent ) +{ + entity shieldEnt = ent + if ( IsSoul( ent ) ) + { + shieldEnt = ent.GetTitan() + if ( !shieldEnt ) + return + } + + float shieldHealthFrac = GetShieldHealthFrac( shieldEnt ) + + int shieldBreakFX = GetParticleSystemIndex( SHIELD_BREAK_FX ) + + local attachID + if ( shieldEnt.IsTitan() ) + attachID = shieldEnt.LookupAttachment( "exp_torso_main" ) + else + attachID = shieldEnt.LookupAttachment( "ref" ) // TEMP + + local shieldFXHandle = StartParticleEffectOnEntity( shieldEnt, shieldBreakFX, FX_PATTACH_POINT_FOLLOW, attachID ) + EffectSetControlPointVector( shieldFXHandle, 1, GetShieldEffectCurrentColor( 1 - shieldHealthFrac ) ) +} + +function PlayIt( entity victim ) +{ + float shieldHealthFrac = GetShieldHealthFrac( victim ) + + int shieldbodyFX = GetParticleSystemIndex( SHIELD_BODY_FX ) + local attachID + if ( victim.IsTitan() ) + attachID = victim.LookupAttachment( "exp_torso_main" ) + else + attachID = victim.LookupAttachment( "ref" ) // TEMP + + local shieldFXHandle = StartParticleEffectOnEntity( victim, shieldbodyFX, FX_PATTACH_POINT_FOLLOW, attachID ) + + EffectSetControlPointVector( shieldFXHandle, 1, GetShieldEffectCurrentColor( 1 - shieldHealthFrac ) ) +} + +function PlayShieldHitEffect( PlayerDidDamageParams params ) +{ + entity player = GetLocalViewPlayer() + entity victim = params.victim + //vector damagePosition = params.damagePosition + //int hitBox = params.hitBox + //int damageType = params.damageType + //float damageAmount = params.damageAmount + //int damageFlags = params.damageFlags + //int hitGroup = params.hitGroup + //entity weapon = params.weapon + //float distanceFromAttackOrigin = params.distanceFromAttackOrigin + + //shieldFX <- GetParticleSystemIndex( SHIELD_FX ) + //StartParticleEffectInWorld( shieldFX, damagePosition, player.GetViewVector() * -1 ) + + float shieldHealthFrac = GetShieldHealthFrac( victim ) + + int shieldbodyFX = GetParticleSystemIndex( SHIELD_BODY_FX ) + local attachID + if ( victim.IsTitan() ) + attachID = victim.LookupAttachment( "exp_torso_main" ) + else + attachID = victim.LookupAttachment( "ref" ) // TEMP + + local shieldFXHandle = StartParticleEffectOnEntity( victim, shieldbodyFX, FX_PATTACH_POINT_FOLLOW, attachID ) + + EffectSetControlPointVector( shieldFXHandle, 1, GetShieldEffectCurrentColor( 1 - shieldHealthFrac ) ) +} + +const table SHIELD_COLOR_CHARGE_FULL = { r = 115, g = 247, b = 255 } // blue +const table SHIELD_COLOR_CHARGE_MED = { r = 200, g = 128, b = 80 } // orange +const table SHIELD_COLOR_CHARGE_EMPTY = { r = 200, g = 80, b = 80 } // red + +const SHIELD_COLOR_CROSSOVERFRAC_FULL2MED = 0.75 // from zero to this fraction, fade between full and medium charge colors +const SHIELD_COLOR_CROSSOVERFRAC_MED2EMPTY = 0.95 // from "full2med" to this fraction, fade between medium and empty charge colors + +function GetShieldEffectCurrentColor( shieldHealthFrac ) +{ + local color1 = SHIELD_COLOR_CHARGE_FULL + local color2 = SHIELD_COLOR_CHARGE_MED + local color3 = SHIELD_COLOR_CHARGE_EMPTY + + local crossover1 = SHIELD_COLOR_CROSSOVERFRAC_FULL2MED // from zero to this fraction, fade between color1 and color2 + local crossover2 = SHIELD_COLOR_CROSSOVERFRAC_MED2EMPTY // from crossover1 to this fraction, fade between color2 and color3 + + local colorVec = < 0, 0, 0 > + // 0 = full charge, 1 = no charge remaining + if ( shieldHealthFrac < crossover1 ) + { + colorVec.x = Graph( shieldHealthFrac, 0, crossover1, color1.r, color2.r ) + colorVec.y = Graph( shieldHealthFrac, 0, crossover1, color1.g, color2.g ) + colorVec.z = Graph( shieldHealthFrac, 0, crossover1, color1.b, color2.b ) + } + else if ( shieldHealthFrac < crossover2 ) + { + colorVec.x = Graph( shieldHealthFrac, crossover1, crossover2, color2.r, color3.r ) + colorVec.y = Graph( shieldHealthFrac, crossover1, crossover2, color2.g, color3.g ) + colorVec.z = Graph( shieldHealthFrac, crossover1, crossover2, color2.b, color3.b ) + } + else + { + // for the last bit of overload timer, keep it max danger color + colorVec.x = color3.r + colorVec.y = color3.g + colorVec.z = color3.b + } + + return colorVec +} + + + +void function PlayPlayerDeathSound( entity player ) +{ + if ( IsPlayerEliminated( player ) ) + EmitSoundOnEntity( player, "player_death_begin_elimination" ) + else + EmitSoundOnEntity( player, "Player_Death_Begin" ) +} + +void function StopPlayerDeathSound( entity player ) +{ + StopSoundOnEntity( player, "Player_Death_Begin" ) + EmitSoundOnEntity( player, "Player_Death_PrespawnTransition" ) +} + +function OnClientPlayerAlive( entity player ) +{ + player.Signal( "OnClientPlayerAlive" ) // TEMP; this should not be necessary, but IsWatchingKillReplay is wrong + player.EndSignal( "OnClientPlayerAlive" ) + + UpdateClientHudVisibility( player ) + + if ( IsWatchingReplay() ) + return + + if ( GetGameState() < eGameState.Playing ) + return +} + +function OnClientPlayerDying( entity player ) +{ + player.Signal( "OnClientPlayerDying" ) // TEMP; this should not be necessary, but IsWatchingKillReplay is wrong + player.EndSignal( "OnClientPlayerDying" ) + + entity player = GetLocalClientPlayer() + UpdateClientHudVisibility( player ) +// thread ShowDeathRecap( player ) + + if ( IsWatchingReplay() ) + return + + player.cv.deathTime = Time() + + thread DeathCamCheck( player ) +} + +void function ShowDeathRecap( entity player ) +{ + Assert( player == GetLocalClientPlayer() ) + + DisableCallingCardEvents() + + if ( player.e.recentDamageHistory.len() == 0 ) + return + + DamageHistoryStruct damageHistory = player.e.recentDamageHistory[ 0 ] + + entity attacker = damageHistory.attacker + + if ( !IsValid( attacker ) ) + return + + EndSignal( attacker, "OnDestroy" ) + + if ( !attacker.IsPlayer() ) + return + + if ( attacker.GetTeam() == player.GetTeam() ) + return + + wait( 1.0 ) + + CallsignEvent( eCallSignEvents.YOU, attacker, Localize( "#DEATH_SCREEN_KILLED_YOU" ) ) +} + +void function HideDeathRecap( entity player, var rui ) +{ + float minDisplayTime = 6.0 + float startTime = Time() + + waitthread DeathRecapHideDelay( player ) + wait( 0.5 ) + + float elapsedTime = Time() - startTime + if ( elapsedTime < minDisplayTime ) + wait( minDisplayTime - elapsedTime ) + + RuiSetBool( rui, "playOutro", true ) + RuiSetGameTime( rui, "outroStartTime", Time() ) + + EnableCallingCardEvents() +} + +void function DeathRecapHideDelay( entity player ) +{ + EndSignal( clGlobal.levelEnt, "LocalClientPlayerRespawned" ) + EndSignal( clGlobal.levelEnt, "OnSpectatorMode" ) + + WaitForever() +} + +void function DeathCamCheck( entity player ) +{ + wait GetRespawnButtonCamTime( player ) +} + +void function ServerCallback_ShowNextSpawnMessage( float nextSpawnTime ) +{ + entity player = GetLocalClientPlayer() + float camTime = GetRespawnButtonCamTime( player ) + + file.nextSpawnTime = nextSpawnTime + + if ( nextSpawnTime > Time() + camTime ) + thread ShowSpawnDelayMessage( nextSpawnTime ) +} + + +void function ShowSpawnDelayMessage( nextSpawnTime ) +{ + float waitTime = max( nextSpawnTime - Time(), 0 ) + + if ( waitTime < 1.0 ) + return + + entity player = GetLocalClientPlayer() + + //player.cv.nextSpawnTimeLabel.SetAlpha( 255 ) + //player.cv.nextSpawnTimeLabel.Show() + //player.cv.nextSpawnTimeLabel.SetAutoText( "#GAMEMODE_DEPLOYING_IN_N", HATT_GAME_COUNTDOWN_SECONDS, nextSpawnTime ) + // + //if ( !player.cv.nextSpawnTimeLabel.IsAutoText() ) + // player.cv.nextSpawnTimeLabel.EnableAutoText() + + while ( !IsAlive( player ) && waitTime > 0.0 ) + { + waitTime = max( nextSpawnTime - Time(), 0 ) + + AddPlayerHint( waitTime, 0.25, $"", "#GAMEMODE_DEPLOYING_IN_N", int( waitTime ) ) + + wait 1.0 + } +} +void function ServerCallback_HideNextSpawnMessage() +{ + entity player = GetLocalClientPlayer() + + HidePlayerHint( "#GAMEMODE_DEPLOYING_IN_N" ) +} + +float function GetWaveSpawnTime() +{ + return (file.nextSpawnTime) +} + +bool function IsPlayerEliminated( entity player ) +{ + return (player.GetPlayerGameStat( PGS_ELIMINATED ) > 0) +} + +function PlayerFieryDeath( player ) +{ + player.EndSignal( "OnDestroy" ) + player.EndSignal( "OnClientPlayerAlive" ) + clGlobal.levelEnt.EndSignal( "OnSpectatorMode" ) + + local offset = < 0, 0, 0 > + if ( player.IsTitan() ) + offset = < 0, 0, 96 > + + entity scriptRef = CreatePropDynamic( $"models/dev/empty_model.mdl", player.GetOrigin() + offset, player.GetAngles() ) + scriptRef.SetParent( player ) + + local fxHandle = StartParticleEffectOnEntity( scriptRef, GetParticleSystemIndex( $"P_burn_player" ), FX_PATTACH_ABSORIGIN_FOLLOW, -1 ) + + OnThreadEnd( + function () : ( fxHandle, scriptRef ) + { + EffectStop( fxHandle, false, false ) + if ( IsValid( scriptRef ) ) + scriptRef.Destroy() + } + ) + WaitForever() +} + + +function ServerCallback_GiveMatchLossProtection() +{ + clGlobal.showMatchLossProtection = true +} + +void function EnableDoDeathCallback( entity ent ) +{ + ent.DoDeathCallback( true ) +} + + + +int function UpdateSubText2ForRiffs( AnnouncementData announcement ) +{ + array riffTexts = [] + + if ( IsPilotEliminationBased() ) + riffTexts.append( "#GAMESTATE_NO_RESPAWNING" ) + + if ( Riff_FloorIsLava() ) + riffTexts.append( "#GAMEMODE_FLOOR_IS_LAVA_SUBTEXT2" ) + + if ( level.nv.minimapState == eMinimapState.Hidden ) + riffTexts.append( "#GAMESTATE_NO_MINIMAP" ) + + if ( level.nv.ammoLimit == eAmmoLimit.Limited ) + riffTexts.append( "#GAMESTATE_LIMITED_AMMUNITION" ) + + if ( level.nv.titanAvailability != eTitanAvailability.Default ) + { + switch ( level.nv.titanAvailability ) + { + case eTitanAvailability.Always: + riffTexts.append( "#GAMESTATE_UNLIMITED_TITANS" ) + break + case eTitanAvailability.Once: + riffTexts.append( "#GAMESTATE_ONE_TITAN" ) + break + case eTitanAvailability.Never: + riffTexts.append( "#GAMESTATE_NO_TITANS" ) + break + } + } + + if ( level.nv.allowNPCs != eAllowNPCs.Default ) + { + switch ( level.nv.allowNPCs ) + { + case eAllowNPCs.None: + //riffTexts.append( "#GAMESTATE_NO_MINIONS" ) + break + + case eAllowNPCs.GruntOnly: + riffTexts.append( "#GAMESTATE_GRUNTS_ONLY" ) + break + + case eAllowNPCs.SpectreOnly: + riffTexts.append( "#GAMESTATE_SPECTRES_ONLY" ) + break + } + } + + float pilotHealthMultiplier = GetCurrentPlaylistVarFloat( "pilot_health_multiplier", 0.0 ) + if ( pilotHealthMultiplier != 0.0 && pilotHealthMultiplier <= 1.5 ) + riffTexts.append( "#GAMESTATE_LOW_PILOT_HEALTH" ) + else if ( pilotHealthMultiplier > 1.5 ) + riffTexts.append( "#GAMESTATE_HIGH_PILOT_HEALTH" ) + + switch ( riffTexts.len() ) + { + case 1: + Announcement_SetSubText2( announcement, riffTexts[0] ) + break + case 2: + Announcement_SetSubText2( announcement, "#GAMEMODE_ANNOUNCEMENT_SUBTEXT_2", riffTexts[0], riffTexts[1] ) + break + case 3: + Announcement_SetSubText2( announcement, "#GAMEMODE_ANNOUNCEMENT_SUBTEXT_3", riffTexts[0], riffTexts[1], riffTexts[2] ) + break + case 4: + Announcement_SetSubText2( announcement, "#GAMEMODE_ANNOUNCEMENT_SUBTEXT_4", riffTexts[0], riffTexts[1], riffTexts[2], riffTexts[3] ) + break + case 5: + Announcement_SetSubText2( announcement, "#GAMEMODE_ANNOUNCEMENT_SUBTEXT_5", riffTexts[0], riffTexts[1], riffTexts[2], riffTexts[3], riffTexts[4] ) + break + + default: + Announcement_SetSubText2( announcement, "", "" ) + return 0 + } + + return riffTexts.len() +} + + +void function ServerCallback_GameModeAnnouncement() +{ + entity player = GetLocalClientPlayer() + string gameMode = GameRules_GetGameMode() + + if ( GameMode_GetCustomIntroAnnouncement( gameMode ) != null ) + { + void functionref(entity) func = GameMode_GetCustomIntroAnnouncement( gameMode ) + func(player) + return + } + + int team = player.GetTeam() + + local totalDuration = 0.0 + + AnnouncementData announcement + + if ( GetGameState() == eGameState.Epilogue ) + { + // never gets hit?? + announcement = Announcement_Create( "#GAMEMODE_EPILOGUE" ) + } + else + { + announcement = Announcement_Create( GAMETYPE_TEXT[gameMode] ) + announcement.announcementStyle = ANNOUNCEMENT_STYLE_BIG + + Announcement_SetIcon( announcement, GAMETYPE_ICON[gameMode] ) + Announcement_SetSubText( announcement, GAMEDESC_CURRENT ) + + if ( GameMode_IsDefined( gameMode ) ) + { + if ( GameMode_GetAttackDesc( gameMode ) != "" && team == level.nv.attackingTeam ) + Announcement_SetSubText( announcement, GameMode_GetAttackDesc( gameMode ) ) + + if ( GameMode_GetDefendDesc( gameMode ) != "" && team != level.nv.attackingTeam ) + Announcement_SetSubText( announcement, GameMode_GetDefendDesc( gameMode ) ) + } + } + + int numRiffs = UpdateSubText2ForRiffs( announcement ) + float announcementDuration = numRiffs + DEFAULT_GAMEMODE_ANNOUNCEMENT_DURATION + if ( gameMode == COLISEUM ) + announcementDuration = 2.3 //JFS: Make coliseum announcement disappear with the black bars. Note that the rui fade out sequence time is a floor on how low announcementDuration can be set to + + Announcement_SetDuration( announcement, announcementDuration ) + totalDuration += announcementDuration + + AnnouncementFromClass( player, announcement ) // TODO: team specific goals + + if ( clGlobal.showMatchLossProtection ) + { + announcementDuration = 2.0 + totalDuration += announcementDuration + delaythread( announcementDuration ) DeathHintDisplay( "#LATE_JOIN_NO_LOSS" ) + } + else if ( clGlobal.canShowLateJoinMessage ) + { + if ( level.nv.matchProgress > 5 || GetRoundsPlayed() > 0 ) + { + announcementDuration = 2.0 + totalDuration += announcementDuration + delaythread( announcementDuration ) DeathHintDisplay( "#LATE_JOIN" ) + } + } + clGlobal.showMatchLossProtection = false + clGlobal.canShowLateJoinMessage = false + + if ( Riff_FloorIsLava() ) + { + announcementDuration = 10.0 + totalDuration += announcementDuration + //printt( "Total duration delayed for lava announcement: " + totalDuration ) + delaythread( totalDuration ) PlayConversationToLocalClient( "floor_is_lava_announcement" ) + } +} + + +function MainHud_InitScoreBars( vgui, entity player, scoreGroup ) +{ + local hudScores = {} + vgui.s.scoreboardProgressBars <- hudScores + + local panel = vgui.GetPanel() + + hudScores.GameInfoBG <- scoreGroup.CreateElement( "GameInfoBG", panel ) + + string gameMode = GameRules_GetGameMode() + int friendlyTeam = player.GetTeam() + + #if HAS_GAMEMODES + if ( IsFFAGame() ) + { + return + } + #endif + + int enemyTeam = friendlyTeam == TEAM_IMC ? TEAM_MILITIA : TEAM_IMC + + if ( IsRoundBased() ) + { + level.scoreLimit[TEAM_IMC] <- GetRoundScoreLimit_FromPlaylist() + level.scoreLimit[TEAM_MILITIA] <- GetRoundScoreLimit_FromPlaylist() + } + else + { + level.scoreLimit[TEAM_IMC] <- GetScoreLimit_FromPlaylist() + level.scoreLimit[TEAM_MILITIA] <- GetScoreLimit_FromPlaylist() + } + + #if HAS_GAMEMODES + Assert( gameMode == GameRules_GetGameMode() ) + switch ( gameMode ) + { + case CAPTURE_THE_FLAG: + vgui.s.friendlyFlag <- scoreGroup.CreateElement( "FriendlyFlag", panel ) + vgui.s.enemyFlag <- scoreGroup.CreateElement( "EnemyFlag", panel ) + + vgui.s.friendlyFlagLabel <- scoreGroup.CreateElement( "FriendlyFlagLabel", panel ) + vgui.s.enemyFlagLabel <- scoreGroup.CreateElement( "EnemyFlagLabel", panel ) + + thread CaptureTheFlagThink( vgui, player ) + break + + case MARKED_FOR_DEATH: + case MARKED_FOR_DEATH_PRO: + thread MarkedForDeathHudThink( vgui, player, scoreGroup ) + break + } + #endif + + thread TitanEliminationThink( vgui, player ) + + thread RoundScoreThink( vgui, scoreGroup, player ) + + vgui.s.scoreboardProgressGroup <- scoreGroup + + hudScores.GameInfoBG.Show() + + local scoreboardArrays = {} + vgui.s.scoreboardArrays <- scoreboardArrays + + //if ( ShouldUsePlayerStatusCount() ) //Can't just do PilotEliminationBased check here because it isn't set when first connecting + //{ + // //ToDo: Eventually turn it on for normal Titan count too. Need to make sure "Titan ready but not called in yet" icon doesn't get hidden by this element + // hudScores.Player_Status_BG <- scoreGroup.CreateElement( "Player_Status_BG", panel ) + // hudScores.Player_Status_BG.Show() + // + // CreatePlayerStatusElementsFriendly( scoreboardArrays, scoreGroup, panel ) + // CreatePlayerStatusElementsEnemy( scoreboardArrays, scoreGroup, panel ) + // + // thread ScoreBarsPlayerStatusThink( vgui, player, scoreboardArrays.FriendlyPlayerStatusCount, scoreboardArrays.EnemyPlayerStatusCount ) + //} + //else + //{ + // hudScores.Player_Status_BG <- scoreGroup.CreateElement( "Player_Status_BG", panel ) + // hudScores.Player_Status_BG.Show() + // thread ScoreBarsTitanCountThink( vgui, player, hudScores.FriendlyTitanCount, hudScores.FriendlyTitanReadyCount, hudScores.EnemyTitanCount ) + //} + + if ( IsWatchingReplay() ) + vgui.s.scoreboardProgressGroup.Hide() + + UpdatePlayerStatusCounts() + + if ( IsSuddenDeathGameMode() ) + thread SuddenDeathHUDThink( vgui, player ) +} + +function CaptureTheFlagThink( vgui, entity player ) +{ + vgui.EndSignal( "OnDestroy" ) + + if ( vgui instanceof C_VGuiScreen ) + player.EndSignal( "OnDestroy" ) + + vgui.s.friendlyFlag.Show() + vgui.s.enemyFlag.Show() + vgui.s.friendlyFlagLabel.Show() + vgui.s.enemyFlagLabel.Show() + + while ( GetGameState() < eGameState.Epilogue ) + { + if ( "friendlyFlagState" in player.s ) + { + switch ( player.s.friendlyFlagState ) + { + case eFlagState.None: + vgui.s.friendlyFlagLabel.SetText( "" ) + break + case eFlagState.Home: + vgui.s.friendlyFlagLabel.SetText( "#GAMEMODE_FLAG_HOME" ) + break + case eFlagState.Held: + vgui.s.friendlyFlagLabel.SetText( player.s.friendlyFlagCarrierName ) + break + case eFlagState.Away: + vgui.s.friendlyFlagLabel.SetText( "#GAMEMODE_FLAG_DROPPED" ) + break + } + + switch ( player.s.enemyFlagState ) + { + case eFlagState.None: + vgui.s.enemyFlagLabel.SetText( "" ) + break + case eFlagState.Home: + vgui.s.enemyFlagLabel.SetText( "#GAMEMODE_FLAG_HOME" ) + break + case eFlagState.Held: + vgui.s.enemyFlagLabel.SetText( player.s.enemyFlagCarrierName ) + break + case eFlagState.Away: + vgui.s.enemyFlagLabel.SetText( "#GAMEMODE_FLAG_DROPPED" ) + break + } + } + + clGlobal.levelEnt.WaitSignal( "FlagUpdate" ) + + WaitEndFrame() + } + + vgui.s.friendlyFlag.Hide() + vgui.s.enemyFlag.Hide() + vgui.s.friendlyFlagLabel.Hide() + vgui.s.enemyFlagLabel.Hide() +} + + + +function TitanEliminationThink( vgui, entity player ) +{ + vgui.EndSignal( "OnDestroy" ) + player.EndSignal( "OnDestroy" ) + + if ( player != GetLocalClientPlayer() ) + return + + OnThreadEnd( + function() : ( player ) + { + if ( !IsValid( player ) ) + return + + if ( player.cv.hud.s.lastEventNotificationText == "#GAMEMODE_CALLINTITAN_COUNTDOWN" ) + HideEventNotification() + } + ) + + while ( true ) + { + if ( Riff_EliminationMode() == eEliminationMode.Titans ) + { + if ( IsAlive( player ) && GamePlayingOrSuddenDeath() && level.nv.secondsTitanCheckTime > Time() && !player.IsTitan() && !IsValid( player.GetPetTitan() ) && player.GetNextTitanRespawnAvailable() >= 0 ) + { + SetTimedEventNotificationHATT( level.nv.secondsTitanCheckTime - Time(), "#GAMEMODE_CALLINTITAN_COUNTDOWN", HATT_GAME_COUNTDOWN_SECONDS_MILLISECONDS, level.nv.secondsTitanCheckTime ) + } + else if ( player.cv.hud.s.lastEventNotificationText == "#GAMEMODE_CALLINTITAN_COUNTDOWN" ) + { + HideEventNotification() + } + } + else if ( Riff_EliminationMode() == eEliminationMode.Pilots ) + { + + } + + WaitSignal( player, "UpdateLastTitanStanding", "PetTitanChanged", "OnDeath" ) + } +} + +function RoundScoreThink( var vgui, var scoreGroup, entity player ) +{ + vgui.EndSignal( "OnDestroy" ) + player.EndSignal( "OnDestroy" ) + + FlagWait( "EntitiesDidLoad" ) //Have to do this because the nv that determines if RoundBased or not might not get set yet + + int friendlyTeam = player.GetTeam() + int enemyTeam = friendlyTeam == TEAM_IMC ? TEAM_MILITIA : TEAM_IMC + + local isRoundBased = IsRoundBased() + bool showRoundScore = true + int roundScoreLimit = GetRoundScoreLimit_FromPlaylist() + int scoreLimit = GetScoreLimit_FromPlaylist() + + if ( isRoundBased && showRoundScore ) + { + level.scoreLimit[TEAM_IMC] <- roundScoreLimit + level.scoreLimit[TEAM_MILITIA] <- roundScoreLimit + } + else + { + level.scoreLimit[TEAM_IMC] <- scoreLimit + level.scoreLimit[TEAM_MILITIA] <- scoreLimit + } + + local hudScores = vgui.s.scoreboardProgressBars + + while ( true ) + { + if ( isRoundBased && showRoundScore ) + { + hudScores.Friendly_Number.SetAutoText( "", HATT_FRIENDLY_TEAM_ROUND_SCORE, 0 ) + hudScores.Enemy_Number.SetAutoText( "", HATT_ENEMY_TEAM_ROUND_SCORE, 0 ) + } + + hudScores.ScoresFriendly.SetBarProgressRemap( 0, level.scoreLimit[friendlyTeam], 0.011, 0.96 ) + hudScores.ScoresEnemy.SetBarProgressRemap( 0, level.scoreLimit[enemyTeam], 0.011, 0.96 ) + wait 1.0 + } +} + +function CreatePlayerStatusElementsFriendly( scoreboardArrays, scoreGroup, panel ) +{ + scoreboardArrays.FriendlyPlayerStatusCount <- arrayofsize( 8 ) + + for ( int i = 0; i < 8; ++i ) + { + scoreboardArrays.FriendlyPlayerStatusCount[ i ] = scoreGroup.CreateElement( "Friendly_Player_Status_" + i, panel ) + } +} + +function CreatePlayerStatusElementsEnemy( scoreboardArrays, scoreGroup, panel ) +{ + scoreboardArrays.EnemyPlayerStatusCount <- arrayofsize( 8 ) + + for ( int i = 0; i < 8; ++i ) + { + scoreboardArrays.EnemyPlayerStatusCount[ i ] = scoreGroup.CreateElement( "Enemy_Player_Status_" + i, panel ) + } +} + + +function ScoreBarsPlayerStatusThink( vgui, entity player, friendlyPlayerStatusElem, enemyPlayerStatusElem ) +{ + int friendlyTeam = player.GetTeam() + int enemyTeam = friendlyTeam == TEAM_IMC ? TEAM_MILITIA : TEAM_IMC + + vgui.EndSignal( "OnDestroy" ) + + while( true ) + { + clGlobal.levelEnt.WaitSignal( "UpdatePlayerStatusCounts" ) + + if ( IsWatchingReplay() ) //Don't update visibility if the scoreboardgroup should be hidden + continue + + UpdatePlayerStatusForTeam( friendlyTeam, friendlyPlayerStatusElem, $"ui/icon_status_titan_friendly", $"ui/icon_status_pilot_friendly", $"ui/icon_status_burncard_friendly", $"ui/icon_status_burncard_friendly" ) + UpdatePlayerStatusForTeam( enemyTeam, enemyPlayerStatusElem, $"ui/icon_status_titan_enemy", $"ui/icon_status_pilot_enemy", $"ui/icon_status_burncard_enemy", $"ui/icon_status_burncard_enemy" ) + } +} + +function CountPlayerStatusTypes( array teamPlayers ) +{ + table resultTable = { + titanWithBurnCard = 0, + titan = 0, + pilotWithBurnCard = 0 + pilot = 0, + } + + foreach ( player in teamPlayers ) + { + entity playerPetTitan = player.GetPetTitan() + + if ( !IsAlive( player ) ) + { + if ( IsAlive( playerPetTitan ) ) + resultTable.titan++ + } + else + { + if ( player.IsTitan() ) + resultTable.titan++ + else if ( IsAlive( playerPetTitan ) ) + resultTable.titan++ + else + resultTable.pilot++ + } + + } + + return resultTable +} + + +function UpdatePlayerStatusForTeam( int team, teamStatusElem, titanImage, pilotImage, pilotBurnCardImage, titanBurnCardImage ) +{ + array teamPlayers = GetPlayerArrayOfTeam( team ) + local teamResultTable = CountPlayerStatusTypes( teamPlayers ) + + int maxElems = 8 + + int index = 0 + int currentElem = 0 + + for ( index = 0; index < teamResultTable.titanWithBurnCard && currentElem < maxElems; index++, currentElem++ ) + { + teamStatusElem[ currentElem ].Show() + teamStatusElem[ currentElem ].SetImage( titanBurnCardImage ) + } + + for ( index = 0; index < teamResultTable.titan && index < maxElems; index++, currentElem++ ) + { + teamStatusElem[ currentElem ].Show() + teamStatusElem[ currentElem ].SetImage( titanImage ) + } + + for ( index = 0; index < teamResultTable.pilotWithBurnCard && index < maxElems; index++, currentElem++ ) + { + teamStatusElem[ currentElem ].Show() + teamStatusElem[ currentElem ].SetImage( pilotBurnCardImage ) + } + + for ( index = 0; index < teamResultTable.pilot && index < maxElems; index++, currentElem++ ) + { + teamStatusElem[ currentElem ].Show() + teamStatusElem[ currentElem ].SetImage( pilotImage ) + } + + for( ; currentElem < maxElems; currentElem++ ) + { + teamStatusElem[ currentElem ].Hide() + } +} +function SuddenDeathHUDThink( vgui, entity player ) +{ + Signal( player, "SuddenDeathHUDThink" ) + player.EndSignal( "SuddenDeathHUDThink" ) + vgui.EndSignal( "OnDestroy" ) + + while ( GetGameState() != eGameState.SuddenDeath ) + WaitSignal( player, "GameStateChanged" ) + + EndSignal( player, "GameStateChanged" ) + + local hudScores = vgui.s.scoreboardProgressBars + + OnThreadEnd( + function() : ( hudScores, player ) + { + if ( !IsValid( hudScores ) ) + return + + hudScores.GameInfo_Label.SetColor( 255, 255, 255, 255 ) + + string restoredGameModeLabelText = GAMETYPE_TEXT[ GameRules_GetGameMode() ] + hudScores.GameModeLabel.SetText( restoredGameModeLabelText ) + + if ( player == GetLocalClientPlayer() ) + { + local scoreElemsClient = player.cv.clientHud.s.mainVGUI.s.scoreboardProgressGroup.elements + scoreElemsClient.GameModeLabel.SetText( restoredGameModeLabelText ) + } + } + ) + + string gameModeLabelText = "" + + switch ( GAMETYPE ) + { + case CAPTURE_THE_FLAG: + gameModeLabelText = "#GAMEMODE_CAPTURE_THE_FLAG_SUDDEN_DEATH" + break + + case TEAM_DEATHMATCH: + case HARDCORE_TDM: + gameModeLabelText = "#GAMEMODE_PILOT_HUNTER_SUDDEN_DEATH" + break + + default: + gameModeLabelText = GAMETYPE_TEXT[ GameRules_GetGameMode() ] + } + + hudScores.GameModeLabel.SetText( gameModeLabelText ) + + if ( player == GetLocalClientPlayer() ) + { + local scoreElemsClient = player.cv.clientHud.s.mainVGUI.s.scoreboardProgressGroup.elements + scoreElemsClient.GameModeLabel.SetText( gameModeLabelText ) + } + + float startTime = Time() + float pulseFrac = 0.0 + + while ( true ) + { + pulseFrac = Graph( GetPulseFrac( 1.0, startTime ), 0.0, 1.0, 0.05, 1.0 ) + hudScores.GameInfo_Label.SetColor( 255, 255, 255, 255 * pulseFrac ) + + wait( 0.0 ) + } +} diff --git a/Northstar.Client/mod/scripts/vscripts/client/cl_sp_player.gnut b/Northstar.Client/mod/scripts/vscripts/client/cl_sp_player.gnut new file mode 100644 index 000000000..cb27b4e3f --- /dev/null +++ b/Northstar.Client/mod/scripts/vscripts/client/cl_sp_player.gnut @@ -0,0 +1,1863 @@ +untyped + +global function ClPlayer_Init +global function SCB_CheckPoint + +global function PlayIt +global function JumpRandomlyForever + +global function ClientCodeCallback_PlayerDidDamage +global function ClientCodeCallback_PlayerSpawned +//global function ClientCodeCallback_OnHudReloadScheme +global function ClientCodeCallback_HUDThink +global function Player_AddPlayer +global function Player_AddClient + +global function ServerCallback_PlayerConnectedOrDisconnected +global function ClientCodeCallback_PlayerDisconnected +global function ServerCallback_PlayerChangedTeams +//global function ClientCodeCallback_OnModelChanged +global function ServerCallback_RodeoerEjectWarning +global function ServerCallback_PlayScreenFXWarpJump +global function PlayShieldBreakEffect +global function HandleDoomedState +global function CoreReadyMessage + +global function OnClientPlayerAlive +global function OnClientPlayerDying + +global function ServerCallback_ShowNextSpawnMessage +global function GetWaveSpawnTime +global function ServerCallback_HideNextSpawnMessage + +global function ClientCodeCallback_OnHealthChanged +global function Pressed_TitanNextMode +global function ClientCodeCallback_OnGib +global function ClientPilotSpawned +global function AddCallback_OnPlayerDisconnected + +global function IsPlayerEliminated + +global function ServerCallback_GiveMatchLossProtection +global function ServerCallback_OnEntityKilled +global function ServerCallback_OnTitanKilled + +global function ShouldShowSpawnAsTitanHint +global function ServerCallback_SetAssistInformation + +global function GetShieldEffectCurrentColor +global function ResetAbilityBindings + +global function ServerCallback_ShowDisembarkHint + +#if DEV +global function BloodSprayDecals_Toggle +#endif + +struct { + var orbitalstrike_tracer = null + var law_missile_tracer = null + float nextSpawnTime = 0.0 + var UI_friendlyText +} file + +struct BloodDecalParams +{ + float traceDist + float secondaryTraceDist + asset fxType + asset secondaryFxType +} + +void function ClPlayer_Init() +{ + ClPilotJumpjet_Init() + ClDamageIndicator_Init() + + ClPlayer_Common_Precache() + + RegisterSignal( "OnAnimationDone" ) + RegisterSignal( "OnAnimationInterrupted" ) + RegisterSignal( "OnBleedingOut" ) + RegisterSignal( "PanelAlphaOverTime" ) + RegisterSignal( "OnClientPlayerAlive" ) + RegisterSignal( "OnClientPlayerDying" ) + RegisterSignal( "StopAlertCore" ) + RegisterSignal( "HealthChanged" ) + + FlagInit( "DamageDistancePrint" ) + FlagInit( "EnableTitanModeChange", true ) + FlagInit( "EnableBloodSprayDecals", true ) + + level.vduOpen <- false + level.canSpawnAsTitan <- false + level.grenadeIndicatorEnabled <- true + level.clientsLastKiller <- null + + AddCreateCallback( "player", SetupPlayerAnimEvents ) + Assert ( IsSingleplayer() ) + AddCreateCallback( "player", SpClientPlayerInit ) + SpSharedInit() + + AddCallback_OnPlayerLifeStateChanged( PlayerADSDof ) + AddCreateCallback( "first_person_proxy", SetupFirstPersonProxyEvents ) + AddCreateCallback( "predicted_first_person_proxy", SetupFirstPersonProxyEvents ) + + AddCreateCallback( "player", EnableDoDeathCallback ) + AddCreateCallback( "npc_titan", EnableDoDeathCallback ) + + if ( !IsLobby() ) + { + RegisterServerVarChangeCallback( "gameEndTime", UpdateRespawnHUD ) + } + + AddCreateCallback( "titan_soul", CreateCallback_TitanSoul ) + + file.orbitalstrike_tracer = PrecacheParticleSystem( $"Rocket_Smoke_Large" ) + //DEBUG Remove when bug is fixed. + file.law_missile_tracer = PrecacheParticleSystem( $"wpn_orbital_rocket_tracer" ) + + level.menuHideGroups <- {} + + //PrecacheParticleSystem( SHIELD_FX ) + + level.spawnAsTitanSelected <- false + + AddPlayerFunc( Player_AddPlayer ) + AddCreatePilotCockpitCallback( ClearDisembarkHint ) +} + +entity function FindEnemyRodeoParent( entity player ) +{ + entity ent = player.GetParent() + if ( ent == null ) + return null + + if ( !ent.IsTitan() ) + return null + + if ( ent == player.GetPetTitan() ) + return null + + if ( ent.GetTeam() == player.GetTeam() ) + return null + + return ent +} + +void function SpClientPlayerInit( entity player ) +{ + player.ClientCommand( "clear_loading_progress_detente" ) + player.ClientCommand( "clear_loading_progress_sp_text" ) +} + +void function ClientCodeCallback_PlayerSpawned( entity player ) +{ + if ( !IsValid( player ) ) + return + + // exists on server and client. Clear it when you respawn. + ClearRecentDamageHistory( player ) + + if ( player == GetLocalViewPlayer() ) + { + foreach ( callbackFunc in clGlobal.onLocalViewPlayerSpawnedCallbacks ) + { + callbackFunc( player ) + } + } + + if ( player == GetLocalClientPlayer() ) + { + foreach ( callbackFunc in clGlobal.onLocalClientPlayerSpawnedCallbacks ) + { + thread callbackFunc( player ) + } + } +} + + +void function CoreReadyMessage( entity player, bool isQuick = false ) +{ + if ( !GamePlayingOrSuddenDeath() ) + return + + if ( !IsAlive( player ) ) + return + + if ( GetDoomedState( player ) ) + return + + entity weapon = player.GetOffhandWeapon( OFFHAND_EQUIPMENT ) + + if ( weapon == null ) // JFS + return + + string coreOnlineMessage = expect string( weapon.GetWeaponInfoFileKeyField( "readymessage" ) ) + string coreOnlineHint = expect string( weapon.GetWeaponInfoFileKeyField( "readyhint" ) ) + + if ( isQuick ) + AnnouncementMessageQuick( player, coreOnlineMessage, coreOnlineHint, TEAM_COLOR_YOU ) + else + AnnouncementMessage( player, coreOnlineMessage, coreOnlineHint, TEAM_COLOR_YOU ) +} + +void function ClientPilotSpawned( entity player ) +{ + player.EndSignal( "SettingsChanged" ) + player.EndSignal( "OnDestroy" ) + player.EndSignal( "OnDeath" ) + + if ( player != GetLocalViewPlayer() ) + thread ParentedPlayerJets( player ) +} + +void function Player_AddClient( entity player ) +{ + if ( GetCurrentPlaylistVarInt( "titan_mode_change_allowed", 1 ) ) + RegisterConCommandTriggeredCallback( "+scriptCommand2", Pressed_TitanNextMode ) + + RegisterConCommandTriggeredCallback( "+scriptCommand2", Pressed_RequestTitanfall ) + RegisterConCommandTriggeredCallback( "+scriptCommand2", Pressed_ActivateMobilityGhost ) + + RegisterConCommandTriggeredCallback( "titan_loadout_select", Pressed_TitanLoadoutSelect ) + RegisterConCommandTriggeredCallback( "scoreboard_focus", Pressed_TitanLoadoutSelect ) + RegisterConCommandTriggeredCallback( "scoreboard_toggle_focus", Pressed_TitanLoadoutSelect ) + + Create_DamageIndicatorHUD() + + if ( !IsLobby() ) + { + player.EnableHealthChangedCallback() + + player.cv.deathTime <- 0.0 + player.cv.lastSpawnTime <- 0.0 + player.cv.deathOrigin <- <0.0, 0.0, 0.0> + player.cv.roundSpawnCount <- 0 + + thread CinematicIntroScreen() + } +} + +void function Player_AddPlayer( entity player ) +{ + player.s.weaponUpdateData <- {} + + player.s.trackedAttackers <- {} // for titans + player.classChanged = true +} + +function Pressed_RequestTitanfall( entity player ) +{ + //we are not using titan call-ins in SP for this game so lets disable it since it was still playing sounds and doing all the logic. + const DISABLE_FOR_R2 = true + if ( DISABLE_FOR_R2 ) + return + + if ( player.IsTitan() ) + return + + if ( !IsAlive( player ) ) + return + + if ( player.IsPhaseShifted() ) + return + + if ( player.GetRemoteTurret() != null ) + return + + if ( !IsAlive( player.GetPetTitan() ) ) + { + if ( Riff_TitanQueueLimit() > 0 ) + { + local queuePosition = player.nv.titanQueueNum + if ( queuePosition < 0 || queuePosition >= Riff_TitanQueueLimit() ) + { + player.ClientCommand( "RequestTitanQueueToggle" ) + return + } + } + + #if DEV + printt( player.GetEntIndex(), "Requested replacement Titan from eye pos " + player.EyePosition() + " view angles " + player.EyeAngles() + " player origin " + player.GetOrigin() + " map " + GetMapName() ) + #endif + + player.ClientCommand( "ClientCommand_RequestTitan" ) //Send client command regardless of whether we can call the titan in or not. Server decides + + if ( clGlobal.isAnnouncementActive && clGlobal.activeAnnouncement.messageText == "#HUD_TITAN_READY" ) + { + clGlobal.levelEnt.Signal( "AnnoucementPurge" ) + } + + //PlayMusic( "Music_FR_Militia_TitanFall1" ) + EmitSoundOnEntity( player, "titan_callin" ) + return + } +} + +function Pressed_TitanNextMode( entity player ) +{ + if ( player.IsTitan() ) + return + + if ( IsWatchingReplay() ) + return + + if ( !IsAlive( player ) ) + return + + if ( player.IsPhaseShifted() ) + return + + if ( !IsAlive( player.GetPetTitan() ) ) + return + + if ( !Flag( "EnableTitanModeChange" ) ) + return + + // cannot change modes while titan is incoming + if ( player.GetHotDropImpactTime() ) + return + + player.ClientCommand( "TitanNextMode" ) + + local newMode = player.GetPetTitanMode() + 1 + if ( newMode == eNPCTitanMode.MODE_COUNT ) + newMode = eNPCTitanMode.FOLLOW + + SetAutoTitanModeHudIndicator( player, newMode ) + + local guardModeAlias = GenerateTitanOSAlias( player, AUTO_TITAN_GUARD_MODE_DIAG_SUFFIX ) + local followModeAlias = GenerateTitanOSAlias( player, AUTO_TITAN_FOLLOW_MODE_DIAG_SUFFIX ) + + // prevent the sounds from stomping each other if button is pressed rapidly + StopSoundOnEntity( player, guardModeAlias ) + StopSoundOnEntity( player, AUTO_TITAN_GUARD_MODE_SOUND ) + StopSoundOnEntity( player, followModeAlias ) + StopSoundOnEntity( player, AUTO_TITAN_FOLLOW_MODE_SOUND ) + + if ( newMode == eNPCTitanMode.FOLLOW ) + { + EmitSoundOnEntity( player, followModeAlias ) + EmitSoundOnEntity( player, AUTO_TITAN_FOLLOW_MODE_SOUND ) + } + else if ( newMode == eNPCTitanMode.STAY ) + { + EmitSoundOnEntity( player, guardModeAlias ) + EmitSoundOnEntity( player, AUTO_TITAN_GUARD_MODE_SOUND ) + } +} + +/* +void function ClientCodeCallback_OnHudReloadScheme() +{ +} +*/ + + + +function Pressed_TitanLoadoutSelect( entity player ) +{ + if ( !player.IsTitan() ) + return + + if ( !IsAlive( player ) ) + return + + int ceFlags = player.GetCinematicEventFlags() + + // disable this menu in titan 3P + if ( ceFlags & CE_FLAG_TITAN_3P_CAM ) + return + + // One day, turn these back on if we allow player to set BT's loadout while disembarked + // + // if ( player.IsPhaseShifted() ) + // return + + // if ( player.GetRemoteTurret() != null ) + // return + + // if ( !IsAlive( player.GetPetTitan() ) ) + // return + + + RunUIScript( "OpenSPTitanLoadoutMenu" ) +} + + + +void function ClientCodeCallback_HUDThink() +{ + PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink ) + + entity player = GetLocalViewPlayer() + + if ( !player.p.playerScriptsInitialized ) + { + PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink ) + return + } + + if ( !IsMenuLevel() ) + { + PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink_4 ) + + PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink_4 ) + + PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink_5 ) + UpdateVoiceHUD() + PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink_5 ) + + UpdateScreenFade() + + entity clientPlayer = GetLocalClientPlayer() + if ( !IsWatchingReplay() && clientPlayer.classChanged ) + ClientPlayerClassChanged( clientPlayer, clientPlayer.GetPlayerClass() ) + + PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink_6 ) + SmartAmmo_LockedOntoWarningHUD_Update() + WeaponFlyoutThink( player ) + PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink_6 ) + } + + PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink ) +} + +function ClientPlayerClassChanged( entity player, newClass ) +{ + //printl( "ClientPlayerClassChanged to " + player.GetPlayerClass() ) + player.classChanged = false + + level.vduOpen = false // vdu goes away when class changes + + Assert( !IsServer() ) + Assert( newClass, "No class " ) + + ResetAbilityBindings( player, expect string( newClass ) ) +} + +void function ResetAbilityBindings( entity player, string newClass ) +{ + switch ( newClass ) + { + case "titan": + SetStandardAbilityBindingsForTitan( player ) + SetAbilityBinding( player, 6, "weaponSelectOrdnance", "weaponSelectOrdnance" ) // "+ability 6" + + if ( IsConversationPlaying() ) + SetAbilityBinding( player, 1, "+scriptCommand2", "-scriptCommand2" ) // "+ability 1" + + LinkButtonPair( -1, -1, -1 ) + + break + + case level.pilotClass: + SetStandardAbilityBindingsForPilot( player ) + SetAbilityBinding( player, 6, "weaponSelectOrdnance", "weaponSelectOrdnance" ) // "+ability 6" + + LinkButtonPair( IN_OFFHAND0, IN_OFFHAND1, IN_OFFHAND3 ) + + if ( clGlobal.isAnnouncementActive && (clGlobal.activeAnnouncement.messageText == "#HUD_CORE_ONLINE_STRYDER" || + clGlobal.activeAnnouncement.messageText == "#HUD_CORE_ONLINE_ATLAS" || + clGlobal.activeAnnouncement.messageText == "#HUD_CORE_ONLINE_OGRE" ) ) + { + clGlobal.levelEnt.Signal( "AnnoucementPurge" ) + } + break + + case "spectator": + LinkButtonPair( -1, -1, -1 ) + break + + default: + Assert( 0, "Unknown class " + newClass ) + } +} + +function ShouldShowSpawnAsTitanHint( entity player ) +{ + if ( Time() - player.cv.deathTime < GetRespawnButtonCamTime( player ) ) + return false + + if ( GetGameState() < eGameState.Playing ) + return false + + if ( GetGameState() == eGameState.SwitchingSides ) + return false + + return !IsPlayerEliminated( player ) +} + +function ServerCallback_PlayerChangedTeams( player_eHandle, oldTeam, newTeam ) +{ +} + +function ServerCallback_PlayerConnectedOrDisconnected( player_eHandle, state ) +{ + entity player = GetEntityFromEncodedEHandle( player_eHandle ) + PlayerConnectedOrDisconnected( player, state ) + + if ( !IsLobby() || !IsConnected() ) + UpdatePlayerStatusCounts() +} + +void function AddCallback_OnPlayerDisconnected( void functionref( entity ) callbackFunc ) +{ + Assert( !clGlobal.onPlayerDisconnectedFuncs.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnPlayerDisconnected" ) + + clGlobal.onPlayerDisconnectedFuncs.append( callbackFunc ) +} + +void function ClientCodeCallback_PlayerDisconnected( entity player, string cachedPlayerName ) +{ + PlayerConnectedOrDisconnected( player, 0, cachedPlayerName ) + + if ( ShouldUpdatePlayerStatusCounts() ) + UpdatePlayerStatusCounts() + + // Added via AddCallback_OnPlayerDisconnected + foreach ( callbackFunc in clGlobal.onPlayerDisconnectedFuncs ) + { + callbackFunc( player ) + } +} + +function ShouldUpdatePlayerStatusCounts() +{ + if ( GetGameState() < eGameState.WaitingForPlayers ) + return false + + if ( !IsLobby() ) + return true + + if ( !IsConnected() ) + return true + + return false +} + +function PlayerConnectedOrDisconnected( entity player, state, string disconnectingPlayerName = "" ) +{ +} + +void function ClientCodeCallback_PlayerDidDamage( PlayerDidDamageParams params ) +{ + entity attacker = GetLocalViewPlayer() + if ( !IsValid( attacker ) ) + return + + entity victim = params.victim + if ( !IsValid( victim ) ) + return + + vector damagePosition = params.damagePosition + int hitBox = params.hitBox + int damageType = params.damageType + float damageAmount = params.damageAmount + int damageFlags = params.damageFlags + int hitGroup = params.hitGroup + entity weapon = params.weapon + float distanceFromAttackOrigin = params.distanceFromAttackOrigin + + bool playHitSound = true + bool showCrosshairHitIndicator = true + bool hitIneffective = false + bool victimIsHeavyArmor = false + bool isCritShot = (damageType & DF_CRITICAL) ? true : false + bool isHeadShot = (damageType & DF_HEADSHOT) ? true : false + bool isKillShot = (damageType & DF_KILLSHOT) ? true : false + bool isMelee = (damageType & DF_MELEE) ? true : false + bool isExplosion = (damageType & DF_EXPLOSION) ? true : false + bool isBullet = (damageType & DF_BULLET) ? true : false + bool isShotgun = (damageType & DF_SHOTGUN) ? true : false + bool isDoomFatality = (damageType & DF_DOOM_FATALITY) ? true : false + bool isDoomProtected = ((damageType & DF_DOOM_PROTECTED) && !isDoomFatality) ? true : false + victimIsHeavyArmor = victim.GetArmorType() == ARMOR_TYPE_HEAVY + + isDoomFatality = false + isDoomProtected = false + + if ( isDoomProtected ) + RegisterDoomProtectionHintDamage( damageAmount ) + + bool playKillSound = isKillShot + + if ( !attacker.IsTitan() ) + { + if ( victimIsHeavyArmor ) + { + showCrosshairHitIndicator = true + if ( victim.IsTitan() ) + hitIneffective = false //!IsHitEffectiveVsTitan( victim, damageType ) + else + hitIneffective = isCritShot || isHeadShot || !IsHitEffectiveVsNonTitan( victim, damageType ) + } + else + { + switch ( victim.GetSignifierName() ) + { + case "npc_super_spectre": + //if ( !( damageType & DF_CRITICAL ) ) + // hitIneffective = true + + default: + if ( (damageType & DF_BULLET && damageType & DF_MAX_RANGE) ) + hitIneffective = true + break + } + } + } + else + { + if ( victim.IsTitan() && victim.IsPlayer() ) + { + if ( PlayerHasPassive( victim, ePassives.PAS_BERSERKER ) ) + hitIneffective = true + } + } + + if ( damageType & DF_MAX_RANGE && damageType & DF_BULLET ) + // TODO: this is crap; these damage types should just send DF_NO_HITBEEP + playHitSound = false + + if ( damageType & DF_TITAN_STEP ) + // TODO: this is crap; these damage types should just send DF_NO_HITBEEP + { + playHitSound = false + playKillSound = false + } + + if ( damageType & DF_MELEE ) + // TODO: this is crap; these damage types should just send DF_NO_HITBEEP + { + playHitSound = false + playKillSound = false + } + + if ( damageType & DF_NO_HITBEEP ) + { + playHitSound = false + playKillSound = false + } + + if ( damageFlags & DAMAGEFLAG_VICTIM_HAS_VORTEX ) + showCrosshairHitIndicator = false + + if ( damageType & DF_SHIELD_DAMAGE ) + { + PlayShieldHitEffect( params ) + showCrosshairHitIndicator = true + } + else if ( damageAmount <= 0 ) + { + playHitSound = false + playKillSound = false + showCrosshairHitIndicator = false + } + + if ( damageType & DF_NO_INDICATOR ) + { + playHitSound = false + playKillSound = false + showCrosshairHitIndicator = false + } + + if ( isDoomProtected ) + playHitSound = false + + if ( showCrosshairHitIndicator ) + { + Tracker_PlayerAttackedTarget( attacker, victim ) + + //if ( hitIneffective ) + // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_INEFFECTIVE ) + //else + // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_NORMAL ) + // + //if ( (isCritShot || isDoomFatality) && !isDoomProtected ) + // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_CRITICAL ) + // + //if ( isHeadShot ) + // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_HEADSHOT ) + + if ( IsMultiplayer() && !victim.IsTitan() && !victim.IsHologram() ) + PROTO_HitIndicatorEffect( attacker, victim, damagePosition, isHeadShot, isKillShot ) + + if ( isKillShot ) + KillShotBloodSpray( attacker, victim, damagePosition, isExplosion, isBullet, isShotgun ) + + if ( victim.IsTitan() && isKillShot ) + ClientScreenShake( 8, 10, 1, Vector( 0, 0, 0 ) ) + + BloodSprayDecals( attacker, victim, damagePosition, damageAmount, isHeadShot, isKillShot, isMelee, isExplosion, isBullet, isShotgun ) + + DamageFlyout( damageAmount, damagePosition, victim, isHeadShot || isCritShot, hitIneffective ) + } + + bool playedHitSound = false + if ( playHitSound ) + { + if ( isHeadShot ) + playedHitSound = PlayHeadshotConfirmSound( attacker, victim, isKillShot ) + else if ( playKillSound ) + playedHitSound = PlayKillshotConfirmSound( attacker, victim, damageType ) + } + + if ( IsSpectre( victim ) ) + { + if ( isHeadShot ) + victim.Signal( "SpectreGlowEYEGLOW" ) + } + + // Play a hit sound effect if we didn't play a kill shot sound, and other conditions are met + if ( playHitSound && IsAlive( victim ) && !playedHitSound ) + { + PlayHitSound( victim, attacker, damageFlags, isCritShot, victimIsHeavyArmor, isKillShot, hitGroup ) + } + + if ( PlayerHasPassive( attacker, ePassives.PAS_SMART_CORE ) && isKillShot ) + { + attacker.p.smartCoreKills++ + } + + foreach ( callback in clGlobal.onLocalPlayerDidDamageCallback ) + { + callback( attacker, victim, damagePosition, damageType ) + } +} + +void function PlayHitSound( entity victim, entity attacker, int damageFlags, bool isCritShot, bool victimIsHeavyArmor, bool isKillShot, int hitGroup ) +{ + if ( damageFlags & DAMAGEFLAG_VICTIM_INVINCIBLE ) + { + EmitSoundOnEntity( attacker, "Player.HitbeepInvincible" ) + } + else if ( damageFlags & DAMAGEFLAG_VICTIM_HAS_VORTEX ) + { + EmitSoundOnEntity( attacker, "Player.HitbeepVortex" ) + } + else if ( isCritShot && victimIsHeavyArmor ) + { + EmitSoundOnEntity( attacker, "titan_damage_crit" ) + } + else if ( isCritShot ) + { + EmitSoundOnEntity( attacker, "Player.Hitbeep_crit" ) + } + else + { + EmitSoundOnEntity( attacker, "Player.Hitbeep" ) + } +} + +function PROTO_HitIndicatorEffect( entity player, entity victim, vector damagePosition, bool isHeadShot, bool isKillShot ) +{ + int fxId + if ( isKillShot ) + fxId = GetParticleSystemIndex( $"P_ar_impact_pilot_kill" ) + else if ( isHeadShot && !isKillShot ) + return + // fxId = GetParticleSystemIndex( $"P_ar_impact_pilot_headshot" ) + else + return + // fxId = GetParticleSystemIndex( $"P_ar_impact_pilot" ) + + vector victimVelocity = victim.GetVelocity() + damagePosition += (Length( victimVelocity ) * 0.15) * Normalize( victimVelocity ) + vector fxOffset = damagePosition - victim.GetOrigin() + StartParticleEffectOnEntityWithPos( victim, fxId, FX_PATTACH_ABSORIGIN_FOLLOW, -1, damagePosition - victim.GetOrigin(), <0, 0, 0> ) +} + +void function KillShotBloodSpray( entity player, entity victim, vector damagePosition, bool isExplosion, bool isBullet, bool isShotgun ) +{ + if ( IsSoftenedLocale() ) + return + + if ( !victim.IsHuman() && !IsProwler( victim ) ) + return + + if ( victim.IsMechanical() ) + return + + if ( victim.IsHologram() ) + return + + if ( !isExplosion && !isBullet && !isShotgun ) + return + + int fxId = GetParticleSystemIndex( FX_KILLSHOT_BLOODSPRAY ) + + vector victimVelocity = victim.GetVelocity() + damagePosition += (Length( victimVelocity ) * 0.15) * Normalize( victimVelocity ) + StartParticleEffectOnEntityWithPos( victim, fxId, FX_PATTACH_ABSORIGIN_FOLLOW, -1, damagePosition - victim.GetOrigin(), <0, 0, 0> ) +} + +void function BloodSprayDecals( entity player, entity victim, vector damagePosition, float damageAmount, bool isHeadShot, bool isKillShot, bool isMelee, bool isExplosion, bool isBullet, bool isShotgun ) +{ + if ( IsSoftenedLocale() || !Flag( "EnableBloodSprayDecals" ) ) + return + + if ( !victim.IsHuman() && !IsProwler( victim ) ) + return + + if ( victim.IsMechanical() ) + return + + if ( victim.IsHologram() ) + return + + if ( !isMelee && !isExplosion && !isBullet && !isShotgun ) + return + + // in MP, too expensive to do on every shot + if ( IsMultiplayer() && !isKillShot ) + return + + thread BloodSprayDecals_Think( player, victim, damagePosition, damageAmount, isHeadShot, isKillShot, isMelee, isExplosion, isBullet, isShotgun ) +} + +void function BloodSprayDecals_Think( entity player, entity victim, vector damagePosition, float damageAmount, bool isHeadShot, bool isKillShot, bool isMelee, bool isExplosion, bool isBullet, bool isShotgun ) +{ + player.EndSignal( "OnDestroy" ) + victim.EndSignal( "OnDestroy" ) + + BloodDecalParams params = BloodDecal_GetParams( damageAmount, isHeadShot, isKillShot, isMelee, isExplosion, isBullet, isShotgun ) + float traceDist = params.traceDist + float secondaryTraceDist = params.secondaryTraceDist + asset fxType = params.fxType + asset secondaryFxType = params.secondaryFxType + + int fxId = GetParticleSystemIndex( fxType ) + + // PRIMARY TRACES + vector traceStart = damagePosition + vector traceFwd = player.GetViewVector() + + if ( isExplosion || isMelee ) + { + // for explosion/melee damage, use chest instead of actual damage position + int attachID = victim.LookupAttachment( "CHESTFOCUS" ) + traceStart = victim.GetAttachmentOrigin( attachID ) + + if ( isExplosion ) + traceFwd = AnglesToForward( victim.GetAngles() ) * -1 + } + + vector traceEnd = damagePosition + (traceFwd * traceDist) + //TraceResults traceResult = TraceLine( traceStart, traceEnd, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) + + var trace_primary = DeferredTraceLineHighDetail( traceStart, traceEnd, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) + WaitFrame() + TraceResults traceResult = GetDeferredTraceResult( trace_primary ) + + vector primaryTraceEndPos = traceResult.endPos + vector primaryTraceNormal = traceResult.surfaceNormal + //DebugDrawLine( traceStart, traceEnd, 255, 150, 0, true, 5 ) + //DebugDrawSphere( primaryTraceEndPos, 8.0, 255, 0, 0, true, 5 ) + + bool doGravitySplat = isMelee ? false : true + + if ( traceResult.fraction < 1.0 ) + { + vector normAng = VectorToAngles( traceResult.surfaceNormal ) + vector fxAng = AnglesCompose( normAng, < 90, 0, 0 > ) + + StartParticleEffectInWorld( fxId, primaryTraceEndPos, fxAng ) + //DebugDrawAngles( endPos, fxAng, 5 ) + } + else if ( doGravitySplat ) + { + // trace behind the guy on the ground and put a decal there + float gravitySplatBackTraceDist = 58.0 // how far behind the guy to put the gravity splat + float gravitySplatDownTraceDist = 100.0 // max dist vertically to try to trace and put a gravity splat + vector groundTraceStartPos = damagePosition + (traceFwd * gravitySplatBackTraceDist) + vector groundTraceEndPos = groundTraceStartPos - <0, 0, 100> + + var trace_gravitySplat = DeferredTraceLineHighDetail( groundTraceStartPos, groundTraceEndPos, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) + WaitFrame() + TraceResults downTraceResult = GetDeferredTraceResult( trace_gravitySplat ) + + if ( downTraceResult.fraction < 1.0 ) + { + //DebugDrawLine( groundTraceStartPos, downTraceResult.endPos, 255, 150, 0, true, 5 ) + //DebugDrawSphere( downTraceResult.endPos, 4.0, 255, 0, 0, true, 5 ) + + vector normAng = VectorToAngles( downTraceResult.surfaceNormal ) + vector fxAng = AnglesCompose( normAng, < 90, 0, 0 > ) + + //DebugDrawAngles( downTraceResult.endPos, fxAng, 5 ) + + StartParticleEffectInWorld( fxId, downTraceResult.endPos, fxAng ) + } + } + + // MP doesn't want secondaries, too expensive + if ( IsMultiplayer() ) + return + + // SECONDARY TRACES + array testVecs = [] + vector tempAng = VectorToAngles( traceFwd ) + + if ( isExplosion ) + { + // for explosions, different & more angles for secondary splatter + testVecs.append( AnglesToRight( tempAng ) ) + testVecs.append( AnglesToRight( tempAng ) * -1 ) + testVecs.append( traceFwd * -1 ) + testVecs.append( AnglesToUp( tempAng ) ) + testVecs.append( AnglesToUp( tempAng ) * -1 ) + } + else + { + // mostly to cover edge cases involving corners + vector traceRight = AnglesToRight( tempAng ) + vector traceLeft = traceRight * -1 + vector backLeft = (traceFwd + traceLeft) * 0.5 + vector backRight = (traceFwd + traceRight) * 0.5 + testVecs.append( backRight ) + testVecs.append( backLeft ) + + // add blood on the ground for these weapons too + if ( isBullet || isShotgun ) + testVecs.append( AnglesToUp( tempAng ) * -1 ) + } + + if ( !testVecs.len() ) + return + + array secondaryTraces = [] + foreach ( testVec in testVecs ) + { + vector secondaryTraceEnd = traceStart + (testVec * secondaryTraceDist) + var secondaryTrace = DeferredTraceLineHighDetail( traceStart, secondaryTraceEnd, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) + secondaryTraces.append( secondaryTrace ) + } + + int secondaryFxId = GetParticleSystemIndex( secondaryFxType ) + + float startTime = Time() + array processedResults = [] + while ( processedResults.len() < secondaryTraces.len() ) + { + WaitFrame() + + foreach ( trace in secondaryTraces ) + { + if ( processedResults.contains( trace ) ) + continue + + if ( !IsDeferredTraceFinished( trace ) ) + continue + + processedResults.append( trace ) + + TraceResults traceResult = GetDeferredTraceResult( trace ) + + if ( traceResult.fraction == 1.0 ) + continue + + // don't put secondaries on the same wall as the primary + vector secondaryTraceNormal = traceResult.surfaceNormal + if ( primaryTraceNormal == secondaryTraceNormal ) + continue + + vector normAng = VectorToAngles( secondaryTraceNormal ) + vector fxAng = AnglesCompose( normAng, < 90, 0, 0 > ) + + vector endPos = traceResult.endPos + //DebugDrawSphere( endPos, 4.0, 255, 0, 0, true, 5 ) + StartParticleEffectInWorld( secondaryFxId, endPos, fxAng ) + } + + // timeout if traces aren't returning + if ( Time() - startTime >= 0.3 ) + return + } +} + +BloodDecalParams function BloodDecal_GetParams( float damageAmount, bool isHeadShot, bool isKillShot, bool isMelee, bool isExplosion, bool isBullet, bool isShotgun ) +{ + // default: bullet damage + float traceDist = 175 + float secondaryTraceDist = 100 + asset fxType = FX_BLOODSPRAY_DECAL_SML + asset secondaryFxType = FX_BLOODSPRAY_DECAL_SML + + if ( isBullet ) + { + // HACK- shotguns report isBullet also + if ( isShotgun ) + { + //if ( isKillShot ) + // fxType = FX_BLOODSPRAY_DECAL_LRG + //else + fxType = FX_BLOODSPRAY_DECAL_MED + } + else + { + if ( isKillShot ) + fxType = FX_BLOODSPRAY_DECAL_MED + else + fxType = FX_BLOODSPRAY_DECAL_SML + + if ( damageAmount >= 200 ) + { + traceDist = 216 + fxType = FX_BLOODSPRAY_DECAL_LRG + secondaryFxType = FX_BLOODSPRAY_DECAL_MED + } + } + } + + else if ( isExplosion ) + { + secondaryTraceDist = traceDist + + float maxDmg = 300 + float medDmg = 200 + if ( IsMultiplayer() ) + { + maxDmg = 100 + medDmg = 75 + } + + if ( damageAmount >= maxDmg ) + { + fxType = FX_BLOODSPRAY_DECAL_LRG + secondaryFxType = FX_BLOODSPRAY_DECAL_LRG + } + else if ( damageAmount >= medDmg ) + { + fxType = FX_BLOODSPRAY_DECAL_LRG + secondaryFxType = FX_BLOODSPRAY_DECAL_MED + } + else if ( isKillShot ) + { + fxType = FX_BLOODSPRAY_DECAL_MED + secondaryFxType = FX_BLOODSPRAY_DECAL_MED + } + } + + else if ( isMelee ) + { + traceDist = 96 + + if ( isKillShot ) + fxType = FX_BLOODSPRAY_DECAL_MED + } + + // for kills, increase trace distance a bit + if ( isKillShot ) + { + traceDist = traceDist + (traceDist * 0.1) + secondaryTraceDist = secondaryTraceDist + (secondaryTraceDist * 0.1) + } + + BloodDecalParams params + params.traceDist = traceDist + params.secondaryTraceDist = secondaryTraceDist + params.fxType = fxType + params.secondaryFxType = secondaryFxType + return params +} + +#if DEV +string function BloodSprayDecals_Toggle() +{ + string returnStr = "" + + if ( Flag( "EnableBloodSprayDecals" ) ) + { + FlagClear( "EnableBloodSprayDecals" ) + returnStr = "Blood spray decals DISABLED" + } + else + { + FlagSet( "EnableBloodSprayDecals" ) + returnStr = "Blood spray decals ENABLED" + } + + return returnStr +} +#endif + +function ServerCallback_RodeoerEjectWarning( soulHandle, ejectTime ) +{ + entity soul = GetEntityFromEncodedEHandle( soulHandle ) + + if ( !IsValid( soul ) ) + return + + thread TitanEjectHatchSequence( soul, ejectTime ) +} + +function TitanEjectHatchSequence( soul, ejectTime ) +{ + expect entity( soul ) + + soul.EndSignal( "OnSoulTransfer" ) + soul.EndSignal( "OnTitanDeath" ) + soul.EndSignal( "OnDestroy" ) + + local effects = [] + + OnThreadEnd( + function() : ( effects ) + { + foreach ( effect in effects ) + { + if ( !EffectDoesExist( effect ) ) + continue + + EffectStop( effect, true, true ) + } + } + ) + + int boltCount = 6 + int fxID = GetParticleSystemIndex( $"xo_spark_bolt" ) + + for ( int index = 0; index < boltCount; index++ ) + { + entity titan = soul.GetTitan() + + WaitEndFrame() // so OnTitanDeath/Destroy can happen + + if ( !IsAlive( titan ) ) + return + + if ( !titan.IsTitan() ) + { + printt( "WARNING: " + titan + " is not a Titan!" ) + return + } + + int attachID = titan.LookupAttachment( "HATCH_BOLT" + (index + 1) ) + //printt( "attachID is " + attachID ) + vector boltOrgin = titan.GetAttachmentOrigin( attachID ) + vector boltAngles = titan.GetAttachmentAngles( attachID ) + vector launchVec = AnglesToForward( boltAngles ) * 500 + + CreateClientsideGib( $"models/industrial/bolt_tiny01.mdl", boltOrgin, boltAngles, launchVec, < 0, 0, 0 >, 3.0, 1000.0, 200.0 ) + int effect = PlayFXOnTag( titan, fxID, attachID ) + effects.append( effect ) + EmitSoundOnEntity( titan, "titan_bolt_loose" ) + + wait (ejectTime / boltCount) + } +} + +function ServerCallback_OnEntityKilled( attackerEHandle, victimEHandle, scriptDamageType, damageSourceId ) +{ + expect int( damageSourceId ) + + local isHeadShot = scriptDamageType & DF_HEADSHOT + + entity victim = GetEntityFromEncodedEHandle( victimEHandle ) + entity attacker = attackerEHandle ? GetHeavyWeightEntityFromEncodedEHandle( attackerEHandle ) : null + entity localClientPlayer = GetLocalClientPlayer() + + if ( !IsValid( victim ) ) + return + + Signal( victim, "OnDeath" ) + + if ( damageSourceId == eDamageSourceId.indoor_inferno ) + { + if ( victim == localClientPlayer ) + thread PlayerFieryDeath( victim ) + } + + if ( victim.IsPlayer() && victim != attacker ) + { + if ( attacker == localClientPlayer ) + { + EmitSoundOnEntity( attacker, "Pilot_Killed_Indicator" ) + } + else if ( IsValid( attacker ) && attacker.IsTitan() ) + { + entity bossPlayer = attacker.GetBossPlayer() + if ( bossPlayer && bossPlayer == localClientPlayer ) + EmitSoundOnEntity( bossPlayer, "Pilot_Killed_Indicator" ) + } + } +} + +void function ServerCallback_OnTitanKilled( int attackerEHandle, int victimEHandle, int scriptDamageType, int damageSourceId ) +{ +} + +function PlayTargetEliminatedTitanVO( attacker, victim ) +{ + entity localPlayer = GetLocalViewPlayer() + + if ( attacker != localPlayer ) + return + + if ( !victim.IsPlayer() ) + return + + if ( victim.IsTitan() ) + { + // a bit more delay for a titan explosion to clear + thread TitanCockpit_PlayDialogDelayed( localPlayer, 1.3, "elimTarget" ) + } + else + { + thread TitanCockpit_PlayDialogDelayed( localPlayer, 0.8, "elimEnemyPilot" ) + } +} + +function ServerCallback_SetAssistInformation( damageSourceId, attackerEHandle, entityEHandle, assistTime ) +{ + local ent = GetEntityFromEncodedEHandle ( entityEHandle ) + if ( !ent ) + return + + local latestAssistPlayer = GetEntityFromEncodedEHandle ( attackerEHandle ) + if ( !("latestAssistPlayer" in ent.s) ) + { + ent.s.latestAssistPlayer <- latestAssistPlayer + ent.s.latestAssistDamageSource <- damageSourceId + ent.s.latestAssistTime <- assistTime + } + else + { + ent.s.latestAssistPlayer = latestAssistPlayer + ent.s.latestAssistDamageSource = damageSourceId + ent.s.latestAssistTime = assistTime + } +} + +/* +void function ClientCodeCallback_OnModelChanged( entity ent ) +{ + // OnModelChanged gets called for each model change, but gets processed after the model has done all switches + if ( !IsValid( ent ) ) + return; + + if ( !("creationCount" in ent.s) ) + return; + + Assert( ent instanceof C_BaseAnimating ); +} +*/ + + +void function ClientCodeCallback_OnHealthChanged( entity ent, int oldHealth, int newHealth ) +{ + if ( IsLobby() ) + return + + entity player = GetLocalViewPlayer() + if ( !IsValid( player ) ) + return + + if ( !IsValid( ent ) ) + return + + ent.Signal( "HealthChanged", { oldHealth = oldHealth, newHealth = newHealth } ) +} + + +void function SetupPlayerAnimEvents( entity player ) +{ + SetupPlayerJumpJetAnimEvents( player ) + AddAnimEvent( player, "WallHangAttachDataKnife", WallHangAttachDataKnife ) +} + +void function JumpRandomlyForever() +{ + for (;; ) + { + if ( IsWatchingReplay() ) + { + wait 1 + continue + } + + entity player = GetLocalClientPlayer() + if ( !IsAlive( player ) || player != GetLocalViewPlayer() ) + { + wait 1 + continue + } + + printt( "jump!" ) + player.ClientCommand( "+jump" ) + wait 0 + player.ClientCommand( "-jump" ) + + wait RandomFloatRange( 0.2, 1.1 ) + } +} + +void function RemoteTurretFadeoutAnimEvent( entity ent ) +{ + entity player = GetLocalViewPlayer() + ScreenFade( player, 0, 0, 0, 255, 0.1, 0.25, FFADE_OUT ); +} + +void function SetupFirstPersonProxyEvents( entity firstPersonProxy ) +{ + //printt( "SetupFirstPersonProxyEvents" ) + + AddAnimEvent( firstPersonProxy, "mantle_smallmantle", OnSmallMantle ) + AddAnimEvent( firstPersonProxy, "mantle_mediummantle", OnMediumMantle ) + AddAnimEvent( firstPersonProxy, "mantle_lowmantle", OnLowMantle ) + AddAnimEvent( firstPersonProxy, "mantle_extralowmantle", OnExtraLowMantle ) + AddAnimEvent( firstPersonProxy, "remoteturret_fadeout", RemoteTurretFadeoutAnimEvent ) +} + +void function OnSmallMantle( entity firstPersonProxy ) +{ + entity player = GetLocalViewPlayer() + + if ( PlayerHasPassive( player, ePassives.PAS_STEALTH_MOVEMENT ) ) + { + //printt( "mantle_smallmantle, has stealth passive" ) + } + else + { + //printt( "mantle_smallmantle, no stealth passive" ) + EmitSoundOnEntity( firstPersonProxy, "mantle_smallmantle" ) + } +} + +void function OnMediumMantle( entity firstPersonProxy ) +{ + entity player = GetLocalViewPlayer() + + if ( PlayerHasPassive( player, ePassives.PAS_STEALTH_MOVEMENT ) ) + { + //printt( "mantle_mediummantle, has stealth passive" ) + } + else + { + //printt( "mantle_mediummantle, no stealth passive" ) + EmitSoundOnEntity( firstPersonProxy, "mantle_mediummantle" ) + } +} + +void function OnLowMantle( entity firstPersonProxy ) +{ + entity player = GetLocalViewPlayer() + + if ( PlayerHasPassive( player, ePassives.PAS_STEALTH_MOVEMENT ) ) + { + //printt( "mantle_lowmantle, has stealth passive" ) + } + else + { + //printt( "mantle_lowmantle, no stealth passive" ) + EmitSoundOnEntity( firstPersonProxy, "mantle_lowmantle" ) + } +} + +void function OnExtraLowMantle( entity firstPersonProxy ) +{ + entity player = GetLocalViewPlayer() + + if ( PlayerHasPassive( player, ePassives.PAS_STEALTH_MOVEMENT ) ) + { + //printt( "mantle_extralowmantle, has stealth passive" ) + } + else + { + //printt( "mantle_extralowmantle, no stealth passive" ) + EmitSoundOnEntity( firstPersonProxy, "mantle_extralow" ) + } +} + +void function CreateCallback_TitanSoul( entity ent ) +{ +} + + +void function WallHangAttachDataKnife( entity player ) +{ + int attachIdx = player.LookupAttachment( "l_hand" ) + if ( attachIdx == 0 ) + // hack while i wait for the attachment to be fixed + return + + entity dataknife = CreateClientSidePropDynamic( player.GetAttachmentOrigin( attachIdx ), player.GetAttachmentAngles( attachIdx ), DATA_KNIFE_MODEL ) + dataknife.SetParent( player, "l_hand" ) + + thread DeleteDataKnifeAfterWallHang( player, dataknife ) +} + +void function DeleteDataKnifeAfterWallHang( entity player, entity dataknife ) +{ + OnThreadEnd( + function() : ( dataknife ) + { + if ( IsValid( dataknife ) ) + dataknife.Kill_Deprecated_UseDestroyInstead() + } + ) + + player.EndSignal( "OnDeath" ) + player.EndSignal( "OnDestroy" ) + + for (;; ) + { + Wait( 0.1 ) + if ( !player.IsWallHanging() ) + break + } +} + +bool function ClientCodeCallback_OnGib( entity victim, vector attackDir ) +{ + if ( !victim.IsMechanical() ) + return SpawnFleshGibs( victim, attackDir ) + + return false +} + +bool function SpawnFleshGibs( entity victim, vector attackDir ) +{ + asset modelName = $"models/gibs/human_gibs.mdl" + attackDir = Normalize( attackDir ) + + float cullDist = 2048.0 + if ( "gibDist" in victim.s ) + cullDist = expect float( victim.s.gibDist ) + + vector startOrigin = victim.GetWorldSpaceCenter() + (attackDir * -30) + + vector origin = victim.GetOrigin() + < RandomIntRange( 10, 20 ), RandomIntRange( 10, 20 ), RandomIntRange( 32, 64 ) > + vector angles = < 0, 0, 0 > + vector flingDir = attackDir * RandomIntRange( 80, 200 ) + + int fxID + bool isSoftenedLocale = IsSoftenedLocale() + + if ( isSoftenedLocale ) + { + if ( victim.GetModelName() == FLYER_MODEL ) + fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist_LG_nochunk" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) + else + fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist_nochunk" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) + } + else + { + if ( victim.GetModelName() == FLYER_MODEL ) + fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist_LG" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) + else + fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) + } + + EffectSetControlPointVector( fxID, 1, flingDir ) + + if ( isSoftenedLocale ) + return true + + vector angularVel = < 0, 0, 0 > + float lifeTime = 10.0 + CreateClientsideGibWithBodyGroupGibs( modelName, victim.GetOrigin(), angles, attackDir, angularVel, lifeTime, cullDist, 1024 ) + + return true +} + +function ServerCallback_PlayScreenFXWarpJump() +{ + if ( IsWatchingReplay() ) + return false + + thread PlayScreenFXWarpJump( GetLocalClientPlayer() ) +} + +void function PlayScreenFXWarpJump( entity clientPlayer ) +{ + clientPlayer.EndSignal( "OnDeath" ) + clientPlayer.EndSignal( "OnDestroy" ) + + entity player = GetLocalViewPlayer() + int index = GetParticleSystemIndex( SCREENFX_WARPJUMP ) + int indexD = GetParticleSystemIndex( SCREENFX_WARPJUMPDLIGHT ) + int fxID = StartParticleEffectInWorldWithHandle( index, < 0, 0, 0 >, < 0, 0, 0 > ) + int fxID2 = -1 + if ( IsValid( player.GetCockpit() ) ) + { + fxID2 = StartParticleEffectOnEntity( player, indexD, FX_PATTACH_POINT_FOLLOW, player.GetCockpit().LookupAttachment( "CAMERA" ) ) + EffectSetIsWithCockpit( fxID2, true ) + } + + OnThreadEnd( + function() : ( clientPlayer, fxID, fxID2 ) + { + if ( IsValid( clientPlayer ) && !IsAlive( clientPlayer ) ) + { + EffectStop( fxID, true, false ) + if ( fxID2 > -1 ) + EffectStop( fxID2, true, false ) + } + } + ) + + wait 2.5 + if ( IsValid( player.GetCockpit() ) ) + thread TonemappingUpdateAfterWarpJump() +} + +const TONEMAP_1_START_DURATION = 0.2 +const TONEMAP_1_MAX = 0 +const TONEMAP_1_MIN = 8 +const TONEMAP_2_START_DURATION = 1.0 +const TONEMAP_2_MAX = 8 +const TONEMAP_2_MIN = 4 +const TONEMAP_3_START_DURATION = 5 +const TONEMAP_3_MAX = 40 +const TONEMAP_3_MIN = 1 + +function TonemappingUpdateAfterWarpJump() +{ + AutoExposureSetMaxExposureMultiplier( 500 ); // allow exposure to actually go bright, even if it's clamped in the level. + + local startTime = Time() + while( 1 ) + { + local time = Time() - startTime + float factor = GraphCapped( time, 0, TONEMAP_1_START_DURATION, 1, 0 ) + factor = factor * factor * factor + local toneMapScale = TONEMAP_1_MIN + (TONEMAP_1_MAX - TONEMAP_1_MIN) * factor + AutoExposureSetExposureCompensationBias( toneMapScale ) + AutoExposureSnap() + wait 0 + if ( factor == 0 ) + break; + } + + startTime = Time() + while( 1 ) + { + local time = Time() - startTime + float factor = GraphCapped( time, 0, TONEMAP_2_START_DURATION, 1, 0 ) + local toneMapScale = TONEMAP_2_MIN + (TONEMAP_2_MAX - TONEMAP_2_MIN) * factor + AutoExposureSetExposureCompensationBias( toneMapScale ) + AutoExposureSnap() + wait 0 + if ( factor == 0 ) + break; + } + + AutoExposureSetExposureCompensationBias( 0 ) // clear the exposure bias and allow the exposure to adjust to its normal level at its own pace + + // Ramp the max exposure multiplier back down to 1 (preferably slower than the autoexposure would drive the brightness down) + startTime = Time() + while( 1 ) + { + local time = Time() - startTime + float factor = GraphCapped( time, 0, TONEMAP_3_START_DURATION, 1, 0 ) + local scale = TONEMAP_3_MIN + (TONEMAP_3_MAX - TONEMAP_3_MIN) * factor + AutoExposureSetMaxExposureMultiplier( scale ); + wait 0 + if ( factor == 0 ) + break; + } +} + +function SetPanelAlphaOverTime( panel, alpha, duration ) +{ + // HACK this should be a code command - Mackey + Signal( panel, "PanelAlphaOverTime" ) + EndSignal( panel, "PanelAlphaOverTime" ) + EndSignal( panel, "OnDestroy" ) + + local startTime = Time() + local endTime = startTime + duration + local startAlpha = panel.GetPanelAlpha() + + while( Time() <= endTime ) + { + float a = GraphCapped( Time(), startTime, endTime, startAlpha, alpha ) + panel.SetPanelAlpha( a ) + WaitFrame() + } + + panel.SetPanelAlpha( alpha ) +} + + +bool function ShouldShowOnDeckCard( entity player, cardOnDeck ) +{ + local inPrematch = level.nv.gameState == eGameState.Prematch + + if ( IsAlive( player ) && !IsWatchingReplay() && !inPrematch ) + return false + + if ( inPrematch && !cardOnDeck ) + return false + + return true +} + +function HandleDoomedState( entity player, entity titan ) +{ + bool isDoomed = GetDoomedState( titan ) + if ( isDoomed ) + { + titan.Signal( "Doomed" ) + + if ( HasSoul( titan ) ) + { + entity soul = titan.GetTitanSoul() + soul.Signal( "Doomed" ) + } + } +} + +function PlayShieldBreakEffect( entity ent ) +{ + entity shieldEnt = ent + if ( IsSoul( ent ) ) + { + shieldEnt = ent.GetTitan() + if ( !shieldEnt ) + return + } + + float shieldHealthFrac = GetShieldHealthFrac( shieldEnt ) + + int shieldBreakFX = GetParticleSystemIndex( SHIELD_BREAK_FX ) + + local attachID + if ( shieldEnt.IsTitan() ) + attachID = shieldEnt.LookupAttachment( "exp_torso_main" ) + else + attachID = shieldEnt.LookupAttachment( "ref" ) // TEMP + + local shieldFXHandle = StartParticleEffectOnEntity( shieldEnt, shieldBreakFX, FX_PATTACH_POINT_FOLLOW, attachID ) + EffectSetControlPointVector( shieldFXHandle, 1, GetShieldEffectCurrentColor( 1 - shieldHealthFrac ) ) +} + +function PlayIt( entity victim ) +{ + float shieldHealthFrac = GetShieldHealthFrac( victim ) + + int shieldbodyFX = GetParticleSystemIndex( SHIELD_BODY_FX ) + local attachID + if ( victim.IsTitan() ) + attachID = victim.LookupAttachment( "exp_torso_main" ) + else + attachID = victim.LookupAttachment( "ref" ) // TEMP + + local shieldFXHandle = StartParticleEffectOnEntity( victim, shieldbodyFX, FX_PATTACH_POINT_FOLLOW, attachID ) + + EffectSetControlPointVector( shieldFXHandle, 1, GetShieldEffectCurrentColor( 1 - shieldHealthFrac ) ) +} + +function PlayShieldHitEffect( PlayerDidDamageParams params ) +{ + entity player = GetLocalViewPlayer() + entity victim = params.victim + //vector damagePosition = params.damagePosition + //int hitBox = params.hitBox + //int damageType = params.damageType + //float damageAmount = params.damageAmount + //int damageFlags = params.damageFlags + //int hitGroup = params.hitGroup + //entity weapon = params.weapon + //float distanceFromAttackOrigin = params.distanceFromAttackOrigin + + //shieldFX <- GetParticleSystemIndex( SHIELD_FX ) + //StartParticleEffectInWorld( shieldFX, damagePosition, player.GetViewVector() * -1 ) + + float shieldHealthFrac = GetShieldHealthFrac( victim ) + + int shieldbodyFX = GetParticleSystemIndex( SHIELD_BODY_FX ) + local attachID + if ( victim.IsTitan() ) + attachID = victim.LookupAttachment( "exp_torso_main" ) + else + attachID = victim.LookupAttachment( "ref" ) // TEMP + + local shieldFXHandle = StartParticleEffectOnEntity( victim, shieldbodyFX, FX_PATTACH_POINT_FOLLOW, attachID ) + + EffectSetControlPointVector( shieldFXHandle, 1, GetShieldEffectCurrentColor( 1 - shieldHealthFrac ) ) +} + +const table SHIELD_COLOR_CHARGE_FULL = { r = 115, g = 247, b = 255 } // blue +const table SHIELD_COLOR_CHARGE_MED = { r = 200, g = 128, b = 80 } // orange +const table SHIELD_COLOR_CHARGE_EMPTY = { r = 200, g = 80, b = 80 } // red + +const SHIELD_COLOR_CROSSOVERFRAC_FULL2MED = 0.75 // from zero to this fraction, fade between full and medium charge colors +const SHIELD_COLOR_CROSSOVERFRAC_MED2EMPTY = 0.95 // from "full2med" to this fraction, fade between medium and empty charge colors + +function GetShieldEffectCurrentColor( shieldHealthFrac ) +{ + local color1 = SHIELD_COLOR_CHARGE_FULL + local color2 = SHIELD_COLOR_CHARGE_MED + local color3 = SHIELD_COLOR_CHARGE_EMPTY + + local crossover1 = SHIELD_COLOR_CROSSOVERFRAC_FULL2MED // from zero to this fraction, fade between color1 and color2 + local crossover2 = SHIELD_COLOR_CROSSOVERFRAC_MED2EMPTY // from crossover1 to this fraction, fade between color2 and color3 + + local colorVec = < 0, 0, 0 > + // 0 = full charge, 1 = no charge remaining + if ( shieldHealthFrac < crossover1 ) + { + colorVec.x = Graph( shieldHealthFrac, 0, crossover1, color1.r, color2.r ) + colorVec.y = Graph( shieldHealthFrac, 0, crossover1, color1.g, color2.g ) + colorVec.z = Graph( shieldHealthFrac, 0, crossover1, color1.b, color2.b ) + } + else if ( shieldHealthFrac < crossover2 ) + { + colorVec.x = Graph( shieldHealthFrac, crossover1, crossover2, color2.r, color3.r ) + colorVec.y = Graph( shieldHealthFrac, crossover1, crossover2, color2.g, color3.g ) + colorVec.z = Graph( shieldHealthFrac, crossover1, crossover2, color2.b, color3.b ) + } + else + { + // for the last bit of overload timer, keep it max danger color + colorVec.x = color3.r + colorVec.y = color3.g + colorVec.z = color3.b + } + + return colorVec +} + + +void function UpdateRespawnHUD() +{ + if ( IsWatchingReplay() ) + return + + entity player = GetLocalClientPlayer() + ShowRespawnSelect_SP() +} + +function OnClientPlayerAlive( entity player ) +{ + player.Signal( "OnClientPlayerAlive" ) // TEMP; this should not be necessary, but IsWatchingKillReplay is wrong + player.EndSignal( "OnClientPlayerAlive" ) + + UpdateClientHudVisibility( player ) +} + + +function OnClientPlayerDying( entity player ) +{ + player.Signal( "OnClientPlayerDying" ) // TEMP; this should not be necessary, but IsWatchingKillReplay is wrong + player.EndSignal( "OnClientPlayerDying" ) + + entity player = GetLocalClientPlayer() + UpdateClientHudVisibility( player ) + + if ( IsWatchingReplay() ) + return + + player.cv.deathTime = Time() + + thread DeathCamCheck( player ) +} + +function DeathCamCheck( entity player ) +{ + wait GetRespawnButtonCamTime( player ) +} + +void function ServerCallback_ShowNextSpawnMessage( float nextSpawnTime ) +{ + entity player = GetLocalClientPlayer() + float camTime = GetRespawnButtonCamTime( player ) + + file.nextSpawnTime = nextSpawnTime + + if ( nextSpawnTime > Time() + camTime ) + thread ShowSpawnDelayMessage( nextSpawnTime ) +} + +void function ShowSpawnDelayMessage( nextSpawnTime ) +{ + float waitTime = max( nextSpawnTime - Time(), 0 ) + + if ( waitTime < 1.0 ) + return + + entity player = GetLocalClientPlayer() + + player.cv.nextSpawnTimeLabel.SetAlpha( 255 ) + player.cv.nextSpawnTimeLabel.Show() + player.cv.nextSpawnTimeLabel.SetAutoText( "#GAMEMODE_DEPLOYING_IN_N", HATT_GAME_COUNTDOWN_SECONDS, nextSpawnTime ) + + if ( !player.cv.nextSpawnTimeLabel.IsAutoText() ) + player.cv.nextSpawnTimeLabel.EnableAutoText() +} + +void function ServerCallback_HideNextSpawnMessage() +{ + entity player = GetLocalClientPlayer() + player.cv.nextSpawnTimeLabel.FadeOverTime( 0, 1.0 ) +} + +float function GetWaveSpawnTime() +{ + return (file.nextSpawnTime) +} + +bool function IsPlayerEliminated( entity player ) +{ + return (player.GetPlayerGameStat( PGS_ELIMINATED ) > 0) +} + +function PlayerFieryDeath( player ) +{ + player.EndSignal( "OnDestroy" ) + player.EndSignal( "OnClientPlayerAlive" ) + clGlobal.levelEnt.EndSignal( "OnSpectatorMode" ) + + local offset = < 0, 0, 0 > + if ( player.IsTitan() ) + offset = < 0, 0, 96 > + + entity scriptRef = CreatePropDynamic( $"models/dev/empty_model.mdl", player.GetOrigin() + offset, player.GetAngles() ) + scriptRef.SetParent( player ) + + local fxHandle = StartParticleEffectOnEntity( scriptRef, GetParticleSystemIndex( $"P_burn_player" ), FX_PATTACH_ABSORIGIN_FOLLOW, -1 ) + + OnThreadEnd( + function () : ( fxHandle, scriptRef ) + { + EffectStop( fxHandle, false, false ) + if ( IsValid( scriptRef ) ) + scriptRef.Destroy() + } + ) + WaitForever() +} + + +function ServerCallback_GiveMatchLossProtection() +{ +} + +void function EnableDoDeathCallback( entity ent ) +{ + ent.DoDeathCallback( true ) +} + +void function ServerCallback_ShowDisembarkHint( float showtime ) +{ + AddPlayerHint( showtime, 0.5, $"","#HUD_TITAN_DISEMBARK" ) +} + +void function ClearDisembarkHint( entity cockpit, entity player ) +{ + HidePlayerHint( "#HUD_TITAN_DISEMBARK" ) +} + +void function SCB_CheckPoint() +{ + entity player = GetLocalViewPlayer() + if ( !IsAlive( player ) ) + return + + // put good announce here + AnnouncementMessageCheckpoint( player ) +} From bdf8ff370687c98aceacc1df732a6727bf9eb5dd Mon Sep 17 00:00:00 2001 From: NachosChipeados <103285866+NachosChipeados@users.noreply.github.com> Date: Sat, 12 Oct 2024 21:16:54 -0400 Subject: [PATCH 2/6] Add MP callback --- .../mod/scripts/vscripts/client/cl_player.gnut | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Northstar.Client/mod/scripts/vscripts/client/cl_player.gnut b/Northstar.Client/mod/scripts/vscripts/client/cl_player.gnut index 6ebe65628..aa9658c95 100644 --- a/Northstar.Client/mod/scripts/vscripts/client/cl_player.gnut +++ b/Northstar.Client/mod/scripts/vscripts/client/cl_player.gnut @@ -41,6 +41,7 @@ global function GetWaveSpawnTime global function ServerCallback_HideNextSpawnMessage global function ClientCodeCallback_OnHealthChanged +global function AddCallback_OnCrosshairCurrentTargetChanged global function ClientCodeCallback_OnCrosshairCurrentTargetChanged global function Pressed_TitanNextMode global function ClientCodeCallback_OnGib @@ -70,6 +71,8 @@ struct { var law_missile_tracer = null float nextSpawnTime = 0.0 entity lastEarnedReward // primarily used to check if we should still show the reward message after a delay + + array< void functionref( entity, entity ) > OnCrosshairCurrentTargetChangedCallbacks } file struct BloodDecalParams @@ -1515,6 +1518,13 @@ void function ClientCodeCallback_OnHealthChanged( entity ent, int oldHealth, int ent.Signal( "HealthChanged", { oldHealth = oldHealth, newHealth = newHealth } ) } +void function AddCallback_OnCrosshairCurrentTargetChanged( void functionref( entity, entity ) callbackFunc ) +{ + Assert( !file.OnCrosshairCurrentTargetChangedCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnCrosshairCurrentTargetChanged" ) + + file.OnCrosshairCurrentTargetChangedCallbacks.append( callbackFunc ) +} + void function ClientCodeCallback_OnCrosshairCurrentTargetChanged( entity player, entity newTarget ) { if ( IsLobby() ) @@ -1524,6 +1534,11 @@ void function ClientCodeCallback_OnCrosshairCurrentTargetChanged( entity player, if ( IsValid( newTarget ) ) TryOfferRodeoBatteryHint( newTarget ) + + foreach ( callback in file.OnCrosshairCurrentTargetChangedCallbacks ) + { + callback( player, newTarget ) + } } void function SetupPlayerAnimEvents( entity player ) From 8f820a8707e8581c38f24aec68b1a61c9225287f Mon Sep 17 00:00:00 2001 From: NachosChipeados <103285866+NachosChipeados@users.noreply.github.com> Date: Sat, 12 Oct 2024 21:19:15 -0400 Subject: [PATCH 3/6] Add SP callback --- .../scripts/vscripts/client/cl_sp_player.gnut | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Northstar.Client/mod/scripts/vscripts/client/cl_sp_player.gnut b/Northstar.Client/mod/scripts/vscripts/client/cl_sp_player.gnut index cb27b4e3f..55c773c7d 100644 --- a/Northstar.Client/mod/scripts/vscripts/client/cl_sp_player.gnut +++ b/Northstar.Client/mod/scripts/vscripts/client/cl_sp_player.gnut @@ -31,6 +31,11 @@ global function GetWaveSpawnTime global function ServerCallback_HideNextSpawnMessage global function ClientCodeCallback_OnHealthChanged + +// Northstar +global function AddCallback_OnCrosshairCurrentTargetChanged +global function ClientCodeCallback_OnCrosshairCurrentTargetChanged + global function Pressed_TitanNextMode global function ClientCodeCallback_OnGib global function ClientPilotSpawned @@ -59,6 +64,9 @@ struct { var law_missile_tracer = null float nextSpawnTime = 0.0 var UI_friendlyText + + // Northstar + array< void functionref( entity, entity ) > OnCrosshairCurrentTargetChangedCallbacks } file struct BloodDecalParams @@ -1261,6 +1269,26 @@ void function ClientCodeCallback_OnHealthChanged( entity ent, int oldHealth, int ent.Signal( "HealthChanged", { oldHealth = oldHealth, newHealth = newHealth } ) } +// Northstar +void function AddCallback_OnCrosshairCurrentTargetChanged( void functionref( entity, entity ) callbackFunc ) +{ + Assert( !file.OnCrosshairCurrentTargetChangedCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnCrosshairCurrentTargetChanged" ) + + file.OnCrosshairCurrentTargetChangedCallbacks.append( callbackFunc ) +} + +void function ClientCodeCallback_OnCrosshairCurrentTargetChanged( entity player, entity newTarget ) +{ + if ( IsLobby() ) + return; + if ( !IsValid( player ) ) + return + + foreach ( callback in file.OnCrosshairCurrentTargetChangedCallbacks ) + { + callback( player, newTarget ) + } +} void function SetupPlayerAnimEvents( entity player ) { From eaf586c384d3968e62cb802f7eee4516fe689386 Mon Sep 17 00:00:00 2001 From: NachosChipeados <103285866+NachosChipeados@users.noreply.github.com> Date: Sat, 12 Oct 2024 21:51:18 -0400 Subject: [PATCH 4/6] Update cl_player.gnut --- Northstar.Client/mod/scripts/vscripts/client/cl_player.gnut | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Northstar.Client/mod/scripts/vscripts/client/cl_player.gnut b/Northstar.Client/mod/scripts/vscripts/client/cl_player.gnut index aa9658c95..e818313f1 100644 --- a/Northstar.Client/mod/scripts/vscripts/client/cl_player.gnut +++ b/Northstar.Client/mod/scripts/vscripts/client/cl_player.gnut @@ -41,6 +41,8 @@ global function GetWaveSpawnTime global function ServerCallback_HideNextSpawnMessage global function ClientCodeCallback_OnHealthChanged + +// Northstar global function AddCallback_OnCrosshairCurrentTargetChanged global function ClientCodeCallback_OnCrosshairCurrentTargetChanged global function Pressed_TitanNextMode @@ -72,6 +74,7 @@ struct { float nextSpawnTime = 0.0 entity lastEarnedReward // primarily used to check if we should still show the reward message after a delay + // Northstar array< void functionref( entity, entity ) > OnCrosshairCurrentTargetChangedCallbacks } file @@ -1518,6 +1521,7 @@ void function ClientCodeCallback_OnHealthChanged( entity ent, int oldHealth, int ent.Signal( "HealthChanged", { oldHealth = oldHealth, newHealth = newHealth } ) } +// Northstar void function AddCallback_OnCrosshairCurrentTargetChanged( void functionref( entity, entity ) callbackFunc ) { Assert( !file.OnCrosshairCurrentTargetChangedCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnCrosshairCurrentTargetChanged" ) @@ -1535,6 +1539,7 @@ void function ClientCodeCallback_OnCrosshairCurrentTargetChanged( entity player, if ( IsValid( newTarget ) ) TryOfferRodeoBatteryHint( newTarget ) + // Northstar foreach ( callback in file.OnCrosshairCurrentTargetChangedCallbacks ) { callback( player, newTarget ) From aec153d494b3bc824ebba8d2fd009ebf5776111f Mon Sep 17 00:00:00 2001 From: GeckoEidechse Date: Sat, 16 Nov 2024 11:48:49 +0100 Subject: [PATCH 5/6] Renormalize line endings --- .../scripts/vscripts/client/cl_player.gnut | 5524 ++++++++--------- .../scripts/vscripts/client/cl_sp_player.gnut | 3782 +++++------ 2 files changed, 4653 insertions(+), 4653 deletions(-) diff --git a/Northstar.Client/mod/scripts/vscripts/client/cl_player.gnut b/Northstar.Client/mod/scripts/vscripts/client/cl_player.gnut index e818313f1..19c9fd228 100644 --- a/Northstar.Client/mod/scripts/vscripts/client/cl_player.gnut +++ b/Northstar.Client/mod/scripts/vscripts/client/cl_player.gnut @@ -1,2762 +1,2762 @@ -untyped - -global function ClPlayer_Init - -global function PlayIt -global function JumpRandomlyForever - -global function ClientCodeCallback_PlayerDidDamage -global function ClientCodeCallback_PlayerSpawned -//global function ClientCodeCallback_OnHudReloadScheme -global function ClientCodeCallback_HUDThink -global function Player_AddPlayer -global function Player_AddClient -global function PlayerConnectedOrDisconnected -global function ServerCallback_GameModeAnnouncement -global function MainHud_InitScoreBars - -global function ServerCallback_PlayerConnectedOrDisconnected -global function ClientCodeCallback_PlayerDisconnected -global function ServerCallback_PlayerChangedTeams -global function ClientCodeCallback_OnModelChanged -global function ServerCallback_RodeoerEjectWarning -global function ServerCallback_PlayScreenFXWarpJump -global function PlayShieldBreakEffect -global function PlayShieldActivateEffect -global function HandleDoomedState -global function RewardReadyMessage -global function TitanReadyMessage -global function CoreReadyMessage - -global function ServerCallback_RewardReadyMessage -global function ServerCallback_TitanReadyMessage - -global function OnClientPlayerAlive -global function OnClientPlayerDying -global function PlayPlayerDeathSound -global function StopPlayerDeathSound - -global function ServerCallback_ShowNextSpawnMessage -global function GetWaveSpawnTime -global function ServerCallback_HideNextSpawnMessage - -global function ClientCodeCallback_OnHealthChanged - -// Northstar -global function AddCallback_OnCrosshairCurrentTargetChanged -global function ClientCodeCallback_OnCrosshairCurrentTargetChanged -global function Pressed_TitanNextMode -global function ClientCodeCallback_OnGib -global function ClientPilotSpawned -global function AddCallback_OnPlayerDisconnected - -global function IsPlayerEliminated - -global function ServerCallback_GiveMatchLossProtection -global function ServerCallback_OnEntityKilled -global function ServerCallback_OnTitanKilled - -global function ShouldShowSpawnAsTitanHint -global function ServerCallback_SetAssistInformation - -global function GetShieldEffectCurrentColor -global function ClientPlayerClassChanged - -#if DEV -global function BloodSprayDecals_Toggle -#endif - -const float DEFAULT_GAMEMODE_ANNOUNCEMENT_DURATION = 5.0 - -struct { - var orbitalstrike_tracer = null - var law_missile_tracer = null - float nextSpawnTime = 0.0 - entity lastEarnedReward // primarily used to check if we should still show the reward message after a delay - - // Northstar - array< void functionref( entity, entity ) > OnCrosshairCurrentTargetChangedCallbacks -} file - -struct BloodDecalParams -{ - float traceDist - float secondaryTraceDist - asset fxType - asset secondaryFxType -} - -void function ClPlayer_Init() -{ - ClPilotJumpjet_Init() - ClDamageIndicator_Init() - - ClPlayer_Common_Precache() - - RegisterSignal( "OnAnimationDone" ) - RegisterSignal( "OnAnimationInterrupted" ) - RegisterSignal( "OnBleedingOut" ) - RegisterSignal( "PanelAlphaOverTime" ) - RegisterSignal( "LocalClientPlayerRespawned" ) - RegisterSignal( "OnClientPlayerAlive" ) - RegisterSignal( "OnClientPlayerDying" ) - RegisterSignal( "StopAlertCore" ) - RegisterSignal( "OnSpectatorMode" ) - RegisterSignal( "HealthChanged" ) - - FlagInit( "DamageDistancePrint" ) - FlagInit( "EnableTitanModeChange", true ) - FlagInit( "EnableBloodSprayDecals", true ) - - level.vduOpen <- false - level.canSpawnAsTitan <- false - level.grenadeIndicatorEnabled <- true - level.clientsLastKiller <- null - - AddCreateCallback( "player", SetupPlayerAnimEvents ) - AddCreateCallback( "player", MpClientPlayerInit ) - - AddCreateCallback( "first_person_proxy", SetupFirstPersonProxyEvents ) - AddCreateCallback( "predicted_first_person_proxy", SetupFirstPersonProxyEvents ) - - AddCreateCallback( "player", EnableDoDeathCallback ) - AddCreateCallback( "npc_titan", EnableDoDeathCallback ) - - AddCreateCallback( "titan_soul", CreateCallback_TitanSoul ) - - AddCallback_OnPlayerLifeStateChanged( PlayerADSDof ) - - file.orbitalstrike_tracer = PrecacheParticleSystem( $"Rocket_Smoke_Large" ) - //DEBUG Remove when bug is fixed. - file.law_missile_tracer = PrecacheParticleSystem( $"wpn_orbital_rocket_tracer" ) - - level.menuHideGroups <- {} - - level.spawnAsTitanSelected <- false - - AddPlayerFunc( Player_AddPlayer ) -} - -entity function FindEnemyRodeoParent( entity player ) -{ - entity ent = player.GetParent() - if ( ent == null ) - return null - - if ( !ent.IsTitan() ) - return null - - if ( ent == player.GetPetTitan() ) - return null - - if ( ent.GetTeam() == player.GetTeam() ) - return null - - return ent -} - -void function MpClientPlayerInit( entity player ) -{ - player.ClientCommand( "save_enable 0" ) -} - -void function ClientCodeCallback_PlayerSpawned( entity player ) -{ - if ( !IsValid( player ) ) - return - - if ( IsMenuLevel() ) - return - - ClearCrosshairPriority( crosshairPriorityLevel.ROUND_WINNING_KILL_REPLAY ) - - if ( !level.clientScriptInitialized ) - return - - // exists on server and client. Clear it when you respawn. - ClearRecentDamageHistory( player ) - DamageHistoryStruct blankDamageHistory - clGlobal.lastDamageHistory = blankDamageHistory - - if ( player == GetLocalViewPlayer() ) - { - foreach ( callbackFunc in clGlobal.onLocalViewPlayerSpawnedCallbacks ) - { - callbackFunc( player ) - } - } - - if ( player == GetLocalClientPlayer() ) - { - player.cv.lastSpawnTime = Time() - player.cv.roundSpawnCount++ - - foreach ( callbackFunc in clGlobal.onLocalClientPlayerSpawnedCallbacks ) - { - thread callbackFunc( player ) - } - } - - if ( player.IsTitan() ) - return - - if ( player.GetPlayerClass() == level.pilotClass ) - { - thread ClientPilotSpawned( player ) - } -} - - -void function ServerCallback_TitanReadyMessage() -{ - //thread TitanReadyMessage( 1.5, false ) //Delay was necessary at some point in time according to Brent, might no longer be true? - thread TitanReadyMessage( 0.0, false ) -} - - -void function ServerCallback_RewardReadyMessage( float timeSinceLastRespawn ) -{ - if ( timeSinceLastRespawn < 1.0 ) - thread RewardReadyMessage( 6.0, false ) - else - thread RewardReadyMessage( 0.0, false ) -} - - -void function RewardReadyMessage( float delay = 0.0, bool isQuick = false ) -{ - if ( delay > 0.0 ) - wait delay - - if ( !GamePlayingOrSuddenDeath() ) - return - - entity player = GetLocalClientPlayer() - if ( !IsAlive( player ) || IsSpectating() || IsWatchingKillReplay() ) - return - - if ( player.ContextAction_IsMeleeExecution() ) - return - - entity weapon = player.GetOffhandWeapon( OFFHAND_INVENTORY ) - if ( !IsValid( weapon ) ) - return - - file.lastEarnedReward = weapon - - if ( player.IsTitan() ) - { - EmitSoundOnEntity( player, "HUD_Boost_Card_Earned_1P" ) - - string rewardReadyMessage = expect string( GetWeaponInfoFileKeyField_WithMods_Global( weapon.GetWeaponClassName(), weapon.GetMods(), "readymessage" ) ) - string rewardReadyHint = expect string( GetWeaponInfoFileKeyField_WithMods_Global( weapon.GetWeaponClassName(), weapon.GetMods(), "readyhint" ) ) - asset rewardIcon = GetWeaponInfoFileKeyFieldAsset_WithMods_Global( weapon.GetWeaponClassName(), weapon.GetMods(), "hud_icon" ) - - AnnouncementData announcement = CreateAnnouncementMessageQuick( player, rewardReadyMessage, rewardReadyHint, <1, 0.5, 0>, rewardIcon ) - announcement.displayConditionCallback = LastEarnedRewardStillValid - AnnouncementFromClass( player, announcement ) - } - else - { - EmitSoundOnEntity( player, "HUD_Boost_Card_Earned_1P" ) - - string rewardReadyMessage = expect string( GetWeaponInfoFileKeyField_WithMods_Global( weapon.GetWeaponClassName(), weapon.GetMods(), "readymessage" ) ) - string rewardReadyHint = expect string( GetWeaponInfoFileKeyField_WithMods_Global( weapon.GetWeaponClassName(), weapon.GetMods(), "readyhint" ) ) - asset rewardIcon = GetWeaponInfoFileKeyFieldAsset_WithMods_Global( weapon.GetWeaponClassName(), weapon.GetMods(), "hud_icon" ) - - AnnouncementData announcement = CreateAnnouncementMessageQuick( player, rewardReadyMessage, rewardReadyHint, <1, 0.5, 0>, rewardIcon ) - announcement.displayConditionCallback = LastEarnedRewardStillValid - AnnouncementFromClass( player, announcement ) - } -} - -void function TitanReadyMessage( float delay = 0.0, bool isQuick = false ) -{ - if ( delay > 0.0 ) - wait delay - - if ( !GamePlayingOrSuddenDeath() ) - return - - entity player = GetLocalClientPlayer() - if ( !IsAlive( player ) || IsSpectating() || IsWatchingKillReplay() ) - return - - if ( player.ContextAction_IsMeleeExecution() ) - return - - if ( Riff_TitanAvailability() == eTitanAvailability.Never ) - return - - if ( !IsTitanAvailable( player ) && - (Riff_TitanAvailability() == eTitanAvailability.Custom) - ) - return - - int loadoutIndex = GetPersistentSpawnLoadoutIndex( player, "titan" ) - TitanLoadoutDef loadout = GetTitanLoadoutFromPersistentData( player, loadoutIndex ) - - string titanReadyMessage = GetTitanReadyMessageFromSetFile( loadout.setFile ) - string titanReadyHint = GetTitanReadyHintFromSetFile( loadout.setFile ) - - AnnouncementData announcement = CreateAnnouncementMessageQuick( player, titanReadyMessage, titanReadyHint, TEAM_COLOR_YOU, $"rui/hud/titanfall_marker_arrow_ready" ) - announcement.displayConditionCallback = ConditionNoTitan - AnnouncementFromClass( player, announcement ) - - #if FACTION_DIALOGUE_ENABLED - if ( !isQuick || CoinFlip() ) - PlayFactionDialogueOnLocalClientPlayer( "mp_titanReady" ) //Playing here as opposed to on the server since delay is normally not 0 - #endif - - if ( PlayerEarnMeter_GetMode( player ) == eEarnMeterMode.DEFAULT ) //Help stop spamming "Your Titan is Ready" - { - Cl_EarnMeter_SetLastHintTime( Time() ) - } -} - - -function CoreReadyMessage( entity player, bool isQuick = false ) -{ - if ( !GamePlayingOrSuddenDeath() ) - return - - if ( !IsAlive( player ) ) - return - - if ( GetDoomedState( player ) ) - return - - if ( !player.IsTitan() ) - return - - entity weapon = player.GetOffhandWeapon( OFFHAND_EQUIPMENT ) - - string coreOnlineMessage = expect string( weapon.GetWeaponInfoFileKeyField( "readymessage" ) ) - string coreOnlineHint = expect string( weapon.GetWeaponInfoFileKeyField( "readyhint" ) ) - - if ( isQuick ) - { - AnnouncementData announcement = CreateAnnouncementMessageQuick( player, coreOnlineMessage, coreOnlineHint, TEAM_COLOR_YOU ) - announcement.displayConditionCallback = ConditionPlayerIsTitan - AnnouncementFromClass( player, announcement ) - } - else - { - AnnouncementData announcement = CreateAnnouncementMessage( player, coreOnlineMessage, coreOnlineHint, TEAM_COLOR_YOU ) - announcement.displayConditionCallback = ConditionPlayerIsTitan - announcement.subText = coreOnlineHint - AnnouncementFromClass( player, announcement ) - } -} - - -bool function ConditionPlayerIsTitan() -{ - entity player = GetLocalClientPlayer() - if ( !IsAlive( player ) ) - return false - - return player.IsTitan() -} - - -bool function ConditionPlayerIsNotTitan() -{ - entity player = GetLocalClientPlayer() - if ( !IsAlive( player ) ) - return false - - return !player.IsTitan() -} - -bool function LastEarnedRewardStillValid() -{ - entity player = GetLocalClientPlayer() - if ( !IsAlive( player ) ) - return false - - entity weapon = player.GetOffhandWeapon( OFFHAND_INVENTORY ) - if ( !IsValid( weapon ) ) - return false - - return weapon == file.lastEarnedReward -} - - -bool function ConditionNoTitan() -{ - entity player = GetLocalClientPlayer() - if ( !IsAlive( player ) ) - return false - - if ( IsValid( player.GetPetTitan() ) ) - return false - - return !player.IsTitan() -} - - -function ClientPilotSpawned( entity player ) -{ - player.EndSignal( "SettingsChanged" ) - player.EndSignal( "OnDestroy" ) - player.EndSignal( "OnDeath" ) - - if ( (player != GetLocalViewPlayer()) ) - //Turning off for the time being since the front rodeo spot leaves persistent jumpjets in the face of the TItan - thread ParentedPlayerJets( player ) -} - -void function Player_AddClient( entity player ) -{ - if ( GetCurrentPlaylistVarInt( "titan_mode_change_allowed", 1 ) ) - RegisterConCommandTriggeredCallback( "+scriptCommand2", Pressed_TitanNextMode ) - - RegisterConCommandTriggeredCallback( "+scriptCommand2", Pressed_RequestTitanfall ) - RegisterConCommandTriggeredCallback( "+scriptCommand2", Pressed_ActivateMobilityGhost ) - - RegisterConCommandTriggeredCallback( "+use", Pressed_OfferRodeoBattery ) - RegisterConCommandTriggeredCallback( "+use", Pressed_RequestRodeoBattery ) - RegisterConCommandTriggeredCallback( "+useandreload", Pressed_OfferRodeoBattery ) - RegisterConCommandTriggeredCallback( "+useandreload", Pressed_RequestRodeoBattery ) - - #if MP - RegisterConCommandTriggeredCallback( "+use", Pressed_TryNukeGrenade ) - RegisterConCommandTriggeredCallback( "-use", Released_TryNukeGrenade ) - RegisterConCommandTriggeredCallback( "+useandreload", Pressed_TryNukeGrenade ) - RegisterConCommandTriggeredCallback( "-useandreload", Released_TryNukeGrenade ) - #endif - - Create_DamageIndicatorHUD() - - if ( !IsLobby() ) - { - player.EnableHealthChangedCallback() - - player.cv.deathTime <- 0.0 - player.cv.lastSpawnTime <- 0.0 - player.cv.deathOrigin <- <0.0, 0.0, 0.0> - player.cv.roundSpawnCount <- 0 - - thread CinematicIntroScreen() - } -} - -void function Player_AddPlayer( entity player ) -{ - player.s.weaponUpdateData <- {} - - player.s.trackedAttackers <- {} // for titans - player.classChanged = true -} - -function Pressed_RequestTitanfall( entity player ) -{ - if ( !IsTitanAvailable( player ) ) - return - - #if DEV - printt( player.GetEntIndex(), "Requested replacement Titan from eye pos " + player.EyePosition() + " view angles " + player.EyeAngles() + " player origin " + player.GetOrigin() + " map " + GetMapName() ) - #endif - - player.ClientCommand( "ClientCommand_RequestTitan" ) //Send client command regardless of whether we can call the titan in or not. Server decides - Rumble_Play( "rumble_titanfall_request", {} ) - - // - //if ( player.cv.announcementActive && player.cv.announcementActive.messageText == "#HUD_TITAN_READY" ) - //{ - // clGlobal.levelEnt.Signal( "AnnoucementPurge" ) - //} - // - ////PlayMusic( "Music_FR_Militia_TitanFall1" ) - //EmitSoundOnEntity( player, "titan_callin" ) - //return - //} -} - -function Pressed_TitanNextMode( entity player ) -{ - if ( player.IsTitan() ) - return - - if ( IsWatchingReplay() ) - return - - if ( !IsAlive( player ) ) - return - - if ( player.IsPhaseShifted() ) - return - - if ( !IsAlive( player.GetPetTitan() ) ) - return - - if ( !Flag( "EnableTitanModeChange" ) ) - return - - // cannot change modes while titan is incoming - if ( player.GetHotDropImpactTime() ) - return - - player.ClientCommand( "TitanNextMode" ) - - local newMode = player.GetPetTitanMode() + 1 - if ( newMode == eNPCTitanMode.MODE_COUNT ) - newMode = eNPCTitanMode.FOLLOW - - SetAutoTitanModeHudIndicator( player, newMode ) - - local guardModeAlias = GenerateTitanOSAlias( player, AUTO_TITAN_GUARD_MODE_DIAG_SUFFIX ) - local followModeAlias = GenerateTitanOSAlias( player, AUTO_TITAN_FOLLOW_MODE_DIAG_SUFFIX ) - - // prevent the sounds from stomping each other if button is pressed rapidly - StopSoundOnEntity( player, guardModeAlias ) - StopSoundOnEntity( player, AUTO_TITAN_GUARD_MODE_SOUND ) - StopSoundOnEntity( player, followModeAlias ) - StopSoundOnEntity( player, AUTO_TITAN_FOLLOW_MODE_SOUND ) - - if ( newMode == eNPCTitanMode.FOLLOW ) - { - EmitSoundOnEntity( player, followModeAlias ) - EmitSoundOnEntity( player, AUTO_TITAN_FOLLOW_MODE_SOUND ) - } - else if ( newMode == eNPCTitanMode.STAY ) - { - EmitSoundOnEntity( player, guardModeAlias ) - EmitSoundOnEntity( player, AUTO_TITAN_GUARD_MODE_SOUND ) - } -} - -/* -void function ClientCodeCallback_OnHudReloadScheme() -{ -} -*/ - -void function ClientCodeCallback_HUDThink() -{ - PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink ) - - entity player = GetLocalViewPlayer() - - if ( !player.p.playerScriptsInitialized ) - { - PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink ) - return - } - - if ( !IsMenuLevel() ) - { - PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink_4 ) - - ClGameState_Think() - PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink_4 ) - - PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink_5 ) - UpdateVoiceHUD() - #if PC_PROG - UpdateChatHUDVisibility() - #endif // PC_PROG - PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink_5 ) - - UpdateScreenFade() - - entity clientPlayer = GetLocalClientPlayer() - if ( !IsWatchingKillReplay() && clientPlayer.classChanged ) - { - ClientPlayerClassChanged( clientPlayer, clientPlayer.GetPlayerClass() ) - } - - PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink_6 ) - SmartAmmo_LockedOntoWarningHUD_Update() - WeaponFlyoutThink( player ) - PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink_6 ) - } - - PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink ) -} - -function ClientPlayerClassChanged( entity player, newClass ) -{ - //printl( "ClientPlayerClassChanged to " + player.GetPlayerClass() ) - player.classChanged = false - - level.vduOpen = false // vdu goes away when class changes - - Assert( !IsServer() ) - Assert( newClass, "No class " ) - - switch ( newClass ) - { - case "titan": - SetStandardAbilityBindingsForTitan( player ) - SetAbilityBinding( player, 6, "+offhand4", "-offhand4" ) // "+ability 6" - - LinkButtonPair( -1, -1, -1 ) - break - - case level.pilotClass: - SetStandardAbilityBindingsForPilot( player ) - SetAbilityBinding( player, 6, "+offhand4", "-offhand4" ) // "+ability 6" - - LinkButtonPair( IN_OFFHAND0, IN_OFFHAND1, IN_OFFHAND3 ) - - if ( clGlobal.isAnnouncementActive && (clGlobal.activeAnnouncement.messageText == "#HUD_CORE_ONLINE_STRYDER" || - clGlobal.activeAnnouncement.messageText == "#HUD_CORE_ONLINE_ATLAS" || - clGlobal.activeAnnouncement.messageText == "#HUD_CORE_ONLINE_OGRE" ) ) - { - clGlobal.levelEnt.Signal( "AnnoucementPurge" ) - } - break - - case "spectator": - LinkButtonPair( -1, -1, -1 ) - break - - default: - Assert( 0, "Unknown class " + newClass ) - } - - PlayActionMusic() -} - -function ShouldShowSpawnAsTitanHint( entity player ) -{ - if ( Time() - player.cv.deathTime < GetRespawnButtonCamTime( player ) ) - return false - - if ( GetGameState() < eGameState.Playing ) - return false - - if ( GetGameState() == eGameState.SwitchingSides ) - return false - - return !IsPlayerEliminated( player ) -} - -function ServerCallback_PlayerChangedTeams( player_eHandle, oldTeam, newTeam ) -{ - entity player = GetEntityFromEncodedEHandle( player_eHandle ) - if ( player == null ) - return - Assert( oldTeam != null ) - Assert( newTeam != null ) - - string playerName = player.GetPlayerNameWithClanTag() - vector playerNameColor = OBITUARY_COLOR_ENEMY - string teamString = "ENEMY" - if ( newTeam == GetLocalViewPlayer().GetTeam() ) - { - playerNameColor = OBITUARY_COLOR_FRIENDLY - teamString = "FRIENDLY" - } - - Obituary_Print( playerName, "CHANGED TEAMS TO", teamString, playerNameColor, OBITUARY_COLOR_WEAPON, playerNameColor ) - //"Switching " + player.GetPlayerNameWithClanTag() + " from " + GetTeamStr( team1 ) + " to " + GetTeamStr( team2 ) -} - -function ServerCallback_PlayerConnectedOrDisconnected( player_eHandle, state ) -{ - entity player = GetEntityFromEncodedEHandle( player_eHandle ) - PlayerConnectedOrDisconnected( player, state ) - - if ( !IsLobby() || !IsConnected() ) - UpdatePlayerStatusCounts() -} - -void function AddCallback_OnPlayerDisconnected( void functionref( entity ) callbackFunc ) -{ - Assert( !clGlobal.onPlayerDisconnectedFuncs.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnPlayerDisconnected" ) - - clGlobal.onPlayerDisconnectedFuncs.append( callbackFunc ) -} - -void function ClientCodeCallback_PlayerDisconnected( entity player, string cachedPlayerName ) -{ - PlayerConnectedOrDisconnected( player, 0, cachedPlayerName ) - - if ( ShouldUpdatePlayerStatusCounts() ) - UpdatePlayerStatusCounts() - - // Added via AddCallback_OnPlayerDisconnected - foreach ( callbackFunc in clGlobal.onPlayerDisconnectedFuncs ) - { - callbackFunc( player ) - } -} - -function ShouldUpdatePlayerStatusCounts() -{ - if ( GetGameState() < eGameState.WaitingForPlayers ) - return false - - if ( !IsLobby() ) - return true - - if ( !IsConnected() ) - return true - - return false -} - -function PlayerConnectedOrDisconnected( entity player, state, string disconnectingPlayerName = "" ) -{ - if ( IsLobby() || GetMapName() == "" ) - // HACK: If you are disconnecting GetMapName() in IsLobby() will return "" - return - - if ( !IsValid( player ) ) - return - - Assert( state == 0 || state == 1 ) - - if ( !IsValid( GetLocalViewPlayer() ) ) - return - - string playerName - if ( state == 0 ) - { - if ( disconnectingPlayerName == "" ) - return - - playerName = disconnectingPlayerName - Assert( typeof( playerName ) == "string" ) - } - else - { - playerName = player.GetPlayerNameWithClanTag() - Assert( typeof( playerName ) == "string" ) - } - - vector playerNameColor = player.GetTeam() == GetLocalViewPlayer().GetTeam() ? OBITUARY_COLOR_FRIENDLY : OBITUARY_COLOR_ENEMY - string connectionString = (state == 0) ? "#MP_PLAYER_DISCONNECTED" : "#MP_PLAYER_CONNECTED" - - Obituary_Print_Generic( connectionString, playerName, <255, 255, 255>, playerNameColor ) -} - -void function ClientCodeCallback_PlayerDidDamage( PlayerDidDamageParams params ) -{ - if ( IsWatchingThirdPersonKillReplay() ) - return - - entity attacker = GetLocalViewPlayer() - if ( !IsValid( attacker ) ) - return - - entity victim = params.victim - if ( !IsValid( victim ) ) - return - - vector damagePosition = params.damagePosition - int hitBox = params.hitBox - int damageType = params.damageType - float damageAmount = params.damageAmount - int damageFlags = params.damageFlags - int hitGroup = params.hitGroup - entity weapon = params.weapon - float distanceFromAttackOrigin = params.distanceFromAttackOrigin - - bool playHitSound = true - bool showCrosshairHitIndicator = true - bool hitIneffective = false - bool victimIsHeavyArmor = false - bool isCritShot = (damageType & DF_CRITICAL) ? true : false - bool isHeadShot = (damageType & DF_HEADSHOT) ? true : false - bool isKillShot = (damageType & DF_KILLSHOT) ? true : false - bool isMelee = (damageType & DF_MELEE) ? true : false - bool isExplosion = (damageType & DF_EXPLOSION) ? true : false - bool isBullet = (damageType & DF_BULLET) ? true : false - bool isShotgun = (damageType & DF_SHOTGUN) ? true : false - bool isDoomFatality = (damageType & DF_DOOM_FATALITY) ? true : false - bool isDoomProtected = ((damageType & DF_DOOM_PROTECTED) && !isDoomFatality) ? true : false - victimIsHeavyArmor = victim.GetArmorType() == ARMOR_TYPE_HEAVY - - isDoomFatality = false - isDoomProtected = false - - if ( isDoomProtected ) - RegisterDoomProtectionHintDamage( damageAmount ) - - bool playKillSound = isKillShot - - if ( !attacker.IsTitan() ) - { - if ( victimIsHeavyArmor ) - { - showCrosshairHitIndicator = true - if ( victim.IsTitan() ) - hitIneffective = false //!IsHitEffectiveVsTitan( victim, damageType ) - else - hitIneffective = isCritShot || isHeadShot || !IsHitEffectiveVsNonTitan( victim, damageType ) - } - else - { - switch ( victim.GetSignifierName() ) - { - case "npc_super_spectre": - //if ( !( damageType & DF_CRITICAL ) ) - // hitIneffective = true - - default: - if ( (damageType & DF_BULLET && damageType & DF_MAX_RANGE) ) - hitIneffective = true - break - } - } - } - else - { - if ( victim.IsTitan() && victim.IsPlayer() ) - { - if ( PlayerHasPassive( victim, ePassives.PAS_BERSERKER ) ) - hitIneffective = true - } - } - - if ( damageType & DF_MAX_RANGE && damageType & DF_BULLET ) - // TODO: this is crap; these damage types should just send DF_NO_HITBEEP - playHitSound = false - - if ( damageType & DF_TITAN_STEP ) - // TODO: this is crap; these damage types should just send DF_NO_HITBEEP - { - playHitSound = false - playKillSound = false - } - - if ( damageType & DF_MELEE ) - // TODO: this is crap; these damage types should just send DF_NO_HITBEEP - { - playHitSound = false - playKillSound = false - } - - if ( damageType & DF_NO_HITBEEP ) - { - playHitSound = false - playKillSound = false - } - - if ( damageFlags & DAMAGEFLAG_VICTIM_HAS_VORTEX ) - showCrosshairHitIndicator = false - - if ( damageType & DF_SHIELD_DAMAGE ) - { - PlayShieldHitEffect( params ) - showCrosshairHitIndicator = true - } - else if ( damageAmount <= 0 ) - { - playHitSound = false - playKillSound = false - showCrosshairHitIndicator = false - } - - if ( damageType & DF_NO_INDICATOR ) - { - playHitSound = false - playKillSound = false - showCrosshairHitIndicator = false - } - - if ( isDoomProtected ) - playHitSound = false - - if ( showCrosshairHitIndicator ) - { - Tracker_PlayerAttackedTarget( attacker, victim ) - - //if ( hitIneffective ) - // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_INEFFECTIVE ) - //else - // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_NORMAL ) - // - //if ( (isCritShot || isDoomFatality) && !isDoomProtected ) - // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_CRITICAL ) - // - //if ( isHeadShot ) - // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_HEADSHOT ) - - if ( IsMultiplayer() && !victim.IsTitan() && !victim.IsHologram() ) - PROTO_HitIndicatorEffect( attacker, victim, damagePosition, isHeadShot, isKillShot ) - - if ( isKillShot ) - KillShotBloodSpray( attacker, victim, damagePosition, isExplosion, isBullet, isShotgun ) - - if ( victim.IsTitan() && isKillShot ) - ClientScreenShake( 8, 10, 1, Vector( 0, 0, 0 ) ) - - BloodSprayDecals( attacker, victim, damagePosition, damageAmount, isHeadShot, isKillShot, isMelee, isExplosion, isBullet, isShotgun ) - - DamageFlyout( damageAmount, damagePosition, victim, isHeadShot || isCritShot, hitIneffective ) - } - - bool playedHitSound = false - if ( playHitSound ) - { - if ( isHeadShot ) - playedHitSound = PlayHeadshotConfirmSound( attacker, victim, isKillShot ) - else if ( playKillSound ) - playedHitSound = PlayKillshotConfirmSound( attacker, victim, damageType ) - } - - if ( IsSpectre( victim ) ) - { - if ( isHeadShot ) - victim.Signal( "SpectreGlowEYEGLOW" ) - } - - // Play a hit sound effect if we didn't play a kill shot sound, and other conditions are met - if ( playHitSound && IsAlive( victim ) && !playedHitSound ) - { - PlayHitSound( victim, attacker, damageFlags, isCritShot, victimIsHeavyArmor, isKillShot, hitGroup ) - } - - if ( PlayerHasPassive( attacker, ePassives.PAS_SMART_CORE ) && isKillShot ) - { - attacker.p.smartCoreKills++ - } - - foreach ( callback in clGlobal.onLocalPlayerDidDamageCallback ) - { - callback( attacker, victim, damagePosition, damageType ) - } -} - -void function PlayHitSound( entity victim, entity attacker, int damageFlags, bool isCritShot, bool victimIsHeavyArmor, bool isKillShot, int hitGroup ) -{ - if ( damageFlags & DAMAGEFLAG_VICTIM_INVINCIBLE ) - { - EmitSoundOnEntity( attacker, "Player.HitbeepInvincible" ) - } - else if ( damageFlags & DAMAGEFLAG_VICTIM_HAS_VORTEX ) - { - EmitSoundOnEntity( attacker, "Player.HitbeepVortex" ) - } - else if ( isCritShot && victimIsHeavyArmor ) - { - EmitSoundOnEntity( attacker, "titan_damage_crit" ) - } - else if ( isCritShot ) - { - EmitSoundOnEntity( attacker, "Player.Hitbeep_crit" ) - } - else - { - EmitSoundOnEntity( attacker, "Player.Hitbeep" ) - } -} - -function PROTO_HitIndicatorEffect( entity player, entity victim, vector damagePosition, bool isHeadShot, bool isKillShot ) -{ - int fxId - if ( isKillShot ) - fxId = GetParticleSystemIndex( $"P_ar_impact_pilot_kill" ) - else if ( isHeadShot && !isKillShot ) - return - // fxId = GetParticleSystemIndex( $"P_ar_impact_pilot_headshot" ) - else - return - // fxId = GetParticleSystemIndex( $"P_ar_impact_pilot" ) - - vector victimVelocity = victim.GetVelocity() - damagePosition += (Length( victimVelocity ) * 0.15) * Normalize( victimVelocity ) - vector fxOffset = damagePosition - victim.GetOrigin() - StartParticleEffectOnEntityWithPos( victim, fxId, FX_PATTACH_ABSORIGIN_FOLLOW, -1, damagePosition - victim.GetOrigin(), <0, 0, 0> ) -} - -void function KillShotBloodSpray( entity player, entity victim, vector damagePosition, bool isExplosion, bool isBullet, bool isShotgun ) -{ - if ( IsSoftenedLocale() ) - return - - if ( !victim.IsHuman() && !IsProwler( victim ) ) - return - - if ( victim.IsMechanical() ) - return - - if ( victim.IsHologram() ) - return - - if ( !isExplosion && !isBullet && !isShotgun ) - return - - int fxId = GetParticleSystemIndex( FX_KILLSHOT_BLOODSPRAY ) - - vector victimVelocity = victim.GetVelocity() - damagePosition += (Length( victimVelocity ) * 0.15) * Normalize( victimVelocity ) - StartParticleEffectOnEntityWithPos( victim, fxId, FX_PATTACH_ABSORIGIN_FOLLOW, -1, damagePosition - victim.GetOrigin(), <0, 0, 0> ) -} - -void function BloodSprayDecals( entity player, entity victim, vector damagePosition, float damageAmount, bool isHeadShot, bool isKillShot, bool isMelee, bool isExplosion, bool isBullet, bool isShotgun ) -{ - if ( IsSoftenedLocale() || !Flag( "EnableBloodSprayDecals" ) ) - return - - if ( !victim.IsHuman() && !IsProwler( victim ) ) - return - - if ( victim.IsMechanical() ) - return - - if ( victim.IsHologram() ) - return - - if ( !isMelee && !isExplosion && !isBullet && !isShotgun ) - return - - // in MP, too expensive to do on every shot - if ( IsMultiplayer() && !isKillShot ) - return - - thread BloodSprayDecals_Think( player, victim, damagePosition, damageAmount, isHeadShot, isKillShot, isMelee, isExplosion, isBullet, isShotgun ) -} - -void function BloodSprayDecals_Think( entity player, entity victim, vector damagePosition, float damageAmount, bool isHeadShot, bool isKillShot, bool isMelee, bool isExplosion, bool isBullet, bool isShotgun ) -{ - player.EndSignal( "OnDestroy" ) - victim.EndSignal( "OnDestroy" ) - - BloodDecalParams params = BloodDecal_GetParams( damageAmount, isHeadShot, isKillShot, isMelee, isExplosion, isBullet, isShotgun ) - float traceDist = params.traceDist - float secondaryTraceDist = params.secondaryTraceDist - asset fxType = params.fxType - asset secondaryFxType = params.secondaryFxType - - int fxId = GetParticleSystemIndex( fxType ) - - // PRIMARY TRACES - vector traceStart = damagePosition - vector traceFwd = player.GetViewVector() - - if ( isExplosion || isMelee ) - { - // for explosion/melee damage, use chest instead of actual damage position - int attachID = victim.LookupAttachment( "CHESTFOCUS" ) - traceStart = victim.GetAttachmentOrigin( attachID ) - - if ( isExplosion ) - traceFwd = AnglesToForward( victim.GetAngles() ) * -1 - } - - vector traceEnd = damagePosition + (traceFwd * traceDist) - //TraceResults traceResult = TraceLine( traceStart, traceEnd, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) - - var deferredTrace_primary = DeferredTraceLineHighDetail( traceStart, traceEnd, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) - - while( !IsDeferredTraceFinished( deferredTrace_primary ) ) - WaitFrame() - - TraceResults traceResult = GetDeferredTraceResult( deferredTrace_primary ) - - vector primaryTraceEndPos = traceResult.endPos - vector primaryTraceNormal = traceResult.surfaceNormal - //DebugDrawLine( traceStart, traceEnd, 255, 150, 0, true, 5 ) - //DebugDrawSphere( primaryTraceEndPos, 8.0, 255, 0, 0, true, 5 ) - - bool doGravitySplat = isMelee ? false : true - - if ( traceResult.fraction < 1.0 ) - { - vector normAng = VectorToAngles( traceResult.surfaceNormal ) - vector fxAng = AnglesCompose( normAng, < 90, 0, 0 > ) - - StartParticleEffectInWorld( fxId, primaryTraceEndPos, fxAng ) - //DebugDrawAngles( endPos, fxAng, 5 ) - } - else if ( doGravitySplat ) - { - // trace behind the guy on the ground and put a decal there - float gravitySplatBackTraceDist = 58.0 // how far behind the guy to put the gravity splat - float gravitySplatDownTraceDist = 100.0 // max dist vertically to try to trace and put a gravity splat - vector groundTraceStartPos = damagePosition + (traceFwd * gravitySplatBackTraceDist) - vector groundTraceEndPos = groundTraceStartPos - <0, 0, 100> - - var deferredTrace_gravitySplat = DeferredTraceLineHighDetail( groundTraceStartPos, groundTraceEndPos, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) - - while( !IsDeferredTraceFinished( deferredTrace_gravitySplat ) ) - WaitFrame() - - TraceResults downTraceResult = GetDeferredTraceResult( deferredTrace_gravitySplat ) - - if ( downTraceResult.fraction < 1.0 ) - { - //DebugDrawLine( groundTraceStartPos, downTraceResult.endPos, 255, 150, 0, true, 5 ) - //DebugDrawSphere( downTraceResult.endPos, 4.0, 255, 0, 0, true, 5 ) - - vector normAng = VectorToAngles( downTraceResult.surfaceNormal ) - vector fxAng = AnglesCompose( normAng, < 90, 0, 0 > ) - - //DebugDrawAngles( downTraceResult.endPos, fxAng, 5 ) - - StartParticleEffectInWorld( fxId, downTraceResult.endPos, fxAng ) - } - } - - // MP doesn't want secondaries, too expensive - if ( IsMultiplayer() ) - return - - // SECONDARY TRACES - array testVecs = [] - vector tempAng = VectorToAngles( traceFwd ) - - if ( isExplosion ) - { - // for explosions, different & more angles for secondary splatter - testVecs.append( AnglesToRight( tempAng ) ) - testVecs.append( AnglesToRight( tempAng ) * -1 ) - testVecs.append( traceFwd * -1 ) - testVecs.append( AnglesToUp( tempAng ) ) - testVecs.append( AnglesToUp( tempAng ) * -1 ) - } - else - { - // mostly to cover edge cases involving corners - vector traceRight = AnglesToRight( tempAng ) - vector traceLeft = traceRight * -1 - vector backLeft = (traceFwd + traceLeft) * 0.5 - vector backRight = (traceFwd + traceRight) * 0.5 - testVecs.append( backRight ) - testVecs.append( backLeft ) - - // add blood on the ground for these weapons too - if ( isBullet || isShotgun ) - testVecs.append( AnglesToUp( tempAng ) * -1 ) - } - - if ( !testVecs.len() ) - return - - array secondaryDeferredTraces = [] - foreach ( testVec in testVecs ) - { - vector secondaryTraceEnd = traceStart + (testVec * secondaryTraceDist) - var secondaryDeferredTrace = DeferredTraceLineHighDetail( traceStart, secondaryTraceEnd, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) - secondaryDeferredTraces.append( secondaryDeferredTrace ) - } - - int secondaryFxId = GetParticleSystemIndex( secondaryFxType ) - - float startTime = Time() - array processedResults = [] - while ( processedResults.len() < secondaryDeferredTraces.len() ) - { - WaitFrame() - - foreach ( deferredTrace in secondaryDeferredTraces ) - { - if ( processedResults.contains( deferredTrace ) ) - continue - - if ( !IsDeferredTraceFinished( deferredTrace ) ) - continue - - processedResults.append( deferredTrace ) - - TraceResults traceResult = GetDeferredTraceResult( deferredTrace ) - - if ( traceResult.fraction == 1.0 ) - continue - - // don't put secondaries on the same wall as the primary - vector secondaryTraceNormal = traceResult.surfaceNormal - if ( primaryTraceNormal == secondaryTraceNormal ) - continue - - vector normAng = VectorToAngles( secondaryTraceNormal ) - vector fxAng = AnglesCompose( normAng, < 90, 0, 0 > ) - - vector endPos = traceResult.endPos - //DebugDrawSphere( endPos, 4.0, 255, 0, 0, true, 5 ) - StartParticleEffectInWorld( secondaryFxId, endPos, fxAng ) - } - - // timeout if traces aren't returning - if ( Time() - startTime >= 0.3 ) - return - } -} - -BloodDecalParams function BloodDecal_GetParams( float damageAmount, bool isHeadShot, bool isKillShot, bool isMelee, bool isExplosion, bool isBullet, bool isShotgun ) -{ - // default: bullet damage - float traceDist = 175 - float secondaryTraceDist = 100 - asset fxType = FX_BLOODSPRAY_DECAL_SML - asset secondaryFxType = FX_BLOODSPRAY_DECAL_SML - - if ( isBullet ) - { - // HACK- shotguns report isBullet also - if ( isShotgun ) - { - //if ( isKillShot ) - // fxType = FX_BLOODSPRAY_DECAL_LRG - //else - fxType = FX_BLOODSPRAY_DECAL_MED - } - else - { - if ( isKillShot ) - fxType = FX_BLOODSPRAY_DECAL_MED - else - fxType = FX_BLOODSPRAY_DECAL_SML - - if ( damageAmount >= 200 ) - { - traceDist = 216 - fxType = FX_BLOODSPRAY_DECAL_LRG - secondaryFxType = FX_BLOODSPRAY_DECAL_MED - } - } - } - - else if ( isExplosion ) - { - secondaryTraceDist = traceDist - - float maxDmg = 100 - float medDmg = 75 - - if ( damageAmount >= maxDmg ) - { - fxType = FX_BLOODSPRAY_DECAL_LRG - secondaryFxType = FX_BLOODSPRAY_DECAL_LRG - } - else if ( damageAmount >= medDmg ) - { - fxType = FX_BLOODSPRAY_DECAL_LRG - secondaryFxType = FX_BLOODSPRAY_DECAL_MED - } - else if ( isKillShot ) - { - fxType = FX_BLOODSPRAY_DECAL_MED - secondaryFxType = FX_BLOODSPRAY_DECAL_MED - } - } - - else if ( isMelee ) - { - traceDist = 96 - - if ( isKillShot ) - fxType = FX_BLOODSPRAY_DECAL_MED - } - - // for kills, increase trace distance a bit - if ( isKillShot ) - { - traceDist = traceDist + (traceDist * 0.1) - secondaryTraceDist = secondaryTraceDist + (secondaryTraceDist * 0.1) - } - - BloodDecalParams params - params.traceDist = traceDist - params.secondaryTraceDist = secondaryTraceDist - params.fxType = fxType - params.secondaryFxType = secondaryFxType - return params -} - -#if DEV -string function BloodSprayDecals_Toggle() -{ - string returnStr = "" - - if ( Flag( "EnableBloodSprayDecals" ) ) - { - FlagClear( "EnableBloodSprayDecals" ) - returnStr = "Blood spray decals DISABLED" - } - else - { - FlagSet( "EnableBloodSprayDecals" ) - returnStr = "Blood spray decals ENABLED" - } - - return returnStr -} -#endif - -function ServerCallback_RodeoerEjectWarning( soulHandle, ejectTime ) -{ - entity soul = GetEntityFromEncodedEHandle( soulHandle ) - - if ( !IsValid( soul ) ) - return - - thread TitanEjectHatchSequence( soul, ejectTime ) -} - -function TitanEjectHatchSequence( soul, ejectTime ) -{ - expect entity( soul ) - - soul.EndSignal( "OnSoulTransfer" ) - soul.EndSignal( "OnTitanDeath" ) - soul.EndSignal( "OnDestroy" ) - - local effects = [] - - OnThreadEnd( - function() : ( effects ) - { - foreach ( effect in effects ) - { - if ( !EffectDoesExist( effect ) ) - continue - - EffectStop( effect, true, true ) - } - } - ) - - int boltCount = 6 - int fxID = GetParticleSystemIndex( $"xo_spark_bolt" ) - - for ( int index = 0; index < boltCount; index++ ) - { - entity titan = soul.GetTitan() - - WaitEndFrame() // so OnTitanDeath/Destroy can happen - - if ( !IsAlive( titan ) ) - return - - if ( !titan.IsTitan() ) - { - printt( "WARNING: " + titan + " is not a Titan!" ) - return - } - - int attachID = titan.LookupAttachment( "HATCH_BOLT" + (index + 1) ) - //printt( "attachID is " + attachID ) - vector boltOrgin = titan.GetAttachmentOrigin( attachID ) - vector boltAngles = titan.GetAttachmentAngles( attachID ) - vector launchVec = AnglesToForward( boltAngles ) * 500 - - CreateClientsideGib( $"models/industrial/bolt_tiny01.mdl", boltOrgin, boltAngles, launchVec, < 0, 0, 0 >, 3.0, 1000.0, 200.0 ) - int effect = PlayFXOnTag( titan, fxID, attachID ) - effects.append( effect ) - EmitSoundOnEntity( titan, "titan_bolt_loose" ) - - wait (ejectTime / boltCount) - } -} - -void function ServerCallback_OnEntityKilled( attackerEHandle, victimEHandle, int scriptDamageType, damageSourceId ) -{ - expect int( damageSourceId ) - - bool isHeadShot = (scriptDamageType & DF_HEADSHOT) > 0 - - entity victim = GetEntityFromEncodedEHandle( victimEHandle ) - entity attacker = attackerEHandle ? GetHeavyWeightEntityFromEncodedEHandle( attackerEHandle ) : null - entity localClientPlayer = GetLocalClientPlayer() - - if ( !IsValid( victim ) ) - return - - Signal( victim, "OnDeath" ) - - if ( victim == localClientPlayer ) - { - victim.cv.deathOrigin = victim.GetOrigin() - level.clientsLastKiller = attacker - } - - if ( damageSourceId == eDamageSourceId.indoor_inferno ) - { - if ( victim == localClientPlayer ) - thread PlayerFieryDeath( victim ) - } - - UpdatePlayerStatusCounts() - - if ( IsValid( attacker ) && attacker.IsPlayer() ) - { - PlayTargetEliminatedTitanVO( attacker, victim ) - - if ( attacker == GetLocalViewPlayer() ) - WeaponFlyoutRefresh() // refreshes to display xp gained from kills - } - else if ( victim.IsPlayer() ) - { - if ( ("latestAssistTime" in victim.s) && victim.s.latestAssistTime >= Time() - MAX_NPC_KILL_STEAL_PREVENTION_TIME ) - { - attacker = expect entity( victim.s.latestAssistPlayer ) - damageSourceId = expect int( victim.s.latestAssistDamageSource ) - } - } - - if ( victim.IsPlayer() && victim != attacker ) - { - if ( attacker == localClientPlayer ) - { - thread PlayKillConfirmedSound( "Pilot_Killed_Indicator" ) - } - else if ( IsValid( attacker ) && attacker.IsTitan() ) - { - entity bossPlayer = attacker.GetBossPlayer() - if ( bossPlayer && bossPlayer == localClientPlayer ) - thread PlayKillConfirmedSound( "Pilot_Killed_Indicator" ) - } - } - else if ( (IsGrunt( victim ) || IsSpectre( victim )) && attacker == localClientPlayer ) - { - thread PlayKillConfirmedSound( "HUD_Grunt_Killed_Indicator" ) - } - - //if it's an auto titan, the obit was already printed when doomed - if ( (victim.IsTitan()) && (!victim.IsPlayer()) ) - return - - Obituary( attacker, "", victim, scriptDamageType, damageSourceId, isHeadShot ) -} - - -const float KILL_CONFIRM_DEBOUNCE = 0.025 -void function PlayKillConfirmedSound( string sound ) -{ - while ( true ) - { - if ( Time() - clGlobal.lastKillConfirmTime > KILL_CONFIRM_DEBOUNCE ) - { - clGlobal.lastKillConfirmTime = Time() - EmitSoundOnEntity( GetLocalClientPlayer(), sound ) - return - } - - WaitFrame() - } -} - -void function ServerCallback_OnTitanKilled( int attackerEHandle, int victimEHandle, int scriptDamageType, int damageSourceId ) -{ - //Gets run on every client whenever a titan is doomed by another player - bool isHeadShot = false - entity attacker = attackerEHandle != -1 ? GetEntityFromEncodedEHandle( attackerEHandle ) : null - entity victim = GetEntityFromEncodedEHandle( victimEHandle ) - - if ( (!IsValid( victim )) || (!IsValid( attacker )) ) - return - - //Obit: titans get scored/obits when doomed, so we don't want to just see "Player" in the obit, we want to see "Player's Titan" - bool victimIsOwnedTitan = victim.IsPlayer() - Obituary( attacker, "", victim, scriptDamageType, damageSourceId, isHeadShot, victimIsOwnedTitan ) -} - -function PlayTargetEliminatedTitanVO( attacker, victim ) -{ - entity localPlayer = GetLocalViewPlayer() - - if ( attacker != localPlayer ) - return - - if ( !victim.IsPlayer() ) - return - - if ( victim.IsTitan() ) - { - // a bit more delay for a titan explosion to clear - thread TitanCockpit_PlayDialogDelayed( localPlayer, 1.3, "elimTarget" ) - } - else - { - thread TitanCockpit_PlayDialogDelayed( localPlayer, 0.8, "elimEnemyPilot" ) - } -} - -function ServerCallback_SetAssistInformation( damageSourceId, attackerEHandle, entityEHandle, assistTime ) -{ - local ent = GetHeavyWeightEntityFromEncodedEHandle( entityEHandle ) - if ( !ent ) - return - - local latestAssistPlayer = GetEntityFromEncodedEHandle ( attackerEHandle ) - if ( !("latestAssistPlayer" in ent.s) ) - { - ent.s.latestAssistPlayer <- latestAssistPlayer - ent.s.latestAssistDamageSource <- damageSourceId - ent.s.latestAssistTime <- assistTime - } - else - { - ent.s.latestAssistPlayer = latestAssistPlayer - ent.s.latestAssistDamageSource = damageSourceId - ent.s.latestAssistTime = assistTime - } -} - -void function ClientCodeCallback_OnModelChanged( entity ent ) -{ -/* - // OnModelChanged gets called for each model change, but gets processed after the model has done all switches - - if ( !IsValid( ent ) ) - return; - - if ( !("creationCount" in ent.s) ) - return; - - Assert( ent instanceof C_BaseAnimating ); -*/ -} - - -void function ClientCodeCallback_OnHealthChanged( entity ent, int oldHealth, int newHealth ) -{ - if ( IsLobby() ) - return - - entity player = GetLocalViewPlayer() - if ( !IsValid( player ) ) - return - - if ( !IsValid( ent ) ) - return - - ent.Signal( "HealthChanged", { oldHealth = oldHealth, newHealth = newHealth } ) -} - -// Northstar -void function AddCallback_OnCrosshairCurrentTargetChanged( void functionref( entity, entity ) callbackFunc ) -{ - Assert( !file.OnCrosshairCurrentTargetChangedCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnCrosshairCurrentTargetChanged" ) - - file.OnCrosshairCurrentTargetChangedCallbacks.append( callbackFunc ) -} - -void function ClientCodeCallback_OnCrosshairCurrentTargetChanged( entity player, entity newTarget ) -{ - if ( IsLobby() ) - return; - if ( !IsValid( player ) ) - return - - if ( IsValid( newTarget ) ) - TryOfferRodeoBatteryHint( newTarget ) - - // Northstar - foreach ( callback in file.OnCrosshairCurrentTargetChangedCallbacks ) - { - callback( player, newTarget ) - } -} - -void function SetupPlayerAnimEvents( entity player ) -{ - SetupPlayerJumpJetAnimEvents( player ) - AddAnimEvent( player, "WallHangAttachDataKnife", WallHangAttachDataKnife ) -} - -void function JumpRandomlyForever() -{ - for (;; ) - { - if ( IsWatchingReplay() ) - { - wait 1 - continue - } - - entity player = GetLocalClientPlayer() - if ( !IsAlive( player ) || player != GetLocalViewPlayer() ) - { - wait 1 - continue - } - - printt( "jump!" ) - player.ClientCommand( "+jump" ) - wait 0 - player.ClientCommand( "-jump" ) - - wait RandomFloatRange( 0.2, 1.1 ) - } -} - -void function RemoteTurretFadeoutAnimEvent( entity ent ) -{ - entity player = GetLocalViewPlayer() - ScreenFade( player, 0, 0, 0, 255, 0.1, 0.25, FFADE_OUT ); -} - -void function SetupFirstPersonProxyEvents( entity firstPersonProxy ) -{ - //printt( "SetupFirstPersonProxyEvents" ) - - AddAnimEvent( firstPersonProxy, "mantle_smallmantle", OnSmallMantle ) - AddAnimEvent( firstPersonProxy, "mantle_mediummantle", OnMediumMantle ) - AddAnimEvent( firstPersonProxy, "mantle_lowmantle", OnLowMantle ) - AddAnimEvent( firstPersonProxy, "mantle_extralowmantle", OnExtraLowMantle ) - AddAnimEvent( firstPersonProxy, "remoteturret_fadeout", RemoteTurretFadeoutAnimEvent ) -} - -void function OnSmallMantle( entity firstPersonProxy ) //Was set up in script instead of anim to be able to play quieter sounds with stealth passive. No longer needed, but more work to move inside of anim -{ - entity player = GetLocalViewPlayer() - EmitSoundOnEntity( firstPersonProxy, "mantle_smallmantle" ) -} - -void function OnMediumMantle( entity firstPersonProxy ) //Was set up in script instead of anim to be able to play quieter sounds with stealth passive. No longer needed, but more work to move inside of anim -{ - entity player = GetLocalViewPlayer() - EmitSoundOnEntity( firstPersonProxy, "mantle_mediummantle" ) -} - -void function OnLowMantle( entity firstPersonProxy ) //Was set up in script instead of anim to be able to play quieter sounds with stealth passive. No longer needed, but more work to move inside of anim -{ - entity player = GetLocalViewPlayer() - EmitSoundOnEntity( firstPersonProxy, "mantle_lowmantle" ) -} - -void function OnExtraLowMantle( entity firstPersonProxy ) //Was set up in script instead of anim to be able to play quieter sounds with stealth passive. No longer needed, but more work to move inside of anim -{ - entity player = GetLocalViewPlayer() - EmitSoundOnEntity( firstPersonProxy, "mantle_extralow" ) -} - -void function CreateCallback_TitanSoul( entity ent ) -{ -} - -bool function ShouldHideRespawnSelectionText( entity player ) -{ - if ( player != GetLocalClientPlayer() ) - return false - if ( player.GetPlayerClass() != "spectator" ) - return false - if ( IsWatchingReplay() ) - return false - - return true -} - - - -void function WallHangAttachDataKnife( entity player ) -{ - int attachIdx = player.LookupAttachment( "l_hand" ) - if ( attachIdx == 0 ) - // hack while i wait for the attachment to be fixed - return - - entity dataknife = CreateClientSidePropDynamic( player.GetAttachmentOrigin( attachIdx ), player.GetAttachmentAngles( attachIdx ), DATA_KNIFE_MODEL ) - dataknife.SetParent( player, "l_hand" ) - - thread DeleteDataKnifeAfterWallHang( player, dataknife ) -} - -void function DeleteDataKnifeAfterWallHang( entity player, entity dataknife ) -{ - OnThreadEnd( - function() : ( dataknife ) - { - if ( IsValid( dataknife ) ) - dataknife.Kill_Deprecated_UseDestroyInstead() - } - ) - - player.EndSignal( "OnDeath" ) - player.EndSignal( "OnDestroy" ) - - for (;; ) - { - Wait( 0.1 ) - if ( !player.IsWallHanging() ) - break - } -} - -bool function ClientCodeCallback_OnGib( entity victim, vector attackDir ) -{ - if ( !victim.IsMechanical() ) - return SpawnFleshGibs( victim, attackDir ) - - return false -} - -bool function SpawnFleshGibs( entity victim, vector attackDir ) -{ - asset modelName = $"models/gibs/human_gibs.mdl" - attackDir = Normalize( attackDir ) - - float cullDist = 2048.0 - if ( "gibDist" in victim.s ) - cullDist = expect float( victim.s.gibDist ) - - vector startOrigin = victim.GetWorldSpaceCenter() + (attackDir * -30) - - vector origin = victim.GetOrigin() + < RandomIntRange( 10, 20 ), RandomIntRange( 10, 20 ), RandomIntRange( 32, 64 ) > - vector angles = < 0, 0, 0 > - vector flingDir = attackDir * RandomIntRange( 80, 200 ) - - int fxID - bool isSoftenedLocale = IsSoftenedLocale() - - if ( isSoftenedLocale ) - { - if ( victim.GetModelName() == FLYER_MODEL ) - fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist_LG_nochunk" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) - else - fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist_nochunk" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) - } - else - { - if ( victim.GetModelName() == FLYER_MODEL ) - fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist_LG" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) - else - fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) - } - - EffectSetControlPointVector( fxID, 1, flingDir ) - - if ( isSoftenedLocale ) - return true - - vector angularVel = < 0, 0, 0 > - float lifeTime = 10.0 - CreateClientsideGibWithBodyGroupGibs( modelName, victim.GetOrigin(), angles, attackDir, angularVel, lifeTime, cullDist, 1024 ) - - return true -} - -function ServerCallback_PlayScreenFXWarpJump() -{ - if ( IsWatchingReplay() ) - return false - - thread PlayScreenFXWarpJump( GetLocalClientPlayer() ) -} - -void function PlayScreenFXWarpJump( entity clientPlayer ) -{ - clientPlayer.EndSignal( "OnDeath" ) - clientPlayer.EndSignal( "OnDestroy" ) - - entity player = GetLocalViewPlayer() - int index = GetParticleSystemIndex( SCREENFX_WARPJUMP ) - int indexD = GetParticleSystemIndex( SCREENFX_WARPJUMPDLIGHT ) - int fxID = StartParticleEffectInWorldWithHandle( index, < 0, 0, 0 >, < 0, 0, 0 > ) - int fxID2 = -1 - if ( IsValid( player.GetCockpit() ) ) - { - fxID2 = StartParticleEffectOnEntity( player, indexD, FX_PATTACH_POINT_FOLLOW, player.GetCockpit().LookupAttachment( "CAMERA" ) ) - EffectSetIsWithCockpit( fxID2, true ) - } - - OnThreadEnd( - function() : ( clientPlayer, fxID, fxID2 ) - { - if ( IsValid( clientPlayer ) && !IsAlive( clientPlayer ) ) - { - EffectStop( fxID, true, false ) - if ( fxID2 > -1 ) - EffectStop( fxID2, true, false ) - } - } - ) - - wait 3.2 - if ( IsValid( player.GetCockpit() ) ) - thread TonemappingUpdateAfterWarpJump() -} - -const EXPOSURE_RAMPDOWN_DURATION = 2 -const EXPOSURE_RAMPDOWN_MAX = 20 -const EXPOSURE_RAMPDOWN_MIN = 0 -const MAX_RAMPDOWN_DURATION = 5 -const MAX_RAMPDOWN_MAX = 3 -const MAX_RAMPDOWN_MIN = 1 - -function TonemappingUpdateAfterWarpJump() -{ - // Turn cubemaps black inside drop ship, since it's pretty dark in there anyway and we don't have a great way to take a valid cubemap shot for that location. - SetConVarFloat( "mat_envmap_scale", 0 ); - - AutoExposureSetMaxExposureMultiplier( 500 ); // allow exposure to actually go bright, even if it's clamped in the level. - - // Start the exposure super bright behind the white FX, and ramp it down quickly to normal. - local startTime = Time() - while( 1 ) - { - local time = Time() - startTime - float factor = GraphCapped( time, 0, EXPOSURE_RAMPDOWN_DURATION, 1, 0 ) - local toneMapScale = EXPOSURE_RAMPDOWN_MIN + (EXPOSURE_RAMPDOWN_MAX - EXPOSURE_RAMPDOWN_MIN) * factor * factor * factor * factor - AutoExposureSetExposureCompensationBias( toneMapScale ) - AutoExposureSnap() - wait 0 - if ( factor == 0 ) - break; - } - - // Ramp the max exposure multiplier back down to 1 gently - startTime = Time() - while( 1 ) - { - local time = Time() - startTime - float factor = GraphCapped( time, 0, MAX_RAMPDOWN_DURATION, 1, 0 ) - local scale = MAX_RAMPDOWN_MIN + (MAX_RAMPDOWN_MAX - MAX_RAMPDOWN_MIN) * factor * factor - AutoExposureSetMaxExposureMultiplier( scale ); - wait 0 - if ( factor == 0 ) - break; - } -} - -function SetPanelAlphaOverTime( panel, alpha, duration ) -{ - // HACK this should be a code command - Mackey - Signal( panel, "PanelAlphaOverTime" ) - EndSignal( panel, "PanelAlphaOverTime" ) - EndSignal( panel, "OnDestroy" ) - - local startTime = Time() - local endTime = startTime + duration - local startAlpha = panel.GetPanelAlpha() - - while( Time() <= endTime ) - { - float a = GraphCapped( Time(), startTime, endTime, startAlpha, alpha ) - panel.SetPanelAlpha( a ) - WaitFrame() - } - - panel.SetPanelAlpha( alpha ) -} - - - -function HandleDoomedState( entity player, entity titan ) -{ - bool isDoomed = GetDoomedState( titan ) - if ( isDoomed ) - { - titan.Signal( "Doomed" ) - - if ( HasSoul( titan ) ) - { - entity soul = titan.GetTitanSoul() - soul.Signal( "Doomed" ) - } - } -} - -const asset SHIELD_BREAK_FX = $"P_xo_armor_break_CP" -function PlayShieldBreakEffect( entity ent ) -{ - entity shieldEnt = ent - if ( IsSoul( ent ) ) - { - shieldEnt = ent.GetTitan() - if ( !shieldEnt ) - return - } - - float shieldHealthFrac = GetShieldHealthFrac( shieldEnt ) - - int shieldBreakFX = GetParticleSystemIndex( SHIELD_BREAK_FX ) - - local attachID - if ( shieldEnt.IsTitan() ) - attachID = shieldEnt.LookupAttachment( "exp_torso_main" ) - else - attachID = shieldEnt.LookupAttachment( "ref" ) // TEMP - - local shieldFXHandle = StartParticleEffectOnEntity( shieldEnt, shieldBreakFX, FX_PATTACH_POINT_FOLLOW, attachID ) - EffectSetControlPointVector( shieldFXHandle, 1, GetShieldEffectCurrentColor( 1 - shieldHealthFrac ) ) -} - -function PlayShieldActivateEffect( entity ent ) -{ - entity shieldEnt = ent - if ( IsSoul( ent ) ) - { - shieldEnt = ent.GetTitan() - if ( !shieldEnt ) - return - } - - float shieldHealthFrac = GetShieldHealthFrac( shieldEnt ) - - int shieldBreakFX = GetParticleSystemIndex( SHIELD_BREAK_FX ) - - local attachID - if ( shieldEnt.IsTitan() ) - attachID = shieldEnt.LookupAttachment( "exp_torso_main" ) - else - attachID = shieldEnt.LookupAttachment( "ref" ) // TEMP - - local shieldFXHandle = StartParticleEffectOnEntity( shieldEnt, shieldBreakFX, FX_PATTACH_POINT_FOLLOW, attachID ) - EffectSetControlPointVector( shieldFXHandle, 1, GetShieldEffectCurrentColor( 1 - shieldHealthFrac ) ) -} - -function PlayIt( entity victim ) -{ - float shieldHealthFrac = GetShieldHealthFrac( victim ) - - int shieldbodyFX = GetParticleSystemIndex( SHIELD_BODY_FX ) - local attachID - if ( victim.IsTitan() ) - attachID = victim.LookupAttachment( "exp_torso_main" ) - else - attachID = victim.LookupAttachment( "ref" ) // TEMP - - local shieldFXHandle = StartParticleEffectOnEntity( victim, shieldbodyFX, FX_PATTACH_POINT_FOLLOW, attachID ) - - EffectSetControlPointVector( shieldFXHandle, 1, GetShieldEffectCurrentColor( 1 - shieldHealthFrac ) ) -} - -function PlayShieldHitEffect( PlayerDidDamageParams params ) -{ - entity player = GetLocalViewPlayer() - entity victim = params.victim - //vector damagePosition = params.damagePosition - //int hitBox = params.hitBox - //int damageType = params.damageType - //float damageAmount = params.damageAmount - //int damageFlags = params.damageFlags - //int hitGroup = params.hitGroup - //entity weapon = params.weapon - //float distanceFromAttackOrigin = params.distanceFromAttackOrigin - - //shieldFX <- GetParticleSystemIndex( SHIELD_FX ) - //StartParticleEffectInWorld( shieldFX, damagePosition, player.GetViewVector() * -1 ) - - float shieldHealthFrac = GetShieldHealthFrac( victim ) - - int shieldbodyFX = GetParticleSystemIndex( SHIELD_BODY_FX ) - local attachID - if ( victim.IsTitan() ) - attachID = victim.LookupAttachment( "exp_torso_main" ) - else - attachID = victim.LookupAttachment( "ref" ) // TEMP - - local shieldFXHandle = StartParticleEffectOnEntity( victim, shieldbodyFX, FX_PATTACH_POINT_FOLLOW, attachID ) - - EffectSetControlPointVector( shieldFXHandle, 1, GetShieldEffectCurrentColor( 1 - shieldHealthFrac ) ) -} - -const table SHIELD_COLOR_CHARGE_FULL = { r = 115, g = 247, b = 255 } // blue -const table SHIELD_COLOR_CHARGE_MED = { r = 200, g = 128, b = 80 } // orange -const table SHIELD_COLOR_CHARGE_EMPTY = { r = 200, g = 80, b = 80 } // red - -const SHIELD_COLOR_CROSSOVERFRAC_FULL2MED = 0.75 // from zero to this fraction, fade between full and medium charge colors -const SHIELD_COLOR_CROSSOVERFRAC_MED2EMPTY = 0.95 // from "full2med" to this fraction, fade between medium and empty charge colors - -function GetShieldEffectCurrentColor( shieldHealthFrac ) -{ - local color1 = SHIELD_COLOR_CHARGE_FULL - local color2 = SHIELD_COLOR_CHARGE_MED - local color3 = SHIELD_COLOR_CHARGE_EMPTY - - local crossover1 = SHIELD_COLOR_CROSSOVERFRAC_FULL2MED // from zero to this fraction, fade between color1 and color2 - local crossover2 = SHIELD_COLOR_CROSSOVERFRAC_MED2EMPTY // from crossover1 to this fraction, fade between color2 and color3 - - local colorVec = < 0, 0, 0 > - // 0 = full charge, 1 = no charge remaining - if ( shieldHealthFrac < crossover1 ) - { - colorVec.x = Graph( shieldHealthFrac, 0, crossover1, color1.r, color2.r ) - colorVec.y = Graph( shieldHealthFrac, 0, crossover1, color1.g, color2.g ) - colorVec.z = Graph( shieldHealthFrac, 0, crossover1, color1.b, color2.b ) - } - else if ( shieldHealthFrac < crossover2 ) - { - colorVec.x = Graph( shieldHealthFrac, crossover1, crossover2, color2.r, color3.r ) - colorVec.y = Graph( shieldHealthFrac, crossover1, crossover2, color2.g, color3.g ) - colorVec.z = Graph( shieldHealthFrac, crossover1, crossover2, color2.b, color3.b ) - } - else - { - // for the last bit of overload timer, keep it max danger color - colorVec.x = color3.r - colorVec.y = color3.g - colorVec.z = color3.b - } - - return colorVec -} - - - -void function PlayPlayerDeathSound( entity player ) -{ - if ( IsPlayerEliminated( player ) ) - EmitSoundOnEntity( player, "player_death_begin_elimination" ) - else - EmitSoundOnEntity( player, "Player_Death_Begin" ) -} - -void function StopPlayerDeathSound( entity player ) -{ - StopSoundOnEntity( player, "Player_Death_Begin" ) - EmitSoundOnEntity( player, "Player_Death_PrespawnTransition" ) -} - -function OnClientPlayerAlive( entity player ) -{ - player.Signal( "OnClientPlayerAlive" ) // TEMP; this should not be necessary, but IsWatchingKillReplay is wrong - player.EndSignal( "OnClientPlayerAlive" ) - - UpdateClientHudVisibility( player ) - - if ( IsWatchingReplay() ) - return - - if ( GetGameState() < eGameState.Playing ) - return -} - -function OnClientPlayerDying( entity player ) -{ - player.Signal( "OnClientPlayerDying" ) // TEMP; this should not be necessary, but IsWatchingKillReplay is wrong - player.EndSignal( "OnClientPlayerDying" ) - - entity player = GetLocalClientPlayer() - UpdateClientHudVisibility( player ) -// thread ShowDeathRecap( player ) - - if ( IsWatchingReplay() ) - return - - player.cv.deathTime = Time() - - thread DeathCamCheck( player ) -} - -void function ShowDeathRecap( entity player ) -{ - Assert( player == GetLocalClientPlayer() ) - - DisableCallingCardEvents() - - if ( player.e.recentDamageHistory.len() == 0 ) - return - - DamageHistoryStruct damageHistory = player.e.recentDamageHistory[ 0 ] - - entity attacker = damageHistory.attacker - - if ( !IsValid( attacker ) ) - return - - EndSignal( attacker, "OnDestroy" ) - - if ( !attacker.IsPlayer() ) - return - - if ( attacker.GetTeam() == player.GetTeam() ) - return - - wait( 1.0 ) - - CallsignEvent( eCallSignEvents.YOU, attacker, Localize( "#DEATH_SCREEN_KILLED_YOU" ) ) -} - -void function HideDeathRecap( entity player, var rui ) -{ - float minDisplayTime = 6.0 - float startTime = Time() - - waitthread DeathRecapHideDelay( player ) - wait( 0.5 ) - - float elapsedTime = Time() - startTime - if ( elapsedTime < minDisplayTime ) - wait( minDisplayTime - elapsedTime ) - - RuiSetBool( rui, "playOutro", true ) - RuiSetGameTime( rui, "outroStartTime", Time() ) - - EnableCallingCardEvents() -} - -void function DeathRecapHideDelay( entity player ) -{ - EndSignal( clGlobal.levelEnt, "LocalClientPlayerRespawned" ) - EndSignal( clGlobal.levelEnt, "OnSpectatorMode" ) - - WaitForever() -} - -void function DeathCamCheck( entity player ) -{ - wait GetRespawnButtonCamTime( player ) -} - -void function ServerCallback_ShowNextSpawnMessage( float nextSpawnTime ) -{ - entity player = GetLocalClientPlayer() - float camTime = GetRespawnButtonCamTime( player ) - - file.nextSpawnTime = nextSpawnTime - - if ( nextSpawnTime > Time() + camTime ) - thread ShowSpawnDelayMessage( nextSpawnTime ) -} - - -void function ShowSpawnDelayMessage( nextSpawnTime ) -{ - float waitTime = max( nextSpawnTime - Time(), 0 ) - - if ( waitTime < 1.0 ) - return - - entity player = GetLocalClientPlayer() - - //player.cv.nextSpawnTimeLabel.SetAlpha( 255 ) - //player.cv.nextSpawnTimeLabel.Show() - //player.cv.nextSpawnTimeLabel.SetAutoText( "#GAMEMODE_DEPLOYING_IN_N", HATT_GAME_COUNTDOWN_SECONDS, nextSpawnTime ) - // - //if ( !player.cv.nextSpawnTimeLabel.IsAutoText() ) - // player.cv.nextSpawnTimeLabel.EnableAutoText() - - while ( !IsAlive( player ) && waitTime > 0.0 ) - { - waitTime = max( nextSpawnTime - Time(), 0 ) - - AddPlayerHint( waitTime, 0.25, $"", "#GAMEMODE_DEPLOYING_IN_N", int( waitTime ) ) - - wait 1.0 - } -} -void function ServerCallback_HideNextSpawnMessage() -{ - entity player = GetLocalClientPlayer() - - HidePlayerHint( "#GAMEMODE_DEPLOYING_IN_N" ) -} - -float function GetWaveSpawnTime() -{ - return (file.nextSpawnTime) -} - -bool function IsPlayerEliminated( entity player ) -{ - return (player.GetPlayerGameStat( PGS_ELIMINATED ) > 0) -} - -function PlayerFieryDeath( player ) -{ - player.EndSignal( "OnDestroy" ) - player.EndSignal( "OnClientPlayerAlive" ) - clGlobal.levelEnt.EndSignal( "OnSpectatorMode" ) - - local offset = < 0, 0, 0 > - if ( player.IsTitan() ) - offset = < 0, 0, 96 > - - entity scriptRef = CreatePropDynamic( $"models/dev/empty_model.mdl", player.GetOrigin() + offset, player.GetAngles() ) - scriptRef.SetParent( player ) - - local fxHandle = StartParticleEffectOnEntity( scriptRef, GetParticleSystemIndex( $"P_burn_player" ), FX_PATTACH_ABSORIGIN_FOLLOW, -1 ) - - OnThreadEnd( - function () : ( fxHandle, scriptRef ) - { - EffectStop( fxHandle, false, false ) - if ( IsValid( scriptRef ) ) - scriptRef.Destroy() - } - ) - WaitForever() -} - - -function ServerCallback_GiveMatchLossProtection() -{ - clGlobal.showMatchLossProtection = true -} - -void function EnableDoDeathCallback( entity ent ) -{ - ent.DoDeathCallback( true ) -} - - - -int function UpdateSubText2ForRiffs( AnnouncementData announcement ) -{ - array riffTexts = [] - - if ( IsPilotEliminationBased() ) - riffTexts.append( "#GAMESTATE_NO_RESPAWNING" ) - - if ( Riff_FloorIsLava() ) - riffTexts.append( "#GAMEMODE_FLOOR_IS_LAVA_SUBTEXT2" ) - - if ( level.nv.minimapState == eMinimapState.Hidden ) - riffTexts.append( "#GAMESTATE_NO_MINIMAP" ) - - if ( level.nv.ammoLimit == eAmmoLimit.Limited ) - riffTexts.append( "#GAMESTATE_LIMITED_AMMUNITION" ) - - if ( level.nv.titanAvailability != eTitanAvailability.Default ) - { - switch ( level.nv.titanAvailability ) - { - case eTitanAvailability.Always: - riffTexts.append( "#GAMESTATE_UNLIMITED_TITANS" ) - break - case eTitanAvailability.Once: - riffTexts.append( "#GAMESTATE_ONE_TITAN" ) - break - case eTitanAvailability.Never: - riffTexts.append( "#GAMESTATE_NO_TITANS" ) - break - } - } - - if ( level.nv.allowNPCs != eAllowNPCs.Default ) - { - switch ( level.nv.allowNPCs ) - { - case eAllowNPCs.None: - //riffTexts.append( "#GAMESTATE_NO_MINIONS" ) - break - - case eAllowNPCs.GruntOnly: - riffTexts.append( "#GAMESTATE_GRUNTS_ONLY" ) - break - - case eAllowNPCs.SpectreOnly: - riffTexts.append( "#GAMESTATE_SPECTRES_ONLY" ) - break - } - } - - float pilotHealthMultiplier = GetCurrentPlaylistVarFloat( "pilot_health_multiplier", 0.0 ) - if ( pilotHealthMultiplier != 0.0 && pilotHealthMultiplier <= 1.5 ) - riffTexts.append( "#GAMESTATE_LOW_PILOT_HEALTH" ) - else if ( pilotHealthMultiplier > 1.5 ) - riffTexts.append( "#GAMESTATE_HIGH_PILOT_HEALTH" ) - - switch ( riffTexts.len() ) - { - case 1: - Announcement_SetSubText2( announcement, riffTexts[0] ) - break - case 2: - Announcement_SetSubText2( announcement, "#GAMEMODE_ANNOUNCEMENT_SUBTEXT_2", riffTexts[0], riffTexts[1] ) - break - case 3: - Announcement_SetSubText2( announcement, "#GAMEMODE_ANNOUNCEMENT_SUBTEXT_3", riffTexts[0], riffTexts[1], riffTexts[2] ) - break - case 4: - Announcement_SetSubText2( announcement, "#GAMEMODE_ANNOUNCEMENT_SUBTEXT_4", riffTexts[0], riffTexts[1], riffTexts[2], riffTexts[3] ) - break - case 5: - Announcement_SetSubText2( announcement, "#GAMEMODE_ANNOUNCEMENT_SUBTEXT_5", riffTexts[0], riffTexts[1], riffTexts[2], riffTexts[3], riffTexts[4] ) - break - - default: - Announcement_SetSubText2( announcement, "", "" ) - return 0 - } - - return riffTexts.len() -} - - -void function ServerCallback_GameModeAnnouncement() -{ - entity player = GetLocalClientPlayer() - string gameMode = GameRules_GetGameMode() - - if ( GameMode_GetCustomIntroAnnouncement( gameMode ) != null ) - { - void functionref(entity) func = GameMode_GetCustomIntroAnnouncement( gameMode ) - func(player) - return - } - - int team = player.GetTeam() - - local totalDuration = 0.0 - - AnnouncementData announcement - - if ( GetGameState() == eGameState.Epilogue ) - { - // never gets hit?? - announcement = Announcement_Create( "#GAMEMODE_EPILOGUE" ) - } - else - { - announcement = Announcement_Create( GAMETYPE_TEXT[gameMode] ) - announcement.announcementStyle = ANNOUNCEMENT_STYLE_BIG - - Announcement_SetIcon( announcement, GAMETYPE_ICON[gameMode] ) - Announcement_SetSubText( announcement, GAMEDESC_CURRENT ) - - if ( GameMode_IsDefined( gameMode ) ) - { - if ( GameMode_GetAttackDesc( gameMode ) != "" && team == level.nv.attackingTeam ) - Announcement_SetSubText( announcement, GameMode_GetAttackDesc( gameMode ) ) - - if ( GameMode_GetDefendDesc( gameMode ) != "" && team != level.nv.attackingTeam ) - Announcement_SetSubText( announcement, GameMode_GetDefendDesc( gameMode ) ) - } - } - - int numRiffs = UpdateSubText2ForRiffs( announcement ) - float announcementDuration = numRiffs + DEFAULT_GAMEMODE_ANNOUNCEMENT_DURATION - if ( gameMode == COLISEUM ) - announcementDuration = 2.3 //JFS: Make coliseum announcement disappear with the black bars. Note that the rui fade out sequence time is a floor on how low announcementDuration can be set to - - Announcement_SetDuration( announcement, announcementDuration ) - totalDuration += announcementDuration - - AnnouncementFromClass( player, announcement ) // TODO: team specific goals - - if ( clGlobal.showMatchLossProtection ) - { - announcementDuration = 2.0 - totalDuration += announcementDuration - delaythread( announcementDuration ) DeathHintDisplay( "#LATE_JOIN_NO_LOSS" ) - } - else if ( clGlobal.canShowLateJoinMessage ) - { - if ( level.nv.matchProgress > 5 || GetRoundsPlayed() > 0 ) - { - announcementDuration = 2.0 - totalDuration += announcementDuration - delaythread( announcementDuration ) DeathHintDisplay( "#LATE_JOIN" ) - } - } - clGlobal.showMatchLossProtection = false - clGlobal.canShowLateJoinMessage = false - - if ( Riff_FloorIsLava() ) - { - announcementDuration = 10.0 - totalDuration += announcementDuration - //printt( "Total duration delayed for lava announcement: " + totalDuration ) - delaythread( totalDuration ) PlayConversationToLocalClient( "floor_is_lava_announcement" ) - } -} - - -function MainHud_InitScoreBars( vgui, entity player, scoreGroup ) -{ - local hudScores = {} - vgui.s.scoreboardProgressBars <- hudScores - - local panel = vgui.GetPanel() - - hudScores.GameInfoBG <- scoreGroup.CreateElement( "GameInfoBG", panel ) - - string gameMode = GameRules_GetGameMode() - int friendlyTeam = player.GetTeam() - - #if HAS_GAMEMODES - if ( IsFFAGame() ) - { - return - } - #endif - - int enemyTeam = friendlyTeam == TEAM_IMC ? TEAM_MILITIA : TEAM_IMC - - if ( IsRoundBased() ) - { - level.scoreLimit[TEAM_IMC] <- GetRoundScoreLimit_FromPlaylist() - level.scoreLimit[TEAM_MILITIA] <- GetRoundScoreLimit_FromPlaylist() - } - else - { - level.scoreLimit[TEAM_IMC] <- GetScoreLimit_FromPlaylist() - level.scoreLimit[TEAM_MILITIA] <- GetScoreLimit_FromPlaylist() - } - - #if HAS_GAMEMODES - Assert( gameMode == GameRules_GetGameMode() ) - switch ( gameMode ) - { - case CAPTURE_THE_FLAG: - vgui.s.friendlyFlag <- scoreGroup.CreateElement( "FriendlyFlag", panel ) - vgui.s.enemyFlag <- scoreGroup.CreateElement( "EnemyFlag", panel ) - - vgui.s.friendlyFlagLabel <- scoreGroup.CreateElement( "FriendlyFlagLabel", panel ) - vgui.s.enemyFlagLabel <- scoreGroup.CreateElement( "EnemyFlagLabel", panel ) - - thread CaptureTheFlagThink( vgui, player ) - break - - case MARKED_FOR_DEATH: - case MARKED_FOR_DEATH_PRO: - thread MarkedForDeathHudThink( vgui, player, scoreGroup ) - break - } - #endif - - thread TitanEliminationThink( vgui, player ) - - thread RoundScoreThink( vgui, scoreGroup, player ) - - vgui.s.scoreboardProgressGroup <- scoreGroup - - hudScores.GameInfoBG.Show() - - local scoreboardArrays = {} - vgui.s.scoreboardArrays <- scoreboardArrays - - //if ( ShouldUsePlayerStatusCount() ) //Can't just do PilotEliminationBased check here because it isn't set when first connecting - //{ - // //ToDo: Eventually turn it on for normal Titan count too. Need to make sure "Titan ready but not called in yet" icon doesn't get hidden by this element - // hudScores.Player_Status_BG <- scoreGroup.CreateElement( "Player_Status_BG", panel ) - // hudScores.Player_Status_BG.Show() - // - // CreatePlayerStatusElementsFriendly( scoreboardArrays, scoreGroup, panel ) - // CreatePlayerStatusElementsEnemy( scoreboardArrays, scoreGroup, panel ) - // - // thread ScoreBarsPlayerStatusThink( vgui, player, scoreboardArrays.FriendlyPlayerStatusCount, scoreboardArrays.EnemyPlayerStatusCount ) - //} - //else - //{ - // hudScores.Player_Status_BG <- scoreGroup.CreateElement( "Player_Status_BG", panel ) - // hudScores.Player_Status_BG.Show() - // thread ScoreBarsTitanCountThink( vgui, player, hudScores.FriendlyTitanCount, hudScores.FriendlyTitanReadyCount, hudScores.EnemyTitanCount ) - //} - - if ( IsWatchingReplay() ) - vgui.s.scoreboardProgressGroup.Hide() - - UpdatePlayerStatusCounts() - - if ( IsSuddenDeathGameMode() ) - thread SuddenDeathHUDThink( vgui, player ) -} - -function CaptureTheFlagThink( vgui, entity player ) -{ - vgui.EndSignal( "OnDestroy" ) - - if ( vgui instanceof C_VGuiScreen ) - player.EndSignal( "OnDestroy" ) - - vgui.s.friendlyFlag.Show() - vgui.s.enemyFlag.Show() - vgui.s.friendlyFlagLabel.Show() - vgui.s.enemyFlagLabel.Show() - - while ( GetGameState() < eGameState.Epilogue ) - { - if ( "friendlyFlagState" in player.s ) - { - switch ( player.s.friendlyFlagState ) - { - case eFlagState.None: - vgui.s.friendlyFlagLabel.SetText( "" ) - break - case eFlagState.Home: - vgui.s.friendlyFlagLabel.SetText( "#GAMEMODE_FLAG_HOME" ) - break - case eFlagState.Held: - vgui.s.friendlyFlagLabel.SetText( player.s.friendlyFlagCarrierName ) - break - case eFlagState.Away: - vgui.s.friendlyFlagLabel.SetText( "#GAMEMODE_FLAG_DROPPED" ) - break - } - - switch ( player.s.enemyFlagState ) - { - case eFlagState.None: - vgui.s.enemyFlagLabel.SetText( "" ) - break - case eFlagState.Home: - vgui.s.enemyFlagLabel.SetText( "#GAMEMODE_FLAG_HOME" ) - break - case eFlagState.Held: - vgui.s.enemyFlagLabel.SetText( player.s.enemyFlagCarrierName ) - break - case eFlagState.Away: - vgui.s.enemyFlagLabel.SetText( "#GAMEMODE_FLAG_DROPPED" ) - break - } - } - - clGlobal.levelEnt.WaitSignal( "FlagUpdate" ) - - WaitEndFrame() - } - - vgui.s.friendlyFlag.Hide() - vgui.s.enemyFlag.Hide() - vgui.s.friendlyFlagLabel.Hide() - vgui.s.enemyFlagLabel.Hide() -} - - - -function TitanEliminationThink( vgui, entity player ) -{ - vgui.EndSignal( "OnDestroy" ) - player.EndSignal( "OnDestroy" ) - - if ( player != GetLocalClientPlayer() ) - return - - OnThreadEnd( - function() : ( player ) - { - if ( !IsValid( player ) ) - return - - if ( player.cv.hud.s.lastEventNotificationText == "#GAMEMODE_CALLINTITAN_COUNTDOWN" ) - HideEventNotification() - } - ) - - while ( true ) - { - if ( Riff_EliminationMode() == eEliminationMode.Titans ) - { - if ( IsAlive( player ) && GamePlayingOrSuddenDeath() && level.nv.secondsTitanCheckTime > Time() && !player.IsTitan() && !IsValid( player.GetPetTitan() ) && player.GetNextTitanRespawnAvailable() >= 0 ) - { - SetTimedEventNotificationHATT( level.nv.secondsTitanCheckTime - Time(), "#GAMEMODE_CALLINTITAN_COUNTDOWN", HATT_GAME_COUNTDOWN_SECONDS_MILLISECONDS, level.nv.secondsTitanCheckTime ) - } - else if ( player.cv.hud.s.lastEventNotificationText == "#GAMEMODE_CALLINTITAN_COUNTDOWN" ) - { - HideEventNotification() - } - } - else if ( Riff_EliminationMode() == eEliminationMode.Pilots ) - { - - } - - WaitSignal( player, "UpdateLastTitanStanding", "PetTitanChanged", "OnDeath" ) - } -} - -function RoundScoreThink( var vgui, var scoreGroup, entity player ) -{ - vgui.EndSignal( "OnDestroy" ) - player.EndSignal( "OnDestroy" ) - - FlagWait( "EntitiesDidLoad" ) //Have to do this because the nv that determines if RoundBased or not might not get set yet - - int friendlyTeam = player.GetTeam() - int enemyTeam = friendlyTeam == TEAM_IMC ? TEAM_MILITIA : TEAM_IMC - - local isRoundBased = IsRoundBased() - bool showRoundScore = true - int roundScoreLimit = GetRoundScoreLimit_FromPlaylist() - int scoreLimit = GetScoreLimit_FromPlaylist() - - if ( isRoundBased && showRoundScore ) - { - level.scoreLimit[TEAM_IMC] <- roundScoreLimit - level.scoreLimit[TEAM_MILITIA] <- roundScoreLimit - } - else - { - level.scoreLimit[TEAM_IMC] <- scoreLimit - level.scoreLimit[TEAM_MILITIA] <- scoreLimit - } - - local hudScores = vgui.s.scoreboardProgressBars - - while ( true ) - { - if ( isRoundBased && showRoundScore ) - { - hudScores.Friendly_Number.SetAutoText( "", HATT_FRIENDLY_TEAM_ROUND_SCORE, 0 ) - hudScores.Enemy_Number.SetAutoText( "", HATT_ENEMY_TEAM_ROUND_SCORE, 0 ) - } - - hudScores.ScoresFriendly.SetBarProgressRemap( 0, level.scoreLimit[friendlyTeam], 0.011, 0.96 ) - hudScores.ScoresEnemy.SetBarProgressRemap( 0, level.scoreLimit[enemyTeam], 0.011, 0.96 ) - wait 1.0 - } -} - -function CreatePlayerStatusElementsFriendly( scoreboardArrays, scoreGroup, panel ) -{ - scoreboardArrays.FriendlyPlayerStatusCount <- arrayofsize( 8 ) - - for ( int i = 0; i < 8; ++i ) - { - scoreboardArrays.FriendlyPlayerStatusCount[ i ] = scoreGroup.CreateElement( "Friendly_Player_Status_" + i, panel ) - } -} - -function CreatePlayerStatusElementsEnemy( scoreboardArrays, scoreGroup, panel ) -{ - scoreboardArrays.EnemyPlayerStatusCount <- arrayofsize( 8 ) - - for ( int i = 0; i < 8; ++i ) - { - scoreboardArrays.EnemyPlayerStatusCount[ i ] = scoreGroup.CreateElement( "Enemy_Player_Status_" + i, panel ) - } -} - - -function ScoreBarsPlayerStatusThink( vgui, entity player, friendlyPlayerStatusElem, enemyPlayerStatusElem ) -{ - int friendlyTeam = player.GetTeam() - int enemyTeam = friendlyTeam == TEAM_IMC ? TEAM_MILITIA : TEAM_IMC - - vgui.EndSignal( "OnDestroy" ) - - while( true ) - { - clGlobal.levelEnt.WaitSignal( "UpdatePlayerStatusCounts" ) - - if ( IsWatchingReplay() ) //Don't update visibility if the scoreboardgroup should be hidden - continue - - UpdatePlayerStatusForTeam( friendlyTeam, friendlyPlayerStatusElem, $"ui/icon_status_titan_friendly", $"ui/icon_status_pilot_friendly", $"ui/icon_status_burncard_friendly", $"ui/icon_status_burncard_friendly" ) - UpdatePlayerStatusForTeam( enemyTeam, enemyPlayerStatusElem, $"ui/icon_status_titan_enemy", $"ui/icon_status_pilot_enemy", $"ui/icon_status_burncard_enemy", $"ui/icon_status_burncard_enemy" ) - } -} - -function CountPlayerStatusTypes( array teamPlayers ) -{ - table resultTable = { - titanWithBurnCard = 0, - titan = 0, - pilotWithBurnCard = 0 - pilot = 0, - } - - foreach ( player in teamPlayers ) - { - entity playerPetTitan = player.GetPetTitan() - - if ( !IsAlive( player ) ) - { - if ( IsAlive( playerPetTitan ) ) - resultTable.titan++ - } - else - { - if ( player.IsTitan() ) - resultTable.titan++ - else if ( IsAlive( playerPetTitan ) ) - resultTable.titan++ - else - resultTable.pilot++ - } - - } - - return resultTable -} - - -function UpdatePlayerStatusForTeam( int team, teamStatusElem, titanImage, pilotImage, pilotBurnCardImage, titanBurnCardImage ) -{ - array teamPlayers = GetPlayerArrayOfTeam( team ) - local teamResultTable = CountPlayerStatusTypes( teamPlayers ) - - int maxElems = 8 - - int index = 0 - int currentElem = 0 - - for ( index = 0; index < teamResultTable.titanWithBurnCard && currentElem < maxElems; index++, currentElem++ ) - { - teamStatusElem[ currentElem ].Show() - teamStatusElem[ currentElem ].SetImage( titanBurnCardImage ) - } - - for ( index = 0; index < teamResultTable.titan && index < maxElems; index++, currentElem++ ) - { - teamStatusElem[ currentElem ].Show() - teamStatusElem[ currentElem ].SetImage( titanImage ) - } - - for ( index = 0; index < teamResultTable.pilotWithBurnCard && index < maxElems; index++, currentElem++ ) - { - teamStatusElem[ currentElem ].Show() - teamStatusElem[ currentElem ].SetImage( pilotBurnCardImage ) - } - - for ( index = 0; index < teamResultTable.pilot && index < maxElems; index++, currentElem++ ) - { - teamStatusElem[ currentElem ].Show() - teamStatusElem[ currentElem ].SetImage( pilotImage ) - } - - for( ; currentElem < maxElems; currentElem++ ) - { - teamStatusElem[ currentElem ].Hide() - } -} -function SuddenDeathHUDThink( vgui, entity player ) -{ - Signal( player, "SuddenDeathHUDThink" ) - player.EndSignal( "SuddenDeathHUDThink" ) - vgui.EndSignal( "OnDestroy" ) - - while ( GetGameState() != eGameState.SuddenDeath ) - WaitSignal( player, "GameStateChanged" ) - - EndSignal( player, "GameStateChanged" ) - - local hudScores = vgui.s.scoreboardProgressBars - - OnThreadEnd( - function() : ( hudScores, player ) - { - if ( !IsValid( hudScores ) ) - return - - hudScores.GameInfo_Label.SetColor( 255, 255, 255, 255 ) - - string restoredGameModeLabelText = GAMETYPE_TEXT[ GameRules_GetGameMode() ] - hudScores.GameModeLabel.SetText( restoredGameModeLabelText ) - - if ( player == GetLocalClientPlayer() ) - { - local scoreElemsClient = player.cv.clientHud.s.mainVGUI.s.scoreboardProgressGroup.elements - scoreElemsClient.GameModeLabel.SetText( restoredGameModeLabelText ) - } - } - ) - - string gameModeLabelText = "" - - switch ( GAMETYPE ) - { - case CAPTURE_THE_FLAG: - gameModeLabelText = "#GAMEMODE_CAPTURE_THE_FLAG_SUDDEN_DEATH" - break - - case TEAM_DEATHMATCH: - case HARDCORE_TDM: - gameModeLabelText = "#GAMEMODE_PILOT_HUNTER_SUDDEN_DEATH" - break - - default: - gameModeLabelText = GAMETYPE_TEXT[ GameRules_GetGameMode() ] - } - - hudScores.GameModeLabel.SetText( gameModeLabelText ) - - if ( player == GetLocalClientPlayer() ) - { - local scoreElemsClient = player.cv.clientHud.s.mainVGUI.s.scoreboardProgressGroup.elements - scoreElemsClient.GameModeLabel.SetText( gameModeLabelText ) - } - - float startTime = Time() - float pulseFrac = 0.0 - - while ( true ) - { - pulseFrac = Graph( GetPulseFrac( 1.0, startTime ), 0.0, 1.0, 0.05, 1.0 ) - hudScores.GameInfo_Label.SetColor( 255, 255, 255, 255 * pulseFrac ) - - wait( 0.0 ) - } -} +untyped + +global function ClPlayer_Init + +global function PlayIt +global function JumpRandomlyForever + +global function ClientCodeCallback_PlayerDidDamage +global function ClientCodeCallback_PlayerSpawned +//global function ClientCodeCallback_OnHudReloadScheme +global function ClientCodeCallback_HUDThink +global function Player_AddPlayer +global function Player_AddClient +global function PlayerConnectedOrDisconnected +global function ServerCallback_GameModeAnnouncement +global function MainHud_InitScoreBars + +global function ServerCallback_PlayerConnectedOrDisconnected +global function ClientCodeCallback_PlayerDisconnected +global function ServerCallback_PlayerChangedTeams +global function ClientCodeCallback_OnModelChanged +global function ServerCallback_RodeoerEjectWarning +global function ServerCallback_PlayScreenFXWarpJump +global function PlayShieldBreakEffect +global function PlayShieldActivateEffect +global function HandleDoomedState +global function RewardReadyMessage +global function TitanReadyMessage +global function CoreReadyMessage + +global function ServerCallback_RewardReadyMessage +global function ServerCallback_TitanReadyMessage + +global function OnClientPlayerAlive +global function OnClientPlayerDying +global function PlayPlayerDeathSound +global function StopPlayerDeathSound + +global function ServerCallback_ShowNextSpawnMessage +global function GetWaveSpawnTime +global function ServerCallback_HideNextSpawnMessage + +global function ClientCodeCallback_OnHealthChanged + +// Northstar +global function AddCallback_OnCrosshairCurrentTargetChanged +global function ClientCodeCallback_OnCrosshairCurrentTargetChanged +global function Pressed_TitanNextMode +global function ClientCodeCallback_OnGib +global function ClientPilotSpawned +global function AddCallback_OnPlayerDisconnected + +global function IsPlayerEliminated + +global function ServerCallback_GiveMatchLossProtection +global function ServerCallback_OnEntityKilled +global function ServerCallback_OnTitanKilled + +global function ShouldShowSpawnAsTitanHint +global function ServerCallback_SetAssistInformation + +global function GetShieldEffectCurrentColor +global function ClientPlayerClassChanged + +#if DEV +global function BloodSprayDecals_Toggle +#endif + +const float DEFAULT_GAMEMODE_ANNOUNCEMENT_DURATION = 5.0 + +struct { + var orbitalstrike_tracer = null + var law_missile_tracer = null + float nextSpawnTime = 0.0 + entity lastEarnedReward // primarily used to check if we should still show the reward message after a delay + + // Northstar + array< void functionref( entity, entity ) > OnCrosshairCurrentTargetChangedCallbacks +} file + +struct BloodDecalParams +{ + float traceDist + float secondaryTraceDist + asset fxType + asset secondaryFxType +} + +void function ClPlayer_Init() +{ + ClPilotJumpjet_Init() + ClDamageIndicator_Init() + + ClPlayer_Common_Precache() + + RegisterSignal( "OnAnimationDone" ) + RegisterSignal( "OnAnimationInterrupted" ) + RegisterSignal( "OnBleedingOut" ) + RegisterSignal( "PanelAlphaOverTime" ) + RegisterSignal( "LocalClientPlayerRespawned" ) + RegisterSignal( "OnClientPlayerAlive" ) + RegisterSignal( "OnClientPlayerDying" ) + RegisterSignal( "StopAlertCore" ) + RegisterSignal( "OnSpectatorMode" ) + RegisterSignal( "HealthChanged" ) + + FlagInit( "DamageDistancePrint" ) + FlagInit( "EnableTitanModeChange", true ) + FlagInit( "EnableBloodSprayDecals", true ) + + level.vduOpen <- false + level.canSpawnAsTitan <- false + level.grenadeIndicatorEnabled <- true + level.clientsLastKiller <- null + + AddCreateCallback( "player", SetupPlayerAnimEvents ) + AddCreateCallback( "player", MpClientPlayerInit ) + + AddCreateCallback( "first_person_proxy", SetupFirstPersonProxyEvents ) + AddCreateCallback( "predicted_first_person_proxy", SetupFirstPersonProxyEvents ) + + AddCreateCallback( "player", EnableDoDeathCallback ) + AddCreateCallback( "npc_titan", EnableDoDeathCallback ) + + AddCreateCallback( "titan_soul", CreateCallback_TitanSoul ) + + AddCallback_OnPlayerLifeStateChanged( PlayerADSDof ) + + file.orbitalstrike_tracer = PrecacheParticleSystem( $"Rocket_Smoke_Large" ) + //DEBUG Remove when bug is fixed. + file.law_missile_tracer = PrecacheParticleSystem( $"wpn_orbital_rocket_tracer" ) + + level.menuHideGroups <- {} + + level.spawnAsTitanSelected <- false + + AddPlayerFunc( Player_AddPlayer ) +} + +entity function FindEnemyRodeoParent( entity player ) +{ + entity ent = player.GetParent() + if ( ent == null ) + return null + + if ( !ent.IsTitan() ) + return null + + if ( ent == player.GetPetTitan() ) + return null + + if ( ent.GetTeam() == player.GetTeam() ) + return null + + return ent +} + +void function MpClientPlayerInit( entity player ) +{ + player.ClientCommand( "save_enable 0" ) +} + +void function ClientCodeCallback_PlayerSpawned( entity player ) +{ + if ( !IsValid( player ) ) + return + + if ( IsMenuLevel() ) + return + + ClearCrosshairPriority( crosshairPriorityLevel.ROUND_WINNING_KILL_REPLAY ) + + if ( !level.clientScriptInitialized ) + return + + // exists on server and client. Clear it when you respawn. + ClearRecentDamageHistory( player ) + DamageHistoryStruct blankDamageHistory + clGlobal.lastDamageHistory = blankDamageHistory + + if ( player == GetLocalViewPlayer() ) + { + foreach ( callbackFunc in clGlobal.onLocalViewPlayerSpawnedCallbacks ) + { + callbackFunc( player ) + } + } + + if ( player == GetLocalClientPlayer() ) + { + player.cv.lastSpawnTime = Time() + player.cv.roundSpawnCount++ + + foreach ( callbackFunc in clGlobal.onLocalClientPlayerSpawnedCallbacks ) + { + thread callbackFunc( player ) + } + } + + if ( player.IsTitan() ) + return + + if ( player.GetPlayerClass() == level.pilotClass ) + { + thread ClientPilotSpawned( player ) + } +} + + +void function ServerCallback_TitanReadyMessage() +{ + //thread TitanReadyMessage( 1.5, false ) //Delay was necessary at some point in time according to Brent, might no longer be true? + thread TitanReadyMessage( 0.0, false ) +} + + +void function ServerCallback_RewardReadyMessage( float timeSinceLastRespawn ) +{ + if ( timeSinceLastRespawn < 1.0 ) + thread RewardReadyMessage( 6.0, false ) + else + thread RewardReadyMessage( 0.0, false ) +} + + +void function RewardReadyMessage( float delay = 0.0, bool isQuick = false ) +{ + if ( delay > 0.0 ) + wait delay + + if ( !GamePlayingOrSuddenDeath() ) + return + + entity player = GetLocalClientPlayer() + if ( !IsAlive( player ) || IsSpectating() || IsWatchingKillReplay() ) + return + + if ( player.ContextAction_IsMeleeExecution() ) + return + + entity weapon = player.GetOffhandWeapon( OFFHAND_INVENTORY ) + if ( !IsValid( weapon ) ) + return + + file.lastEarnedReward = weapon + + if ( player.IsTitan() ) + { + EmitSoundOnEntity( player, "HUD_Boost_Card_Earned_1P" ) + + string rewardReadyMessage = expect string( GetWeaponInfoFileKeyField_WithMods_Global( weapon.GetWeaponClassName(), weapon.GetMods(), "readymessage" ) ) + string rewardReadyHint = expect string( GetWeaponInfoFileKeyField_WithMods_Global( weapon.GetWeaponClassName(), weapon.GetMods(), "readyhint" ) ) + asset rewardIcon = GetWeaponInfoFileKeyFieldAsset_WithMods_Global( weapon.GetWeaponClassName(), weapon.GetMods(), "hud_icon" ) + + AnnouncementData announcement = CreateAnnouncementMessageQuick( player, rewardReadyMessage, rewardReadyHint, <1, 0.5, 0>, rewardIcon ) + announcement.displayConditionCallback = LastEarnedRewardStillValid + AnnouncementFromClass( player, announcement ) + } + else + { + EmitSoundOnEntity( player, "HUD_Boost_Card_Earned_1P" ) + + string rewardReadyMessage = expect string( GetWeaponInfoFileKeyField_WithMods_Global( weapon.GetWeaponClassName(), weapon.GetMods(), "readymessage" ) ) + string rewardReadyHint = expect string( GetWeaponInfoFileKeyField_WithMods_Global( weapon.GetWeaponClassName(), weapon.GetMods(), "readyhint" ) ) + asset rewardIcon = GetWeaponInfoFileKeyFieldAsset_WithMods_Global( weapon.GetWeaponClassName(), weapon.GetMods(), "hud_icon" ) + + AnnouncementData announcement = CreateAnnouncementMessageQuick( player, rewardReadyMessage, rewardReadyHint, <1, 0.5, 0>, rewardIcon ) + announcement.displayConditionCallback = LastEarnedRewardStillValid + AnnouncementFromClass( player, announcement ) + } +} + +void function TitanReadyMessage( float delay = 0.0, bool isQuick = false ) +{ + if ( delay > 0.0 ) + wait delay + + if ( !GamePlayingOrSuddenDeath() ) + return + + entity player = GetLocalClientPlayer() + if ( !IsAlive( player ) || IsSpectating() || IsWatchingKillReplay() ) + return + + if ( player.ContextAction_IsMeleeExecution() ) + return + + if ( Riff_TitanAvailability() == eTitanAvailability.Never ) + return + + if ( !IsTitanAvailable( player ) && + (Riff_TitanAvailability() == eTitanAvailability.Custom) + ) + return + + int loadoutIndex = GetPersistentSpawnLoadoutIndex( player, "titan" ) + TitanLoadoutDef loadout = GetTitanLoadoutFromPersistentData( player, loadoutIndex ) + + string titanReadyMessage = GetTitanReadyMessageFromSetFile( loadout.setFile ) + string titanReadyHint = GetTitanReadyHintFromSetFile( loadout.setFile ) + + AnnouncementData announcement = CreateAnnouncementMessageQuick( player, titanReadyMessage, titanReadyHint, TEAM_COLOR_YOU, $"rui/hud/titanfall_marker_arrow_ready" ) + announcement.displayConditionCallback = ConditionNoTitan + AnnouncementFromClass( player, announcement ) + + #if FACTION_DIALOGUE_ENABLED + if ( !isQuick || CoinFlip() ) + PlayFactionDialogueOnLocalClientPlayer( "mp_titanReady" ) //Playing here as opposed to on the server since delay is normally not 0 + #endif + + if ( PlayerEarnMeter_GetMode( player ) == eEarnMeterMode.DEFAULT ) //Help stop spamming "Your Titan is Ready" + { + Cl_EarnMeter_SetLastHintTime( Time() ) + } +} + + +function CoreReadyMessage( entity player, bool isQuick = false ) +{ + if ( !GamePlayingOrSuddenDeath() ) + return + + if ( !IsAlive( player ) ) + return + + if ( GetDoomedState( player ) ) + return + + if ( !player.IsTitan() ) + return + + entity weapon = player.GetOffhandWeapon( OFFHAND_EQUIPMENT ) + + string coreOnlineMessage = expect string( weapon.GetWeaponInfoFileKeyField( "readymessage" ) ) + string coreOnlineHint = expect string( weapon.GetWeaponInfoFileKeyField( "readyhint" ) ) + + if ( isQuick ) + { + AnnouncementData announcement = CreateAnnouncementMessageQuick( player, coreOnlineMessage, coreOnlineHint, TEAM_COLOR_YOU ) + announcement.displayConditionCallback = ConditionPlayerIsTitan + AnnouncementFromClass( player, announcement ) + } + else + { + AnnouncementData announcement = CreateAnnouncementMessage( player, coreOnlineMessage, coreOnlineHint, TEAM_COLOR_YOU ) + announcement.displayConditionCallback = ConditionPlayerIsTitan + announcement.subText = coreOnlineHint + AnnouncementFromClass( player, announcement ) + } +} + + +bool function ConditionPlayerIsTitan() +{ + entity player = GetLocalClientPlayer() + if ( !IsAlive( player ) ) + return false + + return player.IsTitan() +} + + +bool function ConditionPlayerIsNotTitan() +{ + entity player = GetLocalClientPlayer() + if ( !IsAlive( player ) ) + return false + + return !player.IsTitan() +} + +bool function LastEarnedRewardStillValid() +{ + entity player = GetLocalClientPlayer() + if ( !IsAlive( player ) ) + return false + + entity weapon = player.GetOffhandWeapon( OFFHAND_INVENTORY ) + if ( !IsValid( weapon ) ) + return false + + return weapon == file.lastEarnedReward +} + + +bool function ConditionNoTitan() +{ + entity player = GetLocalClientPlayer() + if ( !IsAlive( player ) ) + return false + + if ( IsValid( player.GetPetTitan() ) ) + return false + + return !player.IsTitan() +} + + +function ClientPilotSpawned( entity player ) +{ + player.EndSignal( "SettingsChanged" ) + player.EndSignal( "OnDestroy" ) + player.EndSignal( "OnDeath" ) + + if ( (player != GetLocalViewPlayer()) ) + //Turning off for the time being since the front rodeo spot leaves persistent jumpjets in the face of the TItan + thread ParentedPlayerJets( player ) +} + +void function Player_AddClient( entity player ) +{ + if ( GetCurrentPlaylistVarInt( "titan_mode_change_allowed", 1 ) ) + RegisterConCommandTriggeredCallback( "+scriptCommand2", Pressed_TitanNextMode ) + + RegisterConCommandTriggeredCallback( "+scriptCommand2", Pressed_RequestTitanfall ) + RegisterConCommandTriggeredCallback( "+scriptCommand2", Pressed_ActivateMobilityGhost ) + + RegisterConCommandTriggeredCallback( "+use", Pressed_OfferRodeoBattery ) + RegisterConCommandTriggeredCallback( "+use", Pressed_RequestRodeoBattery ) + RegisterConCommandTriggeredCallback( "+useandreload", Pressed_OfferRodeoBattery ) + RegisterConCommandTriggeredCallback( "+useandreload", Pressed_RequestRodeoBattery ) + + #if MP + RegisterConCommandTriggeredCallback( "+use", Pressed_TryNukeGrenade ) + RegisterConCommandTriggeredCallback( "-use", Released_TryNukeGrenade ) + RegisterConCommandTriggeredCallback( "+useandreload", Pressed_TryNukeGrenade ) + RegisterConCommandTriggeredCallback( "-useandreload", Released_TryNukeGrenade ) + #endif + + Create_DamageIndicatorHUD() + + if ( !IsLobby() ) + { + player.EnableHealthChangedCallback() + + player.cv.deathTime <- 0.0 + player.cv.lastSpawnTime <- 0.0 + player.cv.deathOrigin <- <0.0, 0.0, 0.0> + player.cv.roundSpawnCount <- 0 + + thread CinematicIntroScreen() + } +} + +void function Player_AddPlayer( entity player ) +{ + player.s.weaponUpdateData <- {} + + player.s.trackedAttackers <- {} // for titans + player.classChanged = true +} + +function Pressed_RequestTitanfall( entity player ) +{ + if ( !IsTitanAvailable( player ) ) + return + + #if DEV + printt( player.GetEntIndex(), "Requested replacement Titan from eye pos " + player.EyePosition() + " view angles " + player.EyeAngles() + " player origin " + player.GetOrigin() + " map " + GetMapName() ) + #endif + + player.ClientCommand( "ClientCommand_RequestTitan" ) //Send client command regardless of whether we can call the titan in or not. Server decides + Rumble_Play( "rumble_titanfall_request", {} ) + + // + //if ( player.cv.announcementActive && player.cv.announcementActive.messageText == "#HUD_TITAN_READY" ) + //{ + // clGlobal.levelEnt.Signal( "AnnoucementPurge" ) + //} + // + ////PlayMusic( "Music_FR_Militia_TitanFall1" ) + //EmitSoundOnEntity( player, "titan_callin" ) + //return + //} +} + +function Pressed_TitanNextMode( entity player ) +{ + if ( player.IsTitan() ) + return + + if ( IsWatchingReplay() ) + return + + if ( !IsAlive( player ) ) + return + + if ( player.IsPhaseShifted() ) + return + + if ( !IsAlive( player.GetPetTitan() ) ) + return + + if ( !Flag( "EnableTitanModeChange" ) ) + return + + // cannot change modes while titan is incoming + if ( player.GetHotDropImpactTime() ) + return + + player.ClientCommand( "TitanNextMode" ) + + local newMode = player.GetPetTitanMode() + 1 + if ( newMode == eNPCTitanMode.MODE_COUNT ) + newMode = eNPCTitanMode.FOLLOW + + SetAutoTitanModeHudIndicator( player, newMode ) + + local guardModeAlias = GenerateTitanOSAlias( player, AUTO_TITAN_GUARD_MODE_DIAG_SUFFIX ) + local followModeAlias = GenerateTitanOSAlias( player, AUTO_TITAN_FOLLOW_MODE_DIAG_SUFFIX ) + + // prevent the sounds from stomping each other if button is pressed rapidly + StopSoundOnEntity( player, guardModeAlias ) + StopSoundOnEntity( player, AUTO_TITAN_GUARD_MODE_SOUND ) + StopSoundOnEntity( player, followModeAlias ) + StopSoundOnEntity( player, AUTO_TITAN_FOLLOW_MODE_SOUND ) + + if ( newMode == eNPCTitanMode.FOLLOW ) + { + EmitSoundOnEntity( player, followModeAlias ) + EmitSoundOnEntity( player, AUTO_TITAN_FOLLOW_MODE_SOUND ) + } + else if ( newMode == eNPCTitanMode.STAY ) + { + EmitSoundOnEntity( player, guardModeAlias ) + EmitSoundOnEntity( player, AUTO_TITAN_GUARD_MODE_SOUND ) + } +} + +/* +void function ClientCodeCallback_OnHudReloadScheme() +{ +} +*/ + +void function ClientCodeCallback_HUDThink() +{ + PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink ) + + entity player = GetLocalViewPlayer() + + if ( !player.p.playerScriptsInitialized ) + { + PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink ) + return + } + + if ( !IsMenuLevel() ) + { + PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink_4 ) + + ClGameState_Think() + PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink_4 ) + + PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink_5 ) + UpdateVoiceHUD() + #if PC_PROG + UpdateChatHUDVisibility() + #endif // PC_PROG + PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink_5 ) + + UpdateScreenFade() + + entity clientPlayer = GetLocalClientPlayer() + if ( !IsWatchingKillReplay() && clientPlayer.classChanged ) + { + ClientPlayerClassChanged( clientPlayer, clientPlayer.GetPlayerClass() ) + } + + PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink_6 ) + SmartAmmo_LockedOntoWarningHUD_Update() + WeaponFlyoutThink( player ) + PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink_6 ) + } + + PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink ) +} + +function ClientPlayerClassChanged( entity player, newClass ) +{ + //printl( "ClientPlayerClassChanged to " + player.GetPlayerClass() ) + player.classChanged = false + + level.vduOpen = false // vdu goes away when class changes + + Assert( !IsServer() ) + Assert( newClass, "No class " ) + + switch ( newClass ) + { + case "titan": + SetStandardAbilityBindingsForTitan( player ) + SetAbilityBinding( player, 6, "+offhand4", "-offhand4" ) // "+ability 6" + + LinkButtonPair( -1, -1, -1 ) + break + + case level.pilotClass: + SetStandardAbilityBindingsForPilot( player ) + SetAbilityBinding( player, 6, "+offhand4", "-offhand4" ) // "+ability 6" + + LinkButtonPair( IN_OFFHAND0, IN_OFFHAND1, IN_OFFHAND3 ) + + if ( clGlobal.isAnnouncementActive && (clGlobal.activeAnnouncement.messageText == "#HUD_CORE_ONLINE_STRYDER" || + clGlobal.activeAnnouncement.messageText == "#HUD_CORE_ONLINE_ATLAS" || + clGlobal.activeAnnouncement.messageText == "#HUD_CORE_ONLINE_OGRE" ) ) + { + clGlobal.levelEnt.Signal( "AnnoucementPurge" ) + } + break + + case "spectator": + LinkButtonPair( -1, -1, -1 ) + break + + default: + Assert( 0, "Unknown class " + newClass ) + } + + PlayActionMusic() +} + +function ShouldShowSpawnAsTitanHint( entity player ) +{ + if ( Time() - player.cv.deathTime < GetRespawnButtonCamTime( player ) ) + return false + + if ( GetGameState() < eGameState.Playing ) + return false + + if ( GetGameState() == eGameState.SwitchingSides ) + return false + + return !IsPlayerEliminated( player ) +} + +function ServerCallback_PlayerChangedTeams( player_eHandle, oldTeam, newTeam ) +{ + entity player = GetEntityFromEncodedEHandle( player_eHandle ) + if ( player == null ) + return + Assert( oldTeam != null ) + Assert( newTeam != null ) + + string playerName = player.GetPlayerNameWithClanTag() + vector playerNameColor = OBITUARY_COLOR_ENEMY + string teamString = "ENEMY" + if ( newTeam == GetLocalViewPlayer().GetTeam() ) + { + playerNameColor = OBITUARY_COLOR_FRIENDLY + teamString = "FRIENDLY" + } + + Obituary_Print( playerName, "CHANGED TEAMS TO", teamString, playerNameColor, OBITUARY_COLOR_WEAPON, playerNameColor ) + //"Switching " + player.GetPlayerNameWithClanTag() + " from " + GetTeamStr( team1 ) + " to " + GetTeamStr( team2 ) +} + +function ServerCallback_PlayerConnectedOrDisconnected( player_eHandle, state ) +{ + entity player = GetEntityFromEncodedEHandle( player_eHandle ) + PlayerConnectedOrDisconnected( player, state ) + + if ( !IsLobby() || !IsConnected() ) + UpdatePlayerStatusCounts() +} + +void function AddCallback_OnPlayerDisconnected( void functionref( entity ) callbackFunc ) +{ + Assert( !clGlobal.onPlayerDisconnectedFuncs.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnPlayerDisconnected" ) + + clGlobal.onPlayerDisconnectedFuncs.append( callbackFunc ) +} + +void function ClientCodeCallback_PlayerDisconnected( entity player, string cachedPlayerName ) +{ + PlayerConnectedOrDisconnected( player, 0, cachedPlayerName ) + + if ( ShouldUpdatePlayerStatusCounts() ) + UpdatePlayerStatusCounts() + + // Added via AddCallback_OnPlayerDisconnected + foreach ( callbackFunc in clGlobal.onPlayerDisconnectedFuncs ) + { + callbackFunc( player ) + } +} + +function ShouldUpdatePlayerStatusCounts() +{ + if ( GetGameState() < eGameState.WaitingForPlayers ) + return false + + if ( !IsLobby() ) + return true + + if ( !IsConnected() ) + return true + + return false +} + +function PlayerConnectedOrDisconnected( entity player, state, string disconnectingPlayerName = "" ) +{ + if ( IsLobby() || GetMapName() == "" ) + // HACK: If you are disconnecting GetMapName() in IsLobby() will return "" + return + + if ( !IsValid( player ) ) + return + + Assert( state == 0 || state == 1 ) + + if ( !IsValid( GetLocalViewPlayer() ) ) + return + + string playerName + if ( state == 0 ) + { + if ( disconnectingPlayerName == "" ) + return + + playerName = disconnectingPlayerName + Assert( typeof( playerName ) == "string" ) + } + else + { + playerName = player.GetPlayerNameWithClanTag() + Assert( typeof( playerName ) == "string" ) + } + + vector playerNameColor = player.GetTeam() == GetLocalViewPlayer().GetTeam() ? OBITUARY_COLOR_FRIENDLY : OBITUARY_COLOR_ENEMY + string connectionString = (state == 0) ? "#MP_PLAYER_DISCONNECTED" : "#MP_PLAYER_CONNECTED" + + Obituary_Print_Generic( connectionString, playerName, <255, 255, 255>, playerNameColor ) +} + +void function ClientCodeCallback_PlayerDidDamage( PlayerDidDamageParams params ) +{ + if ( IsWatchingThirdPersonKillReplay() ) + return + + entity attacker = GetLocalViewPlayer() + if ( !IsValid( attacker ) ) + return + + entity victim = params.victim + if ( !IsValid( victim ) ) + return + + vector damagePosition = params.damagePosition + int hitBox = params.hitBox + int damageType = params.damageType + float damageAmount = params.damageAmount + int damageFlags = params.damageFlags + int hitGroup = params.hitGroup + entity weapon = params.weapon + float distanceFromAttackOrigin = params.distanceFromAttackOrigin + + bool playHitSound = true + bool showCrosshairHitIndicator = true + bool hitIneffective = false + bool victimIsHeavyArmor = false + bool isCritShot = (damageType & DF_CRITICAL) ? true : false + bool isHeadShot = (damageType & DF_HEADSHOT) ? true : false + bool isKillShot = (damageType & DF_KILLSHOT) ? true : false + bool isMelee = (damageType & DF_MELEE) ? true : false + bool isExplosion = (damageType & DF_EXPLOSION) ? true : false + bool isBullet = (damageType & DF_BULLET) ? true : false + bool isShotgun = (damageType & DF_SHOTGUN) ? true : false + bool isDoomFatality = (damageType & DF_DOOM_FATALITY) ? true : false + bool isDoomProtected = ((damageType & DF_DOOM_PROTECTED) && !isDoomFatality) ? true : false + victimIsHeavyArmor = victim.GetArmorType() == ARMOR_TYPE_HEAVY + + isDoomFatality = false + isDoomProtected = false + + if ( isDoomProtected ) + RegisterDoomProtectionHintDamage( damageAmount ) + + bool playKillSound = isKillShot + + if ( !attacker.IsTitan() ) + { + if ( victimIsHeavyArmor ) + { + showCrosshairHitIndicator = true + if ( victim.IsTitan() ) + hitIneffective = false //!IsHitEffectiveVsTitan( victim, damageType ) + else + hitIneffective = isCritShot || isHeadShot || !IsHitEffectiveVsNonTitan( victim, damageType ) + } + else + { + switch ( victim.GetSignifierName() ) + { + case "npc_super_spectre": + //if ( !( damageType & DF_CRITICAL ) ) + // hitIneffective = true + + default: + if ( (damageType & DF_BULLET && damageType & DF_MAX_RANGE) ) + hitIneffective = true + break + } + } + } + else + { + if ( victim.IsTitan() && victim.IsPlayer() ) + { + if ( PlayerHasPassive( victim, ePassives.PAS_BERSERKER ) ) + hitIneffective = true + } + } + + if ( damageType & DF_MAX_RANGE && damageType & DF_BULLET ) + // TODO: this is crap; these damage types should just send DF_NO_HITBEEP + playHitSound = false + + if ( damageType & DF_TITAN_STEP ) + // TODO: this is crap; these damage types should just send DF_NO_HITBEEP + { + playHitSound = false + playKillSound = false + } + + if ( damageType & DF_MELEE ) + // TODO: this is crap; these damage types should just send DF_NO_HITBEEP + { + playHitSound = false + playKillSound = false + } + + if ( damageType & DF_NO_HITBEEP ) + { + playHitSound = false + playKillSound = false + } + + if ( damageFlags & DAMAGEFLAG_VICTIM_HAS_VORTEX ) + showCrosshairHitIndicator = false + + if ( damageType & DF_SHIELD_DAMAGE ) + { + PlayShieldHitEffect( params ) + showCrosshairHitIndicator = true + } + else if ( damageAmount <= 0 ) + { + playHitSound = false + playKillSound = false + showCrosshairHitIndicator = false + } + + if ( damageType & DF_NO_INDICATOR ) + { + playHitSound = false + playKillSound = false + showCrosshairHitIndicator = false + } + + if ( isDoomProtected ) + playHitSound = false + + if ( showCrosshairHitIndicator ) + { + Tracker_PlayerAttackedTarget( attacker, victim ) + + //if ( hitIneffective ) + // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_INEFFECTIVE ) + //else + // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_NORMAL ) + // + //if ( (isCritShot || isDoomFatality) && !isDoomProtected ) + // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_CRITICAL ) + // + //if ( isHeadShot ) + // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_HEADSHOT ) + + if ( IsMultiplayer() && !victim.IsTitan() && !victim.IsHologram() ) + PROTO_HitIndicatorEffect( attacker, victim, damagePosition, isHeadShot, isKillShot ) + + if ( isKillShot ) + KillShotBloodSpray( attacker, victim, damagePosition, isExplosion, isBullet, isShotgun ) + + if ( victim.IsTitan() && isKillShot ) + ClientScreenShake( 8, 10, 1, Vector( 0, 0, 0 ) ) + + BloodSprayDecals( attacker, victim, damagePosition, damageAmount, isHeadShot, isKillShot, isMelee, isExplosion, isBullet, isShotgun ) + + DamageFlyout( damageAmount, damagePosition, victim, isHeadShot || isCritShot, hitIneffective ) + } + + bool playedHitSound = false + if ( playHitSound ) + { + if ( isHeadShot ) + playedHitSound = PlayHeadshotConfirmSound( attacker, victim, isKillShot ) + else if ( playKillSound ) + playedHitSound = PlayKillshotConfirmSound( attacker, victim, damageType ) + } + + if ( IsSpectre( victim ) ) + { + if ( isHeadShot ) + victim.Signal( "SpectreGlowEYEGLOW" ) + } + + // Play a hit sound effect if we didn't play a kill shot sound, and other conditions are met + if ( playHitSound && IsAlive( victim ) && !playedHitSound ) + { + PlayHitSound( victim, attacker, damageFlags, isCritShot, victimIsHeavyArmor, isKillShot, hitGroup ) + } + + if ( PlayerHasPassive( attacker, ePassives.PAS_SMART_CORE ) && isKillShot ) + { + attacker.p.smartCoreKills++ + } + + foreach ( callback in clGlobal.onLocalPlayerDidDamageCallback ) + { + callback( attacker, victim, damagePosition, damageType ) + } +} + +void function PlayHitSound( entity victim, entity attacker, int damageFlags, bool isCritShot, bool victimIsHeavyArmor, bool isKillShot, int hitGroup ) +{ + if ( damageFlags & DAMAGEFLAG_VICTIM_INVINCIBLE ) + { + EmitSoundOnEntity( attacker, "Player.HitbeepInvincible" ) + } + else if ( damageFlags & DAMAGEFLAG_VICTIM_HAS_VORTEX ) + { + EmitSoundOnEntity( attacker, "Player.HitbeepVortex" ) + } + else if ( isCritShot && victimIsHeavyArmor ) + { + EmitSoundOnEntity( attacker, "titan_damage_crit" ) + } + else if ( isCritShot ) + { + EmitSoundOnEntity( attacker, "Player.Hitbeep_crit" ) + } + else + { + EmitSoundOnEntity( attacker, "Player.Hitbeep" ) + } +} + +function PROTO_HitIndicatorEffect( entity player, entity victim, vector damagePosition, bool isHeadShot, bool isKillShot ) +{ + int fxId + if ( isKillShot ) + fxId = GetParticleSystemIndex( $"P_ar_impact_pilot_kill" ) + else if ( isHeadShot && !isKillShot ) + return + // fxId = GetParticleSystemIndex( $"P_ar_impact_pilot_headshot" ) + else + return + // fxId = GetParticleSystemIndex( $"P_ar_impact_pilot" ) + + vector victimVelocity = victim.GetVelocity() + damagePosition += (Length( victimVelocity ) * 0.15) * Normalize( victimVelocity ) + vector fxOffset = damagePosition - victim.GetOrigin() + StartParticleEffectOnEntityWithPos( victim, fxId, FX_PATTACH_ABSORIGIN_FOLLOW, -1, damagePosition - victim.GetOrigin(), <0, 0, 0> ) +} + +void function KillShotBloodSpray( entity player, entity victim, vector damagePosition, bool isExplosion, bool isBullet, bool isShotgun ) +{ + if ( IsSoftenedLocale() ) + return + + if ( !victim.IsHuman() && !IsProwler( victim ) ) + return + + if ( victim.IsMechanical() ) + return + + if ( victim.IsHologram() ) + return + + if ( !isExplosion && !isBullet && !isShotgun ) + return + + int fxId = GetParticleSystemIndex( FX_KILLSHOT_BLOODSPRAY ) + + vector victimVelocity = victim.GetVelocity() + damagePosition += (Length( victimVelocity ) * 0.15) * Normalize( victimVelocity ) + StartParticleEffectOnEntityWithPos( victim, fxId, FX_PATTACH_ABSORIGIN_FOLLOW, -1, damagePosition - victim.GetOrigin(), <0, 0, 0> ) +} + +void function BloodSprayDecals( entity player, entity victim, vector damagePosition, float damageAmount, bool isHeadShot, bool isKillShot, bool isMelee, bool isExplosion, bool isBullet, bool isShotgun ) +{ + if ( IsSoftenedLocale() || !Flag( "EnableBloodSprayDecals" ) ) + return + + if ( !victim.IsHuman() && !IsProwler( victim ) ) + return + + if ( victim.IsMechanical() ) + return + + if ( victim.IsHologram() ) + return + + if ( !isMelee && !isExplosion && !isBullet && !isShotgun ) + return + + // in MP, too expensive to do on every shot + if ( IsMultiplayer() && !isKillShot ) + return + + thread BloodSprayDecals_Think( player, victim, damagePosition, damageAmount, isHeadShot, isKillShot, isMelee, isExplosion, isBullet, isShotgun ) +} + +void function BloodSprayDecals_Think( entity player, entity victim, vector damagePosition, float damageAmount, bool isHeadShot, bool isKillShot, bool isMelee, bool isExplosion, bool isBullet, bool isShotgun ) +{ + player.EndSignal( "OnDestroy" ) + victim.EndSignal( "OnDestroy" ) + + BloodDecalParams params = BloodDecal_GetParams( damageAmount, isHeadShot, isKillShot, isMelee, isExplosion, isBullet, isShotgun ) + float traceDist = params.traceDist + float secondaryTraceDist = params.secondaryTraceDist + asset fxType = params.fxType + asset secondaryFxType = params.secondaryFxType + + int fxId = GetParticleSystemIndex( fxType ) + + // PRIMARY TRACES + vector traceStart = damagePosition + vector traceFwd = player.GetViewVector() + + if ( isExplosion || isMelee ) + { + // for explosion/melee damage, use chest instead of actual damage position + int attachID = victim.LookupAttachment( "CHESTFOCUS" ) + traceStart = victim.GetAttachmentOrigin( attachID ) + + if ( isExplosion ) + traceFwd = AnglesToForward( victim.GetAngles() ) * -1 + } + + vector traceEnd = damagePosition + (traceFwd * traceDist) + //TraceResults traceResult = TraceLine( traceStart, traceEnd, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) + + var deferredTrace_primary = DeferredTraceLineHighDetail( traceStart, traceEnd, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) + + while( !IsDeferredTraceFinished( deferredTrace_primary ) ) + WaitFrame() + + TraceResults traceResult = GetDeferredTraceResult( deferredTrace_primary ) + + vector primaryTraceEndPos = traceResult.endPos + vector primaryTraceNormal = traceResult.surfaceNormal + //DebugDrawLine( traceStart, traceEnd, 255, 150, 0, true, 5 ) + //DebugDrawSphere( primaryTraceEndPos, 8.0, 255, 0, 0, true, 5 ) + + bool doGravitySplat = isMelee ? false : true + + if ( traceResult.fraction < 1.0 ) + { + vector normAng = VectorToAngles( traceResult.surfaceNormal ) + vector fxAng = AnglesCompose( normAng, < 90, 0, 0 > ) + + StartParticleEffectInWorld( fxId, primaryTraceEndPos, fxAng ) + //DebugDrawAngles( endPos, fxAng, 5 ) + } + else if ( doGravitySplat ) + { + // trace behind the guy on the ground and put a decal there + float gravitySplatBackTraceDist = 58.0 // how far behind the guy to put the gravity splat + float gravitySplatDownTraceDist = 100.0 // max dist vertically to try to trace and put a gravity splat + vector groundTraceStartPos = damagePosition + (traceFwd * gravitySplatBackTraceDist) + vector groundTraceEndPos = groundTraceStartPos - <0, 0, 100> + + var deferredTrace_gravitySplat = DeferredTraceLineHighDetail( groundTraceStartPos, groundTraceEndPos, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) + + while( !IsDeferredTraceFinished( deferredTrace_gravitySplat ) ) + WaitFrame() + + TraceResults downTraceResult = GetDeferredTraceResult( deferredTrace_gravitySplat ) + + if ( downTraceResult.fraction < 1.0 ) + { + //DebugDrawLine( groundTraceStartPos, downTraceResult.endPos, 255, 150, 0, true, 5 ) + //DebugDrawSphere( downTraceResult.endPos, 4.0, 255, 0, 0, true, 5 ) + + vector normAng = VectorToAngles( downTraceResult.surfaceNormal ) + vector fxAng = AnglesCompose( normAng, < 90, 0, 0 > ) + + //DebugDrawAngles( downTraceResult.endPos, fxAng, 5 ) + + StartParticleEffectInWorld( fxId, downTraceResult.endPos, fxAng ) + } + } + + // MP doesn't want secondaries, too expensive + if ( IsMultiplayer() ) + return + + // SECONDARY TRACES + array testVecs = [] + vector tempAng = VectorToAngles( traceFwd ) + + if ( isExplosion ) + { + // for explosions, different & more angles for secondary splatter + testVecs.append( AnglesToRight( tempAng ) ) + testVecs.append( AnglesToRight( tempAng ) * -1 ) + testVecs.append( traceFwd * -1 ) + testVecs.append( AnglesToUp( tempAng ) ) + testVecs.append( AnglesToUp( tempAng ) * -1 ) + } + else + { + // mostly to cover edge cases involving corners + vector traceRight = AnglesToRight( tempAng ) + vector traceLeft = traceRight * -1 + vector backLeft = (traceFwd + traceLeft) * 0.5 + vector backRight = (traceFwd + traceRight) * 0.5 + testVecs.append( backRight ) + testVecs.append( backLeft ) + + // add blood on the ground for these weapons too + if ( isBullet || isShotgun ) + testVecs.append( AnglesToUp( tempAng ) * -1 ) + } + + if ( !testVecs.len() ) + return + + array secondaryDeferredTraces = [] + foreach ( testVec in testVecs ) + { + vector secondaryTraceEnd = traceStart + (testVec * secondaryTraceDist) + var secondaryDeferredTrace = DeferredTraceLineHighDetail( traceStart, secondaryTraceEnd, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) + secondaryDeferredTraces.append( secondaryDeferredTrace ) + } + + int secondaryFxId = GetParticleSystemIndex( secondaryFxType ) + + float startTime = Time() + array processedResults = [] + while ( processedResults.len() < secondaryDeferredTraces.len() ) + { + WaitFrame() + + foreach ( deferredTrace in secondaryDeferredTraces ) + { + if ( processedResults.contains( deferredTrace ) ) + continue + + if ( !IsDeferredTraceFinished( deferredTrace ) ) + continue + + processedResults.append( deferredTrace ) + + TraceResults traceResult = GetDeferredTraceResult( deferredTrace ) + + if ( traceResult.fraction == 1.0 ) + continue + + // don't put secondaries on the same wall as the primary + vector secondaryTraceNormal = traceResult.surfaceNormal + if ( primaryTraceNormal == secondaryTraceNormal ) + continue + + vector normAng = VectorToAngles( secondaryTraceNormal ) + vector fxAng = AnglesCompose( normAng, < 90, 0, 0 > ) + + vector endPos = traceResult.endPos + //DebugDrawSphere( endPos, 4.0, 255, 0, 0, true, 5 ) + StartParticleEffectInWorld( secondaryFxId, endPos, fxAng ) + } + + // timeout if traces aren't returning + if ( Time() - startTime >= 0.3 ) + return + } +} + +BloodDecalParams function BloodDecal_GetParams( float damageAmount, bool isHeadShot, bool isKillShot, bool isMelee, bool isExplosion, bool isBullet, bool isShotgun ) +{ + // default: bullet damage + float traceDist = 175 + float secondaryTraceDist = 100 + asset fxType = FX_BLOODSPRAY_DECAL_SML + asset secondaryFxType = FX_BLOODSPRAY_DECAL_SML + + if ( isBullet ) + { + // HACK- shotguns report isBullet also + if ( isShotgun ) + { + //if ( isKillShot ) + // fxType = FX_BLOODSPRAY_DECAL_LRG + //else + fxType = FX_BLOODSPRAY_DECAL_MED + } + else + { + if ( isKillShot ) + fxType = FX_BLOODSPRAY_DECAL_MED + else + fxType = FX_BLOODSPRAY_DECAL_SML + + if ( damageAmount >= 200 ) + { + traceDist = 216 + fxType = FX_BLOODSPRAY_DECAL_LRG + secondaryFxType = FX_BLOODSPRAY_DECAL_MED + } + } + } + + else if ( isExplosion ) + { + secondaryTraceDist = traceDist + + float maxDmg = 100 + float medDmg = 75 + + if ( damageAmount >= maxDmg ) + { + fxType = FX_BLOODSPRAY_DECAL_LRG + secondaryFxType = FX_BLOODSPRAY_DECAL_LRG + } + else if ( damageAmount >= medDmg ) + { + fxType = FX_BLOODSPRAY_DECAL_LRG + secondaryFxType = FX_BLOODSPRAY_DECAL_MED + } + else if ( isKillShot ) + { + fxType = FX_BLOODSPRAY_DECAL_MED + secondaryFxType = FX_BLOODSPRAY_DECAL_MED + } + } + + else if ( isMelee ) + { + traceDist = 96 + + if ( isKillShot ) + fxType = FX_BLOODSPRAY_DECAL_MED + } + + // for kills, increase trace distance a bit + if ( isKillShot ) + { + traceDist = traceDist + (traceDist * 0.1) + secondaryTraceDist = secondaryTraceDist + (secondaryTraceDist * 0.1) + } + + BloodDecalParams params + params.traceDist = traceDist + params.secondaryTraceDist = secondaryTraceDist + params.fxType = fxType + params.secondaryFxType = secondaryFxType + return params +} + +#if DEV +string function BloodSprayDecals_Toggle() +{ + string returnStr = "" + + if ( Flag( "EnableBloodSprayDecals" ) ) + { + FlagClear( "EnableBloodSprayDecals" ) + returnStr = "Blood spray decals DISABLED" + } + else + { + FlagSet( "EnableBloodSprayDecals" ) + returnStr = "Blood spray decals ENABLED" + } + + return returnStr +} +#endif + +function ServerCallback_RodeoerEjectWarning( soulHandle, ejectTime ) +{ + entity soul = GetEntityFromEncodedEHandle( soulHandle ) + + if ( !IsValid( soul ) ) + return + + thread TitanEjectHatchSequence( soul, ejectTime ) +} + +function TitanEjectHatchSequence( soul, ejectTime ) +{ + expect entity( soul ) + + soul.EndSignal( "OnSoulTransfer" ) + soul.EndSignal( "OnTitanDeath" ) + soul.EndSignal( "OnDestroy" ) + + local effects = [] + + OnThreadEnd( + function() : ( effects ) + { + foreach ( effect in effects ) + { + if ( !EffectDoesExist( effect ) ) + continue + + EffectStop( effect, true, true ) + } + } + ) + + int boltCount = 6 + int fxID = GetParticleSystemIndex( $"xo_spark_bolt" ) + + for ( int index = 0; index < boltCount; index++ ) + { + entity titan = soul.GetTitan() + + WaitEndFrame() // so OnTitanDeath/Destroy can happen + + if ( !IsAlive( titan ) ) + return + + if ( !titan.IsTitan() ) + { + printt( "WARNING: " + titan + " is not a Titan!" ) + return + } + + int attachID = titan.LookupAttachment( "HATCH_BOLT" + (index + 1) ) + //printt( "attachID is " + attachID ) + vector boltOrgin = titan.GetAttachmentOrigin( attachID ) + vector boltAngles = titan.GetAttachmentAngles( attachID ) + vector launchVec = AnglesToForward( boltAngles ) * 500 + + CreateClientsideGib( $"models/industrial/bolt_tiny01.mdl", boltOrgin, boltAngles, launchVec, < 0, 0, 0 >, 3.0, 1000.0, 200.0 ) + int effect = PlayFXOnTag( titan, fxID, attachID ) + effects.append( effect ) + EmitSoundOnEntity( titan, "titan_bolt_loose" ) + + wait (ejectTime / boltCount) + } +} + +void function ServerCallback_OnEntityKilled( attackerEHandle, victimEHandle, int scriptDamageType, damageSourceId ) +{ + expect int( damageSourceId ) + + bool isHeadShot = (scriptDamageType & DF_HEADSHOT) > 0 + + entity victim = GetEntityFromEncodedEHandle( victimEHandle ) + entity attacker = attackerEHandle ? GetHeavyWeightEntityFromEncodedEHandle( attackerEHandle ) : null + entity localClientPlayer = GetLocalClientPlayer() + + if ( !IsValid( victim ) ) + return + + Signal( victim, "OnDeath" ) + + if ( victim == localClientPlayer ) + { + victim.cv.deathOrigin = victim.GetOrigin() + level.clientsLastKiller = attacker + } + + if ( damageSourceId == eDamageSourceId.indoor_inferno ) + { + if ( victim == localClientPlayer ) + thread PlayerFieryDeath( victim ) + } + + UpdatePlayerStatusCounts() + + if ( IsValid( attacker ) && attacker.IsPlayer() ) + { + PlayTargetEliminatedTitanVO( attacker, victim ) + + if ( attacker == GetLocalViewPlayer() ) + WeaponFlyoutRefresh() // refreshes to display xp gained from kills + } + else if ( victim.IsPlayer() ) + { + if ( ("latestAssistTime" in victim.s) && victim.s.latestAssistTime >= Time() - MAX_NPC_KILL_STEAL_PREVENTION_TIME ) + { + attacker = expect entity( victim.s.latestAssistPlayer ) + damageSourceId = expect int( victim.s.latestAssistDamageSource ) + } + } + + if ( victim.IsPlayer() && victim != attacker ) + { + if ( attacker == localClientPlayer ) + { + thread PlayKillConfirmedSound( "Pilot_Killed_Indicator" ) + } + else if ( IsValid( attacker ) && attacker.IsTitan() ) + { + entity bossPlayer = attacker.GetBossPlayer() + if ( bossPlayer && bossPlayer == localClientPlayer ) + thread PlayKillConfirmedSound( "Pilot_Killed_Indicator" ) + } + } + else if ( (IsGrunt( victim ) || IsSpectre( victim )) && attacker == localClientPlayer ) + { + thread PlayKillConfirmedSound( "HUD_Grunt_Killed_Indicator" ) + } + + //if it's an auto titan, the obit was already printed when doomed + if ( (victim.IsTitan()) && (!victim.IsPlayer()) ) + return + + Obituary( attacker, "", victim, scriptDamageType, damageSourceId, isHeadShot ) +} + + +const float KILL_CONFIRM_DEBOUNCE = 0.025 +void function PlayKillConfirmedSound( string sound ) +{ + while ( true ) + { + if ( Time() - clGlobal.lastKillConfirmTime > KILL_CONFIRM_DEBOUNCE ) + { + clGlobal.lastKillConfirmTime = Time() + EmitSoundOnEntity( GetLocalClientPlayer(), sound ) + return + } + + WaitFrame() + } +} + +void function ServerCallback_OnTitanKilled( int attackerEHandle, int victimEHandle, int scriptDamageType, int damageSourceId ) +{ + //Gets run on every client whenever a titan is doomed by another player + bool isHeadShot = false + entity attacker = attackerEHandle != -1 ? GetEntityFromEncodedEHandle( attackerEHandle ) : null + entity victim = GetEntityFromEncodedEHandle( victimEHandle ) + + if ( (!IsValid( victim )) || (!IsValid( attacker )) ) + return + + //Obit: titans get scored/obits when doomed, so we don't want to just see "Player" in the obit, we want to see "Player's Titan" + bool victimIsOwnedTitan = victim.IsPlayer() + Obituary( attacker, "", victim, scriptDamageType, damageSourceId, isHeadShot, victimIsOwnedTitan ) +} + +function PlayTargetEliminatedTitanVO( attacker, victim ) +{ + entity localPlayer = GetLocalViewPlayer() + + if ( attacker != localPlayer ) + return + + if ( !victim.IsPlayer() ) + return + + if ( victim.IsTitan() ) + { + // a bit more delay for a titan explosion to clear + thread TitanCockpit_PlayDialogDelayed( localPlayer, 1.3, "elimTarget" ) + } + else + { + thread TitanCockpit_PlayDialogDelayed( localPlayer, 0.8, "elimEnemyPilot" ) + } +} + +function ServerCallback_SetAssistInformation( damageSourceId, attackerEHandle, entityEHandle, assistTime ) +{ + local ent = GetHeavyWeightEntityFromEncodedEHandle( entityEHandle ) + if ( !ent ) + return + + local latestAssistPlayer = GetEntityFromEncodedEHandle ( attackerEHandle ) + if ( !("latestAssistPlayer" in ent.s) ) + { + ent.s.latestAssistPlayer <- latestAssistPlayer + ent.s.latestAssistDamageSource <- damageSourceId + ent.s.latestAssistTime <- assistTime + } + else + { + ent.s.latestAssistPlayer = latestAssistPlayer + ent.s.latestAssistDamageSource = damageSourceId + ent.s.latestAssistTime = assistTime + } +} + +void function ClientCodeCallback_OnModelChanged( entity ent ) +{ +/* + // OnModelChanged gets called for each model change, but gets processed after the model has done all switches + + if ( !IsValid( ent ) ) + return; + + if ( !("creationCount" in ent.s) ) + return; + + Assert( ent instanceof C_BaseAnimating ); +*/ +} + + +void function ClientCodeCallback_OnHealthChanged( entity ent, int oldHealth, int newHealth ) +{ + if ( IsLobby() ) + return + + entity player = GetLocalViewPlayer() + if ( !IsValid( player ) ) + return + + if ( !IsValid( ent ) ) + return + + ent.Signal( "HealthChanged", { oldHealth = oldHealth, newHealth = newHealth } ) +} + +// Northstar +void function AddCallback_OnCrosshairCurrentTargetChanged( void functionref( entity, entity ) callbackFunc ) +{ + Assert( !file.OnCrosshairCurrentTargetChangedCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnCrosshairCurrentTargetChanged" ) + + file.OnCrosshairCurrentTargetChangedCallbacks.append( callbackFunc ) +} + +void function ClientCodeCallback_OnCrosshairCurrentTargetChanged( entity player, entity newTarget ) +{ + if ( IsLobby() ) + return; + if ( !IsValid( player ) ) + return + + if ( IsValid( newTarget ) ) + TryOfferRodeoBatteryHint( newTarget ) + + // Northstar + foreach ( callback in file.OnCrosshairCurrentTargetChangedCallbacks ) + { + callback( player, newTarget ) + } +} + +void function SetupPlayerAnimEvents( entity player ) +{ + SetupPlayerJumpJetAnimEvents( player ) + AddAnimEvent( player, "WallHangAttachDataKnife", WallHangAttachDataKnife ) +} + +void function JumpRandomlyForever() +{ + for (;; ) + { + if ( IsWatchingReplay() ) + { + wait 1 + continue + } + + entity player = GetLocalClientPlayer() + if ( !IsAlive( player ) || player != GetLocalViewPlayer() ) + { + wait 1 + continue + } + + printt( "jump!" ) + player.ClientCommand( "+jump" ) + wait 0 + player.ClientCommand( "-jump" ) + + wait RandomFloatRange( 0.2, 1.1 ) + } +} + +void function RemoteTurretFadeoutAnimEvent( entity ent ) +{ + entity player = GetLocalViewPlayer() + ScreenFade( player, 0, 0, 0, 255, 0.1, 0.25, FFADE_OUT ); +} + +void function SetupFirstPersonProxyEvents( entity firstPersonProxy ) +{ + //printt( "SetupFirstPersonProxyEvents" ) + + AddAnimEvent( firstPersonProxy, "mantle_smallmantle", OnSmallMantle ) + AddAnimEvent( firstPersonProxy, "mantle_mediummantle", OnMediumMantle ) + AddAnimEvent( firstPersonProxy, "mantle_lowmantle", OnLowMantle ) + AddAnimEvent( firstPersonProxy, "mantle_extralowmantle", OnExtraLowMantle ) + AddAnimEvent( firstPersonProxy, "remoteturret_fadeout", RemoteTurretFadeoutAnimEvent ) +} + +void function OnSmallMantle( entity firstPersonProxy ) //Was set up in script instead of anim to be able to play quieter sounds with stealth passive. No longer needed, but more work to move inside of anim +{ + entity player = GetLocalViewPlayer() + EmitSoundOnEntity( firstPersonProxy, "mantle_smallmantle" ) +} + +void function OnMediumMantle( entity firstPersonProxy ) //Was set up in script instead of anim to be able to play quieter sounds with stealth passive. No longer needed, but more work to move inside of anim +{ + entity player = GetLocalViewPlayer() + EmitSoundOnEntity( firstPersonProxy, "mantle_mediummantle" ) +} + +void function OnLowMantle( entity firstPersonProxy ) //Was set up in script instead of anim to be able to play quieter sounds with stealth passive. No longer needed, but more work to move inside of anim +{ + entity player = GetLocalViewPlayer() + EmitSoundOnEntity( firstPersonProxy, "mantle_lowmantle" ) +} + +void function OnExtraLowMantle( entity firstPersonProxy ) //Was set up in script instead of anim to be able to play quieter sounds with stealth passive. No longer needed, but more work to move inside of anim +{ + entity player = GetLocalViewPlayer() + EmitSoundOnEntity( firstPersonProxy, "mantle_extralow" ) +} + +void function CreateCallback_TitanSoul( entity ent ) +{ +} + +bool function ShouldHideRespawnSelectionText( entity player ) +{ + if ( player != GetLocalClientPlayer() ) + return false + if ( player.GetPlayerClass() != "spectator" ) + return false + if ( IsWatchingReplay() ) + return false + + return true +} + + + +void function WallHangAttachDataKnife( entity player ) +{ + int attachIdx = player.LookupAttachment( "l_hand" ) + if ( attachIdx == 0 ) + // hack while i wait for the attachment to be fixed + return + + entity dataknife = CreateClientSidePropDynamic( player.GetAttachmentOrigin( attachIdx ), player.GetAttachmentAngles( attachIdx ), DATA_KNIFE_MODEL ) + dataknife.SetParent( player, "l_hand" ) + + thread DeleteDataKnifeAfterWallHang( player, dataknife ) +} + +void function DeleteDataKnifeAfterWallHang( entity player, entity dataknife ) +{ + OnThreadEnd( + function() : ( dataknife ) + { + if ( IsValid( dataknife ) ) + dataknife.Kill_Deprecated_UseDestroyInstead() + } + ) + + player.EndSignal( "OnDeath" ) + player.EndSignal( "OnDestroy" ) + + for (;; ) + { + Wait( 0.1 ) + if ( !player.IsWallHanging() ) + break + } +} + +bool function ClientCodeCallback_OnGib( entity victim, vector attackDir ) +{ + if ( !victim.IsMechanical() ) + return SpawnFleshGibs( victim, attackDir ) + + return false +} + +bool function SpawnFleshGibs( entity victim, vector attackDir ) +{ + asset modelName = $"models/gibs/human_gibs.mdl" + attackDir = Normalize( attackDir ) + + float cullDist = 2048.0 + if ( "gibDist" in victim.s ) + cullDist = expect float( victim.s.gibDist ) + + vector startOrigin = victim.GetWorldSpaceCenter() + (attackDir * -30) + + vector origin = victim.GetOrigin() + < RandomIntRange( 10, 20 ), RandomIntRange( 10, 20 ), RandomIntRange( 32, 64 ) > + vector angles = < 0, 0, 0 > + vector flingDir = attackDir * RandomIntRange( 80, 200 ) + + int fxID + bool isSoftenedLocale = IsSoftenedLocale() + + if ( isSoftenedLocale ) + { + if ( victim.GetModelName() == FLYER_MODEL ) + fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist_LG_nochunk" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) + else + fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist_nochunk" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) + } + else + { + if ( victim.GetModelName() == FLYER_MODEL ) + fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist_LG" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) + else + fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) + } + + EffectSetControlPointVector( fxID, 1, flingDir ) + + if ( isSoftenedLocale ) + return true + + vector angularVel = < 0, 0, 0 > + float lifeTime = 10.0 + CreateClientsideGibWithBodyGroupGibs( modelName, victim.GetOrigin(), angles, attackDir, angularVel, lifeTime, cullDist, 1024 ) + + return true +} + +function ServerCallback_PlayScreenFXWarpJump() +{ + if ( IsWatchingReplay() ) + return false + + thread PlayScreenFXWarpJump( GetLocalClientPlayer() ) +} + +void function PlayScreenFXWarpJump( entity clientPlayer ) +{ + clientPlayer.EndSignal( "OnDeath" ) + clientPlayer.EndSignal( "OnDestroy" ) + + entity player = GetLocalViewPlayer() + int index = GetParticleSystemIndex( SCREENFX_WARPJUMP ) + int indexD = GetParticleSystemIndex( SCREENFX_WARPJUMPDLIGHT ) + int fxID = StartParticleEffectInWorldWithHandle( index, < 0, 0, 0 >, < 0, 0, 0 > ) + int fxID2 = -1 + if ( IsValid( player.GetCockpit() ) ) + { + fxID2 = StartParticleEffectOnEntity( player, indexD, FX_PATTACH_POINT_FOLLOW, player.GetCockpit().LookupAttachment( "CAMERA" ) ) + EffectSetIsWithCockpit( fxID2, true ) + } + + OnThreadEnd( + function() : ( clientPlayer, fxID, fxID2 ) + { + if ( IsValid( clientPlayer ) && !IsAlive( clientPlayer ) ) + { + EffectStop( fxID, true, false ) + if ( fxID2 > -1 ) + EffectStop( fxID2, true, false ) + } + } + ) + + wait 3.2 + if ( IsValid( player.GetCockpit() ) ) + thread TonemappingUpdateAfterWarpJump() +} + +const EXPOSURE_RAMPDOWN_DURATION = 2 +const EXPOSURE_RAMPDOWN_MAX = 20 +const EXPOSURE_RAMPDOWN_MIN = 0 +const MAX_RAMPDOWN_DURATION = 5 +const MAX_RAMPDOWN_MAX = 3 +const MAX_RAMPDOWN_MIN = 1 + +function TonemappingUpdateAfterWarpJump() +{ + // Turn cubemaps black inside drop ship, since it's pretty dark in there anyway and we don't have a great way to take a valid cubemap shot for that location. + SetConVarFloat( "mat_envmap_scale", 0 ); + + AutoExposureSetMaxExposureMultiplier( 500 ); // allow exposure to actually go bright, even if it's clamped in the level. + + // Start the exposure super bright behind the white FX, and ramp it down quickly to normal. + local startTime = Time() + while( 1 ) + { + local time = Time() - startTime + float factor = GraphCapped( time, 0, EXPOSURE_RAMPDOWN_DURATION, 1, 0 ) + local toneMapScale = EXPOSURE_RAMPDOWN_MIN + (EXPOSURE_RAMPDOWN_MAX - EXPOSURE_RAMPDOWN_MIN) * factor * factor * factor * factor + AutoExposureSetExposureCompensationBias( toneMapScale ) + AutoExposureSnap() + wait 0 + if ( factor == 0 ) + break; + } + + // Ramp the max exposure multiplier back down to 1 gently + startTime = Time() + while( 1 ) + { + local time = Time() - startTime + float factor = GraphCapped( time, 0, MAX_RAMPDOWN_DURATION, 1, 0 ) + local scale = MAX_RAMPDOWN_MIN + (MAX_RAMPDOWN_MAX - MAX_RAMPDOWN_MIN) * factor * factor + AutoExposureSetMaxExposureMultiplier( scale ); + wait 0 + if ( factor == 0 ) + break; + } +} + +function SetPanelAlphaOverTime( panel, alpha, duration ) +{ + // HACK this should be a code command - Mackey + Signal( panel, "PanelAlphaOverTime" ) + EndSignal( panel, "PanelAlphaOverTime" ) + EndSignal( panel, "OnDestroy" ) + + local startTime = Time() + local endTime = startTime + duration + local startAlpha = panel.GetPanelAlpha() + + while( Time() <= endTime ) + { + float a = GraphCapped( Time(), startTime, endTime, startAlpha, alpha ) + panel.SetPanelAlpha( a ) + WaitFrame() + } + + panel.SetPanelAlpha( alpha ) +} + + + +function HandleDoomedState( entity player, entity titan ) +{ + bool isDoomed = GetDoomedState( titan ) + if ( isDoomed ) + { + titan.Signal( "Doomed" ) + + if ( HasSoul( titan ) ) + { + entity soul = titan.GetTitanSoul() + soul.Signal( "Doomed" ) + } + } +} + +const asset SHIELD_BREAK_FX = $"P_xo_armor_break_CP" +function PlayShieldBreakEffect( entity ent ) +{ + entity shieldEnt = ent + if ( IsSoul( ent ) ) + { + shieldEnt = ent.GetTitan() + if ( !shieldEnt ) + return + } + + float shieldHealthFrac = GetShieldHealthFrac( shieldEnt ) + + int shieldBreakFX = GetParticleSystemIndex( SHIELD_BREAK_FX ) + + local attachID + if ( shieldEnt.IsTitan() ) + attachID = shieldEnt.LookupAttachment( "exp_torso_main" ) + else + attachID = shieldEnt.LookupAttachment( "ref" ) // TEMP + + local shieldFXHandle = StartParticleEffectOnEntity( shieldEnt, shieldBreakFX, FX_PATTACH_POINT_FOLLOW, attachID ) + EffectSetControlPointVector( shieldFXHandle, 1, GetShieldEffectCurrentColor( 1 - shieldHealthFrac ) ) +} + +function PlayShieldActivateEffect( entity ent ) +{ + entity shieldEnt = ent + if ( IsSoul( ent ) ) + { + shieldEnt = ent.GetTitan() + if ( !shieldEnt ) + return + } + + float shieldHealthFrac = GetShieldHealthFrac( shieldEnt ) + + int shieldBreakFX = GetParticleSystemIndex( SHIELD_BREAK_FX ) + + local attachID + if ( shieldEnt.IsTitan() ) + attachID = shieldEnt.LookupAttachment( "exp_torso_main" ) + else + attachID = shieldEnt.LookupAttachment( "ref" ) // TEMP + + local shieldFXHandle = StartParticleEffectOnEntity( shieldEnt, shieldBreakFX, FX_PATTACH_POINT_FOLLOW, attachID ) + EffectSetControlPointVector( shieldFXHandle, 1, GetShieldEffectCurrentColor( 1 - shieldHealthFrac ) ) +} + +function PlayIt( entity victim ) +{ + float shieldHealthFrac = GetShieldHealthFrac( victim ) + + int shieldbodyFX = GetParticleSystemIndex( SHIELD_BODY_FX ) + local attachID + if ( victim.IsTitan() ) + attachID = victim.LookupAttachment( "exp_torso_main" ) + else + attachID = victim.LookupAttachment( "ref" ) // TEMP + + local shieldFXHandle = StartParticleEffectOnEntity( victim, shieldbodyFX, FX_PATTACH_POINT_FOLLOW, attachID ) + + EffectSetControlPointVector( shieldFXHandle, 1, GetShieldEffectCurrentColor( 1 - shieldHealthFrac ) ) +} + +function PlayShieldHitEffect( PlayerDidDamageParams params ) +{ + entity player = GetLocalViewPlayer() + entity victim = params.victim + //vector damagePosition = params.damagePosition + //int hitBox = params.hitBox + //int damageType = params.damageType + //float damageAmount = params.damageAmount + //int damageFlags = params.damageFlags + //int hitGroup = params.hitGroup + //entity weapon = params.weapon + //float distanceFromAttackOrigin = params.distanceFromAttackOrigin + + //shieldFX <- GetParticleSystemIndex( SHIELD_FX ) + //StartParticleEffectInWorld( shieldFX, damagePosition, player.GetViewVector() * -1 ) + + float shieldHealthFrac = GetShieldHealthFrac( victim ) + + int shieldbodyFX = GetParticleSystemIndex( SHIELD_BODY_FX ) + local attachID + if ( victim.IsTitan() ) + attachID = victim.LookupAttachment( "exp_torso_main" ) + else + attachID = victim.LookupAttachment( "ref" ) // TEMP + + local shieldFXHandle = StartParticleEffectOnEntity( victim, shieldbodyFX, FX_PATTACH_POINT_FOLLOW, attachID ) + + EffectSetControlPointVector( shieldFXHandle, 1, GetShieldEffectCurrentColor( 1 - shieldHealthFrac ) ) +} + +const table SHIELD_COLOR_CHARGE_FULL = { r = 115, g = 247, b = 255 } // blue +const table SHIELD_COLOR_CHARGE_MED = { r = 200, g = 128, b = 80 } // orange +const table SHIELD_COLOR_CHARGE_EMPTY = { r = 200, g = 80, b = 80 } // red + +const SHIELD_COLOR_CROSSOVERFRAC_FULL2MED = 0.75 // from zero to this fraction, fade between full and medium charge colors +const SHIELD_COLOR_CROSSOVERFRAC_MED2EMPTY = 0.95 // from "full2med" to this fraction, fade between medium and empty charge colors + +function GetShieldEffectCurrentColor( shieldHealthFrac ) +{ + local color1 = SHIELD_COLOR_CHARGE_FULL + local color2 = SHIELD_COLOR_CHARGE_MED + local color3 = SHIELD_COLOR_CHARGE_EMPTY + + local crossover1 = SHIELD_COLOR_CROSSOVERFRAC_FULL2MED // from zero to this fraction, fade between color1 and color2 + local crossover2 = SHIELD_COLOR_CROSSOVERFRAC_MED2EMPTY // from crossover1 to this fraction, fade between color2 and color3 + + local colorVec = < 0, 0, 0 > + // 0 = full charge, 1 = no charge remaining + if ( shieldHealthFrac < crossover1 ) + { + colorVec.x = Graph( shieldHealthFrac, 0, crossover1, color1.r, color2.r ) + colorVec.y = Graph( shieldHealthFrac, 0, crossover1, color1.g, color2.g ) + colorVec.z = Graph( shieldHealthFrac, 0, crossover1, color1.b, color2.b ) + } + else if ( shieldHealthFrac < crossover2 ) + { + colorVec.x = Graph( shieldHealthFrac, crossover1, crossover2, color2.r, color3.r ) + colorVec.y = Graph( shieldHealthFrac, crossover1, crossover2, color2.g, color3.g ) + colorVec.z = Graph( shieldHealthFrac, crossover1, crossover2, color2.b, color3.b ) + } + else + { + // for the last bit of overload timer, keep it max danger color + colorVec.x = color3.r + colorVec.y = color3.g + colorVec.z = color3.b + } + + return colorVec +} + + + +void function PlayPlayerDeathSound( entity player ) +{ + if ( IsPlayerEliminated( player ) ) + EmitSoundOnEntity( player, "player_death_begin_elimination" ) + else + EmitSoundOnEntity( player, "Player_Death_Begin" ) +} + +void function StopPlayerDeathSound( entity player ) +{ + StopSoundOnEntity( player, "Player_Death_Begin" ) + EmitSoundOnEntity( player, "Player_Death_PrespawnTransition" ) +} + +function OnClientPlayerAlive( entity player ) +{ + player.Signal( "OnClientPlayerAlive" ) // TEMP; this should not be necessary, but IsWatchingKillReplay is wrong + player.EndSignal( "OnClientPlayerAlive" ) + + UpdateClientHudVisibility( player ) + + if ( IsWatchingReplay() ) + return + + if ( GetGameState() < eGameState.Playing ) + return +} + +function OnClientPlayerDying( entity player ) +{ + player.Signal( "OnClientPlayerDying" ) // TEMP; this should not be necessary, but IsWatchingKillReplay is wrong + player.EndSignal( "OnClientPlayerDying" ) + + entity player = GetLocalClientPlayer() + UpdateClientHudVisibility( player ) +// thread ShowDeathRecap( player ) + + if ( IsWatchingReplay() ) + return + + player.cv.deathTime = Time() + + thread DeathCamCheck( player ) +} + +void function ShowDeathRecap( entity player ) +{ + Assert( player == GetLocalClientPlayer() ) + + DisableCallingCardEvents() + + if ( player.e.recentDamageHistory.len() == 0 ) + return + + DamageHistoryStruct damageHistory = player.e.recentDamageHistory[ 0 ] + + entity attacker = damageHistory.attacker + + if ( !IsValid( attacker ) ) + return + + EndSignal( attacker, "OnDestroy" ) + + if ( !attacker.IsPlayer() ) + return + + if ( attacker.GetTeam() == player.GetTeam() ) + return + + wait( 1.0 ) + + CallsignEvent( eCallSignEvents.YOU, attacker, Localize( "#DEATH_SCREEN_KILLED_YOU" ) ) +} + +void function HideDeathRecap( entity player, var rui ) +{ + float minDisplayTime = 6.0 + float startTime = Time() + + waitthread DeathRecapHideDelay( player ) + wait( 0.5 ) + + float elapsedTime = Time() - startTime + if ( elapsedTime < minDisplayTime ) + wait( minDisplayTime - elapsedTime ) + + RuiSetBool( rui, "playOutro", true ) + RuiSetGameTime( rui, "outroStartTime", Time() ) + + EnableCallingCardEvents() +} + +void function DeathRecapHideDelay( entity player ) +{ + EndSignal( clGlobal.levelEnt, "LocalClientPlayerRespawned" ) + EndSignal( clGlobal.levelEnt, "OnSpectatorMode" ) + + WaitForever() +} + +void function DeathCamCheck( entity player ) +{ + wait GetRespawnButtonCamTime( player ) +} + +void function ServerCallback_ShowNextSpawnMessage( float nextSpawnTime ) +{ + entity player = GetLocalClientPlayer() + float camTime = GetRespawnButtonCamTime( player ) + + file.nextSpawnTime = nextSpawnTime + + if ( nextSpawnTime > Time() + camTime ) + thread ShowSpawnDelayMessage( nextSpawnTime ) +} + + +void function ShowSpawnDelayMessage( nextSpawnTime ) +{ + float waitTime = max( nextSpawnTime - Time(), 0 ) + + if ( waitTime < 1.0 ) + return + + entity player = GetLocalClientPlayer() + + //player.cv.nextSpawnTimeLabel.SetAlpha( 255 ) + //player.cv.nextSpawnTimeLabel.Show() + //player.cv.nextSpawnTimeLabel.SetAutoText( "#GAMEMODE_DEPLOYING_IN_N", HATT_GAME_COUNTDOWN_SECONDS, nextSpawnTime ) + // + //if ( !player.cv.nextSpawnTimeLabel.IsAutoText() ) + // player.cv.nextSpawnTimeLabel.EnableAutoText() + + while ( !IsAlive( player ) && waitTime > 0.0 ) + { + waitTime = max( nextSpawnTime - Time(), 0 ) + + AddPlayerHint( waitTime, 0.25, $"", "#GAMEMODE_DEPLOYING_IN_N", int( waitTime ) ) + + wait 1.0 + } +} +void function ServerCallback_HideNextSpawnMessage() +{ + entity player = GetLocalClientPlayer() + + HidePlayerHint( "#GAMEMODE_DEPLOYING_IN_N" ) +} + +float function GetWaveSpawnTime() +{ + return (file.nextSpawnTime) +} + +bool function IsPlayerEliminated( entity player ) +{ + return (player.GetPlayerGameStat( PGS_ELIMINATED ) > 0) +} + +function PlayerFieryDeath( player ) +{ + player.EndSignal( "OnDestroy" ) + player.EndSignal( "OnClientPlayerAlive" ) + clGlobal.levelEnt.EndSignal( "OnSpectatorMode" ) + + local offset = < 0, 0, 0 > + if ( player.IsTitan() ) + offset = < 0, 0, 96 > + + entity scriptRef = CreatePropDynamic( $"models/dev/empty_model.mdl", player.GetOrigin() + offset, player.GetAngles() ) + scriptRef.SetParent( player ) + + local fxHandle = StartParticleEffectOnEntity( scriptRef, GetParticleSystemIndex( $"P_burn_player" ), FX_PATTACH_ABSORIGIN_FOLLOW, -1 ) + + OnThreadEnd( + function () : ( fxHandle, scriptRef ) + { + EffectStop( fxHandle, false, false ) + if ( IsValid( scriptRef ) ) + scriptRef.Destroy() + } + ) + WaitForever() +} + + +function ServerCallback_GiveMatchLossProtection() +{ + clGlobal.showMatchLossProtection = true +} + +void function EnableDoDeathCallback( entity ent ) +{ + ent.DoDeathCallback( true ) +} + + + +int function UpdateSubText2ForRiffs( AnnouncementData announcement ) +{ + array riffTexts = [] + + if ( IsPilotEliminationBased() ) + riffTexts.append( "#GAMESTATE_NO_RESPAWNING" ) + + if ( Riff_FloorIsLava() ) + riffTexts.append( "#GAMEMODE_FLOOR_IS_LAVA_SUBTEXT2" ) + + if ( level.nv.minimapState == eMinimapState.Hidden ) + riffTexts.append( "#GAMESTATE_NO_MINIMAP" ) + + if ( level.nv.ammoLimit == eAmmoLimit.Limited ) + riffTexts.append( "#GAMESTATE_LIMITED_AMMUNITION" ) + + if ( level.nv.titanAvailability != eTitanAvailability.Default ) + { + switch ( level.nv.titanAvailability ) + { + case eTitanAvailability.Always: + riffTexts.append( "#GAMESTATE_UNLIMITED_TITANS" ) + break + case eTitanAvailability.Once: + riffTexts.append( "#GAMESTATE_ONE_TITAN" ) + break + case eTitanAvailability.Never: + riffTexts.append( "#GAMESTATE_NO_TITANS" ) + break + } + } + + if ( level.nv.allowNPCs != eAllowNPCs.Default ) + { + switch ( level.nv.allowNPCs ) + { + case eAllowNPCs.None: + //riffTexts.append( "#GAMESTATE_NO_MINIONS" ) + break + + case eAllowNPCs.GruntOnly: + riffTexts.append( "#GAMESTATE_GRUNTS_ONLY" ) + break + + case eAllowNPCs.SpectreOnly: + riffTexts.append( "#GAMESTATE_SPECTRES_ONLY" ) + break + } + } + + float pilotHealthMultiplier = GetCurrentPlaylistVarFloat( "pilot_health_multiplier", 0.0 ) + if ( pilotHealthMultiplier != 0.0 && pilotHealthMultiplier <= 1.5 ) + riffTexts.append( "#GAMESTATE_LOW_PILOT_HEALTH" ) + else if ( pilotHealthMultiplier > 1.5 ) + riffTexts.append( "#GAMESTATE_HIGH_PILOT_HEALTH" ) + + switch ( riffTexts.len() ) + { + case 1: + Announcement_SetSubText2( announcement, riffTexts[0] ) + break + case 2: + Announcement_SetSubText2( announcement, "#GAMEMODE_ANNOUNCEMENT_SUBTEXT_2", riffTexts[0], riffTexts[1] ) + break + case 3: + Announcement_SetSubText2( announcement, "#GAMEMODE_ANNOUNCEMENT_SUBTEXT_3", riffTexts[0], riffTexts[1], riffTexts[2] ) + break + case 4: + Announcement_SetSubText2( announcement, "#GAMEMODE_ANNOUNCEMENT_SUBTEXT_4", riffTexts[0], riffTexts[1], riffTexts[2], riffTexts[3] ) + break + case 5: + Announcement_SetSubText2( announcement, "#GAMEMODE_ANNOUNCEMENT_SUBTEXT_5", riffTexts[0], riffTexts[1], riffTexts[2], riffTexts[3], riffTexts[4] ) + break + + default: + Announcement_SetSubText2( announcement, "", "" ) + return 0 + } + + return riffTexts.len() +} + + +void function ServerCallback_GameModeAnnouncement() +{ + entity player = GetLocalClientPlayer() + string gameMode = GameRules_GetGameMode() + + if ( GameMode_GetCustomIntroAnnouncement( gameMode ) != null ) + { + void functionref(entity) func = GameMode_GetCustomIntroAnnouncement( gameMode ) + func(player) + return + } + + int team = player.GetTeam() + + local totalDuration = 0.0 + + AnnouncementData announcement + + if ( GetGameState() == eGameState.Epilogue ) + { + // never gets hit?? + announcement = Announcement_Create( "#GAMEMODE_EPILOGUE" ) + } + else + { + announcement = Announcement_Create( GAMETYPE_TEXT[gameMode] ) + announcement.announcementStyle = ANNOUNCEMENT_STYLE_BIG + + Announcement_SetIcon( announcement, GAMETYPE_ICON[gameMode] ) + Announcement_SetSubText( announcement, GAMEDESC_CURRENT ) + + if ( GameMode_IsDefined( gameMode ) ) + { + if ( GameMode_GetAttackDesc( gameMode ) != "" && team == level.nv.attackingTeam ) + Announcement_SetSubText( announcement, GameMode_GetAttackDesc( gameMode ) ) + + if ( GameMode_GetDefendDesc( gameMode ) != "" && team != level.nv.attackingTeam ) + Announcement_SetSubText( announcement, GameMode_GetDefendDesc( gameMode ) ) + } + } + + int numRiffs = UpdateSubText2ForRiffs( announcement ) + float announcementDuration = numRiffs + DEFAULT_GAMEMODE_ANNOUNCEMENT_DURATION + if ( gameMode == COLISEUM ) + announcementDuration = 2.3 //JFS: Make coliseum announcement disappear with the black bars. Note that the rui fade out sequence time is a floor on how low announcementDuration can be set to + + Announcement_SetDuration( announcement, announcementDuration ) + totalDuration += announcementDuration + + AnnouncementFromClass( player, announcement ) // TODO: team specific goals + + if ( clGlobal.showMatchLossProtection ) + { + announcementDuration = 2.0 + totalDuration += announcementDuration + delaythread( announcementDuration ) DeathHintDisplay( "#LATE_JOIN_NO_LOSS" ) + } + else if ( clGlobal.canShowLateJoinMessage ) + { + if ( level.nv.matchProgress > 5 || GetRoundsPlayed() > 0 ) + { + announcementDuration = 2.0 + totalDuration += announcementDuration + delaythread( announcementDuration ) DeathHintDisplay( "#LATE_JOIN" ) + } + } + clGlobal.showMatchLossProtection = false + clGlobal.canShowLateJoinMessage = false + + if ( Riff_FloorIsLava() ) + { + announcementDuration = 10.0 + totalDuration += announcementDuration + //printt( "Total duration delayed for lava announcement: " + totalDuration ) + delaythread( totalDuration ) PlayConversationToLocalClient( "floor_is_lava_announcement" ) + } +} + + +function MainHud_InitScoreBars( vgui, entity player, scoreGroup ) +{ + local hudScores = {} + vgui.s.scoreboardProgressBars <- hudScores + + local panel = vgui.GetPanel() + + hudScores.GameInfoBG <- scoreGroup.CreateElement( "GameInfoBG", panel ) + + string gameMode = GameRules_GetGameMode() + int friendlyTeam = player.GetTeam() + + #if HAS_GAMEMODES + if ( IsFFAGame() ) + { + return + } + #endif + + int enemyTeam = friendlyTeam == TEAM_IMC ? TEAM_MILITIA : TEAM_IMC + + if ( IsRoundBased() ) + { + level.scoreLimit[TEAM_IMC] <- GetRoundScoreLimit_FromPlaylist() + level.scoreLimit[TEAM_MILITIA] <- GetRoundScoreLimit_FromPlaylist() + } + else + { + level.scoreLimit[TEAM_IMC] <- GetScoreLimit_FromPlaylist() + level.scoreLimit[TEAM_MILITIA] <- GetScoreLimit_FromPlaylist() + } + + #if HAS_GAMEMODES + Assert( gameMode == GameRules_GetGameMode() ) + switch ( gameMode ) + { + case CAPTURE_THE_FLAG: + vgui.s.friendlyFlag <- scoreGroup.CreateElement( "FriendlyFlag", panel ) + vgui.s.enemyFlag <- scoreGroup.CreateElement( "EnemyFlag", panel ) + + vgui.s.friendlyFlagLabel <- scoreGroup.CreateElement( "FriendlyFlagLabel", panel ) + vgui.s.enemyFlagLabel <- scoreGroup.CreateElement( "EnemyFlagLabel", panel ) + + thread CaptureTheFlagThink( vgui, player ) + break + + case MARKED_FOR_DEATH: + case MARKED_FOR_DEATH_PRO: + thread MarkedForDeathHudThink( vgui, player, scoreGroup ) + break + } + #endif + + thread TitanEliminationThink( vgui, player ) + + thread RoundScoreThink( vgui, scoreGroup, player ) + + vgui.s.scoreboardProgressGroup <- scoreGroup + + hudScores.GameInfoBG.Show() + + local scoreboardArrays = {} + vgui.s.scoreboardArrays <- scoreboardArrays + + //if ( ShouldUsePlayerStatusCount() ) //Can't just do PilotEliminationBased check here because it isn't set when first connecting + //{ + // //ToDo: Eventually turn it on for normal Titan count too. Need to make sure "Titan ready but not called in yet" icon doesn't get hidden by this element + // hudScores.Player_Status_BG <- scoreGroup.CreateElement( "Player_Status_BG", panel ) + // hudScores.Player_Status_BG.Show() + // + // CreatePlayerStatusElementsFriendly( scoreboardArrays, scoreGroup, panel ) + // CreatePlayerStatusElementsEnemy( scoreboardArrays, scoreGroup, panel ) + // + // thread ScoreBarsPlayerStatusThink( vgui, player, scoreboardArrays.FriendlyPlayerStatusCount, scoreboardArrays.EnemyPlayerStatusCount ) + //} + //else + //{ + // hudScores.Player_Status_BG <- scoreGroup.CreateElement( "Player_Status_BG", panel ) + // hudScores.Player_Status_BG.Show() + // thread ScoreBarsTitanCountThink( vgui, player, hudScores.FriendlyTitanCount, hudScores.FriendlyTitanReadyCount, hudScores.EnemyTitanCount ) + //} + + if ( IsWatchingReplay() ) + vgui.s.scoreboardProgressGroup.Hide() + + UpdatePlayerStatusCounts() + + if ( IsSuddenDeathGameMode() ) + thread SuddenDeathHUDThink( vgui, player ) +} + +function CaptureTheFlagThink( vgui, entity player ) +{ + vgui.EndSignal( "OnDestroy" ) + + if ( vgui instanceof C_VGuiScreen ) + player.EndSignal( "OnDestroy" ) + + vgui.s.friendlyFlag.Show() + vgui.s.enemyFlag.Show() + vgui.s.friendlyFlagLabel.Show() + vgui.s.enemyFlagLabel.Show() + + while ( GetGameState() < eGameState.Epilogue ) + { + if ( "friendlyFlagState" in player.s ) + { + switch ( player.s.friendlyFlagState ) + { + case eFlagState.None: + vgui.s.friendlyFlagLabel.SetText( "" ) + break + case eFlagState.Home: + vgui.s.friendlyFlagLabel.SetText( "#GAMEMODE_FLAG_HOME" ) + break + case eFlagState.Held: + vgui.s.friendlyFlagLabel.SetText( player.s.friendlyFlagCarrierName ) + break + case eFlagState.Away: + vgui.s.friendlyFlagLabel.SetText( "#GAMEMODE_FLAG_DROPPED" ) + break + } + + switch ( player.s.enemyFlagState ) + { + case eFlagState.None: + vgui.s.enemyFlagLabel.SetText( "" ) + break + case eFlagState.Home: + vgui.s.enemyFlagLabel.SetText( "#GAMEMODE_FLAG_HOME" ) + break + case eFlagState.Held: + vgui.s.enemyFlagLabel.SetText( player.s.enemyFlagCarrierName ) + break + case eFlagState.Away: + vgui.s.enemyFlagLabel.SetText( "#GAMEMODE_FLAG_DROPPED" ) + break + } + } + + clGlobal.levelEnt.WaitSignal( "FlagUpdate" ) + + WaitEndFrame() + } + + vgui.s.friendlyFlag.Hide() + vgui.s.enemyFlag.Hide() + vgui.s.friendlyFlagLabel.Hide() + vgui.s.enemyFlagLabel.Hide() +} + + + +function TitanEliminationThink( vgui, entity player ) +{ + vgui.EndSignal( "OnDestroy" ) + player.EndSignal( "OnDestroy" ) + + if ( player != GetLocalClientPlayer() ) + return + + OnThreadEnd( + function() : ( player ) + { + if ( !IsValid( player ) ) + return + + if ( player.cv.hud.s.lastEventNotificationText == "#GAMEMODE_CALLINTITAN_COUNTDOWN" ) + HideEventNotification() + } + ) + + while ( true ) + { + if ( Riff_EliminationMode() == eEliminationMode.Titans ) + { + if ( IsAlive( player ) && GamePlayingOrSuddenDeath() && level.nv.secondsTitanCheckTime > Time() && !player.IsTitan() && !IsValid( player.GetPetTitan() ) && player.GetNextTitanRespawnAvailable() >= 0 ) + { + SetTimedEventNotificationHATT( level.nv.secondsTitanCheckTime - Time(), "#GAMEMODE_CALLINTITAN_COUNTDOWN", HATT_GAME_COUNTDOWN_SECONDS_MILLISECONDS, level.nv.secondsTitanCheckTime ) + } + else if ( player.cv.hud.s.lastEventNotificationText == "#GAMEMODE_CALLINTITAN_COUNTDOWN" ) + { + HideEventNotification() + } + } + else if ( Riff_EliminationMode() == eEliminationMode.Pilots ) + { + + } + + WaitSignal( player, "UpdateLastTitanStanding", "PetTitanChanged", "OnDeath" ) + } +} + +function RoundScoreThink( var vgui, var scoreGroup, entity player ) +{ + vgui.EndSignal( "OnDestroy" ) + player.EndSignal( "OnDestroy" ) + + FlagWait( "EntitiesDidLoad" ) //Have to do this because the nv that determines if RoundBased or not might not get set yet + + int friendlyTeam = player.GetTeam() + int enemyTeam = friendlyTeam == TEAM_IMC ? TEAM_MILITIA : TEAM_IMC + + local isRoundBased = IsRoundBased() + bool showRoundScore = true + int roundScoreLimit = GetRoundScoreLimit_FromPlaylist() + int scoreLimit = GetScoreLimit_FromPlaylist() + + if ( isRoundBased && showRoundScore ) + { + level.scoreLimit[TEAM_IMC] <- roundScoreLimit + level.scoreLimit[TEAM_MILITIA] <- roundScoreLimit + } + else + { + level.scoreLimit[TEAM_IMC] <- scoreLimit + level.scoreLimit[TEAM_MILITIA] <- scoreLimit + } + + local hudScores = vgui.s.scoreboardProgressBars + + while ( true ) + { + if ( isRoundBased && showRoundScore ) + { + hudScores.Friendly_Number.SetAutoText( "", HATT_FRIENDLY_TEAM_ROUND_SCORE, 0 ) + hudScores.Enemy_Number.SetAutoText( "", HATT_ENEMY_TEAM_ROUND_SCORE, 0 ) + } + + hudScores.ScoresFriendly.SetBarProgressRemap( 0, level.scoreLimit[friendlyTeam], 0.011, 0.96 ) + hudScores.ScoresEnemy.SetBarProgressRemap( 0, level.scoreLimit[enemyTeam], 0.011, 0.96 ) + wait 1.0 + } +} + +function CreatePlayerStatusElementsFriendly( scoreboardArrays, scoreGroup, panel ) +{ + scoreboardArrays.FriendlyPlayerStatusCount <- arrayofsize( 8 ) + + for ( int i = 0; i < 8; ++i ) + { + scoreboardArrays.FriendlyPlayerStatusCount[ i ] = scoreGroup.CreateElement( "Friendly_Player_Status_" + i, panel ) + } +} + +function CreatePlayerStatusElementsEnemy( scoreboardArrays, scoreGroup, panel ) +{ + scoreboardArrays.EnemyPlayerStatusCount <- arrayofsize( 8 ) + + for ( int i = 0; i < 8; ++i ) + { + scoreboardArrays.EnemyPlayerStatusCount[ i ] = scoreGroup.CreateElement( "Enemy_Player_Status_" + i, panel ) + } +} + + +function ScoreBarsPlayerStatusThink( vgui, entity player, friendlyPlayerStatusElem, enemyPlayerStatusElem ) +{ + int friendlyTeam = player.GetTeam() + int enemyTeam = friendlyTeam == TEAM_IMC ? TEAM_MILITIA : TEAM_IMC + + vgui.EndSignal( "OnDestroy" ) + + while( true ) + { + clGlobal.levelEnt.WaitSignal( "UpdatePlayerStatusCounts" ) + + if ( IsWatchingReplay() ) //Don't update visibility if the scoreboardgroup should be hidden + continue + + UpdatePlayerStatusForTeam( friendlyTeam, friendlyPlayerStatusElem, $"ui/icon_status_titan_friendly", $"ui/icon_status_pilot_friendly", $"ui/icon_status_burncard_friendly", $"ui/icon_status_burncard_friendly" ) + UpdatePlayerStatusForTeam( enemyTeam, enemyPlayerStatusElem, $"ui/icon_status_titan_enemy", $"ui/icon_status_pilot_enemy", $"ui/icon_status_burncard_enemy", $"ui/icon_status_burncard_enemy" ) + } +} + +function CountPlayerStatusTypes( array teamPlayers ) +{ + table resultTable = { + titanWithBurnCard = 0, + titan = 0, + pilotWithBurnCard = 0 + pilot = 0, + } + + foreach ( player in teamPlayers ) + { + entity playerPetTitan = player.GetPetTitan() + + if ( !IsAlive( player ) ) + { + if ( IsAlive( playerPetTitan ) ) + resultTable.titan++ + } + else + { + if ( player.IsTitan() ) + resultTable.titan++ + else if ( IsAlive( playerPetTitan ) ) + resultTable.titan++ + else + resultTable.pilot++ + } + + } + + return resultTable +} + + +function UpdatePlayerStatusForTeam( int team, teamStatusElem, titanImage, pilotImage, pilotBurnCardImage, titanBurnCardImage ) +{ + array teamPlayers = GetPlayerArrayOfTeam( team ) + local teamResultTable = CountPlayerStatusTypes( teamPlayers ) + + int maxElems = 8 + + int index = 0 + int currentElem = 0 + + for ( index = 0; index < teamResultTable.titanWithBurnCard && currentElem < maxElems; index++, currentElem++ ) + { + teamStatusElem[ currentElem ].Show() + teamStatusElem[ currentElem ].SetImage( titanBurnCardImage ) + } + + for ( index = 0; index < teamResultTable.titan && index < maxElems; index++, currentElem++ ) + { + teamStatusElem[ currentElem ].Show() + teamStatusElem[ currentElem ].SetImage( titanImage ) + } + + for ( index = 0; index < teamResultTable.pilotWithBurnCard && index < maxElems; index++, currentElem++ ) + { + teamStatusElem[ currentElem ].Show() + teamStatusElem[ currentElem ].SetImage( pilotBurnCardImage ) + } + + for ( index = 0; index < teamResultTable.pilot && index < maxElems; index++, currentElem++ ) + { + teamStatusElem[ currentElem ].Show() + teamStatusElem[ currentElem ].SetImage( pilotImage ) + } + + for( ; currentElem < maxElems; currentElem++ ) + { + teamStatusElem[ currentElem ].Hide() + } +} +function SuddenDeathHUDThink( vgui, entity player ) +{ + Signal( player, "SuddenDeathHUDThink" ) + player.EndSignal( "SuddenDeathHUDThink" ) + vgui.EndSignal( "OnDestroy" ) + + while ( GetGameState() != eGameState.SuddenDeath ) + WaitSignal( player, "GameStateChanged" ) + + EndSignal( player, "GameStateChanged" ) + + local hudScores = vgui.s.scoreboardProgressBars + + OnThreadEnd( + function() : ( hudScores, player ) + { + if ( !IsValid( hudScores ) ) + return + + hudScores.GameInfo_Label.SetColor( 255, 255, 255, 255 ) + + string restoredGameModeLabelText = GAMETYPE_TEXT[ GameRules_GetGameMode() ] + hudScores.GameModeLabel.SetText( restoredGameModeLabelText ) + + if ( player == GetLocalClientPlayer() ) + { + local scoreElemsClient = player.cv.clientHud.s.mainVGUI.s.scoreboardProgressGroup.elements + scoreElemsClient.GameModeLabel.SetText( restoredGameModeLabelText ) + } + } + ) + + string gameModeLabelText = "" + + switch ( GAMETYPE ) + { + case CAPTURE_THE_FLAG: + gameModeLabelText = "#GAMEMODE_CAPTURE_THE_FLAG_SUDDEN_DEATH" + break + + case TEAM_DEATHMATCH: + case HARDCORE_TDM: + gameModeLabelText = "#GAMEMODE_PILOT_HUNTER_SUDDEN_DEATH" + break + + default: + gameModeLabelText = GAMETYPE_TEXT[ GameRules_GetGameMode() ] + } + + hudScores.GameModeLabel.SetText( gameModeLabelText ) + + if ( player == GetLocalClientPlayer() ) + { + local scoreElemsClient = player.cv.clientHud.s.mainVGUI.s.scoreboardProgressGroup.elements + scoreElemsClient.GameModeLabel.SetText( gameModeLabelText ) + } + + float startTime = Time() + float pulseFrac = 0.0 + + while ( true ) + { + pulseFrac = Graph( GetPulseFrac( 1.0, startTime ), 0.0, 1.0, 0.05, 1.0 ) + hudScores.GameInfo_Label.SetColor( 255, 255, 255, 255 * pulseFrac ) + + wait( 0.0 ) + } +} diff --git a/Northstar.Client/mod/scripts/vscripts/client/cl_sp_player.gnut b/Northstar.Client/mod/scripts/vscripts/client/cl_sp_player.gnut index 55c773c7d..ecb46cc2a 100644 --- a/Northstar.Client/mod/scripts/vscripts/client/cl_sp_player.gnut +++ b/Northstar.Client/mod/scripts/vscripts/client/cl_sp_player.gnut @@ -1,1891 +1,1891 @@ -untyped - -global function ClPlayer_Init -global function SCB_CheckPoint - -global function PlayIt -global function JumpRandomlyForever - -global function ClientCodeCallback_PlayerDidDamage -global function ClientCodeCallback_PlayerSpawned -//global function ClientCodeCallback_OnHudReloadScheme -global function ClientCodeCallback_HUDThink -global function Player_AddPlayer -global function Player_AddClient - -global function ServerCallback_PlayerConnectedOrDisconnected -global function ClientCodeCallback_PlayerDisconnected -global function ServerCallback_PlayerChangedTeams -//global function ClientCodeCallback_OnModelChanged -global function ServerCallback_RodeoerEjectWarning -global function ServerCallback_PlayScreenFXWarpJump -global function PlayShieldBreakEffect -global function HandleDoomedState -global function CoreReadyMessage - -global function OnClientPlayerAlive -global function OnClientPlayerDying - -global function ServerCallback_ShowNextSpawnMessage -global function GetWaveSpawnTime -global function ServerCallback_HideNextSpawnMessage - -global function ClientCodeCallback_OnHealthChanged - -// Northstar -global function AddCallback_OnCrosshairCurrentTargetChanged -global function ClientCodeCallback_OnCrosshairCurrentTargetChanged - -global function Pressed_TitanNextMode -global function ClientCodeCallback_OnGib -global function ClientPilotSpawned -global function AddCallback_OnPlayerDisconnected - -global function IsPlayerEliminated - -global function ServerCallback_GiveMatchLossProtection -global function ServerCallback_OnEntityKilled -global function ServerCallback_OnTitanKilled - -global function ShouldShowSpawnAsTitanHint -global function ServerCallback_SetAssistInformation - -global function GetShieldEffectCurrentColor -global function ResetAbilityBindings - -global function ServerCallback_ShowDisembarkHint - -#if DEV -global function BloodSprayDecals_Toggle -#endif - -struct { - var orbitalstrike_tracer = null - var law_missile_tracer = null - float nextSpawnTime = 0.0 - var UI_friendlyText - - // Northstar - array< void functionref( entity, entity ) > OnCrosshairCurrentTargetChangedCallbacks -} file - -struct BloodDecalParams -{ - float traceDist - float secondaryTraceDist - asset fxType - asset secondaryFxType -} - -void function ClPlayer_Init() -{ - ClPilotJumpjet_Init() - ClDamageIndicator_Init() - - ClPlayer_Common_Precache() - - RegisterSignal( "OnAnimationDone" ) - RegisterSignal( "OnAnimationInterrupted" ) - RegisterSignal( "OnBleedingOut" ) - RegisterSignal( "PanelAlphaOverTime" ) - RegisterSignal( "OnClientPlayerAlive" ) - RegisterSignal( "OnClientPlayerDying" ) - RegisterSignal( "StopAlertCore" ) - RegisterSignal( "HealthChanged" ) - - FlagInit( "DamageDistancePrint" ) - FlagInit( "EnableTitanModeChange", true ) - FlagInit( "EnableBloodSprayDecals", true ) - - level.vduOpen <- false - level.canSpawnAsTitan <- false - level.grenadeIndicatorEnabled <- true - level.clientsLastKiller <- null - - AddCreateCallback( "player", SetupPlayerAnimEvents ) - Assert ( IsSingleplayer() ) - AddCreateCallback( "player", SpClientPlayerInit ) - SpSharedInit() - - AddCallback_OnPlayerLifeStateChanged( PlayerADSDof ) - AddCreateCallback( "first_person_proxy", SetupFirstPersonProxyEvents ) - AddCreateCallback( "predicted_first_person_proxy", SetupFirstPersonProxyEvents ) - - AddCreateCallback( "player", EnableDoDeathCallback ) - AddCreateCallback( "npc_titan", EnableDoDeathCallback ) - - if ( !IsLobby() ) - { - RegisterServerVarChangeCallback( "gameEndTime", UpdateRespawnHUD ) - } - - AddCreateCallback( "titan_soul", CreateCallback_TitanSoul ) - - file.orbitalstrike_tracer = PrecacheParticleSystem( $"Rocket_Smoke_Large" ) - //DEBUG Remove when bug is fixed. - file.law_missile_tracer = PrecacheParticleSystem( $"wpn_orbital_rocket_tracer" ) - - level.menuHideGroups <- {} - - //PrecacheParticleSystem( SHIELD_FX ) - - level.spawnAsTitanSelected <- false - - AddPlayerFunc( Player_AddPlayer ) - AddCreatePilotCockpitCallback( ClearDisembarkHint ) -} - -entity function FindEnemyRodeoParent( entity player ) -{ - entity ent = player.GetParent() - if ( ent == null ) - return null - - if ( !ent.IsTitan() ) - return null - - if ( ent == player.GetPetTitan() ) - return null - - if ( ent.GetTeam() == player.GetTeam() ) - return null - - return ent -} - -void function SpClientPlayerInit( entity player ) -{ - player.ClientCommand( "clear_loading_progress_detente" ) - player.ClientCommand( "clear_loading_progress_sp_text" ) -} - -void function ClientCodeCallback_PlayerSpawned( entity player ) -{ - if ( !IsValid( player ) ) - return - - // exists on server and client. Clear it when you respawn. - ClearRecentDamageHistory( player ) - - if ( player == GetLocalViewPlayer() ) - { - foreach ( callbackFunc in clGlobal.onLocalViewPlayerSpawnedCallbacks ) - { - callbackFunc( player ) - } - } - - if ( player == GetLocalClientPlayer() ) - { - foreach ( callbackFunc in clGlobal.onLocalClientPlayerSpawnedCallbacks ) - { - thread callbackFunc( player ) - } - } -} - - -void function CoreReadyMessage( entity player, bool isQuick = false ) -{ - if ( !GamePlayingOrSuddenDeath() ) - return - - if ( !IsAlive( player ) ) - return - - if ( GetDoomedState( player ) ) - return - - entity weapon = player.GetOffhandWeapon( OFFHAND_EQUIPMENT ) - - if ( weapon == null ) // JFS - return - - string coreOnlineMessage = expect string( weapon.GetWeaponInfoFileKeyField( "readymessage" ) ) - string coreOnlineHint = expect string( weapon.GetWeaponInfoFileKeyField( "readyhint" ) ) - - if ( isQuick ) - AnnouncementMessageQuick( player, coreOnlineMessage, coreOnlineHint, TEAM_COLOR_YOU ) - else - AnnouncementMessage( player, coreOnlineMessage, coreOnlineHint, TEAM_COLOR_YOU ) -} - -void function ClientPilotSpawned( entity player ) -{ - player.EndSignal( "SettingsChanged" ) - player.EndSignal( "OnDestroy" ) - player.EndSignal( "OnDeath" ) - - if ( player != GetLocalViewPlayer() ) - thread ParentedPlayerJets( player ) -} - -void function Player_AddClient( entity player ) -{ - if ( GetCurrentPlaylistVarInt( "titan_mode_change_allowed", 1 ) ) - RegisterConCommandTriggeredCallback( "+scriptCommand2", Pressed_TitanNextMode ) - - RegisterConCommandTriggeredCallback( "+scriptCommand2", Pressed_RequestTitanfall ) - RegisterConCommandTriggeredCallback( "+scriptCommand2", Pressed_ActivateMobilityGhost ) - - RegisterConCommandTriggeredCallback( "titan_loadout_select", Pressed_TitanLoadoutSelect ) - RegisterConCommandTriggeredCallback( "scoreboard_focus", Pressed_TitanLoadoutSelect ) - RegisterConCommandTriggeredCallback( "scoreboard_toggle_focus", Pressed_TitanLoadoutSelect ) - - Create_DamageIndicatorHUD() - - if ( !IsLobby() ) - { - player.EnableHealthChangedCallback() - - player.cv.deathTime <- 0.0 - player.cv.lastSpawnTime <- 0.0 - player.cv.deathOrigin <- <0.0, 0.0, 0.0> - player.cv.roundSpawnCount <- 0 - - thread CinematicIntroScreen() - } -} - -void function Player_AddPlayer( entity player ) -{ - player.s.weaponUpdateData <- {} - - player.s.trackedAttackers <- {} // for titans - player.classChanged = true -} - -function Pressed_RequestTitanfall( entity player ) -{ - //we are not using titan call-ins in SP for this game so lets disable it since it was still playing sounds and doing all the logic. - const DISABLE_FOR_R2 = true - if ( DISABLE_FOR_R2 ) - return - - if ( player.IsTitan() ) - return - - if ( !IsAlive( player ) ) - return - - if ( player.IsPhaseShifted() ) - return - - if ( player.GetRemoteTurret() != null ) - return - - if ( !IsAlive( player.GetPetTitan() ) ) - { - if ( Riff_TitanQueueLimit() > 0 ) - { - local queuePosition = player.nv.titanQueueNum - if ( queuePosition < 0 || queuePosition >= Riff_TitanQueueLimit() ) - { - player.ClientCommand( "RequestTitanQueueToggle" ) - return - } - } - - #if DEV - printt( player.GetEntIndex(), "Requested replacement Titan from eye pos " + player.EyePosition() + " view angles " + player.EyeAngles() + " player origin " + player.GetOrigin() + " map " + GetMapName() ) - #endif - - player.ClientCommand( "ClientCommand_RequestTitan" ) //Send client command regardless of whether we can call the titan in or not. Server decides - - if ( clGlobal.isAnnouncementActive && clGlobal.activeAnnouncement.messageText == "#HUD_TITAN_READY" ) - { - clGlobal.levelEnt.Signal( "AnnoucementPurge" ) - } - - //PlayMusic( "Music_FR_Militia_TitanFall1" ) - EmitSoundOnEntity( player, "titan_callin" ) - return - } -} - -function Pressed_TitanNextMode( entity player ) -{ - if ( player.IsTitan() ) - return - - if ( IsWatchingReplay() ) - return - - if ( !IsAlive( player ) ) - return - - if ( player.IsPhaseShifted() ) - return - - if ( !IsAlive( player.GetPetTitan() ) ) - return - - if ( !Flag( "EnableTitanModeChange" ) ) - return - - // cannot change modes while titan is incoming - if ( player.GetHotDropImpactTime() ) - return - - player.ClientCommand( "TitanNextMode" ) - - local newMode = player.GetPetTitanMode() + 1 - if ( newMode == eNPCTitanMode.MODE_COUNT ) - newMode = eNPCTitanMode.FOLLOW - - SetAutoTitanModeHudIndicator( player, newMode ) - - local guardModeAlias = GenerateTitanOSAlias( player, AUTO_TITAN_GUARD_MODE_DIAG_SUFFIX ) - local followModeAlias = GenerateTitanOSAlias( player, AUTO_TITAN_FOLLOW_MODE_DIAG_SUFFIX ) - - // prevent the sounds from stomping each other if button is pressed rapidly - StopSoundOnEntity( player, guardModeAlias ) - StopSoundOnEntity( player, AUTO_TITAN_GUARD_MODE_SOUND ) - StopSoundOnEntity( player, followModeAlias ) - StopSoundOnEntity( player, AUTO_TITAN_FOLLOW_MODE_SOUND ) - - if ( newMode == eNPCTitanMode.FOLLOW ) - { - EmitSoundOnEntity( player, followModeAlias ) - EmitSoundOnEntity( player, AUTO_TITAN_FOLLOW_MODE_SOUND ) - } - else if ( newMode == eNPCTitanMode.STAY ) - { - EmitSoundOnEntity( player, guardModeAlias ) - EmitSoundOnEntity( player, AUTO_TITAN_GUARD_MODE_SOUND ) - } -} - -/* -void function ClientCodeCallback_OnHudReloadScheme() -{ -} -*/ - - - -function Pressed_TitanLoadoutSelect( entity player ) -{ - if ( !player.IsTitan() ) - return - - if ( !IsAlive( player ) ) - return - - int ceFlags = player.GetCinematicEventFlags() - - // disable this menu in titan 3P - if ( ceFlags & CE_FLAG_TITAN_3P_CAM ) - return - - // One day, turn these back on if we allow player to set BT's loadout while disembarked - // - // if ( player.IsPhaseShifted() ) - // return - - // if ( player.GetRemoteTurret() != null ) - // return - - // if ( !IsAlive( player.GetPetTitan() ) ) - // return - - - RunUIScript( "OpenSPTitanLoadoutMenu" ) -} - - - -void function ClientCodeCallback_HUDThink() -{ - PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink ) - - entity player = GetLocalViewPlayer() - - if ( !player.p.playerScriptsInitialized ) - { - PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink ) - return - } - - if ( !IsMenuLevel() ) - { - PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink_4 ) - - PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink_4 ) - - PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink_5 ) - UpdateVoiceHUD() - PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink_5 ) - - UpdateScreenFade() - - entity clientPlayer = GetLocalClientPlayer() - if ( !IsWatchingReplay() && clientPlayer.classChanged ) - ClientPlayerClassChanged( clientPlayer, clientPlayer.GetPlayerClass() ) - - PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink_6 ) - SmartAmmo_LockedOntoWarningHUD_Update() - WeaponFlyoutThink( player ) - PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink_6 ) - } - - PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink ) -} - -function ClientPlayerClassChanged( entity player, newClass ) -{ - //printl( "ClientPlayerClassChanged to " + player.GetPlayerClass() ) - player.classChanged = false - - level.vduOpen = false // vdu goes away when class changes - - Assert( !IsServer() ) - Assert( newClass, "No class " ) - - ResetAbilityBindings( player, expect string( newClass ) ) -} - -void function ResetAbilityBindings( entity player, string newClass ) -{ - switch ( newClass ) - { - case "titan": - SetStandardAbilityBindingsForTitan( player ) - SetAbilityBinding( player, 6, "weaponSelectOrdnance", "weaponSelectOrdnance" ) // "+ability 6" - - if ( IsConversationPlaying() ) - SetAbilityBinding( player, 1, "+scriptCommand2", "-scriptCommand2" ) // "+ability 1" - - LinkButtonPair( -1, -1, -1 ) - - break - - case level.pilotClass: - SetStandardAbilityBindingsForPilot( player ) - SetAbilityBinding( player, 6, "weaponSelectOrdnance", "weaponSelectOrdnance" ) // "+ability 6" - - LinkButtonPair( IN_OFFHAND0, IN_OFFHAND1, IN_OFFHAND3 ) - - if ( clGlobal.isAnnouncementActive && (clGlobal.activeAnnouncement.messageText == "#HUD_CORE_ONLINE_STRYDER" || - clGlobal.activeAnnouncement.messageText == "#HUD_CORE_ONLINE_ATLAS" || - clGlobal.activeAnnouncement.messageText == "#HUD_CORE_ONLINE_OGRE" ) ) - { - clGlobal.levelEnt.Signal( "AnnoucementPurge" ) - } - break - - case "spectator": - LinkButtonPair( -1, -1, -1 ) - break - - default: - Assert( 0, "Unknown class " + newClass ) - } -} - -function ShouldShowSpawnAsTitanHint( entity player ) -{ - if ( Time() - player.cv.deathTime < GetRespawnButtonCamTime( player ) ) - return false - - if ( GetGameState() < eGameState.Playing ) - return false - - if ( GetGameState() == eGameState.SwitchingSides ) - return false - - return !IsPlayerEliminated( player ) -} - -function ServerCallback_PlayerChangedTeams( player_eHandle, oldTeam, newTeam ) -{ -} - -function ServerCallback_PlayerConnectedOrDisconnected( player_eHandle, state ) -{ - entity player = GetEntityFromEncodedEHandle( player_eHandle ) - PlayerConnectedOrDisconnected( player, state ) - - if ( !IsLobby() || !IsConnected() ) - UpdatePlayerStatusCounts() -} - -void function AddCallback_OnPlayerDisconnected( void functionref( entity ) callbackFunc ) -{ - Assert( !clGlobal.onPlayerDisconnectedFuncs.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnPlayerDisconnected" ) - - clGlobal.onPlayerDisconnectedFuncs.append( callbackFunc ) -} - -void function ClientCodeCallback_PlayerDisconnected( entity player, string cachedPlayerName ) -{ - PlayerConnectedOrDisconnected( player, 0, cachedPlayerName ) - - if ( ShouldUpdatePlayerStatusCounts() ) - UpdatePlayerStatusCounts() - - // Added via AddCallback_OnPlayerDisconnected - foreach ( callbackFunc in clGlobal.onPlayerDisconnectedFuncs ) - { - callbackFunc( player ) - } -} - -function ShouldUpdatePlayerStatusCounts() -{ - if ( GetGameState() < eGameState.WaitingForPlayers ) - return false - - if ( !IsLobby() ) - return true - - if ( !IsConnected() ) - return true - - return false -} - -function PlayerConnectedOrDisconnected( entity player, state, string disconnectingPlayerName = "" ) -{ -} - -void function ClientCodeCallback_PlayerDidDamage( PlayerDidDamageParams params ) -{ - entity attacker = GetLocalViewPlayer() - if ( !IsValid( attacker ) ) - return - - entity victim = params.victim - if ( !IsValid( victim ) ) - return - - vector damagePosition = params.damagePosition - int hitBox = params.hitBox - int damageType = params.damageType - float damageAmount = params.damageAmount - int damageFlags = params.damageFlags - int hitGroup = params.hitGroup - entity weapon = params.weapon - float distanceFromAttackOrigin = params.distanceFromAttackOrigin - - bool playHitSound = true - bool showCrosshairHitIndicator = true - bool hitIneffective = false - bool victimIsHeavyArmor = false - bool isCritShot = (damageType & DF_CRITICAL) ? true : false - bool isHeadShot = (damageType & DF_HEADSHOT) ? true : false - bool isKillShot = (damageType & DF_KILLSHOT) ? true : false - bool isMelee = (damageType & DF_MELEE) ? true : false - bool isExplosion = (damageType & DF_EXPLOSION) ? true : false - bool isBullet = (damageType & DF_BULLET) ? true : false - bool isShotgun = (damageType & DF_SHOTGUN) ? true : false - bool isDoomFatality = (damageType & DF_DOOM_FATALITY) ? true : false - bool isDoomProtected = ((damageType & DF_DOOM_PROTECTED) && !isDoomFatality) ? true : false - victimIsHeavyArmor = victim.GetArmorType() == ARMOR_TYPE_HEAVY - - isDoomFatality = false - isDoomProtected = false - - if ( isDoomProtected ) - RegisterDoomProtectionHintDamage( damageAmount ) - - bool playKillSound = isKillShot - - if ( !attacker.IsTitan() ) - { - if ( victimIsHeavyArmor ) - { - showCrosshairHitIndicator = true - if ( victim.IsTitan() ) - hitIneffective = false //!IsHitEffectiveVsTitan( victim, damageType ) - else - hitIneffective = isCritShot || isHeadShot || !IsHitEffectiveVsNonTitan( victim, damageType ) - } - else - { - switch ( victim.GetSignifierName() ) - { - case "npc_super_spectre": - //if ( !( damageType & DF_CRITICAL ) ) - // hitIneffective = true - - default: - if ( (damageType & DF_BULLET && damageType & DF_MAX_RANGE) ) - hitIneffective = true - break - } - } - } - else - { - if ( victim.IsTitan() && victim.IsPlayer() ) - { - if ( PlayerHasPassive( victim, ePassives.PAS_BERSERKER ) ) - hitIneffective = true - } - } - - if ( damageType & DF_MAX_RANGE && damageType & DF_BULLET ) - // TODO: this is crap; these damage types should just send DF_NO_HITBEEP - playHitSound = false - - if ( damageType & DF_TITAN_STEP ) - // TODO: this is crap; these damage types should just send DF_NO_HITBEEP - { - playHitSound = false - playKillSound = false - } - - if ( damageType & DF_MELEE ) - // TODO: this is crap; these damage types should just send DF_NO_HITBEEP - { - playHitSound = false - playKillSound = false - } - - if ( damageType & DF_NO_HITBEEP ) - { - playHitSound = false - playKillSound = false - } - - if ( damageFlags & DAMAGEFLAG_VICTIM_HAS_VORTEX ) - showCrosshairHitIndicator = false - - if ( damageType & DF_SHIELD_DAMAGE ) - { - PlayShieldHitEffect( params ) - showCrosshairHitIndicator = true - } - else if ( damageAmount <= 0 ) - { - playHitSound = false - playKillSound = false - showCrosshairHitIndicator = false - } - - if ( damageType & DF_NO_INDICATOR ) - { - playHitSound = false - playKillSound = false - showCrosshairHitIndicator = false - } - - if ( isDoomProtected ) - playHitSound = false - - if ( showCrosshairHitIndicator ) - { - Tracker_PlayerAttackedTarget( attacker, victim ) - - //if ( hitIneffective ) - // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_INEFFECTIVE ) - //else - // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_NORMAL ) - // - //if ( (isCritShot || isDoomFatality) && !isDoomProtected ) - // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_CRITICAL ) - // - //if ( isHeadShot ) - // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_HEADSHOT ) - - if ( IsMultiplayer() && !victim.IsTitan() && !victim.IsHologram() ) - PROTO_HitIndicatorEffect( attacker, victim, damagePosition, isHeadShot, isKillShot ) - - if ( isKillShot ) - KillShotBloodSpray( attacker, victim, damagePosition, isExplosion, isBullet, isShotgun ) - - if ( victim.IsTitan() && isKillShot ) - ClientScreenShake( 8, 10, 1, Vector( 0, 0, 0 ) ) - - BloodSprayDecals( attacker, victim, damagePosition, damageAmount, isHeadShot, isKillShot, isMelee, isExplosion, isBullet, isShotgun ) - - DamageFlyout( damageAmount, damagePosition, victim, isHeadShot || isCritShot, hitIneffective ) - } - - bool playedHitSound = false - if ( playHitSound ) - { - if ( isHeadShot ) - playedHitSound = PlayHeadshotConfirmSound( attacker, victim, isKillShot ) - else if ( playKillSound ) - playedHitSound = PlayKillshotConfirmSound( attacker, victim, damageType ) - } - - if ( IsSpectre( victim ) ) - { - if ( isHeadShot ) - victim.Signal( "SpectreGlowEYEGLOW" ) - } - - // Play a hit sound effect if we didn't play a kill shot sound, and other conditions are met - if ( playHitSound && IsAlive( victim ) && !playedHitSound ) - { - PlayHitSound( victim, attacker, damageFlags, isCritShot, victimIsHeavyArmor, isKillShot, hitGroup ) - } - - if ( PlayerHasPassive( attacker, ePassives.PAS_SMART_CORE ) && isKillShot ) - { - attacker.p.smartCoreKills++ - } - - foreach ( callback in clGlobal.onLocalPlayerDidDamageCallback ) - { - callback( attacker, victim, damagePosition, damageType ) - } -} - -void function PlayHitSound( entity victim, entity attacker, int damageFlags, bool isCritShot, bool victimIsHeavyArmor, bool isKillShot, int hitGroup ) -{ - if ( damageFlags & DAMAGEFLAG_VICTIM_INVINCIBLE ) - { - EmitSoundOnEntity( attacker, "Player.HitbeepInvincible" ) - } - else if ( damageFlags & DAMAGEFLAG_VICTIM_HAS_VORTEX ) - { - EmitSoundOnEntity( attacker, "Player.HitbeepVortex" ) - } - else if ( isCritShot && victimIsHeavyArmor ) - { - EmitSoundOnEntity( attacker, "titan_damage_crit" ) - } - else if ( isCritShot ) - { - EmitSoundOnEntity( attacker, "Player.Hitbeep_crit" ) - } - else - { - EmitSoundOnEntity( attacker, "Player.Hitbeep" ) - } -} - -function PROTO_HitIndicatorEffect( entity player, entity victim, vector damagePosition, bool isHeadShot, bool isKillShot ) -{ - int fxId - if ( isKillShot ) - fxId = GetParticleSystemIndex( $"P_ar_impact_pilot_kill" ) - else if ( isHeadShot && !isKillShot ) - return - // fxId = GetParticleSystemIndex( $"P_ar_impact_pilot_headshot" ) - else - return - // fxId = GetParticleSystemIndex( $"P_ar_impact_pilot" ) - - vector victimVelocity = victim.GetVelocity() - damagePosition += (Length( victimVelocity ) * 0.15) * Normalize( victimVelocity ) - vector fxOffset = damagePosition - victim.GetOrigin() - StartParticleEffectOnEntityWithPos( victim, fxId, FX_PATTACH_ABSORIGIN_FOLLOW, -1, damagePosition - victim.GetOrigin(), <0, 0, 0> ) -} - -void function KillShotBloodSpray( entity player, entity victim, vector damagePosition, bool isExplosion, bool isBullet, bool isShotgun ) -{ - if ( IsSoftenedLocale() ) - return - - if ( !victim.IsHuman() && !IsProwler( victim ) ) - return - - if ( victim.IsMechanical() ) - return - - if ( victim.IsHologram() ) - return - - if ( !isExplosion && !isBullet && !isShotgun ) - return - - int fxId = GetParticleSystemIndex( FX_KILLSHOT_BLOODSPRAY ) - - vector victimVelocity = victim.GetVelocity() - damagePosition += (Length( victimVelocity ) * 0.15) * Normalize( victimVelocity ) - StartParticleEffectOnEntityWithPos( victim, fxId, FX_PATTACH_ABSORIGIN_FOLLOW, -1, damagePosition - victim.GetOrigin(), <0, 0, 0> ) -} - -void function BloodSprayDecals( entity player, entity victim, vector damagePosition, float damageAmount, bool isHeadShot, bool isKillShot, bool isMelee, bool isExplosion, bool isBullet, bool isShotgun ) -{ - if ( IsSoftenedLocale() || !Flag( "EnableBloodSprayDecals" ) ) - return - - if ( !victim.IsHuman() && !IsProwler( victim ) ) - return - - if ( victim.IsMechanical() ) - return - - if ( victim.IsHologram() ) - return - - if ( !isMelee && !isExplosion && !isBullet && !isShotgun ) - return - - // in MP, too expensive to do on every shot - if ( IsMultiplayer() && !isKillShot ) - return - - thread BloodSprayDecals_Think( player, victim, damagePosition, damageAmount, isHeadShot, isKillShot, isMelee, isExplosion, isBullet, isShotgun ) -} - -void function BloodSprayDecals_Think( entity player, entity victim, vector damagePosition, float damageAmount, bool isHeadShot, bool isKillShot, bool isMelee, bool isExplosion, bool isBullet, bool isShotgun ) -{ - player.EndSignal( "OnDestroy" ) - victim.EndSignal( "OnDestroy" ) - - BloodDecalParams params = BloodDecal_GetParams( damageAmount, isHeadShot, isKillShot, isMelee, isExplosion, isBullet, isShotgun ) - float traceDist = params.traceDist - float secondaryTraceDist = params.secondaryTraceDist - asset fxType = params.fxType - asset secondaryFxType = params.secondaryFxType - - int fxId = GetParticleSystemIndex( fxType ) - - // PRIMARY TRACES - vector traceStart = damagePosition - vector traceFwd = player.GetViewVector() - - if ( isExplosion || isMelee ) - { - // for explosion/melee damage, use chest instead of actual damage position - int attachID = victim.LookupAttachment( "CHESTFOCUS" ) - traceStart = victim.GetAttachmentOrigin( attachID ) - - if ( isExplosion ) - traceFwd = AnglesToForward( victim.GetAngles() ) * -1 - } - - vector traceEnd = damagePosition + (traceFwd * traceDist) - //TraceResults traceResult = TraceLine( traceStart, traceEnd, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) - - var trace_primary = DeferredTraceLineHighDetail( traceStart, traceEnd, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) - WaitFrame() - TraceResults traceResult = GetDeferredTraceResult( trace_primary ) - - vector primaryTraceEndPos = traceResult.endPos - vector primaryTraceNormal = traceResult.surfaceNormal - //DebugDrawLine( traceStart, traceEnd, 255, 150, 0, true, 5 ) - //DebugDrawSphere( primaryTraceEndPos, 8.0, 255, 0, 0, true, 5 ) - - bool doGravitySplat = isMelee ? false : true - - if ( traceResult.fraction < 1.0 ) - { - vector normAng = VectorToAngles( traceResult.surfaceNormal ) - vector fxAng = AnglesCompose( normAng, < 90, 0, 0 > ) - - StartParticleEffectInWorld( fxId, primaryTraceEndPos, fxAng ) - //DebugDrawAngles( endPos, fxAng, 5 ) - } - else if ( doGravitySplat ) - { - // trace behind the guy on the ground and put a decal there - float gravitySplatBackTraceDist = 58.0 // how far behind the guy to put the gravity splat - float gravitySplatDownTraceDist = 100.0 // max dist vertically to try to trace and put a gravity splat - vector groundTraceStartPos = damagePosition + (traceFwd * gravitySplatBackTraceDist) - vector groundTraceEndPos = groundTraceStartPos - <0, 0, 100> - - var trace_gravitySplat = DeferredTraceLineHighDetail( groundTraceStartPos, groundTraceEndPos, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) - WaitFrame() - TraceResults downTraceResult = GetDeferredTraceResult( trace_gravitySplat ) - - if ( downTraceResult.fraction < 1.0 ) - { - //DebugDrawLine( groundTraceStartPos, downTraceResult.endPos, 255, 150, 0, true, 5 ) - //DebugDrawSphere( downTraceResult.endPos, 4.0, 255, 0, 0, true, 5 ) - - vector normAng = VectorToAngles( downTraceResult.surfaceNormal ) - vector fxAng = AnglesCompose( normAng, < 90, 0, 0 > ) - - //DebugDrawAngles( downTraceResult.endPos, fxAng, 5 ) - - StartParticleEffectInWorld( fxId, downTraceResult.endPos, fxAng ) - } - } - - // MP doesn't want secondaries, too expensive - if ( IsMultiplayer() ) - return - - // SECONDARY TRACES - array testVecs = [] - vector tempAng = VectorToAngles( traceFwd ) - - if ( isExplosion ) - { - // for explosions, different & more angles for secondary splatter - testVecs.append( AnglesToRight( tempAng ) ) - testVecs.append( AnglesToRight( tempAng ) * -1 ) - testVecs.append( traceFwd * -1 ) - testVecs.append( AnglesToUp( tempAng ) ) - testVecs.append( AnglesToUp( tempAng ) * -1 ) - } - else - { - // mostly to cover edge cases involving corners - vector traceRight = AnglesToRight( tempAng ) - vector traceLeft = traceRight * -1 - vector backLeft = (traceFwd + traceLeft) * 0.5 - vector backRight = (traceFwd + traceRight) * 0.5 - testVecs.append( backRight ) - testVecs.append( backLeft ) - - // add blood on the ground for these weapons too - if ( isBullet || isShotgun ) - testVecs.append( AnglesToUp( tempAng ) * -1 ) - } - - if ( !testVecs.len() ) - return - - array secondaryTraces = [] - foreach ( testVec in testVecs ) - { - vector secondaryTraceEnd = traceStart + (testVec * secondaryTraceDist) - var secondaryTrace = DeferredTraceLineHighDetail( traceStart, secondaryTraceEnd, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) - secondaryTraces.append( secondaryTrace ) - } - - int secondaryFxId = GetParticleSystemIndex( secondaryFxType ) - - float startTime = Time() - array processedResults = [] - while ( processedResults.len() < secondaryTraces.len() ) - { - WaitFrame() - - foreach ( trace in secondaryTraces ) - { - if ( processedResults.contains( trace ) ) - continue - - if ( !IsDeferredTraceFinished( trace ) ) - continue - - processedResults.append( trace ) - - TraceResults traceResult = GetDeferredTraceResult( trace ) - - if ( traceResult.fraction == 1.0 ) - continue - - // don't put secondaries on the same wall as the primary - vector secondaryTraceNormal = traceResult.surfaceNormal - if ( primaryTraceNormal == secondaryTraceNormal ) - continue - - vector normAng = VectorToAngles( secondaryTraceNormal ) - vector fxAng = AnglesCompose( normAng, < 90, 0, 0 > ) - - vector endPos = traceResult.endPos - //DebugDrawSphere( endPos, 4.0, 255, 0, 0, true, 5 ) - StartParticleEffectInWorld( secondaryFxId, endPos, fxAng ) - } - - // timeout if traces aren't returning - if ( Time() - startTime >= 0.3 ) - return - } -} - -BloodDecalParams function BloodDecal_GetParams( float damageAmount, bool isHeadShot, bool isKillShot, bool isMelee, bool isExplosion, bool isBullet, bool isShotgun ) -{ - // default: bullet damage - float traceDist = 175 - float secondaryTraceDist = 100 - asset fxType = FX_BLOODSPRAY_DECAL_SML - asset secondaryFxType = FX_BLOODSPRAY_DECAL_SML - - if ( isBullet ) - { - // HACK- shotguns report isBullet also - if ( isShotgun ) - { - //if ( isKillShot ) - // fxType = FX_BLOODSPRAY_DECAL_LRG - //else - fxType = FX_BLOODSPRAY_DECAL_MED - } - else - { - if ( isKillShot ) - fxType = FX_BLOODSPRAY_DECAL_MED - else - fxType = FX_BLOODSPRAY_DECAL_SML - - if ( damageAmount >= 200 ) - { - traceDist = 216 - fxType = FX_BLOODSPRAY_DECAL_LRG - secondaryFxType = FX_BLOODSPRAY_DECAL_MED - } - } - } - - else if ( isExplosion ) - { - secondaryTraceDist = traceDist - - float maxDmg = 300 - float medDmg = 200 - if ( IsMultiplayer() ) - { - maxDmg = 100 - medDmg = 75 - } - - if ( damageAmount >= maxDmg ) - { - fxType = FX_BLOODSPRAY_DECAL_LRG - secondaryFxType = FX_BLOODSPRAY_DECAL_LRG - } - else if ( damageAmount >= medDmg ) - { - fxType = FX_BLOODSPRAY_DECAL_LRG - secondaryFxType = FX_BLOODSPRAY_DECAL_MED - } - else if ( isKillShot ) - { - fxType = FX_BLOODSPRAY_DECAL_MED - secondaryFxType = FX_BLOODSPRAY_DECAL_MED - } - } - - else if ( isMelee ) - { - traceDist = 96 - - if ( isKillShot ) - fxType = FX_BLOODSPRAY_DECAL_MED - } - - // for kills, increase trace distance a bit - if ( isKillShot ) - { - traceDist = traceDist + (traceDist * 0.1) - secondaryTraceDist = secondaryTraceDist + (secondaryTraceDist * 0.1) - } - - BloodDecalParams params - params.traceDist = traceDist - params.secondaryTraceDist = secondaryTraceDist - params.fxType = fxType - params.secondaryFxType = secondaryFxType - return params -} - -#if DEV -string function BloodSprayDecals_Toggle() -{ - string returnStr = "" - - if ( Flag( "EnableBloodSprayDecals" ) ) - { - FlagClear( "EnableBloodSprayDecals" ) - returnStr = "Blood spray decals DISABLED" - } - else - { - FlagSet( "EnableBloodSprayDecals" ) - returnStr = "Blood spray decals ENABLED" - } - - return returnStr -} -#endif - -function ServerCallback_RodeoerEjectWarning( soulHandle, ejectTime ) -{ - entity soul = GetEntityFromEncodedEHandle( soulHandle ) - - if ( !IsValid( soul ) ) - return - - thread TitanEjectHatchSequence( soul, ejectTime ) -} - -function TitanEjectHatchSequence( soul, ejectTime ) -{ - expect entity( soul ) - - soul.EndSignal( "OnSoulTransfer" ) - soul.EndSignal( "OnTitanDeath" ) - soul.EndSignal( "OnDestroy" ) - - local effects = [] - - OnThreadEnd( - function() : ( effects ) - { - foreach ( effect in effects ) - { - if ( !EffectDoesExist( effect ) ) - continue - - EffectStop( effect, true, true ) - } - } - ) - - int boltCount = 6 - int fxID = GetParticleSystemIndex( $"xo_spark_bolt" ) - - for ( int index = 0; index < boltCount; index++ ) - { - entity titan = soul.GetTitan() - - WaitEndFrame() // so OnTitanDeath/Destroy can happen - - if ( !IsAlive( titan ) ) - return - - if ( !titan.IsTitan() ) - { - printt( "WARNING: " + titan + " is not a Titan!" ) - return - } - - int attachID = titan.LookupAttachment( "HATCH_BOLT" + (index + 1) ) - //printt( "attachID is " + attachID ) - vector boltOrgin = titan.GetAttachmentOrigin( attachID ) - vector boltAngles = titan.GetAttachmentAngles( attachID ) - vector launchVec = AnglesToForward( boltAngles ) * 500 - - CreateClientsideGib( $"models/industrial/bolt_tiny01.mdl", boltOrgin, boltAngles, launchVec, < 0, 0, 0 >, 3.0, 1000.0, 200.0 ) - int effect = PlayFXOnTag( titan, fxID, attachID ) - effects.append( effect ) - EmitSoundOnEntity( titan, "titan_bolt_loose" ) - - wait (ejectTime / boltCount) - } -} - -function ServerCallback_OnEntityKilled( attackerEHandle, victimEHandle, scriptDamageType, damageSourceId ) -{ - expect int( damageSourceId ) - - local isHeadShot = scriptDamageType & DF_HEADSHOT - - entity victim = GetEntityFromEncodedEHandle( victimEHandle ) - entity attacker = attackerEHandle ? GetHeavyWeightEntityFromEncodedEHandle( attackerEHandle ) : null - entity localClientPlayer = GetLocalClientPlayer() - - if ( !IsValid( victim ) ) - return - - Signal( victim, "OnDeath" ) - - if ( damageSourceId == eDamageSourceId.indoor_inferno ) - { - if ( victim == localClientPlayer ) - thread PlayerFieryDeath( victim ) - } - - if ( victim.IsPlayer() && victim != attacker ) - { - if ( attacker == localClientPlayer ) - { - EmitSoundOnEntity( attacker, "Pilot_Killed_Indicator" ) - } - else if ( IsValid( attacker ) && attacker.IsTitan() ) - { - entity bossPlayer = attacker.GetBossPlayer() - if ( bossPlayer && bossPlayer == localClientPlayer ) - EmitSoundOnEntity( bossPlayer, "Pilot_Killed_Indicator" ) - } - } -} - -void function ServerCallback_OnTitanKilled( int attackerEHandle, int victimEHandle, int scriptDamageType, int damageSourceId ) -{ -} - -function PlayTargetEliminatedTitanVO( attacker, victim ) -{ - entity localPlayer = GetLocalViewPlayer() - - if ( attacker != localPlayer ) - return - - if ( !victim.IsPlayer() ) - return - - if ( victim.IsTitan() ) - { - // a bit more delay for a titan explosion to clear - thread TitanCockpit_PlayDialogDelayed( localPlayer, 1.3, "elimTarget" ) - } - else - { - thread TitanCockpit_PlayDialogDelayed( localPlayer, 0.8, "elimEnemyPilot" ) - } -} - -function ServerCallback_SetAssistInformation( damageSourceId, attackerEHandle, entityEHandle, assistTime ) -{ - local ent = GetEntityFromEncodedEHandle ( entityEHandle ) - if ( !ent ) - return - - local latestAssistPlayer = GetEntityFromEncodedEHandle ( attackerEHandle ) - if ( !("latestAssistPlayer" in ent.s) ) - { - ent.s.latestAssistPlayer <- latestAssistPlayer - ent.s.latestAssistDamageSource <- damageSourceId - ent.s.latestAssistTime <- assistTime - } - else - { - ent.s.latestAssistPlayer = latestAssistPlayer - ent.s.latestAssistDamageSource = damageSourceId - ent.s.latestAssistTime = assistTime - } -} - -/* -void function ClientCodeCallback_OnModelChanged( entity ent ) -{ - // OnModelChanged gets called for each model change, but gets processed after the model has done all switches - if ( !IsValid( ent ) ) - return; - - if ( !("creationCount" in ent.s) ) - return; - - Assert( ent instanceof C_BaseAnimating ); -} -*/ - - -void function ClientCodeCallback_OnHealthChanged( entity ent, int oldHealth, int newHealth ) -{ - if ( IsLobby() ) - return - - entity player = GetLocalViewPlayer() - if ( !IsValid( player ) ) - return - - if ( !IsValid( ent ) ) - return - - ent.Signal( "HealthChanged", { oldHealth = oldHealth, newHealth = newHealth } ) -} - -// Northstar -void function AddCallback_OnCrosshairCurrentTargetChanged( void functionref( entity, entity ) callbackFunc ) -{ - Assert( !file.OnCrosshairCurrentTargetChangedCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnCrosshairCurrentTargetChanged" ) - - file.OnCrosshairCurrentTargetChangedCallbacks.append( callbackFunc ) -} - -void function ClientCodeCallback_OnCrosshairCurrentTargetChanged( entity player, entity newTarget ) -{ - if ( IsLobby() ) - return; - if ( !IsValid( player ) ) - return - - foreach ( callback in file.OnCrosshairCurrentTargetChangedCallbacks ) - { - callback( player, newTarget ) - } -} - -void function SetupPlayerAnimEvents( entity player ) -{ - SetupPlayerJumpJetAnimEvents( player ) - AddAnimEvent( player, "WallHangAttachDataKnife", WallHangAttachDataKnife ) -} - -void function JumpRandomlyForever() -{ - for (;; ) - { - if ( IsWatchingReplay() ) - { - wait 1 - continue - } - - entity player = GetLocalClientPlayer() - if ( !IsAlive( player ) || player != GetLocalViewPlayer() ) - { - wait 1 - continue - } - - printt( "jump!" ) - player.ClientCommand( "+jump" ) - wait 0 - player.ClientCommand( "-jump" ) - - wait RandomFloatRange( 0.2, 1.1 ) - } -} - -void function RemoteTurretFadeoutAnimEvent( entity ent ) -{ - entity player = GetLocalViewPlayer() - ScreenFade( player, 0, 0, 0, 255, 0.1, 0.25, FFADE_OUT ); -} - -void function SetupFirstPersonProxyEvents( entity firstPersonProxy ) -{ - //printt( "SetupFirstPersonProxyEvents" ) - - AddAnimEvent( firstPersonProxy, "mantle_smallmantle", OnSmallMantle ) - AddAnimEvent( firstPersonProxy, "mantle_mediummantle", OnMediumMantle ) - AddAnimEvent( firstPersonProxy, "mantle_lowmantle", OnLowMantle ) - AddAnimEvent( firstPersonProxy, "mantle_extralowmantle", OnExtraLowMantle ) - AddAnimEvent( firstPersonProxy, "remoteturret_fadeout", RemoteTurretFadeoutAnimEvent ) -} - -void function OnSmallMantle( entity firstPersonProxy ) -{ - entity player = GetLocalViewPlayer() - - if ( PlayerHasPassive( player, ePassives.PAS_STEALTH_MOVEMENT ) ) - { - //printt( "mantle_smallmantle, has stealth passive" ) - } - else - { - //printt( "mantle_smallmantle, no stealth passive" ) - EmitSoundOnEntity( firstPersonProxy, "mantle_smallmantle" ) - } -} - -void function OnMediumMantle( entity firstPersonProxy ) -{ - entity player = GetLocalViewPlayer() - - if ( PlayerHasPassive( player, ePassives.PAS_STEALTH_MOVEMENT ) ) - { - //printt( "mantle_mediummantle, has stealth passive" ) - } - else - { - //printt( "mantle_mediummantle, no stealth passive" ) - EmitSoundOnEntity( firstPersonProxy, "mantle_mediummantle" ) - } -} - -void function OnLowMantle( entity firstPersonProxy ) -{ - entity player = GetLocalViewPlayer() - - if ( PlayerHasPassive( player, ePassives.PAS_STEALTH_MOVEMENT ) ) - { - //printt( "mantle_lowmantle, has stealth passive" ) - } - else - { - //printt( "mantle_lowmantle, no stealth passive" ) - EmitSoundOnEntity( firstPersonProxy, "mantle_lowmantle" ) - } -} - -void function OnExtraLowMantle( entity firstPersonProxy ) -{ - entity player = GetLocalViewPlayer() - - if ( PlayerHasPassive( player, ePassives.PAS_STEALTH_MOVEMENT ) ) - { - //printt( "mantle_extralowmantle, has stealth passive" ) - } - else - { - //printt( "mantle_extralowmantle, no stealth passive" ) - EmitSoundOnEntity( firstPersonProxy, "mantle_extralow" ) - } -} - -void function CreateCallback_TitanSoul( entity ent ) -{ -} - - -void function WallHangAttachDataKnife( entity player ) -{ - int attachIdx = player.LookupAttachment( "l_hand" ) - if ( attachIdx == 0 ) - // hack while i wait for the attachment to be fixed - return - - entity dataknife = CreateClientSidePropDynamic( player.GetAttachmentOrigin( attachIdx ), player.GetAttachmentAngles( attachIdx ), DATA_KNIFE_MODEL ) - dataknife.SetParent( player, "l_hand" ) - - thread DeleteDataKnifeAfterWallHang( player, dataknife ) -} - -void function DeleteDataKnifeAfterWallHang( entity player, entity dataknife ) -{ - OnThreadEnd( - function() : ( dataknife ) - { - if ( IsValid( dataknife ) ) - dataknife.Kill_Deprecated_UseDestroyInstead() - } - ) - - player.EndSignal( "OnDeath" ) - player.EndSignal( "OnDestroy" ) - - for (;; ) - { - Wait( 0.1 ) - if ( !player.IsWallHanging() ) - break - } -} - -bool function ClientCodeCallback_OnGib( entity victim, vector attackDir ) -{ - if ( !victim.IsMechanical() ) - return SpawnFleshGibs( victim, attackDir ) - - return false -} - -bool function SpawnFleshGibs( entity victim, vector attackDir ) -{ - asset modelName = $"models/gibs/human_gibs.mdl" - attackDir = Normalize( attackDir ) - - float cullDist = 2048.0 - if ( "gibDist" in victim.s ) - cullDist = expect float( victim.s.gibDist ) - - vector startOrigin = victim.GetWorldSpaceCenter() + (attackDir * -30) - - vector origin = victim.GetOrigin() + < RandomIntRange( 10, 20 ), RandomIntRange( 10, 20 ), RandomIntRange( 32, 64 ) > - vector angles = < 0, 0, 0 > - vector flingDir = attackDir * RandomIntRange( 80, 200 ) - - int fxID - bool isSoftenedLocale = IsSoftenedLocale() - - if ( isSoftenedLocale ) - { - if ( victim.GetModelName() == FLYER_MODEL ) - fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist_LG_nochunk" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) - else - fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist_nochunk" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) - } - else - { - if ( victim.GetModelName() == FLYER_MODEL ) - fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist_LG" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) - else - fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) - } - - EffectSetControlPointVector( fxID, 1, flingDir ) - - if ( isSoftenedLocale ) - return true - - vector angularVel = < 0, 0, 0 > - float lifeTime = 10.0 - CreateClientsideGibWithBodyGroupGibs( modelName, victim.GetOrigin(), angles, attackDir, angularVel, lifeTime, cullDist, 1024 ) - - return true -} - -function ServerCallback_PlayScreenFXWarpJump() -{ - if ( IsWatchingReplay() ) - return false - - thread PlayScreenFXWarpJump( GetLocalClientPlayer() ) -} - -void function PlayScreenFXWarpJump( entity clientPlayer ) -{ - clientPlayer.EndSignal( "OnDeath" ) - clientPlayer.EndSignal( "OnDestroy" ) - - entity player = GetLocalViewPlayer() - int index = GetParticleSystemIndex( SCREENFX_WARPJUMP ) - int indexD = GetParticleSystemIndex( SCREENFX_WARPJUMPDLIGHT ) - int fxID = StartParticleEffectInWorldWithHandle( index, < 0, 0, 0 >, < 0, 0, 0 > ) - int fxID2 = -1 - if ( IsValid( player.GetCockpit() ) ) - { - fxID2 = StartParticleEffectOnEntity( player, indexD, FX_PATTACH_POINT_FOLLOW, player.GetCockpit().LookupAttachment( "CAMERA" ) ) - EffectSetIsWithCockpit( fxID2, true ) - } - - OnThreadEnd( - function() : ( clientPlayer, fxID, fxID2 ) - { - if ( IsValid( clientPlayer ) && !IsAlive( clientPlayer ) ) - { - EffectStop( fxID, true, false ) - if ( fxID2 > -1 ) - EffectStop( fxID2, true, false ) - } - } - ) - - wait 2.5 - if ( IsValid( player.GetCockpit() ) ) - thread TonemappingUpdateAfterWarpJump() -} - -const TONEMAP_1_START_DURATION = 0.2 -const TONEMAP_1_MAX = 0 -const TONEMAP_1_MIN = 8 -const TONEMAP_2_START_DURATION = 1.0 -const TONEMAP_2_MAX = 8 -const TONEMAP_2_MIN = 4 -const TONEMAP_3_START_DURATION = 5 -const TONEMAP_3_MAX = 40 -const TONEMAP_3_MIN = 1 - -function TonemappingUpdateAfterWarpJump() -{ - AutoExposureSetMaxExposureMultiplier( 500 ); // allow exposure to actually go bright, even if it's clamped in the level. - - local startTime = Time() - while( 1 ) - { - local time = Time() - startTime - float factor = GraphCapped( time, 0, TONEMAP_1_START_DURATION, 1, 0 ) - factor = factor * factor * factor - local toneMapScale = TONEMAP_1_MIN + (TONEMAP_1_MAX - TONEMAP_1_MIN) * factor - AutoExposureSetExposureCompensationBias( toneMapScale ) - AutoExposureSnap() - wait 0 - if ( factor == 0 ) - break; - } - - startTime = Time() - while( 1 ) - { - local time = Time() - startTime - float factor = GraphCapped( time, 0, TONEMAP_2_START_DURATION, 1, 0 ) - local toneMapScale = TONEMAP_2_MIN + (TONEMAP_2_MAX - TONEMAP_2_MIN) * factor - AutoExposureSetExposureCompensationBias( toneMapScale ) - AutoExposureSnap() - wait 0 - if ( factor == 0 ) - break; - } - - AutoExposureSetExposureCompensationBias( 0 ) // clear the exposure bias and allow the exposure to adjust to its normal level at its own pace - - // Ramp the max exposure multiplier back down to 1 (preferably slower than the autoexposure would drive the brightness down) - startTime = Time() - while( 1 ) - { - local time = Time() - startTime - float factor = GraphCapped( time, 0, TONEMAP_3_START_DURATION, 1, 0 ) - local scale = TONEMAP_3_MIN + (TONEMAP_3_MAX - TONEMAP_3_MIN) * factor - AutoExposureSetMaxExposureMultiplier( scale ); - wait 0 - if ( factor == 0 ) - break; - } -} - -function SetPanelAlphaOverTime( panel, alpha, duration ) -{ - // HACK this should be a code command - Mackey - Signal( panel, "PanelAlphaOverTime" ) - EndSignal( panel, "PanelAlphaOverTime" ) - EndSignal( panel, "OnDestroy" ) - - local startTime = Time() - local endTime = startTime + duration - local startAlpha = panel.GetPanelAlpha() - - while( Time() <= endTime ) - { - float a = GraphCapped( Time(), startTime, endTime, startAlpha, alpha ) - panel.SetPanelAlpha( a ) - WaitFrame() - } - - panel.SetPanelAlpha( alpha ) -} - - -bool function ShouldShowOnDeckCard( entity player, cardOnDeck ) -{ - local inPrematch = level.nv.gameState == eGameState.Prematch - - if ( IsAlive( player ) && !IsWatchingReplay() && !inPrematch ) - return false - - if ( inPrematch && !cardOnDeck ) - return false - - return true -} - -function HandleDoomedState( entity player, entity titan ) -{ - bool isDoomed = GetDoomedState( titan ) - if ( isDoomed ) - { - titan.Signal( "Doomed" ) - - if ( HasSoul( titan ) ) - { - entity soul = titan.GetTitanSoul() - soul.Signal( "Doomed" ) - } - } -} - -function PlayShieldBreakEffect( entity ent ) -{ - entity shieldEnt = ent - if ( IsSoul( ent ) ) - { - shieldEnt = ent.GetTitan() - if ( !shieldEnt ) - return - } - - float shieldHealthFrac = GetShieldHealthFrac( shieldEnt ) - - int shieldBreakFX = GetParticleSystemIndex( SHIELD_BREAK_FX ) - - local attachID - if ( shieldEnt.IsTitan() ) - attachID = shieldEnt.LookupAttachment( "exp_torso_main" ) - else - attachID = shieldEnt.LookupAttachment( "ref" ) // TEMP - - local shieldFXHandle = StartParticleEffectOnEntity( shieldEnt, shieldBreakFX, FX_PATTACH_POINT_FOLLOW, attachID ) - EffectSetControlPointVector( shieldFXHandle, 1, GetShieldEffectCurrentColor( 1 - shieldHealthFrac ) ) -} - -function PlayIt( entity victim ) -{ - float shieldHealthFrac = GetShieldHealthFrac( victim ) - - int shieldbodyFX = GetParticleSystemIndex( SHIELD_BODY_FX ) - local attachID - if ( victim.IsTitan() ) - attachID = victim.LookupAttachment( "exp_torso_main" ) - else - attachID = victim.LookupAttachment( "ref" ) // TEMP - - local shieldFXHandle = StartParticleEffectOnEntity( victim, shieldbodyFX, FX_PATTACH_POINT_FOLLOW, attachID ) - - EffectSetControlPointVector( shieldFXHandle, 1, GetShieldEffectCurrentColor( 1 - shieldHealthFrac ) ) -} - -function PlayShieldHitEffect( PlayerDidDamageParams params ) -{ - entity player = GetLocalViewPlayer() - entity victim = params.victim - //vector damagePosition = params.damagePosition - //int hitBox = params.hitBox - //int damageType = params.damageType - //float damageAmount = params.damageAmount - //int damageFlags = params.damageFlags - //int hitGroup = params.hitGroup - //entity weapon = params.weapon - //float distanceFromAttackOrigin = params.distanceFromAttackOrigin - - //shieldFX <- GetParticleSystemIndex( SHIELD_FX ) - //StartParticleEffectInWorld( shieldFX, damagePosition, player.GetViewVector() * -1 ) - - float shieldHealthFrac = GetShieldHealthFrac( victim ) - - int shieldbodyFX = GetParticleSystemIndex( SHIELD_BODY_FX ) - local attachID - if ( victim.IsTitan() ) - attachID = victim.LookupAttachment( "exp_torso_main" ) - else - attachID = victim.LookupAttachment( "ref" ) // TEMP - - local shieldFXHandle = StartParticleEffectOnEntity( victim, shieldbodyFX, FX_PATTACH_POINT_FOLLOW, attachID ) - - EffectSetControlPointVector( shieldFXHandle, 1, GetShieldEffectCurrentColor( 1 - shieldHealthFrac ) ) -} - -const table SHIELD_COLOR_CHARGE_FULL = { r = 115, g = 247, b = 255 } // blue -const table SHIELD_COLOR_CHARGE_MED = { r = 200, g = 128, b = 80 } // orange -const table SHIELD_COLOR_CHARGE_EMPTY = { r = 200, g = 80, b = 80 } // red - -const SHIELD_COLOR_CROSSOVERFRAC_FULL2MED = 0.75 // from zero to this fraction, fade between full and medium charge colors -const SHIELD_COLOR_CROSSOVERFRAC_MED2EMPTY = 0.95 // from "full2med" to this fraction, fade between medium and empty charge colors - -function GetShieldEffectCurrentColor( shieldHealthFrac ) -{ - local color1 = SHIELD_COLOR_CHARGE_FULL - local color2 = SHIELD_COLOR_CHARGE_MED - local color3 = SHIELD_COLOR_CHARGE_EMPTY - - local crossover1 = SHIELD_COLOR_CROSSOVERFRAC_FULL2MED // from zero to this fraction, fade between color1 and color2 - local crossover2 = SHIELD_COLOR_CROSSOVERFRAC_MED2EMPTY // from crossover1 to this fraction, fade between color2 and color3 - - local colorVec = < 0, 0, 0 > - // 0 = full charge, 1 = no charge remaining - if ( shieldHealthFrac < crossover1 ) - { - colorVec.x = Graph( shieldHealthFrac, 0, crossover1, color1.r, color2.r ) - colorVec.y = Graph( shieldHealthFrac, 0, crossover1, color1.g, color2.g ) - colorVec.z = Graph( shieldHealthFrac, 0, crossover1, color1.b, color2.b ) - } - else if ( shieldHealthFrac < crossover2 ) - { - colorVec.x = Graph( shieldHealthFrac, crossover1, crossover2, color2.r, color3.r ) - colorVec.y = Graph( shieldHealthFrac, crossover1, crossover2, color2.g, color3.g ) - colorVec.z = Graph( shieldHealthFrac, crossover1, crossover2, color2.b, color3.b ) - } - else - { - // for the last bit of overload timer, keep it max danger color - colorVec.x = color3.r - colorVec.y = color3.g - colorVec.z = color3.b - } - - return colorVec -} - - -void function UpdateRespawnHUD() -{ - if ( IsWatchingReplay() ) - return - - entity player = GetLocalClientPlayer() - ShowRespawnSelect_SP() -} - -function OnClientPlayerAlive( entity player ) -{ - player.Signal( "OnClientPlayerAlive" ) // TEMP; this should not be necessary, but IsWatchingKillReplay is wrong - player.EndSignal( "OnClientPlayerAlive" ) - - UpdateClientHudVisibility( player ) -} - - -function OnClientPlayerDying( entity player ) -{ - player.Signal( "OnClientPlayerDying" ) // TEMP; this should not be necessary, but IsWatchingKillReplay is wrong - player.EndSignal( "OnClientPlayerDying" ) - - entity player = GetLocalClientPlayer() - UpdateClientHudVisibility( player ) - - if ( IsWatchingReplay() ) - return - - player.cv.deathTime = Time() - - thread DeathCamCheck( player ) -} - -function DeathCamCheck( entity player ) -{ - wait GetRespawnButtonCamTime( player ) -} - -void function ServerCallback_ShowNextSpawnMessage( float nextSpawnTime ) -{ - entity player = GetLocalClientPlayer() - float camTime = GetRespawnButtonCamTime( player ) - - file.nextSpawnTime = nextSpawnTime - - if ( nextSpawnTime > Time() + camTime ) - thread ShowSpawnDelayMessage( nextSpawnTime ) -} - -void function ShowSpawnDelayMessage( nextSpawnTime ) -{ - float waitTime = max( nextSpawnTime - Time(), 0 ) - - if ( waitTime < 1.0 ) - return - - entity player = GetLocalClientPlayer() - - player.cv.nextSpawnTimeLabel.SetAlpha( 255 ) - player.cv.nextSpawnTimeLabel.Show() - player.cv.nextSpawnTimeLabel.SetAutoText( "#GAMEMODE_DEPLOYING_IN_N", HATT_GAME_COUNTDOWN_SECONDS, nextSpawnTime ) - - if ( !player.cv.nextSpawnTimeLabel.IsAutoText() ) - player.cv.nextSpawnTimeLabel.EnableAutoText() -} - -void function ServerCallback_HideNextSpawnMessage() -{ - entity player = GetLocalClientPlayer() - player.cv.nextSpawnTimeLabel.FadeOverTime( 0, 1.0 ) -} - -float function GetWaveSpawnTime() -{ - return (file.nextSpawnTime) -} - -bool function IsPlayerEliminated( entity player ) -{ - return (player.GetPlayerGameStat( PGS_ELIMINATED ) > 0) -} - -function PlayerFieryDeath( player ) -{ - player.EndSignal( "OnDestroy" ) - player.EndSignal( "OnClientPlayerAlive" ) - clGlobal.levelEnt.EndSignal( "OnSpectatorMode" ) - - local offset = < 0, 0, 0 > - if ( player.IsTitan() ) - offset = < 0, 0, 96 > - - entity scriptRef = CreatePropDynamic( $"models/dev/empty_model.mdl", player.GetOrigin() + offset, player.GetAngles() ) - scriptRef.SetParent( player ) - - local fxHandle = StartParticleEffectOnEntity( scriptRef, GetParticleSystemIndex( $"P_burn_player" ), FX_PATTACH_ABSORIGIN_FOLLOW, -1 ) - - OnThreadEnd( - function () : ( fxHandle, scriptRef ) - { - EffectStop( fxHandle, false, false ) - if ( IsValid( scriptRef ) ) - scriptRef.Destroy() - } - ) - WaitForever() -} - - -function ServerCallback_GiveMatchLossProtection() -{ -} - -void function EnableDoDeathCallback( entity ent ) -{ - ent.DoDeathCallback( true ) -} - -void function ServerCallback_ShowDisembarkHint( float showtime ) -{ - AddPlayerHint( showtime, 0.5, $"","#HUD_TITAN_DISEMBARK" ) -} - -void function ClearDisembarkHint( entity cockpit, entity player ) -{ - HidePlayerHint( "#HUD_TITAN_DISEMBARK" ) -} - -void function SCB_CheckPoint() -{ - entity player = GetLocalViewPlayer() - if ( !IsAlive( player ) ) - return - - // put good announce here - AnnouncementMessageCheckpoint( player ) -} +untyped + +global function ClPlayer_Init +global function SCB_CheckPoint + +global function PlayIt +global function JumpRandomlyForever + +global function ClientCodeCallback_PlayerDidDamage +global function ClientCodeCallback_PlayerSpawned +//global function ClientCodeCallback_OnHudReloadScheme +global function ClientCodeCallback_HUDThink +global function Player_AddPlayer +global function Player_AddClient + +global function ServerCallback_PlayerConnectedOrDisconnected +global function ClientCodeCallback_PlayerDisconnected +global function ServerCallback_PlayerChangedTeams +//global function ClientCodeCallback_OnModelChanged +global function ServerCallback_RodeoerEjectWarning +global function ServerCallback_PlayScreenFXWarpJump +global function PlayShieldBreakEffect +global function HandleDoomedState +global function CoreReadyMessage + +global function OnClientPlayerAlive +global function OnClientPlayerDying + +global function ServerCallback_ShowNextSpawnMessage +global function GetWaveSpawnTime +global function ServerCallback_HideNextSpawnMessage + +global function ClientCodeCallback_OnHealthChanged + +// Northstar +global function AddCallback_OnCrosshairCurrentTargetChanged +global function ClientCodeCallback_OnCrosshairCurrentTargetChanged + +global function Pressed_TitanNextMode +global function ClientCodeCallback_OnGib +global function ClientPilotSpawned +global function AddCallback_OnPlayerDisconnected + +global function IsPlayerEliminated + +global function ServerCallback_GiveMatchLossProtection +global function ServerCallback_OnEntityKilled +global function ServerCallback_OnTitanKilled + +global function ShouldShowSpawnAsTitanHint +global function ServerCallback_SetAssistInformation + +global function GetShieldEffectCurrentColor +global function ResetAbilityBindings + +global function ServerCallback_ShowDisembarkHint + +#if DEV +global function BloodSprayDecals_Toggle +#endif + +struct { + var orbitalstrike_tracer = null + var law_missile_tracer = null + float nextSpawnTime = 0.0 + var UI_friendlyText + + // Northstar + array< void functionref( entity, entity ) > OnCrosshairCurrentTargetChangedCallbacks +} file + +struct BloodDecalParams +{ + float traceDist + float secondaryTraceDist + asset fxType + asset secondaryFxType +} + +void function ClPlayer_Init() +{ + ClPilotJumpjet_Init() + ClDamageIndicator_Init() + + ClPlayer_Common_Precache() + + RegisterSignal( "OnAnimationDone" ) + RegisterSignal( "OnAnimationInterrupted" ) + RegisterSignal( "OnBleedingOut" ) + RegisterSignal( "PanelAlphaOverTime" ) + RegisterSignal( "OnClientPlayerAlive" ) + RegisterSignal( "OnClientPlayerDying" ) + RegisterSignal( "StopAlertCore" ) + RegisterSignal( "HealthChanged" ) + + FlagInit( "DamageDistancePrint" ) + FlagInit( "EnableTitanModeChange", true ) + FlagInit( "EnableBloodSprayDecals", true ) + + level.vduOpen <- false + level.canSpawnAsTitan <- false + level.grenadeIndicatorEnabled <- true + level.clientsLastKiller <- null + + AddCreateCallback( "player", SetupPlayerAnimEvents ) + Assert ( IsSingleplayer() ) + AddCreateCallback( "player", SpClientPlayerInit ) + SpSharedInit() + + AddCallback_OnPlayerLifeStateChanged( PlayerADSDof ) + AddCreateCallback( "first_person_proxy", SetupFirstPersonProxyEvents ) + AddCreateCallback( "predicted_first_person_proxy", SetupFirstPersonProxyEvents ) + + AddCreateCallback( "player", EnableDoDeathCallback ) + AddCreateCallback( "npc_titan", EnableDoDeathCallback ) + + if ( !IsLobby() ) + { + RegisterServerVarChangeCallback( "gameEndTime", UpdateRespawnHUD ) + } + + AddCreateCallback( "titan_soul", CreateCallback_TitanSoul ) + + file.orbitalstrike_tracer = PrecacheParticleSystem( $"Rocket_Smoke_Large" ) + //DEBUG Remove when bug is fixed. + file.law_missile_tracer = PrecacheParticleSystem( $"wpn_orbital_rocket_tracer" ) + + level.menuHideGroups <- {} + + //PrecacheParticleSystem( SHIELD_FX ) + + level.spawnAsTitanSelected <- false + + AddPlayerFunc( Player_AddPlayer ) + AddCreatePilotCockpitCallback( ClearDisembarkHint ) +} + +entity function FindEnemyRodeoParent( entity player ) +{ + entity ent = player.GetParent() + if ( ent == null ) + return null + + if ( !ent.IsTitan() ) + return null + + if ( ent == player.GetPetTitan() ) + return null + + if ( ent.GetTeam() == player.GetTeam() ) + return null + + return ent +} + +void function SpClientPlayerInit( entity player ) +{ + player.ClientCommand( "clear_loading_progress_detente" ) + player.ClientCommand( "clear_loading_progress_sp_text" ) +} + +void function ClientCodeCallback_PlayerSpawned( entity player ) +{ + if ( !IsValid( player ) ) + return + + // exists on server and client. Clear it when you respawn. + ClearRecentDamageHistory( player ) + + if ( player == GetLocalViewPlayer() ) + { + foreach ( callbackFunc in clGlobal.onLocalViewPlayerSpawnedCallbacks ) + { + callbackFunc( player ) + } + } + + if ( player == GetLocalClientPlayer() ) + { + foreach ( callbackFunc in clGlobal.onLocalClientPlayerSpawnedCallbacks ) + { + thread callbackFunc( player ) + } + } +} + + +void function CoreReadyMessage( entity player, bool isQuick = false ) +{ + if ( !GamePlayingOrSuddenDeath() ) + return + + if ( !IsAlive( player ) ) + return + + if ( GetDoomedState( player ) ) + return + + entity weapon = player.GetOffhandWeapon( OFFHAND_EQUIPMENT ) + + if ( weapon == null ) // JFS + return + + string coreOnlineMessage = expect string( weapon.GetWeaponInfoFileKeyField( "readymessage" ) ) + string coreOnlineHint = expect string( weapon.GetWeaponInfoFileKeyField( "readyhint" ) ) + + if ( isQuick ) + AnnouncementMessageQuick( player, coreOnlineMessage, coreOnlineHint, TEAM_COLOR_YOU ) + else + AnnouncementMessage( player, coreOnlineMessage, coreOnlineHint, TEAM_COLOR_YOU ) +} + +void function ClientPilotSpawned( entity player ) +{ + player.EndSignal( "SettingsChanged" ) + player.EndSignal( "OnDestroy" ) + player.EndSignal( "OnDeath" ) + + if ( player != GetLocalViewPlayer() ) + thread ParentedPlayerJets( player ) +} + +void function Player_AddClient( entity player ) +{ + if ( GetCurrentPlaylistVarInt( "titan_mode_change_allowed", 1 ) ) + RegisterConCommandTriggeredCallback( "+scriptCommand2", Pressed_TitanNextMode ) + + RegisterConCommandTriggeredCallback( "+scriptCommand2", Pressed_RequestTitanfall ) + RegisterConCommandTriggeredCallback( "+scriptCommand2", Pressed_ActivateMobilityGhost ) + + RegisterConCommandTriggeredCallback( "titan_loadout_select", Pressed_TitanLoadoutSelect ) + RegisterConCommandTriggeredCallback( "scoreboard_focus", Pressed_TitanLoadoutSelect ) + RegisterConCommandTriggeredCallback( "scoreboard_toggle_focus", Pressed_TitanLoadoutSelect ) + + Create_DamageIndicatorHUD() + + if ( !IsLobby() ) + { + player.EnableHealthChangedCallback() + + player.cv.deathTime <- 0.0 + player.cv.lastSpawnTime <- 0.0 + player.cv.deathOrigin <- <0.0, 0.0, 0.0> + player.cv.roundSpawnCount <- 0 + + thread CinematicIntroScreen() + } +} + +void function Player_AddPlayer( entity player ) +{ + player.s.weaponUpdateData <- {} + + player.s.trackedAttackers <- {} // for titans + player.classChanged = true +} + +function Pressed_RequestTitanfall( entity player ) +{ + //we are not using titan call-ins in SP for this game so lets disable it since it was still playing sounds and doing all the logic. + const DISABLE_FOR_R2 = true + if ( DISABLE_FOR_R2 ) + return + + if ( player.IsTitan() ) + return + + if ( !IsAlive( player ) ) + return + + if ( player.IsPhaseShifted() ) + return + + if ( player.GetRemoteTurret() != null ) + return + + if ( !IsAlive( player.GetPetTitan() ) ) + { + if ( Riff_TitanQueueLimit() > 0 ) + { + local queuePosition = player.nv.titanQueueNum + if ( queuePosition < 0 || queuePosition >= Riff_TitanQueueLimit() ) + { + player.ClientCommand( "RequestTitanQueueToggle" ) + return + } + } + + #if DEV + printt( player.GetEntIndex(), "Requested replacement Titan from eye pos " + player.EyePosition() + " view angles " + player.EyeAngles() + " player origin " + player.GetOrigin() + " map " + GetMapName() ) + #endif + + player.ClientCommand( "ClientCommand_RequestTitan" ) //Send client command regardless of whether we can call the titan in or not. Server decides + + if ( clGlobal.isAnnouncementActive && clGlobal.activeAnnouncement.messageText == "#HUD_TITAN_READY" ) + { + clGlobal.levelEnt.Signal( "AnnoucementPurge" ) + } + + //PlayMusic( "Music_FR_Militia_TitanFall1" ) + EmitSoundOnEntity( player, "titan_callin" ) + return + } +} + +function Pressed_TitanNextMode( entity player ) +{ + if ( player.IsTitan() ) + return + + if ( IsWatchingReplay() ) + return + + if ( !IsAlive( player ) ) + return + + if ( player.IsPhaseShifted() ) + return + + if ( !IsAlive( player.GetPetTitan() ) ) + return + + if ( !Flag( "EnableTitanModeChange" ) ) + return + + // cannot change modes while titan is incoming + if ( player.GetHotDropImpactTime() ) + return + + player.ClientCommand( "TitanNextMode" ) + + local newMode = player.GetPetTitanMode() + 1 + if ( newMode == eNPCTitanMode.MODE_COUNT ) + newMode = eNPCTitanMode.FOLLOW + + SetAutoTitanModeHudIndicator( player, newMode ) + + local guardModeAlias = GenerateTitanOSAlias( player, AUTO_TITAN_GUARD_MODE_DIAG_SUFFIX ) + local followModeAlias = GenerateTitanOSAlias( player, AUTO_TITAN_FOLLOW_MODE_DIAG_SUFFIX ) + + // prevent the sounds from stomping each other if button is pressed rapidly + StopSoundOnEntity( player, guardModeAlias ) + StopSoundOnEntity( player, AUTO_TITAN_GUARD_MODE_SOUND ) + StopSoundOnEntity( player, followModeAlias ) + StopSoundOnEntity( player, AUTO_TITAN_FOLLOW_MODE_SOUND ) + + if ( newMode == eNPCTitanMode.FOLLOW ) + { + EmitSoundOnEntity( player, followModeAlias ) + EmitSoundOnEntity( player, AUTO_TITAN_FOLLOW_MODE_SOUND ) + } + else if ( newMode == eNPCTitanMode.STAY ) + { + EmitSoundOnEntity( player, guardModeAlias ) + EmitSoundOnEntity( player, AUTO_TITAN_GUARD_MODE_SOUND ) + } +} + +/* +void function ClientCodeCallback_OnHudReloadScheme() +{ +} +*/ + + + +function Pressed_TitanLoadoutSelect( entity player ) +{ + if ( !player.IsTitan() ) + return + + if ( !IsAlive( player ) ) + return + + int ceFlags = player.GetCinematicEventFlags() + + // disable this menu in titan 3P + if ( ceFlags & CE_FLAG_TITAN_3P_CAM ) + return + + // One day, turn these back on if we allow player to set BT's loadout while disembarked + // + // if ( player.IsPhaseShifted() ) + // return + + // if ( player.GetRemoteTurret() != null ) + // return + + // if ( !IsAlive( player.GetPetTitan() ) ) + // return + + + RunUIScript( "OpenSPTitanLoadoutMenu" ) +} + + + +void function ClientCodeCallback_HUDThink() +{ + PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink ) + + entity player = GetLocalViewPlayer() + + if ( !player.p.playerScriptsInitialized ) + { + PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink ) + return + } + + if ( !IsMenuLevel() ) + { + PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink_4 ) + + PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink_4 ) + + PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink_5 ) + UpdateVoiceHUD() + PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink_5 ) + + UpdateScreenFade() + + entity clientPlayer = GetLocalClientPlayer() + if ( !IsWatchingReplay() && clientPlayer.classChanged ) + ClientPlayerClassChanged( clientPlayer, clientPlayer.GetPlayerClass() ) + + PerfStart( PerfIndexClient.ClientCodeCallback_HUDThink_6 ) + SmartAmmo_LockedOntoWarningHUD_Update() + WeaponFlyoutThink( player ) + PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink_6 ) + } + + PerfEnd( PerfIndexClient.ClientCodeCallback_HUDThink ) +} + +function ClientPlayerClassChanged( entity player, newClass ) +{ + //printl( "ClientPlayerClassChanged to " + player.GetPlayerClass() ) + player.classChanged = false + + level.vduOpen = false // vdu goes away when class changes + + Assert( !IsServer() ) + Assert( newClass, "No class " ) + + ResetAbilityBindings( player, expect string( newClass ) ) +} + +void function ResetAbilityBindings( entity player, string newClass ) +{ + switch ( newClass ) + { + case "titan": + SetStandardAbilityBindingsForTitan( player ) + SetAbilityBinding( player, 6, "weaponSelectOrdnance", "weaponSelectOrdnance" ) // "+ability 6" + + if ( IsConversationPlaying() ) + SetAbilityBinding( player, 1, "+scriptCommand2", "-scriptCommand2" ) // "+ability 1" + + LinkButtonPair( -1, -1, -1 ) + + break + + case level.pilotClass: + SetStandardAbilityBindingsForPilot( player ) + SetAbilityBinding( player, 6, "weaponSelectOrdnance", "weaponSelectOrdnance" ) // "+ability 6" + + LinkButtonPair( IN_OFFHAND0, IN_OFFHAND1, IN_OFFHAND3 ) + + if ( clGlobal.isAnnouncementActive && (clGlobal.activeAnnouncement.messageText == "#HUD_CORE_ONLINE_STRYDER" || + clGlobal.activeAnnouncement.messageText == "#HUD_CORE_ONLINE_ATLAS" || + clGlobal.activeAnnouncement.messageText == "#HUD_CORE_ONLINE_OGRE" ) ) + { + clGlobal.levelEnt.Signal( "AnnoucementPurge" ) + } + break + + case "spectator": + LinkButtonPair( -1, -1, -1 ) + break + + default: + Assert( 0, "Unknown class " + newClass ) + } +} + +function ShouldShowSpawnAsTitanHint( entity player ) +{ + if ( Time() - player.cv.deathTime < GetRespawnButtonCamTime( player ) ) + return false + + if ( GetGameState() < eGameState.Playing ) + return false + + if ( GetGameState() == eGameState.SwitchingSides ) + return false + + return !IsPlayerEliminated( player ) +} + +function ServerCallback_PlayerChangedTeams( player_eHandle, oldTeam, newTeam ) +{ +} + +function ServerCallback_PlayerConnectedOrDisconnected( player_eHandle, state ) +{ + entity player = GetEntityFromEncodedEHandle( player_eHandle ) + PlayerConnectedOrDisconnected( player, state ) + + if ( !IsLobby() || !IsConnected() ) + UpdatePlayerStatusCounts() +} + +void function AddCallback_OnPlayerDisconnected( void functionref( entity ) callbackFunc ) +{ + Assert( !clGlobal.onPlayerDisconnectedFuncs.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnPlayerDisconnected" ) + + clGlobal.onPlayerDisconnectedFuncs.append( callbackFunc ) +} + +void function ClientCodeCallback_PlayerDisconnected( entity player, string cachedPlayerName ) +{ + PlayerConnectedOrDisconnected( player, 0, cachedPlayerName ) + + if ( ShouldUpdatePlayerStatusCounts() ) + UpdatePlayerStatusCounts() + + // Added via AddCallback_OnPlayerDisconnected + foreach ( callbackFunc in clGlobal.onPlayerDisconnectedFuncs ) + { + callbackFunc( player ) + } +} + +function ShouldUpdatePlayerStatusCounts() +{ + if ( GetGameState() < eGameState.WaitingForPlayers ) + return false + + if ( !IsLobby() ) + return true + + if ( !IsConnected() ) + return true + + return false +} + +function PlayerConnectedOrDisconnected( entity player, state, string disconnectingPlayerName = "" ) +{ +} + +void function ClientCodeCallback_PlayerDidDamage( PlayerDidDamageParams params ) +{ + entity attacker = GetLocalViewPlayer() + if ( !IsValid( attacker ) ) + return + + entity victim = params.victim + if ( !IsValid( victim ) ) + return + + vector damagePosition = params.damagePosition + int hitBox = params.hitBox + int damageType = params.damageType + float damageAmount = params.damageAmount + int damageFlags = params.damageFlags + int hitGroup = params.hitGroup + entity weapon = params.weapon + float distanceFromAttackOrigin = params.distanceFromAttackOrigin + + bool playHitSound = true + bool showCrosshairHitIndicator = true + bool hitIneffective = false + bool victimIsHeavyArmor = false + bool isCritShot = (damageType & DF_CRITICAL) ? true : false + bool isHeadShot = (damageType & DF_HEADSHOT) ? true : false + bool isKillShot = (damageType & DF_KILLSHOT) ? true : false + bool isMelee = (damageType & DF_MELEE) ? true : false + bool isExplosion = (damageType & DF_EXPLOSION) ? true : false + bool isBullet = (damageType & DF_BULLET) ? true : false + bool isShotgun = (damageType & DF_SHOTGUN) ? true : false + bool isDoomFatality = (damageType & DF_DOOM_FATALITY) ? true : false + bool isDoomProtected = ((damageType & DF_DOOM_PROTECTED) && !isDoomFatality) ? true : false + victimIsHeavyArmor = victim.GetArmorType() == ARMOR_TYPE_HEAVY + + isDoomFatality = false + isDoomProtected = false + + if ( isDoomProtected ) + RegisterDoomProtectionHintDamage( damageAmount ) + + bool playKillSound = isKillShot + + if ( !attacker.IsTitan() ) + { + if ( victimIsHeavyArmor ) + { + showCrosshairHitIndicator = true + if ( victim.IsTitan() ) + hitIneffective = false //!IsHitEffectiveVsTitan( victim, damageType ) + else + hitIneffective = isCritShot || isHeadShot || !IsHitEffectiveVsNonTitan( victim, damageType ) + } + else + { + switch ( victim.GetSignifierName() ) + { + case "npc_super_spectre": + //if ( !( damageType & DF_CRITICAL ) ) + // hitIneffective = true + + default: + if ( (damageType & DF_BULLET && damageType & DF_MAX_RANGE) ) + hitIneffective = true + break + } + } + } + else + { + if ( victim.IsTitan() && victim.IsPlayer() ) + { + if ( PlayerHasPassive( victim, ePassives.PAS_BERSERKER ) ) + hitIneffective = true + } + } + + if ( damageType & DF_MAX_RANGE && damageType & DF_BULLET ) + // TODO: this is crap; these damage types should just send DF_NO_HITBEEP + playHitSound = false + + if ( damageType & DF_TITAN_STEP ) + // TODO: this is crap; these damage types should just send DF_NO_HITBEEP + { + playHitSound = false + playKillSound = false + } + + if ( damageType & DF_MELEE ) + // TODO: this is crap; these damage types should just send DF_NO_HITBEEP + { + playHitSound = false + playKillSound = false + } + + if ( damageType & DF_NO_HITBEEP ) + { + playHitSound = false + playKillSound = false + } + + if ( damageFlags & DAMAGEFLAG_VICTIM_HAS_VORTEX ) + showCrosshairHitIndicator = false + + if ( damageType & DF_SHIELD_DAMAGE ) + { + PlayShieldHitEffect( params ) + showCrosshairHitIndicator = true + } + else if ( damageAmount <= 0 ) + { + playHitSound = false + playKillSound = false + showCrosshairHitIndicator = false + } + + if ( damageType & DF_NO_INDICATOR ) + { + playHitSound = false + playKillSound = false + showCrosshairHitIndicator = false + } + + if ( isDoomProtected ) + playHitSound = false + + if ( showCrosshairHitIndicator ) + { + Tracker_PlayerAttackedTarget( attacker, victim ) + + //if ( hitIneffective ) + // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_INEFFECTIVE ) + //else + // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_NORMAL ) + // + //if ( (isCritShot || isDoomFatality) && !isDoomProtected ) + // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_CRITICAL ) + // + //if ( isHeadShot ) + // Crosshair_ShowHitIndicator( CROSSHAIR_HIT_HEADSHOT ) + + if ( IsMultiplayer() && !victim.IsTitan() && !victim.IsHologram() ) + PROTO_HitIndicatorEffect( attacker, victim, damagePosition, isHeadShot, isKillShot ) + + if ( isKillShot ) + KillShotBloodSpray( attacker, victim, damagePosition, isExplosion, isBullet, isShotgun ) + + if ( victim.IsTitan() && isKillShot ) + ClientScreenShake( 8, 10, 1, Vector( 0, 0, 0 ) ) + + BloodSprayDecals( attacker, victim, damagePosition, damageAmount, isHeadShot, isKillShot, isMelee, isExplosion, isBullet, isShotgun ) + + DamageFlyout( damageAmount, damagePosition, victim, isHeadShot || isCritShot, hitIneffective ) + } + + bool playedHitSound = false + if ( playHitSound ) + { + if ( isHeadShot ) + playedHitSound = PlayHeadshotConfirmSound( attacker, victim, isKillShot ) + else if ( playKillSound ) + playedHitSound = PlayKillshotConfirmSound( attacker, victim, damageType ) + } + + if ( IsSpectre( victim ) ) + { + if ( isHeadShot ) + victim.Signal( "SpectreGlowEYEGLOW" ) + } + + // Play a hit sound effect if we didn't play a kill shot sound, and other conditions are met + if ( playHitSound && IsAlive( victim ) && !playedHitSound ) + { + PlayHitSound( victim, attacker, damageFlags, isCritShot, victimIsHeavyArmor, isKillShot, hitGroup ) + } + + if ( PlayerHasPassive( attacker, ePassives.PAS_SMART_CORE ) && isKillShot ) + { + attacker.p.smartCoreKills++ + } + + foreach ( callback in clGlobal.onLocalPlayerDidDamageCallback ) + { + callback( attacker, victim, damagePosition, damageType ) + } +} + +void function PlayHitSound( entity victim, entity attacker, int damageFlags, bool isCritShot, bool victimIsHeavyArmor, bool isKillShot, int hitGroup ) +{ + if ( damageFlags & DAMAGEFLAG_VICTIM_INVINCIBLE ) + { + EmitSoundOnEntity( attacker, "Player.HitbeepInvincible" ) + } + else if ( damageFlags & DAMAGEFLAG_VICTIM_HAS_VORTEX ) + { + EmitSoundOnEntity( attacker, "Player.HitbeepVortex" ) + } + else if ( isCritShot && victimIsHeavyArmor ) + { + EmitSoundOnEntity( attacker, "titan_damage_crit" ) + } + else if ( isCritShot ) + { + EmitSoundOnEntity( attacker, "Player.Hitbeep_crit" ) + } + else + { + EmitSoundOnEntity( attacker, "Player.Hitbeep" ) + } +} + +function PROTO_HitIndicatorEffect( entity player, entity victim, vector damagePosition, bool isHeadShot, bool isKillShot ) +{ + int fxId + if ( isKillShot ) + fxId = GetParticleSystemIndex( $"P_ar_impact_pilot_kill" ) + else if ( isHeadShot && !isKillShot ) + return + // fxId = GetParticleSystemIndex( $"P_ar_impact_pilot_headshot" ) + else + return + // fxId = GetParticleSystemIndex( $"P_ar_impact_pilot" ) + + vector victimVelocity = victim.GetVelocity() + damagePosition += (Length( victimVelocity ) * 0.15) * Normalize( victimVelocity ) + vector fxOffset = damagePosition - victim.GetOrigin() + StartParticleEffectOnEntityWithPos( victim, fxId, FX_PATTACH_ABSORIGIN_FOLLOW, -1, damagePosition - victim.GetOrigin(), <0, 0, 0> ) +} + +void function KillShotBloodSpray( entity player, entity victim, vector damagePosition, bool isExplosion, bool isBullet, bool isShotgun ) +{ + if ( IsSoftenedLocale() ) + return + + if ( !victim.IsHuman() && !IsProwler( victim ) ) + return + + if ( victim.IsMechanical() ) + return + + if ( victim.IsHologram() ) + return + + if ( !isExplosion && !isBullet && !isShotgun ) + return + + int fxId = GetParticleSystemIndex( FX_KILLSHOT_BLOODSPRAY ) + + vector victimVelocity = victim.GetVelocity() + damagePosition += (Length( victimVelocity ) * 0.15) * Normalize( victimVelocity ) + StartParticleEffectOnEntityWithPos( victim, fxId, FX_PATTACH_ABSORIGIN_FOLLOW, -1, damagePosition - victim.GetOrigin(), <0, 0, 0> ) +} + +void function BloodSprayDecals( entity player, entity victim, vector damagePosition, float damageAmount, bool isHeadShot, bool isKillShot, bool isMelee, bool isExplosion, bool isBullet, bool isShotgun ) +{ + if ( IsSoftenedLocale() || !Flag( "EnableBloodSprayDecals" ) ) + return + + if ( !victim.IsHuman() && !IsProwler( victim ) ) + return + + if ( victim.IsMechanical() ) + return + + if ( victim.IsHologram() ) + return + + if ( !isMelee && !isExplosion && !isBullet && !isShotgun ) + return + + // in MP, too expensive to do on every shot + if ( IsMultiplayer() && !isKillShot ) + return + + thread BloodSprayDecals_Think( player, victim, damagePosition, damageAmount, isHeadShot, isKillShot, isMelee, isExplosion, isBullet, isShotgun ) +} + +void function BloodSprayDecals_Think( entity player, entity victim, vector damagePosition, float damageAmount, bool isHeadShot, bool isKillShot, bool isMelee, bool isExplosion, bool isBullet, bool isShotgun ) +{ + player.EndSignal( "OnDestroy" ) + victim.EndSignal( "OnDestroy" ) + + BloodDecalParams params = BloodDecal_GetParams( damageAmount, isHeadShot, isKillShot, isMelee, isExplosion, isBullet, isShotgun ) + float traceDist = params.traceDist + float secondaryTraceDist = params.secondaryTraceDist + asset fxType = params.fxType + asset secondaryFxType = params.secondaryFxType + + int fxId = GetParticleSystemIndex( fxType ) + + // PRIMARY TRACES + vector traceStart = damagePosition + vector traceFwd = player.GetViewVector() + + if ( isExplosion || isMelee ) + { + // for explosion/melee damage, use chest instead of actual damage position + int attachID = victim.LookupAttachment( "CHESTFOCUS" ) + traceStart = victim.GetAttachmentOrigin( attachID ) + + if ( isExplosion ) + traceFwd = AnglesToForward( victim.GetAngles() ) * -1 + } + + vector traceEnd = damagePosition + (traceFwd * traceDist) + //TraceResults traceResult = TraceLine( traceStart, traceEnd, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) + + var trace_primary = DeferredTraceLineHighDetail( traceStart, traceEnd, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) + WaitFrame() + TraceResults traceResult = GetDeferredTraceResult( trace_primary ) + + vector primaryTraceEndPos = traceResult.endPos + vector primaryTraceNormal = traceResult.surfaceNormal + //DebugDrawLine( traceStart, traceEnd, 255, 150, 0, true, 5 ) + //DebugDrawSphere( primaryTraceEndPos, 8.0, 255, 0, 0, true, 5 ) + + bool doGravitySplat = isMelee ? false : true + + if ( traceResult.fraction < 1.0 ) + { + vector normAng = VectorToAngles( traceResult.surfaceNormal ) + vector fxAng = AnglesCompose( normAng, < 90, 0, 0 > ) + + StartParticleEffectInWorld( fxId, primaryTraceEndPos, fxAng ) + //DebugDrawAngles( endPos, fxAng, 5 ) + } + else if ( doGravitySplat ) + { + // trace behind the guy on the ground and put a decal there + float gravitySplatBackTraceDist = 58.0 // how far behind the guy to put the gravity splat + float gravitySplatDownTraceDist = 100.0 // max dist vertically to try to trace and put a gravity splat + vector groundTraceStartPos = damagePosition + (traceFwd * gravitySplatBackTraceDist) + vector groundTraceEndPos = groundTraceStartPos - <0, 0, 100> + + var trace_gravitySplat = DeferredTraceLineHighDetail( groundTraceStartPos, groundTraceEndPos, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) + WaitFrame() + TraceResults downTraceResult = GetDeferredTraceResult( trace_gravitySplat ) + + if ( downTraceResult.fraction < 1.0 ) + { + //DebugDrawLine( groundTraceStartPos, downTraceResult.endPos, 255, 150, 0, true, 5 ) + //DebugDrawSphere( downTraceResult.endPos, 4.0, 255, 0, 0, true, 5 ) + + vector normAng = VectorToAngles( downTraceResult.surfaceNormal ) + vector fxAng = AnglesCompose( normAng, < 90, 0, 0 > ) + + //DebugDrawAngles( downTraceResult.endPos, fxAng, 5 ) + + StartParticleEffectInWorld( fxId, downTraceResult.endPos, fxAng ) + } + } + + // MP doesn't want secondaries, too expensive + if ( IsMultiplayer() ) + return + + // SECONDARY TRACES + array testVecs = [] + vector tempAng = VectorToAngles( traceFwd ) + + if ( isExplosion ) + { + // for explosions, different & more angles for secondary splatter + testVecs.append( AnglesToRight( tempAng ) ) + testVecs.append( AnglesToRight( tempAng ) * -1 ) + testVecs.append( traceFwd * -1 ) + testVecs.append( AnglesToUp( tempAng ) ) + testVecs.append( AnglesToUp( tempAng ) * -1 ) + } + else + { + // mostly to cover edge cases involving corners + vector traceRight = AnglesToRight( tempAng ) + vector traceLeft = traceRight * -1 + vector backLeft = (traceFwd + traceLeft) * 0.5 + vector backRight = (traceFwd + traceRight) * 0.5 + testVecs.append( backRight ) + testVecs.append( backLeft ) + + // add blood on the ground for these weapons too + if ( isBullet || isShotgun ) + testVecs.append( AnglesToUp( tempAng ) * -1 ) + } + + if ( !testVecs.len() ) + return + + array secondaryTraces = [] + foreach ( testVec in testVecs ) + { + vector secondaryTraceEnd = traceStart + (testVec * secondaryTraceDist) + var secondaryTrace = DeferredTraceLineHighDetail( traceStart, secondaryTraceEnd, victim, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE ) + secondaryTraces.append( secondaryTrace ) + } + + int secondaryFxId = GetParticleSystemIndex( secondaryFxType ) + + float startTime = Time() + array processedResults = [] + while ( processedResults.len() < secondaryTraces.len() ) + { + WaitFrame() + + foreach ( trace in secondaryTraces ) + { + if ( processedResults.contains( trace ) ) + continue + + if ( !IsDeferredTraceFinished( trace ) ) + continue + + processedResults.append( trace ) + + TraceResults traceResult = GetDeferredTraceResult( trace ) + + if ( traceResult.fraction == 1.0 ) + continue + + // don't put secondaries on the same wall as the primary + vector secondaryTraceNormal = traceResult.surfaceNormal + if ( primaryTraceNormal == secondaryTraceNormal ) + continue + + vector normAng = VectorToAngles( secondaryTraceNormal ) + vector fxAng = AnglesCompose( normAng, < 90, 0, 0 > ) + + vector endPos = traceResult.endPos + //DebugDrawSphere( endPos, 4.0, 255, 0, 0, true, 5 ) + StartParticleEffectInWorld( secondaryFxId, endPos, fxAng ) + } + + // timeout if traces aren't returning + if ( Time() - startTime >= 0.3 ) + return + } +} + +BloodDecalParams function BloodDecal_GetParams( float damageAmount, bool isHeadShot, bool isKillShot, bool isMelee, bool isExplosion, bool isBullet, bool isShotgun ) +{ + // default: bullet damage + float traceDist = 175 + float secondaryTraceDist = 100 + asset fxType = FX_BLOODSPRAY_DECAL_SML + asset secondaryFxType = FX_BLOODSPRAY_DECAL_SML + + if ( isBullet ) + { + // HACK- shotguns report isBullet also + if ( isShotgun ) + { + //if ( isKillShot ) + // fxType = FX_BLOODSPRAY_DECAL_LRG + //else + fxType = FX_BLOODSPRAY_DECAL_MED + } + else + { + if ( isKillShot ) + fxType = FX_BLOODSPRAY_DECAL_MED + else + fxType = FX_BLOODSPRAY_DECAL_SML + + if ( damageAmount >= 200 ) + { + traceDist = 216 + fxType = FX_BLOODSPRAY_DECAL_LRG + secondaryFxType = FX_BLOODSPRAY_DECAL_MED + } + } + } + + else if ( isExplosion ) + { + secondaryTraceDist = traceDist + + float maxDmg = 300 + float medDmg = 200 + if ( IsMultiplayer() ) + { + maxDmg = 100 + medDmg = 75 + } + + if ( damageAmount >= maxDmg ) + { + fxType = FX_BLOODSPRAY_DECAL_LRG + secondaryFxType = FX_BLOODSPRAY_DECAL_LRG + } + else if ( damageAmount >= medDmg ) + { + fxType = FX_BLOODSPRAY_DECAL_LRG + secondaryFxType = FX_BLOODSPRAY_DECAL_MED + } + else if ( isKillShot ) + { + fxType = FX_BLOODSPRAY_DECAL_MED + secondaryFxType = FX_BLOODSPRAY_DECAL_MED + } + } + + else if ( isMelee ) + { + traceDist = 96 + + if ( isKillShot ) + fxType = FX_BLOODSPRAY_DECAL_MED + } + + // for kills, increase trace distance a bit + if ( isKillShot ) + { + traceDist = traceDist + (traceDist * 0.1) + secondaryTraceDist = secondaryTraceDist + (secondaryTraceDist * 0.1) + } + + BloodDecalParams params + params.traceDist = traceDist + params.secondaryTraceDist = secondaryTraceDist + params.fxType = fxType + params.secondaryFxType = secondaryFxType + return params +} + +#if DEV +string function BloodSprayDecals_Toggle() +{ + string returnStr = "" + + if ( Flag( "EnableBloodSprayDecals" ) ) + { + FlagClear( "EnableBloodSprayDecals" ) + returnStr = "Blood spray decals DISABLED" + } + else + { + FlagSet( "EnableBloodSprayDecals" ) + returnStr = "Blood spray decals ENABLED" + } + + return returnStr +} +#endif + +function ServerCallback_RodeoerEjectWarning( soulHandle, ejectTime ) +{ + entity soul = GetEntityFromEncodedEHandle( soulHandle ) + + if ( !IsValid( soul ) ) + return + + thread TitanEjectHatchSequence( soul, ejectTime ) +} + +function TitanEjectHatchSequence( soul, ejectTime ) +{ + expect entity( soul ) + + soul.EndSignal( "OnSoulTransfer" ) + soul.EndSignal( "OnTitanDeath" ) + soul.EndSignal( "OnDestroy" ) + + local effects = [] + + OnThreadEnd( + function() : ( effects ) + { + foreach ( effect in effects ) + { + if ( !EffectDoesExist( effect ) ) + continue + + EffectStop( effect, true, true ) + } + } + ) + + int boltCount = 6 + int fxID = GetParticleSystemIndex( $"xo_spark_bolt" ) + + for ( int index = 0; index < boltCount; index++ ) + { + entity titan = soul.GetTitan() + + WaitEndFrame() // so OnTitanDeath/Destroy can happen + + if ( !IsAlive( titan ) ) + return + + if ( !titan.IsTitan() ) + { + printt( "WARNING: " + titan + " is not a Titan!" ) + return + } + + int attachID = titan.LookupAttachment( "HATCH_BOLT" + (index + 1) ) + //printt( "attachID is " + attachID ) + vector boltOrgin = titan.GetAttachmentOrigin( attachID ) + vector boltAngles = titan.GetAttachmentAngles( attachID ) + vector launchVec = AnglesToForward( boltAngles ) * 500 + + CreateClientsideGib( $"models/industrial/bolt_tiny01.mdl", boltOrgin, boltAngles, launchVec, < 0, 0, 0 >, 3.0, 1000.0, 200.0 ) + int effect = PlayFXOnTag( titan, fxID, attachID ) + effects.append( effect ) + EmitSoundOnEntity( titan, "titan_bolt_loose" ) + + wait (ejectTime / boltCount) + } +} + +function ServerCallback_OnEntityKilled( attackerEHandle, victimEHandle, scriptDamageType, damageSourceId ) +{ + expect int( damageSourceId ) + + local isHeadShot = scriptDamageType & DF_HEADSHOT + + entity victim = GetEntityFromEncodedEHandle( victimEHandle ) + entity attacker = attackerEHandle ? GetHeavyWeightEntityFromEncodedEHandle( attackerEHandle ) : null + entity localClientPlayer = GetLocalClientPlayer() + + if ( !IsValid( victim ) ) + return + + Signal( victim, "OnDeath" ) + + if ( damageSourceId == eDamageSourceId.indoor_inferno ) + { + if ( victim == localClientPlayer ) + thread PlayerFieryDeath( victim ) + } + + if ( victim.IsPlayer() && victim != attacker ) + { + if ( attacker == localClientPlayer ) + { + EmitSoundOnEntity( attacker, "Pilot_Killed_Indicator" ) + } + else if ( IsValid( attacker ) && attacker.IsTitan() ) + { + entity bossPlayer = attacker.GetBossPlayer() + if ( bossPlayer && bossPlayer == localClientPlayer ) + EmitSoundOnEntity( bossPlayer, "Pilot_Killed_Indicator" ) + } + } +} + +void function ServerCallback_OnTitanKilled( int attackerEHandle, int victimEHandle, int scriptDamageType, int damageSourceId ) +{ +} + +function PlayTargetEliminatedTitanVO( attacker, victim ) +{ + entity localPlayer = GetLocalViewPlayer() + + if ( attacker != localPlayer ) + return + + if ( !victim.IsPlayer() ) + return + + if ( victim.IsTitan() ) + { + // a bit more delay for a titan explosion to clear + thread TitanCockpit_PlayDialogDelayed( localPlayer, 1.3, "elimTarget" ) + } + else + { + thread TitanCockpit_PlayDialogDelayed( localPlayer, 0.8, "elimEnemyPilot" ) + } +} + +function ServerCallback_SetAssistInformation( damageSourceId, attackerEHandle, entityEHandle, assistTime ) +{ + local ent = GetEntityFromEncodedEHandle ( entityEHandle ) + if ( !ent ) + return + + local latestAssistPlayer = GetEntityFromEncodedEHandle ( attackerEHandle ) + if ( !("latestAssistPlayer" in ent.s) ) + { + ent.s.latestAssistPlayer <- latestAssistPlayer + ent.s.latestAssistDamageSource <- damageSourceId + ent.s.latestAssistTime <- assistTime + } + else + { + ent.s.latestAssistPlayer = latestAssistPlayer + ent.s.latestAssistDamageSource = damageSourceId + ent.s.latestAssistTime = assistTime + } +} + +/* +void function ClientCodeCallback_OnModelChanged( entity ent ) +{ + // OnModelChanged gets called for each model change, but gets processed after the model has done all switches + if ( !IsValid( ent ) ) + return; + + if ( !("creationCount" in ent.s) ) + return; + + Assert( ent instanceof C_BaseAnimating ); +} +*/ + + +void function ClientCodeCallback_OnHealthChanged( entity ent, int oldHealth, int newHealth ) +{ + if ( IsLobby() ) + return + + entity player = GetLocalViewPlayer() + if ( !IsValid( player ) ) + return + + if ( !IsValid( ent ) ) + return + + ent.Signal( "HealthChanged", { oldHealth = oldHealth, newHealth = newHealth } ) +} + +// Northstar +void function AddCallback_OnCrosshairCurrentTargetChanged( void functionref( entity, entity ) callbackFunc ) +{ + Assert( !file.OnCrosshairCurrentTargetChangedCallbacks.contains( callbackFunc ), "Already added " + string( callbackFunc ) + " with AddCallback_OnCrosshairCurrentTargetChanged" ) + + file.OnCrosshairCurrentTargetChangedCallbacks.append( callbackFunc ) +} + +void function ClientCodeCallback_OnCrosshairCurrentTargetChanged( entity player, entity newTarget ) +{ + if ( IsLobby() ) + return; + if ( !IsValid( player ) ) + return + + foreach ( callback in file.OnCrosshairCurrentTargetChangedCallbacks ) + { + callback( player, newTarget ) + } +} + +void function SetupPlayerAnimEvents( entity player ) +{ + SetupPlayerJumpJetAnimEvents( player ) + AddAnimEvent( player, "WallHangAttachDataKnife", WallHangAttachDataKnife ) +} + +void function JumpRandomlyForever() +{ + for (;; ) + { + if ( IsWatchingReplay() ) + { + wait 1 + continue + } + + entity player = GetLocalClientPlayer() + if ( !IsAlive( player ) || player != GetLocalViewPlayer() ) + { + wait 1 + continue + } + + printt( "jump!" ) + player.ClientCommand( "+jump" ) + wait 0 + player.ClientCommand( "-jump" ) + + wait RandomFloatRange( 0.2, 1.1 ) + } +} + +void function RemoteTurretFadeoutAnimEvent( entity ent ) +{ + entity player = GetLocalViewPlayer() + ScreenFade( player, 0, 0, 0, 255, 0.1, 0.25, FFADE_OUT ); +} + +void function SetupFirstPersonProxyEvents( entity firstPersonProxy ) +{ + //printt( "SetupFirstPersonProxyEvents" ) + + AddAnimEvent( firstPersonProxy, "mantle_smallmantle", OnSmallMantle ) + AddAnimEvent( firstPersonProxy, "mantle_mediummantle", OnMediumMantle ) + AddAnimEvent( firstPersonProxy, "mantle_lowmantle", OnLowMantle ) + AddAnimEvent( firstPersonProxy, "mantle_extralowmantle", OnExtraLowMantle ) + AddAnimEvent( firstPersonProxy, "remoteturret_fadeout", RemoteTurretFadeoutAnimEvent ) +} + +void function OnSmallMantle( entity firstPersonProxy ) +{ + entity player = GetLocalViewPlayer() + + if ( PlayerHasPassive( player, ePassives.PAS_STEALTH_MOVEMENT ) ) + { + //printt( "mantle_smallmantle, has stealth passive" ) + } + else + { + //printt( "mantle_smallmantle, no stealth passive" ) + EmitSoundOnEntity( firstPersonProxy, "mantle_smallmantle" ) + } +} + +void function OnMediumMantle( entity firstPersonProxy ) +{ + entity player = GetLocalViewPlayer() + + if ( PlayerHasPassive( player, ePassives.PAS_STEALTH_MOVEMENT ) ) + { + //printt( "mantle_mediummantle, has stealth passive" ) + } + else + { + //printt( "mantle_mediummantle, no stealth passive" ) + EmitSoundOnEntity( firstPersonProxy, "mantle_mediummantle" ) + } +} + +void function OnLowMantle( entity firstPersonProxy ) +{ + entity player = GetLocalViewPlayer() + + if ( PlayerHasPassive( player, ePassives.PAS_STEALTH_MOVEMENT ) ) + { + //printt( "mantle_lowmantle, has stealth passive" ) + } + else + { + //printt( "mantle_lowmantle, no stealth passive" ) + EmitSoundOnEntity( firstPersonProxy, "mantle_lowmantle" ) + } +} + +void function OnExtraLowMantle( entity firstPersonProxy ) +{ + entity player = GetLocalViewPlayer() + + if ( PlayerHasPassive( player, ePassives.PAS_STEALTH_MOVEMENT ) ) + { + //printt( "mantle_extralowmantle, has stealth passive" ) + } + else + { + //printt( "mantle_extralowmantle, no stealth passive" ) + EmitSoundOnEntity( firstPersonProxy, "mantle_extralow" ) + } +} + +void function CreateCallback_TitanSoul( entity ent ) +{ +} + + +void function WallHangAttachDataKnife( entity player ) +{ + int attachIdx = player.LookupAttachment( "l_hand" ) + if ( attachIdx == 0 ) + // hack while i wait for the attachment to be fixed + return + + entity dataknife = CreateClientSidePropDynamic( player.GetAttachmentOrigin( attachIdx ), player.GetAttachmentAngles( attachIdx ), DATA_KNIFE_MODEL ) + dataknife.SetParent( player, "l_hand" ) + + thread DeleteDataKnifeAfterWallHang( player, dataknife ) +} + +void function DeleteDataKnifeAfterWallHang( entity player, entity dataknife ) +{ + OnThreadEnd( + function() : ( dataknife ) + { + if ( IsValid( dataknife ) ) + dataknife.Kill_Deprecated_UseDestroyInstead() + } + ) + + player.EndSignal( "OnDeath" ) + player.EndSignal( "OnDestroy" ) + + for (;; ) + { + Wait( 0.1 ) + if ( !player.IsWallHanging() ) + break + } +} + +bool function ClientCodeCallback_OnGib( entity victim, vector attackDir ) +{ + if ( !victim.IsMechanical() ) + return SpawnFleshGibs( victim, attackDir ) + + return false +} + +bool function SpawnFleshGibs( entity victim, vector attackDir ) +{ + asset modelName = $"models/gibs/human_gibs.mdl" + attackDir = Normalize( attackDir ) + + float cullDist = 2048.0 + if ( "gibDist" in victim.s ) + cullDist = expect float( victim.s.gibDist ) + + vector startOrigin = victim.GetWorldSpaceCenter() + (attackDir * -30) + + vector origin = victim.GetOrigin() + < RandomIntRange( 10, 20 ), RandomIntRange( 10, 20 ), RandomIntRange( 32, 64 ) > + vector angles = < 0, 0, 0 > + vector flingDir = attackDir * RandomIntRange( 80, 200 ) + + int fxID + bool isSoftenedLocale = IsSoftenedLocale() + + if ( isSoftenedLocale ) + { + if ( victim.GetModelName() == FLYER_MODEL ) + fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist_LG_nochunk" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) + else + fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist_nochunk" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) + } + else + { + if ( victim.GetModelName() == FLYER_MODEL ) + fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist_LG" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) + else + fxID = StartParticleEffectOnEntity( victim, GetParticleSystemIndex( $"death_pinkmist" ), FX_PATTACH_ABSORIGIN_FOLLOW, 0 ) + } + + EffectSetControlPointVector( fxID, 1, flingDir ) + + if ( isSoftenedLocale ) + return true + + vector angularVel = < 0, 0, 0 > + float lifeTime = 10.0 + CreateClientsideGibWithBodyGroupGibs( modelName, victim.GetOrigin(), angles, attackDir, angularVel, lifeTime, cullDist, 1024 ) + + return true +} + +function ServerCallback_PlayScreenFXWarpJump() +{ + if ( IsWatchingReplay() ) + return false + + thread PlayScreenFXWarpJump( GetLocalClientPlayer() ) +} + +void function PlayScreenFXWarpJump( entity clientPlayer ) +{ + clientPlayer.EndSignal( "OnDeath" ) + clientPlayer.EndSignal( "OnDestroy" ) + + entity player = GetLocalViewPlayer() + int index = GetParticleSystemIndex( SCREENFX_WARPJUMP ) + int indexD = GetParticleSystemIndex( SCREENFX_WARPJUMPDLIGHT ) + int fxID = StartParticleEffectInWorldWithHandle( index, < 0, 0, 0 >, < 0, 0, 0 > ) + int fxID2 = -1 + if ( IsValid( player.GetCockpit() ) ) + { + fxID2 = StartParticleEffectOnEntity( player, indexD, FX_PATTACH_POINT_FOLLOW, player.GetCockpit().LookupAttachment( "CAMERA" ) ) + EffectSetIsWithCockpit( fxID2, true ) + } + + OnThreadEnd( + function() : ( clientPlayer, fxID, fxID2 ) + { + if ( IsValid( clientPlayer ) && !IsAlive( clientPlayer ) ) + { + EffectStop( fxID, true, false ) + if ( fxID2 > -1 ) + EffectStop( fxID2, true, false ) + } + } + ) + + wait 2.5 + if ( IsValid( player.GetCockpit() ) ) + thread TonemappingUpdateAfterWarpJump() +} + +const TONEMAP_1_START_DURATION = 0.2 +const TONEMAP_1_MAX = 0 +const TONEMAP_1_MIN = 8 +const TONEMAP_2_START_DURATION = 1.0 +const TONEMAP_2_MAX = 8 +const TONEMAP_2_MIN = 4 +const TONEMAP_3_START_DURATION = 5 +const TONEMAP_3_MAX = 40 +const TONEMAP_3_MIN = 1 + +function TonemappingUpdateAfterWarpJump() +{ + AutoExposureSetMaxExposureMultiplier( 500 ); // allow exposure to actually go bright, even if it's clamped in the level. + + local startTime = Time() + while( 1 ) + { + local time = Time() - startTime + float factor = GraphCapped( time, 0, TONEMAP_1_START_DURATION, 1, 0 ) + factor = factor * factor * factor + local toneMapScale = TONEMAP_1_MIN + (TONEMAP_1_MAX - TONEMAP_1_MIN) * factor + AutoExposureSetExposureCompensationBias( toneMapScale ) + AutoExposureSnap() + wait 0 + if ( factor == 0 ) + break; + } + + startTime = Time() + while( 1 ) + { + local time = Time() - startTime + float factor = GraphCapped( time, 0, TONEMAP_2_START_DURATION, 1, 0 ) + local toneMapScale = TONEMAP_2_MIN + (TONEMAP_2_MAX - TONEMAP_2_MIN) * factor + AutoExposureSetExposureCompensationBias( toneMapScale ) + AutoExposureSnap() + wait 0 + if ( factor == 0 ) + break; + } + + AutoExposureSetExposureCompensationBias( 0 ) // clear the exposure bias and allow the exposure to adjust to its normal level at its own pace + + // Ramp the max exposure multiplier back down to 1 (preferably slower than the autoexposure would drive the brightness down) + startTime = Time() + while( 1 ) + { + local time = Time() - startTime + float factor = GraphCapped( time, 0, TONEMAP_3_START_DURATION, 1, 0 ) + local scale = TONEMAP_3_MIN + (TONEMAP_3_MAX - TONEMAP_3_MIN) * factor + AutoExposureSetMaxExposureMultiplier( scale ); + wait 0 + if ( factor == 0 ) + break; + } +} + +function SetPanelAlphaOverTime( panel, alpha, duration ) +{ + // HACK this should be a code command - Mackey + Signal( panel, "PanelAlphaOverTime" ) + EndSignal( panel, "PanelAlphaOverTime" ) + EndSignal( panel, "OnDestroy" ) + + local startTime = Time() + local endTime = startTime + duration + local startAlpha = panel.GetPanelAlpha() + + while( Time() <= endTime ) + { + float a = GraphCapped( Time(), startTime, endTime, startAlpha, alpha ) + panel.SetPanelAlpha( a ) + WaitFrame() + } + + panel.SetPanelAlpha( alpha ) +} + + +bool function ShouldShowOnDeckCard( entity player, cardOnDeck ) +{ + local inPrematch = level.nv.gameState == eGameState.Prematch + + if ( IsAlive( player ) && !IsWatchingReplay() && !inPrematch ) + return false + + if ( inPrematch && !cardOnDeck ) + return false + + return true +} + +function HandleDoomedState( entity player, entity titan ) +{ + bool isDoomed = GetDoomedState( titan ) + if ( isDoomed ) + { + titan.Signal( "Doomed" ) + + if ( HasSoul( titan ) ) + { + entity soul = titan.GetTitanSoul() + soul.Signal( "Doomed" ) + } + } +} + +function PlayShieldBreakEffect( entity ent ) +{ + entity shieldEnt = ent + if ( IsSoul( ent ) ) + { + shieldEnt = ent.GetTitan() + if ( !shieldEnt ) + return + } + + float shieldHealthFrac = GetShieldHealthFrac( shieldEnt ) + + int shieldBreakFX = GetParticleSystemIndex( SHIELD_BREAK_FX ) + + local attachID + if ( shieldEnt.IsTitan() ) + attachID = shieldEnt.LookupAttachment( "exp_torso_main" ) + else + attachID = shieldEnt.LookupAttachment( "ref" ) // TEMP + + local shieldFXHandle = StartParticleEffectOnEntity( shieldEnt, shieldBreakFX, FX_PATTACH_POINT_FOLLOW, attachID ) + EffectSetControlPointVector( shieldFXHandle, 1, GetShieldEffectCurrentColor( 1 - shieldHealthFrac ) ) +} + +function PlayIt( entity victim ) +{ + float shieldHealthFrac = GetShieldHealthFrac( victim ) + + int shieldbodyFX = GetParticleSystemIndex( SHIELD_BODY_FX ) + local attachID + if ( victim.IsTitan() ) + attachID = victim.LookupAttachment( "exp_torso_main" ) + else + attachID = victim.LookupAttachment( "ref" ) // TEMP + + local shieldFXHandle = StartParticleEffectOnEntity( victim, shieldbodyFX, FX_PATTACH_POINT_FOLLOW, attachID ) + + EffectSetControlPointVector( shieldFXHandle, 1, GetShieldEffectCurrentColor( 1 - shieldHealthFrac ) ) +} + +function PlayShieldHitEffect( PlayerDidDamageParams params ) +{ + entity player = GetLocalViewPlayer() + entity victim = params.victim + //vector damagePosition = params.damagePosition + //int hitBox = params.hitBox + //int damageType = params.damageType + //float damageAmount = params.damageAmount + //int damageFlags = params.damageFlags + //int hitGroup = params.hitGroup + //entity weapon = params.weapon + //float distanceFromAttackOrigin = params.distanceFromAttackOrigin + + //shieldFX <- GetParticleSystemIndex( SHIELD_FX ) + //StartParticleEffectInWorld( shieldFX, damagePosition, player.GetViewVector() * -1 ) + + float shieldHealthFrac = GetShieldHealthFrac( victim ) + + int shieldbodyFX = GetParticleSystemIndex( SHIELD_BODY_FX ) + local attachID + if ( victim.IsTitan() ) + attachID = victim.LookupAttachment( "exp_torso_main" ) + else + attachID = victim.LookupAttachment( "ref" ) // TEMP + + local shieldFXHandle = StartParticleEffectOnEntity( victim, shieldbodyFX, FX_PATTACH_POINT_FOLLOW, attachID ) + + EffectSetControlPointVector( shieldFXHandle, 1, GetShieldEffectCurrentColor( 1 - shieldHealthFrac ) ) +} + +const table SHIELD_COLOR_CHARGE_FULL = { r = 115, g = 247, b = 255 } // blue +const table SHIELD_COLOR_CHARGE_MED = { r = 200, g = 128, b = 80 } // orange +const table SHIELD_COLOR_CHARGE_EMPTY = { r = 200, g = 80, b = 80 } // red + +const SHIELD_COLOR_CROSSOVERFRAC_FULL2MED = 0.75 // from zero to this fraction, fade between full and medium charge colors +const SHIELD_COLOR_CROSSOVERFRAC_MED2EMPTY = 0.95 // from "full2med" to this fraction, fade between medium and empty charge colors + +function GetShieldEffectCurrentColor( shieldHealthFrac ) +{ + local color1 = SHIELD_COLOR_CHARGE_FULL + local color2 = SHIELD_COLOR_CHARGE_MED + local color3 = SHIELD_COLOR_CHARGE_EMPTY + + local crossover1 = SHIELD_COLOR_CROSSOVERFRAC_FULL2MED // from zero to this fraction, fade between color1 and color2 + local crossover2 = SHIELD_COLOR_CROSSOVERFRAC_MED2EMPTY // from crossover1 to this fraction, fade between color2 and color3 + + local colorVec = < 0, 0, 0 > + // 0 = full charge, 1 = no charge remaining + if ( shieldHealthFrac < crossover1 ) + { + colorVec.x = Graph( shieldHealthFrac, 0, crossover1, color1.r, color2.r ) + colorVec.y = Graph( shieldHealthFrac, 0, crossover1, color1.g, color2.g ) + colorVec.z = Graph( shieldHealthFrac, 0, crossover1, color1.b, color2.b ) + } + else if ( shieldHealthFrac < crossover2 ) + { + colorVec.x = Graph( shieldHealthFrac, crossover1, crossover2, color2.r, color3.r ) + colorVec.y = Graph( shieldHealthFrac, crossover1, crossover2, color2.g, color3.g ) + colorVec.z = Graph( shieldHealthFrac, crossover1, crossover2, color2.b, color3.b ) + } + else + { + // for the last bit of overload timer, keep it max danger color + colorVec.x = color3.r + colorVec.y = color3.g + colorVec.z = color3.b + } + + return colorVec +} + + +void function UpdateRespawnHUD() +{ + if ( IsWatchingReplay() ) + return + + entity player = GetLocalClientPlayer() + ShowRespawnSelect_SP() +} + +function OnClientPlayerAlive( entity player ) +{ + player.Signal( "OnClientPlayerAlive" ) // TEMP; this should not be necessary, but IsWatchingKillReplay is wrong + player.EndSignal( "OnClientPlayerAlive" ) + + UpdateClientHudVisibility( player ) +} + + +function OnClientPlayerDying( entity player ) +{ + player.Signal( "OnClientPlayerDying" ) // TEMP; this should not be necessary, but IsWatchingKillReplay is wrong + player.EndSignal( "OnClientPlayerDying" ) + + entity player = GetLocalClientPlayer() + UpdateClientHudVisibility( player ) + + if ( IsWatchingReplay() ) + return + + player.cv.deathTime = Time() + + thread DeathCamCheck( player ) +} + +function DeathCamCheck( entity player ) +{ + wait GetRespawnButtonCamTime( player ) +} + +void function ServerCallback_ShowNextSpawnMessage( float nextSpawnTime ) +{ + entity player = GetLocalClientPlayer() + float camTime = GetRespawnButtonCamTime( player ) + + file.nextSpawnTime = nextSpawnTime + + if ( nextSpawnTime > Time() + camTime ) + thread ShowSpawnDelayMessage( nextSpawnTime ) +} + +void function ShowSpawnDelayMessage( nextSpawnTime ) +{ + float waitTime = max( nextSpawnTime - Time(), 0 ) + + if ( waitTime < 1.0 ) + return + + entity player = GetLocalClientPlayer() + + player.cv.nextSpawnTimeLabel.SetAlpha( 255 ) + player.cv.nextSpawnTimeLabel.Show() + player.cv.nextSpawnTimeLabel.SetAutoText( "#GAMEMODE_DEPLOYING_IN_N", HATT_GAME_COUNTDOWN_SECONDS, nextSpawnTime ) + + if ( !player.cv.nextSpawnTimeLabel.IsAutoText() ) + player.cv.nextSpawnTimeLabel.EnableAutoText() +} + +void function ServerCallback_HideNextSpawnMessage() +{ + entity player = GetLocalClientPlayer() + player.cv.nextSpawnTimeLabel.FadeOverTime( 0, 1.0 ) +} + +float function GetWaveSpawnTime() +{ + return (file.nextSpawnTime) +} + +bool function IsPlayerEliminated( entity player ) +{ + return (player.GetPlayerGameStat( PGS_ELIMINATED ) > 0) +} + +function PlayerFieryDeath( player ) +{ + player.EndSignal( "OnDestroy" ) + player.EndSignal( "OnClientPlayerAlive" ) + clGlobal.levelEnt.EndSignal( "OnSpectatorMode" ) + + local offset = < 0, 0, 0 > + if ( player.IsTitan() ) + offset = < 0, 0, 96 > + + entity scriptRef = CreatePropDynamic( $"models/dev/empty_model.mdl", player.GetOrigin() + offset, player.GetAngles() ) + scriptRef.SetParent( player ) + + local fxHandle = StartParticleEffectOnEntity( scriptRef, GetParticleSystemIndex( $"P_burn_player" ), FX_PATTACH_ABSORIGIN_FOLLOW, -1 ) + + OnThreadEnd( + function () : ( fxHandle, scriptRef ) + { + EffectStop( fxHandle, false, false ) + if ( IsValid( scriptRef ) ) + scriptRef.Destroy() + } + ) + WaitForever() +} + + +function ServerCallback_GiveMatchLossProtection() +{ +} + +void function EnableDoDeathCallback( entity ent ) +{ + ent.DoDeathCallback( true ) +} + +void function ServerCallback_ShowDisembarkHint( float showtime ) +{ + AddPlayerHint( showtime, 0.5, $"","#HUD_TITAN_DISEMBARK" ) +} + +void function ClearDisembarkHint( entity cockpit, entity player ) +{ + HidePlayerHint( "#HUD_TITAN_DISEMBARK" ) +} + +void function SCB_CheckPoint() +{ + entity player = GetLocalViewPlayer() + if ( !IsAlive( player ) ) + return + + // put good announce here + AnnouncementMessageCheckpoint( player ) +} From 52a6ffdbfa068ebbe0d0020cfec0ccde14facc56 Mon Sep 17 00:00:00 2001 From: NachosChipeados <103285866+NachosChipeados@users.noreply.github.com> Date: Sat, 16 Nov 2024 21:49:01 -0400 Subject: [PATCH 6/6] Remove unnecesary IsLobby check --- Northstar.Client/mod/scripts/vscripts/client/cl_sp_player.gnut | 2 -- 1 file changed, 2 deletions(-) diff --git a/Northstar.Client/mod/scripts/vscripts/client/cl_sp_player.gnut b/Northstar.Client/mod/scripts/vscripts/client/cl_sp_player.gnut index ecb46cc2a..833fcf7c1 100644 --- a/Northstar.Client/mod/scripts/vscripts/client/cl_sp_player.gnut +++ b/Northstar.Client/mod/scripts/vscripts/client/cl_sp_player.gnut @@ -1279,8 +1279,6 @@ void function AddCallback_OnCrosshairCurrentTargetChanged( void functionref( ent void function ClientCodeCallback_OnCrosshairCurrentTargetChanged( entity player, entity newTarget ) { - if ( IsLobby() ) - return; if ( !IsValid( player ) ) return