diff --git a/ronin/resource/ui/menus/timer_settings.menu b/ronin/resource/ui/menus/timer_settings.menu index b0fa4a7..e068adb 100644 --- a/ronin/resource/ui/menus/timer_settings.menu +++ b/ronin/resource/ui/menus/timer_settings.menu @@ -59,7 +59,7 @@ resource/ui/menus/srm_settings.menu controlSettingsFile "resource/ui/menus/panels/matchmaking_status.res" } - "CategoryLabel" + "EnableLabel" { ControlName Label xpos 113 @@ -69,11 +69,49 @@ resource/ui/menus/srm_settings.menu visible 1 enabled 1 auto_wide_tocontents 1 + labelText "GENERAL" + textAlignment west + fgcolor_override "255 255 255 255" + bgcolor_override "0 0 0 200" + font JBMonoBold_27 + } + Enable + { + "ControlName" "CNestedPanel" + xpos 0 + ypos 4 + wide 740 + tall 40 + visible 1 + enabled 1 + zpos 10 + + pin_to_sibling EnableLabel + pin_to_sibling_corner BOTTOM_LEFT + pin_corner_to_sibling TOP_LEFT + + controlSettingsFile "resource/ui/menus/panels/button_carousel.res" + } + + "CategoryLabel" + { + ControlName Label + xpos 0 + ypos 8 + wide 740 + tall 40 + visible 1 + enabled 1 + auto_wide_tocontents 1 labelText "RULESET AND CATEGORY" textAlignment west fgcolor_override "255 255 255 255" bgcolor_override "0 0 0 200" font JBMonoBold_27 + + pin_to_sibling Enable + pin_to_sibling_corner BOTTOM_LEFT + pin_corner_to_sibling TOP_LEFT } Category diff --git a/ronin/scripts/vscripts/cl_mapspawn.gnut b/ronin/scripts/vscripts/cl_mapspawn.gnut new file mode 100644 index 0000000..c683b8c --- /dev/null +++ b/ronin/scripts/vscripts/cl_mapspawn.gnut @@ -0,0 +1,369 @@ + +untyped + +//******************************************************************************************** +// _cl_mapspawn.nut +// Called on newgame or transitions, BEFORE entities have been created and initialized +//******************************************************************************************** + +global function ClientCodeCallback_MapSpawn + +global function ClientCodeCallback_RunClientConnectScripts +global function Init_PlayerScripts +global function CodeCallback_EntityVarChanged +global function ServerCallback_ClientInitComplete +global function ClientCodeCallback_OnDropShipCinematicEventStateChanged +global function ClientCodeCallback_OnDetenteEnded +global function PerfInitLabels + +global var Hud = null + +global bool TITAN_CORE_ENABLED = true + +void function ClientCodeCallback_MapSpawn() +{ + ScriptCompilerTest() + LevelVarInit() + + Hud = getroottable().Hud + + level.scoreLimit <- {} + level.scoreLimit[TEAM_IMC] <- GetScoreLimit_FromPlaylist() + level.scoreLimit[TEAM_MILITIA] <- GetScoreLimit_FromPlaylist() + + level.clientVars <- {} + + level.onPlayerLifeStateChanged <- [] + + level.clientScriptInitialized <- false + + RegisterSignal( "CachedLoadoutsReady" ) + RegisterSignal( "EndSetPresentationType" ) + RegisterSignal( "forever" ) + RegisterSignal( "UpdateTitanCounts" ) + RegisterSignal( "UpdatePlayerStatusCounts" ) + RegisterSignal( "ControllerModeChanged" ) + RegisterSignal( "PulseROE" ) + RegisterSignal( "OnDeath" ) + + FlagInit( "ClientInitComplete" ) + FlagInit( "EntitiesDidLoad" ) + + if ( !IsLobby() ) + { + PrecacheRes( "vgui_icon" ) + PrecacheRes( "vgui_binding" ) + PrecacheRes( "vgui_jump_quest" ) + PrecacheRes( "vgui_titan_ammo" ) + PrecacheRes( "vgui_xpbar" ) + PrecacheRes( "vgui_titan_threat" ) + PrecacheRes( "vgui_titan_vdu" ) + PrecacheRes( "vgui_fullscreen_titan" ) + PrecacheRes( "vgui_fullscreen_pilot" ) + PrecacheRes( "vgui_pilot_launcher_screen" ) + PrecacheRes( "vgui_titan_emp" ) + PrecacheRes( "vgui_enemy_announce" ) + PrecacheRes( "Coop_TeamScoreEventNotification" ) + PrecacheRes( "control_panel_generic_screen" ) + PrecacheRes( "vgui_callsign_menu" ) + } + + #if DEV + ClModelViewInit() + #endif + + float[2] screenSize = GetScreenSize() + + clGlobal.topoFullScreen = RuiTopology_CreatePlane( <0,0,0>, , <0,screenSize[1],0>, false ) + + if ( screenSize[0] / screenSize[1] <= 1.6 ) + { + clGlobal.topoCockpitHud = RuiTopology_CreateSphere( COCKPIT_RUI_OFFSET_1610_TEMP, <0, -1, 0>, <0, 0, -1>, COCKPIT_RUI_RADIUS, COCKPIT_RUI_WIDTH, COCKPIT_RUI_HEIGHT, COCKPIT_RUI_SUBDIV ) + clGlobal.topoCockpitHudPermanent = RuiTopology_CreateSphere( COCKPIT_RUI_OFFSET_1610_TEMP, <0, -1, 0>, <0, 0, -1>, COCKPIT_RUI_RADIUS, COCKPIT_RUI_WIDTH, COCKPIT_RUI_HEIGHT, COCKPIT_RUI_SUBDIV ) + } + else + { + clGlobal.topoCockpitHud = RuiTopology_CreateSphere( COCKPIT_RUI_OFFSET, <0, -1, 0>, <0, 0, -1>, COCKPIT_RUI_RADIUS, COCKPIT_RUI_WIDTH, COCKPIT_RUI_HEIGHT, COCKPIT_RUI_SUBDIV ) + clGlobal.topoCockpitHudPermanent = RuiTopology_CreateSphere( COCKPIT_RUI_OFFSET, <0, -1, 0>, <0, 0, -1>, COCKPIT_RUI_RADIUS, COCKPIT_RUI_WIDTH, COCKPIT_RUI_HEIGHT, COCKPIT_RUI_SUBDIV ) + } + RuiTopology_ShareWithCode( clGlobal.topoCockpitHudPermanent, RUI_CODE_TOPO_PERMANENT_COCKPIT ); + RuiTopology_ShareWithCode( clGlobal.topoCockpitHud, RUI_CODE_TOPO_ANIMATED_COCKPIT ); + + clGlobal.topoTitanCockpitHud = RuiTopology_CreateSphere( < -120, 0, -TITAN_COCKPIT_TOPO_RADIUS * deg_sin( TITAN_COCKPIT_ROTATION_ANGLE )>, <0, -1, 0>, , TITAN_COCKPIT_TOPO_RADIUS, TITAN_COCKPIT_RUI_SCREEN_WIDTH, TITAN_COCKPIT_RUI_SCREEN_WIDTH / 1.7665, COCKPIT_RUI_SUBDIV ) + clGlobal.topoTitanCockpitLowerHud = RuiTopology_CreatePlane( <0,0,0>, <0, -TITAN_COCKPIT_LOWER_RUI_SCREEN_SQUARE_SIZE, 0>, <0, 0, -TITAN_COCKPIT_LOWER_RUI_SCREEN_SQUARE_SIZE * TITAN_COCKPIT_LOWER_RUI_SCREEN_HEIGHT_SCALE>, true ) + clGlobal.topoTitanCockpitInstrument1 = RuiTopology_CreatePlane( <0,0,0>, <0, -1, 0>, <0, 0, -1>, true ) + + SPMP_Shared_Init() + TimerOverlay_Init() +} + +/* +const VGUI_TITAN_NOSAFE_X = 0.15 +const VGUI_TITAN_NOSAFE_Y = 0.075 +const VGUI_PILOT_NOSAFE_X = 0.10 +const VGUI_PILOT_NOSAFE_Y = 0.10 + +function MainHud_InitAspectRatio( entity cockpit, entity player, bool isTitan ) +{ + entity vgui = cockpit.e.mainVGUI + local panel = vgui.GetPanel() + + vgui.e.screen = HudElement( "Screen", panel ) + vgui.e.safeArea = HudElement( "SafeArea", panel ) + vgui.e.safeAreaCenter = HudElement( "SafeAreaCenter", panel ) + + local safeWidth = vgui.e.safeArea.GetWidth() + local safeWidthCenter = vgui.e.safeAreaCenter.GetWidth() + + if ( level.safeAreaScale != 1.0 ) + { + if ( isTitan ) + { + vgui.e.safeArea.SetBaseSize( vgui.e.screen.GetWidth() - (vgui.e.screen.GetWidth() * VGUI_TITAN_NOSAFE_X), vgui.e.screen.GetHeight() - (vgui.e.screen.GetHeight() * VGUI_TITAN_NOSAFE_Y) ) + vgui.e.safeAreaCenter.SetBaseSize( vgui.e.screen.GetWidth() - (vgui.e.screen.GetWidth() * VGUI_TITAN_NOSAFE_X), vgui.e.screen.GetHeight() - (vgui.e.screen.GetHeight() * VGUI_TITAN_NOSAFE_Y) ) + } + else + { + vgui.e.safeArea.SetBaseSize( vgui.e.screen.GetWidth() - (vgui.e.screen.GetWidth() * VGUI_PILOT_NOSAFE_X), vgui.e.screen.GetHeight() - (vgui.e.screen.GetHeight() * VGUI_PILOT_NOSAFE_Y) ) + vgui.e.safeAreaCenter.SetBaseSize( vgui.e.screen.GetWidth() - (vgui.e.screen.GetWidth() * VGUI_PILOT_NOSAFE_X), vgui.e.screen.GetHeight() - (vgui.e.screen.GetHeight() * (VGUI_PILOT_NOSAFE_Y * 0.5)) ) + } + } + + vgui.e.safeArea.SetScaleX( level.aspectRatioScale ) + vgui.e.safeAreaCenter.SetScaleX( level.aspectRatioScale ) +} +*/ + +void function PrecacheRes( resFile ) +{ + var vgui = CreateClientsideVGuiScreen( resFile, VGUI_SCREEN_PASS_WORLD, Vector(0,0,0), Vector(0,0,0), 4, 4 ) + Assert( vgui != null, "Failed to precache res file " + resFile ) + vgui.Destroy() +} + + +void function ClientCodeCallback_RunClientConnectScripts( entity player ) +{ + Assert( IsValid( player ) ) + Assert( player == GetLocalClientPlayer() ) + thread RunClientConnectScriptsThreaded( player ) +} + +function RunClientConnectScriptsThreaded( entity player ) +{ + Assert( IsValid( player ) ) + Assert( player == GetLocalClientPlayer() ) + + if ( level.clientScriptInitialized ) + return + + player.cv = level.clientVars + + clGlobal.levelEnt = CreateClientSidePointCamera( Vector( 0, 0, 0 ), Vector( 0, 0, 0 ), 50 ) + clGlobal.levelEnt.Hide() + Assert( clGlobal.levelEnt ) + + Init_ClientScripts( player ) + + FlagWait( "ClientInitComplete" ) + + player = GetLocalClientPlayer() + + Assert( IsValid( player ) ) + Assert( player == GetLocalClientPlayer() ) + + level.clientScriptInitialized = true + + RunCallbacks_EntitiesDidLoad() + + FlagSet( "EntitiesDidLoad" ) + + #if DURANGO_PROG + // Update game progress based off gen and xp + Assert( IsValid( player ) ) + Assert( player == GetLocalClientPlayer() ); + Assert( GetMaxPlayerLevel() > 0 ); + + if ( player.GetGen() > 1 ) + { + Durango_SetGameProgress( 1.0 ) + } + else + { + // temp fix for level curve bug + float result = float( player.GetLevel() ) / GetMaxPlayerLevel() + if ( result > 1.0 ) + result = 1.0 + Durango_SetGameProgress( result ) + } + #endif // DURANGO_PROG +} + +function Init_ClientScripts( entity player ) +{ + level.safeAreaScale <- GetSafeAreaScaleSetting() + + level.screenSize <- Hud.GetScreenSize() + + local aspectRatio = level.screenSize[0].tofloat() / level.screenSize[1].tofloat() + level.aspectRatioScale <- min( aspectRatio / (16.0/9.0), 1.0 ) + + level.safeArea <- HudElement( "SafeArea" ) + level.safeAreaCenter <- HudElement( "SafeAreaCenter" ) + + if ( level.safeAreaScale != 1.0 ) + { + level.safeArea.SetWidth( level.screenSize[0] - (level.screenSize[0] * 0.025) ) + level.safeArea.SetHeight( level.screenSize[1] - (level.screenSize[1] * 0.025) ) + level.safeAreaCenter.SetWidth( level.screenSize[0] - (level.screenSize[0] * 0.025) ) + level.safeAreaCenter.SetHeight( level.screenSize[1] - (level.screenSize[1] * 0.025) ) + } + + if ( !IsLobby() ) + { + Player_AddClient( player ) + } + + //ScreenFade_AddClient() + + if ( !IsLobby() ) + { + InitVoiceHUD( player ) + CapturePointHudInit( player ) //TODO: Reexamine this with more CapturePoint refactoring + } + + #if PC_PROG + InitChatHUD() + #endif // PC_PROG + + if ( !IsLobby() ) + { + ClassicMP_AddClient() + KillReplayHud_AddClient() + clGlobal.initScoreboardFunc() + InitChallengePopup() + MainHud_AddClient( player ) + ReplacementTitanHUD_AddClient( player ) + InitCrosshair() + + player.ClientCommand( "ClientStatus " + (IsPartyMember( player ) ? "1" : "0") ) + + // Added via AddCallback_OnClientScriptInit + foreach ( callbackFunc in clGlobal.onClientScriptInitCallback ) + { + callbackFunc( player ) + } + } + else + { + // Added via AddCallback_OnClientScriptInit + foreach ( callbackFunc in clGlobal.onClientScriptInitCallback ) + { + callbackFunc( player ) + } + //BurnCards_AddLobbyClient() // move this to a callback if we need it again`` + } + + Assert( IsMultiplayer() ) + InitDefaultLoadouts() + + DoF_SetNearDepthToDefault() + DoF_SetFarDepthToDefault() + + MenuModels_Init() + thread UpdateCachedLoadouts() // Needs to wait till frame end + + // This is needed to fix save/load issues because server script will wait to hear from the client that dialogue has finished playing, but client script is reinitialized and no longer knows to respond + player.ClientCommand( "AllDialogueFinished" ) +} + + +function Init_PlayerScripts( entity player ) +{ + SmartAmmo_LockedOntoWarningHUD_Init() + + foreach ( addPlayerFunc in clGlobal.addPlayerFuncs ) + { + addPlayerFunc( player ) + } + + if ( IsLobby() ) + thread Lobby_AddPlayer( player ) + + player.p.playerScriptsInitialized = true +} + + + +function CodeCallback_EntityVarChanged( ent, varName, newValue, oldValue ) +{ + local className = ent.GetSignifierName() + + if ( !(className in _entityClassVarChangeCallbacks) ) + return + + if ( !(varName in _entityClassVarChangeCallbacks[className]) ) + return + + foreach ( callbackFunc in _entityClassVarChangeCallbacks[className][varName] ) + { + local infos = callbackFunc.getinfos() + if ( infos.parameters.len() == 2 ) + callbackFunc( ent ) + else + callbackFunc( ent, newValue, oldValue ) + } +} + +// called from _base_gametype::ClientCommand_ClientScriptInitialized() +function ServerCallback_ClientInitComplete() +{ + FlagSet( "ClientInitComplete" ) +} + + +function StripPrefix( stringName, prefix ) +{ + local index = stringName.find( prefix ) + + if ( index != 0 ) + return stringName + + return stringName.slice( prefix.len(), stringName.len() ) +} + + +function GetTableNumContents( Table, contents ) +{ + foreach ( k, v in Table ) + { + contents.num++ + + if ( typeof v == "table" ) + { + GetTableNumContents( v, contents ) + } + } +} + +void function ClientCodeCallback_OnDetenteEnded() +{ +} + +function PerfInitLabels() +{ + PerfClearAll() + + local Table = getconsttable().PerfIndexClient + foreach( label, intval in Table ) + PerfInitLabel( intval, string( label ) ) + + local sharedTable = getconsttable().PerfIndexShared + foreach( label, intval in sharedTable ) + PerfInitLabel( intval + SharedPerfIndexStart, string( label ) ) +} + +function ClientCodeCallback_OnDropShipCinematicEventStateChanged() +{ +} + diff --git a/ronin/scripts/vscripts/sh_codecallbacks.gnut b/ronin/scripts/vscripts/sh_codecallbacks.gnut new file mode 100644 index 0000000..fca8609 --- /dev/null +++ b/ronin/scripts/vscripts/sh_codecallbacks.gnut @@ -0,0 +1,470 @@ +untyped + +global function CodeCallbacksShared_Init + +global function CodeCallback_AnimationDone +global function CodeCallback_AnimationInterrupted +global function CodeCallback_CanUseEntity +global function CodeCallback_CanUseZipline +global function CodeCallback_PlayerClassChanged +global function CodeCallback_OnUseEntity +global function ShouldStopLunging +//global function CodeCallback_OnUsePressed +global function CodeCallback_OnUseReleased +global function CodeCallback_ForceScriptError + +global function AddCallback_PlayerClassChanged + +#if CLIENT +global function CodeCallback_OnTurretCancelPressed +#endif + +#if SERVER +global function CodeCallback_OnWeaponReload +global function SetCallback_OnPlayerReload +#endif + +#if SP +#if CLIENT +global function ClientCodeCallback_OnPickup_MatchCandy +#endif // CLIENT +#if SERVER +global function CodeCallback_ClaimClientSidePickup_MatchCandy +#endif // SERVER +#endif + +#if MP + #if CLIENT + global function ClientCodeCallback_OnPickup_MatchCandy + #endif // CLIENT + #if SERVER + global function CodeCallback_ClaimClientSidePickup_MatchCandy + #endif // SERVER +#endif + + +#if SERVER +struct +{ + void functionref( entity ) Callback_OnPlayerReload +} file +#endif + +#if CLIENT +struct +{ + var disembarkRUI +} file +#endif + +function CodeCallbacksShared_Init() +{ + RegisterSignal( "OnAnimationDone" ) + RegisterSignal( "OnAnimationInterrupted" ) + RegisterSignal( "SettingsChanged" ) + RegisterSignal( "OnPrimaryAttack" ) + RegisterSignal( "OnPlayerUse" ) + + level.unusableByTitan <- {} + level.unusableByTitan[ "prop_control_panel" ] <- true + + #if CLIENT + RegisterSignal( "CancelDisembark" ) + RegisterConCommandTriggeredCallback( "-useAndReload", CancelDisembark ) + RegisterConCommandTriggeredCallback( "-use", CancelDisembark ) + #endif + + #if SERVER + AddClientCommandCallback( "DisembarkTitan", ClientCommand_DisembarkTitan ) + SetCallback_OnPlayerReload( PlayerReloadGamemodeLogic_Default ) + #endif +} + +void function CodeCallback_AnimationDone( entity ent ) +{ + Signal( ent, "OnAnimationDone" ) +} + +void function CodeCallback_AnimationInterrupted( entity ent ) +{ + Signal( ent, "OnAnimationInterrupted" ) +} + +// better to not define these than just return true. Otherwise code will call these function for no reason + +bool function CodeCallback_CanUseEntity( entity player, entity ent ) +{ + if ( !IsAlive( player ) ) + return false + + if ( player.IsPhaseShifted() ) // it's not really there! + return false + + // player could be trying to use self as a titan + if ( ent == player ) + { + if ( player.IsTitan() ) + { + if ( Riff_TitanExitEnabled() == eTitanExitEnabled.Never ) + return false + + #if SERVER + // client doesn't know these things + if ( IsPlayerDisembarking( player ) ) + return false + if ( IsPlayerEmbarking( player ) ) + return false + #endif + + return true + } + return false + } + + if ( ent.IsNPC() ) + { + // handle leeching separately + if ( Leech_IsLeechable( ent ) ) + return false + } + + if ( player.IsTitan() ) + { + return !ent.IsTitan() + } + + if ( ent.IsTitan() && ent.GetBossPlayer() == player ) + { + return PlayerCanEmbarkTitan( player, ent ) + } + + if ( ent.IsNPC() ) + { + // player titan can't use NPCs + if ( !player.IsTitan() ) + { + // not titan NPCs are not usable + //if ( !( ent.IsTitan() || IsTurret( ent ) ) ) + // return false + + if ( !IsAlive( ent ) ) + return false + + + } + } + + // custom overwritable usefunction + return expect bool( ent.useFunction( player, ent ) ) // useFunction should be moved to a struct and given a proper function type +} + +bool function CodeCallback_CanUseZipline( entity player, entity zipline, vector ziplineClosestPoint ) +{ + if ( !player.IsHuman() ) + return false + + return true +} + +void function AddCallback_PlayerClassChanged( void functionref( entity ) callbackFunc ) +{ + #if SERVER + svGlobal.onPlayerClassChangedCallbacks.append( callbackFunc ) + #else + clGlobal.onPlayerClassChangedCallbacks.append( callbackFunc ) + #endif +} + +void function CodeCallback_PlayerClassChanged( entity player ) +{ + if ( !IsValid( player ) ) + return + + if ( IsLobby() ) + return + + player.Signal( "SettingsChanged" ) + string newClass = player.GetPlayerClass() + + #if SERVER + foreach ( callbackFunc in svGlobal.onPlayerClassChangedCallbacks ) + { + callbackFunc( player ) + } + #if MP + if ( IsAlive( player ) && !player.IsTitan() && GetCurrentPlaylistVarFloat( "pilot_health_multiplier", 0.0 ) != 0.0 ) + { + float pilotHealthMultiplier = GetCurrentPlaylistVarFloat( "pilot_health_multiplier", 1.0 ) + int pilotMaxHealth = int( player.GetMaxHealth() * pilotHealthMultiplier ) + player.SetMaxHealth( pilotMaxHealth ) + player.SetHealth( pilotMaxHealth ) + } + #endif + #else + foreach ( callbackFunc in clGlobal.onPlayerClassChangedCallbacks ) + { + callbackFunc( player ) + } + #endif + + #if CLIENT + player.classChanged = true + // Force titan to cast shadows in first person + //player.ForceShadowVisible( newClass == "titan" ); + + if ( newClass == level.pilotClass ) + thread ClientPilotSpawned( player ) + + UpdatePlayerStatusCounts() + + /*if ( IsWatchingReplay() ) + UpdateKillReplayIconPosition()*/ + #else + player.kv.renderColor = "255 255 255 255" + + InitDamageStates( player ) + #endif + + //if ( IsClient() && !player.IsTitan() ) + // HideRodeoAlert() + + //if ( IsPilot( player ) ) + // thread PilotHardLandingThink( player ) + + #if SERVER + player.Signal( "OnChangedPlayerClass" ) + + UpdatePlayerMinimapMaterials( player ) + #endif +} + +#if SERVER +void function CodeCallback_OnWeaponReload( entity weapon ) +{ + entity weaponOwner = weapon.GetWeaponOwner() + if ( !IsAlive( weaponOwner ) ) + return + + if ( !weaponOwner.IsPlayer() ) + return + + if ( !IsPilot( weaponOwner ) ) + return + + //This calls a function that can be set per gamemode. Runs an empty function by default. + file.Callback_OnPlayerReload( weaponOwner ) + + #if GRUNTCHATTER_ENABLED + if ( NPC_GruntChatterSPEnabled( weaponOwner ) ) + GruntChatter_TryPlayerPilotReloading( weaponOwner ) + #endif + + #if BATTLECHATTER_ENABLED + PlayBattleChatterLine( weaponOwner, "bc_pReload" ) + #endif +} + +void function SetCallback_OnPlayerReload( void functionref( entity ) rules ) +{ + file.Callback_OnPlayerReload = rules +} + +void function PlayerReloadGamemodeLogic_Default ( entity player ) +{ + +} +#endif + +bool function CodeCallback_OnUseEntity( entity player, entity ent ) +{ +#if SERVER + // use AddCallback_OnUseEntity( ent, callbackFunc ) to add a on use callback function + if ( "onUseEntityCallbacks" in ent.s ) + { + foreach ( callbackFunc in ent.s.onUseEntityCallbacks ) + { + callbackFunc( ent, player ) + } + } + + if ( ent.IsTitan() ) + { + if ( player == ent ) //player is disembarking/using self + { + ClientCommand_DisembarkTitan( player, [] ) + } + else if ( ent.GetBossPlayer() == player ) + { + Assert( !player.IsTitan() ) + PlayerLungesToEmbark( player, ent ) + return false + } + } + +#endif // SERVER + // RONIN - emit signal for whenever we interact with somethign + Signal( ent, "OnPlayerUse", { player = player } ) + +// #if CLIENT +// if ( ent.IsTitan() && player == ent && IsSingleplayer() ) +// thread BeginDisembark( player ) +// #endif + + // return true to tell code to run its code to use the entity + return true +} + +#if CLIENT +void function BeginDisembark( entity player ) +{ + Signal( player, "CancelDisembark" ) + EndSignal( player, "CancelDisembark" ) + + float startTime = Time() + float disembarkDelay = 0.75 + + ShowDisembarkRUI( Time() + disembarkDelay ) + + while( Time() - startTime < disembarkDelay ) + { + wait 0.05 + + if ( !PlayerCanDisembarkTitan( player ) ) + return + } + + player.ClientCommand( "DisembarkTitan" ) + thread CancelDisembark( player ) +} + +void function CancelDisembark( entity player ) +{ + Signal( player, "CancelDisembark" ) + + if ( file.disembarkRUI != null ) + { + RuiDestroy( file.disembarkRUI ) + file.disembarkRUI = null + } +} + +void function ShowDisembarkRUI( float endTime ) +{ + if ( file.disembarkRUI != null ) + { + RuiDestroy( file.disembarkRUI ) + file.disembarkRUI = null + } + + var rui = RuiCreate( $"ui/disembarking_progress.rpak", clGlobal.topoFullScreen, RUI_DRAW_HUD, 5000 ) + RuiSetGameTime( rui, "startTime", Time() ) + RuiSetGameTime( rui, "endTime", endTime ) + file.disembarkRUI = rui +} +#endif + +#if SERVER +bool function ClientCommand_DisembarkTitan( entity player, array args ) +{ + if ( PlayerCanDisembarkTitan( player ) ) + { + ScreenFade( player, 0, 1, 0, 255, 0.2, 0.2, FFADE_IN | FFADE_PURGE ) + player.CockpitStartDisembark() + Remote_CallFunction_Replay( player, "ServerCallback_TitanDisembark" ) + thread PlayerDisembarksTitan( player ) + } + return true +} +#endif // SERVER + +function IsUsableByTitan( player, ent ) +{ + local classname + #if SERVER + classname = ent.GetClassName() + #else + classname = ent.GetSignifierName() + #endif + + if ( player.IsTitan() && classname in level.unusableByTitan ) + return false + else + return true +} + +function ShouldStopLunging( player, target ) +{ + expect entity( player ) + expect entity( target ) + + if ( !IsAlive( player ) ) + return true + + if ( !IsAlive( target ) ) + return true + + if ( !target.IsTitan() ) + return true + + // ejecting? + return target.GetTitanSoul().IsEjecting() +} + +/* +void function CodeCallback_OnUsePressed( entity player ) +{ +} +*/ + +void function CodeCallback_OnUseReleased( entity player ) +{ + if ( player.Lunge_IsActive() ) + player.Lunge_ClearTarget() +} + +#if SP +#if CLIENT +void function ClientCodeCallback_OnPickup_MatchCandy( int points, int CSPUFLAGflags ) +{ + Assert( 0, "Unexpected usage." ) +} +#endif // CLIENT +#if SERVER +void function CodeCallback_ClaimClientSidePickup_MatchCandy( entity player, int amount, int flags, int recieptID ) +{ + Assert( 0, "Unexpected usage." ) +} +#endif // SERVER +#endif + +#if MP +#if CLIENT +void function ClientCodeCallback_OnPickup_MatchCandy( int points, int CSPUFLAGflags ) +{ + Assert( 0, "Unexpected usage." ) +} +#endif // CLIENT +#if SERVER +void function CodeCallback_ClaimClientSidePickup_MatchCandy( entity player, int amount, int flags, int recieptID ) +{ + Assert( 0, "Unexpected usage." ) +} +#endif // SERVER +#endif + + +#if CLIENT +void function CodeCallback_OnTurretCancelPressed( entity player ) +{ + if ( InPrediction() && !IsFirstTimePredicted() ) + return + + ScreenFade( player, 0, 0, 0, 255, 0.1, 1.0, FFADE_OUT ); +} +#endif // #if CLIENT + +// code triggering script error with useful message +void function CodeCallback_ForceScriptError( entity ent, string errorMsg ) +{ + Assert( 0, errorMsg ) +} diff --git a/ronin/scripts/vscripts/sp/_sp_sh_init.gnut b/ronin/scripts/vscripts/sp/_sp_sh_init.gnut index 88f2de1..d682913 100644 --- a/ronin/scripts/vscripts/sp/_sp_sh_init.gnut +++ b/ronin/scripts/vscripts/sp/_sp_sh_init.gnut @@ -67,6 +67,7 @@ void function Shared_SP_Init() Server_SP_Init() #elseif CLIENT Client_SP_Init() + TimerOverlay_Init() #endif } diff --git a/ronin/scripts/vscripts/sp/cl_sp_hud.gnut b/ronin/scripts/vscripts/sp/cl_sp_hud.gnut index f2ee8ac..bf014b0 100644 --- a/ronin/scripts/vscripts/sp/cl_sp_hud.gnut +++ b/ronin/scripts/vscripts/sp/cl_sp_hud.gnut @@ -23,7 +23,6 @@ void function ClSpHud_Init() // RONIN HUD SCRIPTS Subsplit_Init() - TimerOverlay_Init() SRM_InfoHUD_Init() SRM_TasHUD_Init() thread SRM_InputDisplay_Init() diff --git a/ronin/scripts/vscripts/sp/sh_sp_dialogue.gnut b/ronin/scripts/vscripts/sp/sh_sp_dialogue.gnut new file mode 100644 index 0000000..b38b367 --- /dev/null +++ b/ronin/scripts/vscripts/sp/sh_sp_dialogue.gnut @@ -0,0 +1,1330 @@ +#if CLIENT + untyped + const CONVERSATION_TIMEOUT = 7.0 + const CONVERSATION_INTRO_DURATION = 1.0 + const CONVERSATION_TEXT_REMOVE_DURATION = 0.75 + const CONVERSATION_TEXT_REMOVE_DURATION_TIMEOUT = 1.0 + + const DEBUG_QUEUE_PRINTS = false + + const BT_EYE_GLOW = $"P_BT_eye_SM" +#endif + +global function RegisterDialogue +global function RegisterRadioDialogue +global function SPDialogueInit +global function GetBuddyTitanDialogueEnt + +#if SERVER + global function PlayDialogue + global function PlayBTDialogue + global function PlayGabbyDialogue + global function StopDialogue + global function PlayDialogueForPlayer + global function IsDialoguePlaying + global function PlayerConversation + global function StopConversation + global function StopConversationNow + global function AddConversationCallback + + global function CodeCallback_OnServerAnimEvent + + global function Dev_TestAllConversations +#endif //SERVER + +global function IsConversationPlaying + +#if CLIENT + global function ServerCallback_PlayDialogueOnEntity + global function ServerCallback_PlayDialogueAtPosition + global function ServerCallback_AbortCurrentDialogue + global function ServerCallback_PlayerConversation + global function ServerCallback_StopConversation + + global function QueueAndWait + global function RemoveFromQueue +#endif //CLIENT + +struct DialogueData +{ + string name + string alias + int priority + int cutoffRule // not used yet + string radioDisplayName + int team + bool radioIntercept +} + +struct BTConversation +{ + string name + string btStartAlias + string response_A_player_text + string response_A_player_alias + string response_A_bt_respond_alias + string response_B_player_text + string response_B_player_alias + string response_B_bt_respond_alias + string player_closing_alias_A + string player_closing_alias_B + bool player_closer_played = false + bool response_A_ends_conversation = false + bool response_B_ends_conversation = false + bool allowReplay = false + string waveformName + int waveformTeam + string nextConversationA + string nextConversationB + bool response_A_available = true + bool response_B_available = true + bool response_A_wasSelected = false + bool response_B_wasSelected = false + array callbackFuncs +} + +global struct QueueItem +{ + int priority + int cutoffRule + float timeAdded = -1 + bool active = false + string debugRef = "" + bool success = true +} + +struct +{ + array registeredDialog + array registeredDialogIDs + bool dialoguePlaying + array registeredConversations + array registeredConversationIDs + int conversationsPlaying + int abortConversation + array queue + var currentDialoguePlayingHandle + table callerIDs + table waitingOnDialogue +} file + +// Play priorities +global const PRIORITY_NO_QUEUE = 0 // Plays the dialogue right away, avoiding the queue. +global const PRIORITY_HIGH = 20 // Enters the queue with the highest of priorities, only other items in queue of same priority will play first +global const PRIORITY_NORMAL = 10 +global const PRIORITY_LOW = 5 +global const PRIORITY_LOWEST = 1 + +// Cutoff rules +global const CUTOFF_NEVER = 0 // This dialogue will always finish, it cannot be cutoff when something with higher priority plays over it. +//global const CUTOFF_ALLOWED = 0 // This dialogue will always finish, it cannot be cutoff when something with higher priority plays over it. + +const CONVERSATION_STOP_GRACEFUL = 0 +const CONVERSATION_STOP_IMMEDIATE = 1 + +void function RegisterDialogue( string name, string alias, int priority ) +{ + RegisterDialogueLine( name, alias, priority, "", 0, false ) +} + +void function RegisterRadioDialogue( string name, string alias, int priority, string radioDisplayName, int team, bool radioIntercept = false ) +{ + RegisterDialogueLine( name, alias, priority, radioDisplayName, team, radioIntercept ) +} + +void function RegisterDialogueLine( string name, string alias, int priority, string radioDisplayName, int team, bool radioIntercept ) +{ + if ( !DoesAliasExist( alias ) ) + CodeWarning( "Dialogue " + alias + " can't be registered because the alias does not exist" ) + + DialogueData data + data.name = name + data.alias = alias + data.priority = priority + data.cutoffRule = CUTOFF_NEVER + data.radioDisplayName = radioDisplayName + data.team = team + data.radioIntercept = radioIntercept + + file.registeredDialog.append( data ) + + Assert( file.registeredDialogIDs.find( name ) == -1, "Tried to register dialogue name twice: " + name ) + file.registeredDialogIDs.append( name ) + + Assert( file.registeredDialog.len() == file.registeredDialogIDs.len() ) +} + +void function RegisterPlayerConversation( BTConversation conversation ) +{ + Assert( conversation.name != "" ) + Assert( conversation.response_A_player_text != "" ) + Assert( conversation.response_A_player_alias != "" ) + + if ( conversation.waveformName != "" ) + Assert( conversation.waveformTeam >= 0 && conversation.waveformTeam <= 3 ) + + file.registeredConversations.append( conversation ) + + Assert( file.registeredConversationIDs.find( conversation.name ) == -1 ) + file.registeredConversationIDs.append( conversation.name ) + + Assert( file.registeredConversations.len() == file.registeredConversationIDs.len() ) +} + +#if SERVER +void function Dev_TestAllConversations() +{ + Assert( GetBugReproNum() == 1010 ) + Assert( GetPlayerArray().len() > 0 ) + entity player = GetPlayerArray()[0] + Assert( IsAlive( player ) ) + EndSignal( player, "OnDeath" ) + + foreach( BTConversation conversation in file.registeredConversations ) + { + thread PlayerConversation( conversation.name, player ) + wait 3.0 + StopConversationNow( player ) + } +} +#endif //SERVER + +void function SPDialogueInit() +{ + #if SERVER + RegisterSignal( "DialogueFinishedForID" ) + RegisterSignal( "ConversationFinishedForID" ) + RegisterSignal( "ConversationChoiceForID" ) + RegisterSignal( "AllDialogueFinished" ) + RegisterSignal( "ConversationEnded" ) + RegisterSignal( "fireSalvo" ) + AddClientCommandCallback( "DialogueFinishedForID", ClientCommand_DialogueFinishedForID ) + AddClientCommandCallback( "ConversationFinishedForID", ClientCommand_ConversationFinishedForID ) + AddClientCommandCallback( "ConversationChoiceForID", ClientCommand_ConversationChoiceForID ) + AddClientCommandCallback( "AllDialogueFinished", ClientCommand_AllDialogueFinished ) + #endif // SERVER + + #if CLIENT + RegisterSignal( "QueueUpdated" ) + RegisterSignal( "NextInQueue" ) + RegisterSignal( "RemovedFromQueue" ) + RegisterSignal( "DialogueChoice1" ) + RegisterSignal( "DialogueChoice2" ) + RegisterSignal( "DialogueChoiceTimeout" ) + RegisterSignal( "AbortConversationImmediately" ) + RegisterSignal( "AbortCurrentDialogue" ) + RegisterSignal( "Ronin_DialoguePlaying" ) + RegisterConCommandTriggeredCallback( "+scriptCommand1", Pressed_Choice1 ) + RegisterConCommandTriggeredCallback( "+scriptCommand2", Pressed_Choice2 ) + PrecacheParticleSystem( BT_EYE_GLOW ) + + thread QueueThink() + #endif + + var dataTable = GetDataTable( $"datatable/bt_player_conversations.rpak" ) + int rows = GetDatatableRowCount( dataTable ) + for ( int i = 0 ; i < rows ; i++ ) + { + string mapName = GetDataTableString( dataTable, i, 0 ) + + if ( mapName != GetMapName() && GetBugReproNum() != 1010 ) + continue + + BTConversation conversation + conversation.name = GetDataTableString( dataTable, i, 1 ) + conversation.btStartAlias = GetDataTableString( dataTable, i, 2 ) + conversation.response_A_player_text = GetDataTableString( dataTable, i, 3 ) + conversation.response_A_player_alias = GetDataTableString( dataTable, i, 4 ) + conversation.response_A_bt_respond_alias = GetDataTableString( dataTable, i, 5 ) + conversation.response_B_player_text = GetDataTableString( dataTable, i, 6 ) + conversation.response_B_player_alias = GetDataTableString( dataTable, i, 7 ) + conversation.response_B_bt_respond_alias = GetDataTableString( dataTable, i, 8 ) + conversation.player_closing_alias_A = GetDataTableString( dataTable, i, 9 ) + conversation.player_closing_alias_B = GetDataTableString( dataTable, i, 10 ) + conversation.response_A_ends_conversation = GetDataTableBool( dataTable, i, 11 ) + conversation.response_B_ends_conversation = GetDataTableBool( dataTable, i, 12 ) + conversation.allowReplay = GetDataTableBool( dataTable, i, 13 ) + conversation.waveformName = GetDataTableString( dataTable, i, 14 ) + conversation.waveformTeam = GetDataTableInt( dataTable, i, 15 ) + conversation.nextConversationA = GetDataTableString( dataTable, i, 16 ) + conversation.nextConversationB = GetDataTableString( dataTable, i, 17 ) + conversation.response_A_available = conversation.response_A_player_text != "" + conversation.response_B_available = conversation.response_B_player_text != "" + + if ( GetBugReproNum() == 1010 ) + conversation.btStartAlias = "" + + RegisterPlayerConversation( conversation ) + } +} + +BTConversation function GetPlayerConversationByName( string name ) +{ + int convID = GetPlayerConversationID_ByName( name ) + return file.registeredConversations[convID] +} + +int function GetPlayerConversationID_ByName( string name ) +{ + Assert( file.registeredConversationIDs.find( name ) >= 0, "Conversation " + name + " is not registered" ) + return file.registeredConversationIDs.find( name ) +} + +entity function GetBuddyTitanDialogueEnt( entity buddyTitan ) +{ + entity dialogueEnt + entity titanSoul = buddyTitan.GetTitanSoul() + if ( titanSoul ) + dialogueEnt = titanSoul.GetTitanSoulNetEnt( "dialogueEnt" ) + return dialogueEnt +} + +#if SERVER + + // called by code when an animation does { event AE_SV_VSCRIPT_CALLBACK FrameNumber "some string" } + // and by a script function OnFootstep, apparently. + void function CodeCallback_OnServerAnimEvent( entity ent, string eventName ) + { + PerfStart( PerfIndexServer.CB_OnServerAnimEvent ) + if ( HasAnimEvent( ent, eventName ) ) + thread RunAnimEventCallbacks( ent, eventName ) + + if ( eventName in svGlobal.globalAnimEventCallbacks ) + { + thread svGlobal.globalAnimEventCallbacks[ eventName ]( ent ) + PerfEnd( PerfIndexServer.CB_OnServerAnimEvent ) + return + } + + + // couldn't find this eventName on the ent or the global anim events, + // so try breaking it down. If we didn't find it, it means + // script needs to handle the event, even if it is just to + // do nothing with it + + array tokens = split( eventName, ":" ) + string tokenName = tokens[0] + + switch ( tokenName ) + { + case "worldsound": + GlobalAnimEventWithStringParameter_WorldSound( ent, tokens[1] ) + break + + case "signal": + SendSignalFromTokens( ent, tokens ) + break + + case "flagset": + GlobalAnimEventWithStringParameter_FlagSet( ent, tokens[1] ) + break + + case "dialogue": + // Make sure that animation triggered dialogue uses the correct priority and skips the queue + string name = tokens[1] + Assert( file.registeredDialogIDs.find( name ) >= 0, "Dialogue line " + name + " is not registered" ) + int aliasID = file.registeredDialogIDs.find( name ) + DialogueData data = file.registeredDialog[ aliasID ] + Assert( data.priority == PRIORITY_NO_QUEUE, "Dialogue " + name + " triggered via qc must use PRIORITY_NO_QUEUE" ) + thread PlayDialogue( name, ent ) + break + + case "fireViperSalvo": + int value = tokens[1].tointeger() + ent.Signal( "fireSalvo", { num = value } ) + break + + case "conversation": + thread PlayerConversation( tokens[1], GetPlayerArray()[0], ent ) + break + } + + PerfEnd( PerfIndexServer.CB_OnServerAnimEvent ) + } + + void function PlayDialogue( string name, entity speaker, float delay = 0 ) + { + array players = GetPlayerArray_Alive() + for ( int i = players.len() - 1 ; i >= 0 ; i-- ) + { + if ( i > 0 ) + thread PlayDialogueForPlayer( name, players[i], speaker, delay ) + else + waitthread PlayDialogueForPlayer( name, players[i], speaker, delay ) + } + } + + void function PlayBTDialogue( string name, float delay = 0 ) + { + array players = GetPlayerArray_Alive() + for ( int i = players.len() - 1 ; i >= 0 ; i-- ) + { + entity speaker + entity petTitan = players[i].GetPetTitan() + if ( IsValid( petTitan ) ) + speaker = GetBuddyTitanDialogueEnt( petTitan ) + else + speaker = GetBuddyTitanDialogueEnt( players[i] ) + + // Catch issues like BT is not created yet so the dialogue ent doesn't exist yet. In this case we just use the player for sound position, which gives us the same result anyhow. + if ( !IsValid( speaker ) ) + speaker = players[i] + + if ( i > 0 ) + thread PlayDialogueForPlayer( name, players[i], speaker, delay ) + else + waitthread PlayDialogueForPlayer( name, players[i], speaker, delay ) + } + } + + void function PlayGabbyDialogue( string name, entity speaker, string anim = "face_generic_talker_flat" ) + { + speaker.EndSignal( "OnDestroy" ) + + //for the next game we shouldn't have variants of dialogue and functionality should be done through a CSV - Chad + speaker.Anim_ScriptedAddGestureSequence( anim, false ) + PlayDialogue( name, speaker ) + speaker.Anim_ScriptedRemoveAllGestures() + } + + void function StopDialogue() + { + array players = GetPlayerArray_Alive() + foreach ( player in players ) + Remote_CallFunction_NonReplay( player, "ServerCallback_AbortCurrentDialogue" ) + } + + void function PlayDialogueForPlayer( string name, entity player, entity speaker, float delay = 0 ) + { + if ( !IsValid( speaker ) || !IsValid( player ) ) + return + EndSignal( speaker, "OnDestroy" ) + EndSignal( player, "OnDestroy" ) + + Assert( file.registeredDialogIDs.find( name ) >= 0, "Dialogue line " + name + " is not registered" ) + Assert( player.IsPlayer(), "Tried to play dialogue " + name + " to a non-player" ) + + if ( delay > 0 ) + wait delay + + int aliasID = file.registeredDialogIDs.find( name ) + + file.dialoguePlaying = true + + OnThreadEnd( + function() : ( aliasID ) + { + file.dialoguePlaying = false + if ( aliasID in file.waitingOnDialogue ) + delete file.waitingOnDialogue[aliasID] + } + ) + + if ( !(aliasID in file.waitingOnDialogue) ) + file.waitingOnDialogue[aliasID] <- true + + if ( speaker.IsNPC() || speaker.IsPlayer() || speaker.GetClassName() == "script_mover_lightweight" ) + { + // Players and NPCs will be on the client so we can play on the entity + Remote_CallFunction_NonReplay( player, "ServerCallback_PlayDialogueOnEntity", aliasID, speaker.GetEncodedEHandle() ) + } + else + { + // Other entities like info_targets may not be on the client so we just send the position to emit the sound from (might make this better later) + vector pos = speaker.GetOrigin() + Remote_CallFunction_NonReplay( player, "ServerCallback_PlayDialogueAtPosition", aliasID, pos.x, pos.y, pos.z ) + } + + while( true ) + { + table signalData = WaitSignal( level, "DialogueFinishedForID", "AllDialogueFinished" ) + if ( string( signalData.signal ) == "AllDialogueFinished" ) + break + if ( int( signalData.aliasID ) == aliasID ) + break + if ( !(aliasID in file.waitingOnDialogue) ) + break + } + } + + void function PlayerConversation( string name, entity player, entity speaker = null ) + { + if ( !IsValid( player ) ) + return + EndSignal( player, "OnDeath" ) + + int speakerEHandle = -1 + if ( speaker != null ) + { + Assert( IsValid( speaker ) ) + speakerEHandle = speaker.GetEncodedEHandle() + } + + Assert( player.IsPlayer(), "Tried to play conversation " + name + " to a non-player" ) + + int conversationID = GetPlayerConversationID_ByName( name ) + + //Assert( !file.conversationsPlaying, "Tried to play conversation " + name + " but a conversation is already in progress" ) + + file.conversationsPlaying++ + + OnThreadEnd( + function() : () + { + file.conversationsPlaying-- + } + ) + + Remote_CallFunction_NonReplay( player, "ServerCallback_PlayerConversation", conversationID, speakerEHandle ) + + array choices + while( true ) + { + table signalData = WaitSignal( level, "ConversationFinishedForID", "AllDialogueFinished" ) + //PrintTable( signalData ) + if ( string( signalData.signal ) == "AllDialogueFinished" ) + break + + if ( int( signalData.aliasID ) == conversationID ) + { + if ( signalData.choice1 != -1 ) + choices.append( int( signalData.choice1 ) ) + if ( signalData.choice2 != -1 ) + choices.append( int( signalData.choice2 ) ) + break + } + } + + printt( "Conversation Ended" ) + Signal( player, "ConversationEnded" ) + } + + void function StopConversation( entity player ) + { + StopConversationWithStopType( player, CONVERSATION_STOP_GRACEFUL ) + } + + void function StopConversationNow( entity player ) + { + StopConversationWithStopType( player, CONVERSATION_STOP_IMMEDIATE ) + } + + void function StopConversationWithStopType( entity player, int stopType ) + { + Assert( player.IsPlayer(), "Tried to stop conversation on a non-player" ) + Assert( stopType == CONVERSATION_STOP_GRACEFUL || stopType == CONVERSATION_STOP_IMMEDIATE ) + + if ( file.conversationsPlaying == 0 ) + return + + Remote_CallFunction_NonReplay( player, "ServerCallback_StopConversation", stopType ) + } + + void function AddConversationCallback( string name, void functionref( int ) callbackFunc ) + { + BTConversation data = GetPlayerConversationByName( name ) + data.callbackFuncs.append( callbackFunc ) + } + + bool function ClientCommand_DialogueFinishedForID( entity player, array args ) + { + string aliasID = args[ 0 ] + table signalData = { aliasID = aliasID } + if ( aliasID.tointeger() in file.waitingOnDialogue ) + delete file.waitingOnDialogue[aliasID.tointeger()] + Signal( level, "DialogueFinishedForID", signalData ) + return true + } + + bool function ClientCommand_AllDialogueFinished( entity player, array args ) + { + // This happens when a save is loaded. Server will be waiting to hear back from client that a dialogue alias is finished but client was reset, so we tell the server the line has stopped playing + Signal( level, "AllDialogueFinished" ) + return true + } + + bool function ClientCommand_ConversationFinishedForID( entity player, array args ) + { + table signalData = {} + + signalData.aliasID <- args[ 0 ] + + signalData.choice1 <- -1 + if ( args.len() >= 2 ) + signalData.choice1 = args[1] + + signalData.choice2 <- -1 + if ( args.len() >= 3 ) + signalData.choice2 = args[2] + + Signal( level, "ConversationFinishedForID", signalData ) + return true + } + + bool function ClientCommand_ConversationChoiceForID( entity player, array args ) + { + Assert( args.len() == 2 ) + int conversationID = args[ 0 ].tointeger() + int choice = args[ 1 ].tointeger() + + BTConversation convoData = file.registeredConversations[conversationID] + foreach ( callbackFunc in convoData.callbackFuncs ) + { + thread callbackFunc( choice ) + } + + return true + } + + bool function IsDialoguePlaying() + { + return file.dialoguePlaying + } + + bool function IsConversationPlaying() + { + return file.conversationsPlaying > 0 + } + +#endif //SERVER + + + + + + +#if CLIENT + + QueueItem function QueueAndWait( int priority, int cutoffRule, string debugRef = "" ) + { + // Creates an ID and adds it to the queue. Waits until that ID is up to be played or canceled and returns the result + + // Create queue item + QueueItem queueItem + queueItem.priority = priority + queueItem.cutoffRule = cutoffRule + queueItem.timeAdded = Time() + queueItem.debugRef = debugRef + + // Instant priority items don't use the queue. This may changed based on our needs though, if we need this line to cut off whatever is playing + if ( priority == PRIORITY_NO_QUEUE ) + return queueItem + + // Add it to the queue + _AddToQueue( queueItem ) + + // Wait for it to be up in the queue + table result = WaitSignal( queueItem, "NextInQueue", "RemovedFromQueue" ) + //PrintTable( result ) + + queueItem.success = ( result.signal == "NextInQueue" ) + //printt( "queueItem.success:", queueItem.success ) + + // Return the queue item back to the caller so it can removed it + return queueItem + } + + void function _AddToQueue( QueueItem queueItem ) + { + Assert( !file.queue.contains( queueItem ) ) + + file.queue.append( queueItem ) + file.queue.sort( QueueSort ) + + // Remove anything in the queue that is lower priority + if ( file.queue.len() > 1 ) + { + for ( int i = file.queue.len() - 1 ; i >= 0 ; i-- ) + { + if ( file.queue[i].priority >= queueItem.priority ) + continue + + RemoveFromQueue( file.queue[i] ) + + if ( i == 0 ) + AbortCurrentDialogue() + } + } + + // Tell the queue think function that we have added something in case it was sleeping + Signal( level, "QueueUpdated" ) + } + + void function RemoveFromQueue( QueueItem queueItem ) + { + // Call this to tell the queue that you are done with the current item and it should move onto the next + Signal( queueItem, "RemovedFromQueue" ) + if ( file.queue.contains( queueItem ) ) + file.queue.removebyvalue( queueItem ) + } + + int function QueueSort( QueueItem queueItem1, QueueItem queueItem2 ) + { + if ( queueItem1.active ) + return -1 + if ( queueItem2.active ) + return 1 + + if ( queueItem1.priority > queueItem2.priority ) + return -1 + if ( queueItem1.priority < queueItem2.priority ) + return 1 + + if ( queueItem1.timeAdded < queueItem2.timeAdded ) + return -1 + if ( queueItem1.timeAdded > queueItem2.timeAdded ) + return 1 + + return 0 + } + + void function QueueThink() + { + QueueItem currentItem + while( true ) + { + if ( file.queue.len() == 0 ) + { + if ( DEBUG_QUEUE_PRINTS ) + printt( "Queue empty - waiting for signal" ) + WaitSignal( level, "QueueUpdated" ) + continue + } + + currentItem = file.queue[0] + Signal( currentItem, "NextInQueue" ) + + // Todo: This shouldn't be here, it should be in the dialogue thread because queue is generic, can be used for non-dialogue stuff. Don't want to move it now, but should move next game. + CancelBossConversation() + + currentItem.active = true + if ( DEBUG_QUEUE_PRINTS ) + printt( "waiting for script to clear the queue current item" ) + while( file.queue.len() > 0 && currentItem == file.queue[0] ) + { + if ( DEBUG_QUEUE_PRINTS ) + { + printt( "Queue:" ) + foreach( int i, QueueItem queueItem in file.queue ) + { + if ( queueItem.active ) + printt( " ", i, queueItem.timeAdded, "PRIORITY", queueItem.priority, queueItem.debugRef, "ACTIVE!" ) + else + printt( " ", i, queueItem.timeAdded, "PRIORITY", queueItem.priority, queueItem.debugRef ) + } + } + WaitFrame() + } + } + } + + void function ServerCallback_PlayDialogueOnEntity( int aliasID, int speakerEHandle ) + { + entity speaker = GetEntityFromEncodedEHandle( speakerEHandle ) + thread PlayDialogueAndNotifyServer( aliasID, speaker ) + } + + void function ServerCallback_PlayDialogueAtPosition( int aliasID, float x, float y, float z ) + { + thread PlayDialogueAndNotifyServer( aliasID, null, x, y, z ) + } + + void function PlayDialogueAndNotifyServer( int aliasID, entity speaker, float x = 0, float y = 0, float z = 0 ) + { + Assert( aliasID < file.registeredDialogIDs.len() ) + Assert( aliasID < file.registeredDialog.len() ) + + DialogueData data = file.registeredDialog[ aliasID ] + + if ( DEBUG_QUEUE_PRINTS ) + { + printt( "Dialogue Added to Queue" ) + printt( " name:", data.name ) + } + + QueueItem queueItem = QueueAndWait( data.priority, data.cutoffRule, data.name ) + + // The queue wait is not successful it if was removed before it got in turn (caused by higher priority lines taking over) + if ( !queueItem.success ) + return + + // RONIN - DIALOGUE SIGNALS + table signalData = { + name = data.name + } + Signal( level, "Ronin_DialoguePlaying", signalData ) + + /* + printt( "#################################" ) + printt( "Playing Dialogue" ) + printt( " name:", data.name ) + printt( " alias:", data.alias ) + printt( " priority:", data.priority ) + printt( "#################################" ) + */ + // shortened to one line to reduce console spam- slayback + printt( "Playing Dialogue: name:", data.name, " || alias:", data.alias, " || priority:", data.priority ) + + float duration = GetSoundDuration( data.alias ) + var waveformRUI + + if ( data.radioDisplayName != "" ) + { + waveformRUI = CreateWaveform( data.radioDisplayName, data.team, duration, null, data.radioIntercept ) + } + + if ( IsValid( speaker ) ) + { + EndSignal( speaker, "OnDeath" ) + EndSignal( speaker, "OnDestroy" ) + } + + entity player = GetLocalClientPlayer() + EndSignal( player, "AbortCurrentDialogue" ) + + OnThreadEnd( + function() : ( player, aliasID, waveformRUI, queueItem ) + { + // Notify the server that the alias finished playing + if ( IsValid( player ) ) + player.ClientCommand( "DialogueFinishedForID " + aliasID ) + if ( IsValid ( waveformRUI ) ) + thread DestroyWaveform( waveformRUI ) + file.currentDialoguePlayingHandle = null + RemoveFromQueue( queueItem ) + } + ) + + vector pos = < x, y, z > + + if ( IsValid( speaker ) ) + { + if ( SpeakerIsBuddy( speaker, player ) ) + thread BuddyTitanEyeFlash( player.GetPetTitan(), duration ) + + if ( speaker == player.GetPetTitan() ) + { + speaker = GetBuddyTitanDialogueEnt( speaker ) + Assert( speaker ) + } + + file.currentDialoguePlayingHandle = EmitSoundOnEntity( speaker, data.alias ) + } + else if ( pos != < 0, 0, 0 > ) + { + file.currentDialoguePlayingHandle = EmitSoundAtPosition( TEAM_UNASSIGNED, pos, data.alias ) + } + + wait duration + } + + bool function SpeakerIsBuddy( entity speaker, entity player ) + { + if ( !IsValid( player.GetPetTitan() ) ) + return false + + if ( speaker == player.GetPetTitan() ) + return true + + if ( speaker == GetBuddyTitanDialogueEnt( player.GetPetTitan() ) ) + return true + + return false + } + + void function ServerCallback_AbortCurrentDialogue() + { + AbortCurrentDialogue() + } + + void function AbortCurrentDialogue() + { + if ( file.currentDialoguePlayingHandle != null ) + { + StopSound( file.currentDialoguePlayingHandle ) + Signal( GetLocalClientPlayer(), "AbortCurrentDialogue" ) + } + } + + void function ServerCallback_PlayerConversation( int conversationID, int speakerEHandle ) + { + file.abortConversation = -1 + thread PlayConversationAndNotifyServer( conversationID, speakerEHandle ) + } + + void function ServerCallback_StopConversation( int stopType ) + { + string stopTypeString = stopType == CONVERSATION_STOP_GRACEFUL ? "CONVERSATION_STOP_GRACEFUL" : "CONVERSATION_STOP_IMMEDIATE" + + printt( "STOPING CONVERSATION with StopType", stopTypeString ) + + file.abortConversation = stopType + if ( stopType == CONVERSATION_STOP_IMMEDIATE ) + Signal( level, "AbortConversationImmediately" ) + } + + void function ResetAbortConversation() + { + WaitFrame() + file.abortConversation = -1 + } + + void function PlayConversationAndNotifyServer( int conversationID, int speakerEHandle, bool queue = true ) + { + Assert( conversationID < file.registeredConversationIDs.len() ) + Assert( conversationID < file.registeredConversations.len() ) + + entity speaker = null + if ( speakerEHandle != -1 ) + { + speaker = GetEntityFromEncodedEHandle( speakerEHandle ) + Assert( IsValid( speaker ), "Conversation can't play on the client because it can't find entity with encoded eHandle " + speakerEHandle ) + } + + BTConversation data = file.registeredConversations[ conversationID ] + + // Check that new choices are available + Assert( data.response_A_available || data.response_B_available, "Tried to play conversation " + data.name + " with no choices remaining. This is probably because the conversation already ran once. To allow this, set \"Allow Replay\" to true in the datatable." ) + + QueueItem queueItem + if ( queue ) + queueItem = QueueAndWait( PRIORITY_NORMAL, CUTOFF_NEVER, data.name ) + + printt( "#################################" ) + printt( "Playing Conversation" ) + printt( " name:", data.name ) + printt( " speaker:", speaker ) + printt( "#################################" ) + + entity player = GetLocalClientPlayer() + Assert( IsValid( player ) ) + + EndSignal( player, "OnDeath" ) + EndSignal( player, "OnDestroy" ) + EndSignal( level, "AbortConversationImmediately" ) + if ( IsValid( speaker ) ) + EndSignal( speaker, "OnDeath" ) // We only end signal if the user specified a custom speaker. If it's BT we don't do this because BT can die mid conversation if player embarks, but we still want to continue the conversation + + array choicesPicked + + // Keeps track of what entity is playing a sound and what alias it is so we can stop it later if we need to + table currentLineData + currentLineData.speaker <- null + currentLineData.alias <- "" + + table ruiTable + ruiTable.rui <- null + ruiTable.waveformRUI <- null + + file.conversationsPlaying++ + ResetAbilityBindings( player, player.GetPlayerClass() ) + + OnThreadEnd( + function() : ( player, conversationID, choicesPicked, currentLineData, queueItem, ruiTable ) + { + file.conversationsPlaying-- + + // Notify the server that the alias finished playing + if ( IsValid( player ) ) + { + ResetAbilityBindings( player, player.GetPlayerClass() ) + + string command = "ConversationFinishedForID " + conversationID + foreach( int choice in choicesPicked ) + command += " " + choice.tostring() + + //printt( "Sending client command ", command ) + player.ClientCommand( command ) + } + + if ( file.abortConversation == CONVERSATION_STOP_IMMEDIATE ) + { + if ( IsValid( ruiTable.rui ) ) + RuiDestroyIfAlive( ruiTable.rui ) + + if ( ruiTable.waveformRUI != null ) + { + thread DestroyWaveform_Immediate( ruiTable.waveformRUI ) + ruiTable.waveformRUI = null + } + } + + // If we are stopping the conversation immediately we must stop the current playing sound alias, and also destroy the RUI that may be up + if ( file.abortConversation == CONVERSATION_STOP_IMMEDIATE && IsValid( currentLineData.speaker ) && currentLineData.alias != "" ) + StopSoundOnEntity( currentLineData.speaker, currentLineData.alias ) + + //printt( "Removing conversation from queue" ) + RemoveFromQueue( queueItem ) + + thread ResetAbortConversation() + } + ) + + //########################### + // Play BT line if it exists + //########################### + + float duration + if ( data.btStartAlias != "" ) + { + duration = GetSoundDuration( data.btStartAlias ) + if ( speakerEHandle == -1 ) + { + speaker = player.GetPetTitan() + if ( !IsValid( speaker ) ) + speaker = player + } + if ( SpeakerIsBuddy( speaker, player ) ) + thread BuddyTitanEyeFlash( player.GetPetTitan(), duration ) + + speaker = GetConversationSpeakerEntityIfBT( player, speaker ) + + currentLineData.speaker = speaker + currentLineData.alias = data.btStartAlias + + ruiTable.waveformRUI = null + if ( data.waveformName != "" ) + ruiTable.waveformRUI = CreateWaveform( data.waveformName, data.waveformTeam, duration ) + + // RONIN - DIALOGUE SIGNALS + table signalData = { + name = data.name + } + Signal( level, "Ronin_DialoguePlaying", signalData ) + + EmitSoundOnEntity( speaker, data.btStartAlias ) + wait duration + + if ( ruiTable.waveformRUI != null ) + { + thread DestroyWaveform( ruiTable.waveformRUI ) + ruiTable.waveformRUI = null + } + } + + if ( file.abortConversation != -1 ) + return + + int choice + var soundObj + while( true ) + { + //########################### + // Wait for player response + //########################### + + choice = GetConversationChoice( player, data, ruiTable ) + choicesPicked.append( choice ) + if ( choice == 0 ) + { + DoConversationCallbacks( player, conversationID, choice ) + return + } + + //########################### + // Player speaks choice + //########################### + + wait 0.2 + + string responseAlias = choice == 1 ? data.response_A_player_alias : data.response_B_player_alias + duration = GetSoundDuration( responseAlias ) + currentLineData.speaker = player + currentLineData.alias = responseAlias + soundObj = EmitSoundOnEntity( player, responseAlias ) + WaitSignal( soundObj, "OnSoundFinished" ) + + DoConversationCallbacks( player, conversationID, choice ) + + //############################### + // BT Responds to players choice + //############################### + + wait 0.2 + + responseAlias = choice == 1 ? data.response_A_bt_respond_alias : data.response_B_bt_respond_alias + if ( responseAlias != "" ) + { + duration = GetSoundDuration( responseAlias ) + if ( speakerEHandle == -1 ) + { + speaker = player.GetPetTitan() + } + + if ( !IsValid( speaker ) ) + speaker = player + + Assert( IsValid( speaker ) ) + if ( SpeakerIsBuddy( speaker, player ) ) + thread BuddyTitanEyeFlash( player.GetPetTitan(), duration ) + + speaker = GetConversationSpeakerEntityIfBT( player, speaker ) + + currentLineData.speaker = speaker + currentLineData.alias = responseAlias + + ruiTable.waveformRUI = null + if ( data.waveformName != "" ) + ruiTable.waveformRUI = CreateWaveform( data.waveformName, data.waveformTeam, duration ) + + soundObj = EmitSoundOnEntity( speaker, responseAlias ) + WaitSignal( soundObj, "OnSoundFinished" ) + + if ( ruiTable.waveformRUI != null ) + { + thread DestroyWaveform( ruiTable.waveformRUI ) + ruiTable.waveformRUI = null + } + } + + if ( file.abortConversation != -1 ) + return + + //################################ + // Player closing line, if exists + //################################ + + string closingLine = choice == 1 ? data.player_closing_alias_A : data.player_closing_alias_B + if ( data.player_closer_played && data.player_closing_alias_A == data.player_closing_alias_B ) + closingLine = "" + if ( closingLine != "" ) + { + wait 0.2 + data.player_closer_played = true + duration = GetSoundDuration( closingLine ) + currentLineData.speaker = player + currentLineData.alias = closingLine + soundObj = EmitSoundOnEntity( player, closingLine ) + WaitSignal( soundObj, "OnSoundFinished" ) + } + + if ( file.abortConversation != -1 ) + return + + //################################################## + // Return if we only let the player make one choice + //################################################## + + if ( choice == 1 && data.response_A_ends_conversation ) + break + + if ( choice == 2 && data.response_B_ends_conversation ) + break + + // return if no more responses are available + if ( !data.response_A_available && !data.response_B_available ) + break + } + + // Auto start the next conversation if one is specified + if ( choice == 1 && data.nextConversationA != "" ) + { + Assert( file.registeredConversationIDs.contains( data.nextConversationA ), "Tried to use nextConversationA to invalid conversation name" ) + int nextConversationID = file.registeredConversationIDs.find( data.nextConversationA ) + PlayConversationAndNotifyServer( nextConversationID, speakerEHandle, false ) + } + else if ( choice == 2 && data.nextConversationB != "" ) + { + Assert( file.registeredConversationIDs.contains( data.nextConversationB ), "Tried to use nextConversationB to invalid conversation name" ) + int nextConversationID = file.registeredConversationIDs.find( data.nextConversationB ) + PlayConversationAndNotifyServer( nextConversationID, speakerEHandle, false ) + } + } + + entity function GetConversationSpeakerEntityIfBT( entity player, entity speaker ) + { + //Todo: this was a late fix, but next game should be combined with the same fix that was done for dialogue + if ( speaker == player || speaker == player.GetPetTitan() ) + { + entity petTitan = player.GetPetTitan() + if ( IsValid( petTitan ) ) + speaker = GetBuddyTitanDialogueEnt( petTitan ) + else + speaker = GetBuddyTitanDialogueEnt( player ) + + if ( !IsValid( speaker ) ) + speaker = player + + Assert( speaker ) + } + + return speaker + } + + bool function IsConversationPlaying() + { + return file.conversationsPlaying > 0 + } + + void function DoConversationCallbacks( entity player, int conversationID, int choice ) + { + //################################################## + // Notify server per choice ( for callbacks ) + //################################################## + if ( !IsValid( player ) ) + return + + string command = "ConversationChoiceForID " + conversationID + " " + choice.tostring() + player.ClientCommand( command ) + } + + int function GetConversationChoice( entity player, BTConversation data, table ruiTable ) + { + //################################### + // Show the options RUI + //################################### + + // RUI Choice Box + ruiTable.rui = RuiCreate( $"ui/conversation.rpak", clGlobal.topoFullScreen, RUI_DRAW_HUD, 0 ) + RuiSetFloat( ruiTable.rui, "startTime", Time() ) + RuiSetFloat( ruiTable.rui, "introDuration", CONVERSATION_INTRO_DURATION ) + RuiSetFloat( ruiTable.rui, "timer", CONVERSATION_TIMEOUT ) + RuiSetResolutionToScreenSize( ruiTable.rui ) + + int numChoices = 0 + + if ( data.response_A_player_text != "" ) + { + numChoices++ + RuiSetString( ruiTable.rui, "text1", data.response_A_player_text ) + RuiSetBool( ruiTable.rui, "choice1Available", data.response_A_available ) + RuiSetBool( ruiTable.rui, "choice1WasSelected", data.response_A_wasSelected ) + } + if ( data.response_B_player_text != "" ) + { + numChoices++ + RuiSetString( ruiTable.rui, "text2", data.response_B_player_text ) + RuiSetBool( ruiTable.rui, "choice2Available", data.response_B_available ) + RuiSetBool( ruiTable.rui, "choice2WasSelected", data.response_B_wasSelected ) + } + + RuiSetInt( ruiTable.rui, "numChoices", numChoices ) + + EmitSoundOnEntity( player, "UI_PlayerDialogue_Selection" ) + + //########################################### + // Wait for user selection or timeout + //########################################### + + table results + if ( data.response_A_available && data.response_B_available ) + { + printt( "Waiting for A or B" ) + thread DialogueChoiceTimeout( player, "DialogueChoice1", "DialogueChoice2" ) + results = WaitSignal( player, "DialogueChoice1", "DialogueChoice2", "DialogueChoiceTimeout" ) + } + else if ( data.response_A_available ) + { + printt( "Waiting for A" ) + thread DialogueChoiceTimeout( player, "DialogueChoice1" ) + results = WaitSignal( player, "DialogueChoice1", "DialogueChoiceTimeout" ) + } + else if ( data.response_B_available ) + { + printt( "Waiting for B" ) + thread DialogueChoiceTimeout( player, "DialogueChoice2" ) + results = WaitSignal( player, "DialogueChoice2", "DialogueChoiceTimeout" ) + } + + //################################################# + // User has made selection or timed out, handle it + //################################################# + + int choice + float responseDuration = 0.0 + switch( results.signal ) + { + case "DialogueChoice1": + choice = 1 + responseDuration = GetSoundDuration( data.response_A_player_alias ) + + if ( !data.allowReplay ) + data.response_A_available = false + + data.response_A_wasSelected = true + break + + case "DialogueChoice2": + choice = 2 + responseDuration = GetSoundDuration( data.response_B_player_alias ) + + if ( !data.allowReplay ) + data.response_B_available = false + + data.response_B_wasSelected = true + break + + case "DialogueChoiceTimeout": + default: + choice = 0 + + if ( !data.allowReplay ) + { + data.response_A_available = false + data.response_B_available = false + } + + break + } + + float textFadeOutDuration = choice == 0 ? CONVERSATION_TEXT_REMOVE_DURATION_TIMEOUT : CONVERSATION_TEXT_REMOVE_DURATION + + // Tell the RUI we have made a selection, or lack of one. + RuiSetFloat( ruiTable.rui, "choiceMadeTime", Time() ) + RuiSetFloat( ruiTable.rui, "choiceDuration", responseDuration ) + RuiSetFloat( ruiTable.rui, "textRemoveDuration", textFadeOutDuration ) + RuiSetInt( ruiTable.rui, "choiceMade", choice ) + + if ( choice == 0 ) + EmitSoundOnEntity( player, "UI_PlayerDialogue_Notification" ) + else + EmitSoundOnEntity( player, "ui_holotutorial_Analyzingfinish" ) + + return choice + } + + void function DialogueChoiceTimeout( entity player, cancelTimeoutSignal_A = "", cancelTimeoutSignal_B = "" ) + { + EndSignal( player, "OnDeath" ) + EndSignal( player, "OnDestroy" ) + if ( cancelTimeoutSignal_A != "" ) + EndSignal( player, cancelTimeoutSignal_A ) + if ( cancelTimeoutSignal_B != "" ) + EndSignal( player, cancelTimeoutSignal_B ) + + wait CONVERSATION_INTRO_DURATION + CONVERSATION_TIMEOUT + + if ( IsValid( player ) ) + Signal( player, "DialogueChoiceTimeout" ) + } + + void function Pressed_Choice1( entity player ) + { + Signal( player, "DialogueChoice1" ) + } + + void function Pressed_Choice2( entity player ) + { + Signal( player, "DialogueChoice2" ) + } + + void function BuddyTitanEyeFlash( entity bt, float duration ) + { + EndSignal( bt, "OnDestroy" ) + EndSignal( level, "AbortConversationImmediately" ) + + int effectIndex = GetParticleSystemIndex( BT_EYE_GLOW ) + int attachID = bt.LookupAttachment( "EYEGLOW" ) + int fxID = StartParticleEffectOnEntity( bt, effectIndex, FX_PATTACH_POINT_FOLLOW, attachID ) + + OnThreadEnd( + function() : ( fxID ) + { + EffectStop( fxID, true, true ) + } + ) + + wait duration + } + +#endif // CLIENT \ No newline at end of file diff --git a/ronin/scripts/vscripts/timer/cl_timer_overlay.nut b/ronin/scripts/vscripts/timer/cl_timer_overlay.nut index 9c581ad..f1b5c07 100644 --- a/ronin/scripts/vscripts/timer/cl_timer_overlay.nut +++ b/ronin/scripts/vscripts/timer/cl_timer_overlay.nut @@ -9,6 +9,7 @@ global function AddCallback_TrackingStarted global function Facts_DialoguePlayed global function GetCrosshairWallNormal global function AddCallback_DialoguePlayed +global function TimerStarted global const float SP_LEVEL_TRANSITION_FADETIME = 1.5 global const float SP_LEVEL_TRANSITION_HOLDTIME = 3.0 @@ -53,6 +54,13 @@ void function Delayed_TimerOverlay_Init() RegisterConCommandTriggeredCallback( "ingamemenu_activate", HideTimer ) thread UpdateTimerHUD() //RegisterConCommandTriggeredCallback( "reload", ResetStartPointValue ) + +} + +void function TimerStarted( int seconds, int microseconds) +{ + entity player =GetLocalClientPlayer() + printt(format("%i.%03i", seconds, microseconds)) } void function HideTimer( entity player = null ) @@ -65,7 +73,7 @@ void function SetTimerVisible(bool visible) { if (file.timer == null) return - Hud_SetVisible( file.timer, visible ) + Hud_SetVisible( file.timer, visible && IsSingleplayer() ) } void function SetTime( int seconds, int microseconds, int levelSeconds, int levelMicroseconds, bool runInvalidated, string delta, string levelDelta, string previousDelta, string previousLevelDelta ) @@ -181,6 +189,39 @@ void function UpdateTimerHUD() RunUIScript("SetRunOver") isRunOver = true } + + if (GetMapName() == "sp_crashsite") { + BT7274IL_CheckBattery1() + BT7274IL_CheckBattery2() + } + + if (GetMapName() == "sp_sewers1" /* && subsplits enabled */) + { + BloodAndRustIL_StartCallbacks() + BloodAndRustIL_CheckDoorTrigger() + BloodAndRustIL_CheckEmbark() + } + + if (GetMapName() == "sp_boomtown_end") { + IntoTheAbyss3IL_CheckEmbark() + } + + if (GetMapName() == "sp_hub_timeshift") { + EffectAndCause1IL_CheckHelmet() + } + + if (GetMapName() == "sp_timeshift_spoke02") { + EffectAndCause2IL_StartCallbacks() + EffectAndCause2IL_CheckDialogue() + EffectAndCause2IL_CheckHellroom() + EffectAndCause2IL_CheckVent() + } + + if (GetMapName() == "sp_beacon_spoke0") { + Beacon2IL_Init() + Beacon2IL_CheckDeathWarp() + Beacon2IL_CheckHeatsink() + } } if (GetMapName() == "sp_skyway_v1" && FoldWeapon_HasLevelEnded() && !isRunOver) { @@ -358,6 +399,348 @@ bool function FoldWeapon_HasLevelEnded() return origin.x < -10000 && origin.y > 0 && IsInCutscene() } +// subsplits lol +// BT +bool btGrabbedBattery1 = false +void function BT7274IL_CheckBattery1() { + if (!btGrabbedBattery1) { + entity player = GetLocalClientPlayer() + if (!IsValid( player ) || !IsAlive( player )) + return + + vector origin = player.GetOrigin() + if (DistanceSqr(origin, < -4568, -3669, 2110 >) < 25000 && IsInCutscene()){ + RunUIScript("SplitWithName", "Battery 1") + btGrabbedBattery1 = true + } + } +} + +bool btGrabbedBattery2 = false +void function BT7274IL_CheckBattery2() { + if (!btGrabbedBattery2) { + entity player = GetLocalClientPlayer() + if (!IsValid( player ) || !IsAlive( player )) + return + + vector origin = player.GetOrigin() + if (DistanceSqr(origin, < -4111, 4583, 2330 >) < 25000 && IsInCutscene()){ + RunUIScript("SplitWithName", "Battery 2") + btGrabbedBattery2 = true + } + } +} + + +// BNR +bool bnrCallbacksStarted = false +void function BloodAndRustIL_StartCallbacks() { + // check if SewerSplit_gate_switch is used by the player + if (!bnrCallbacksStarted) { + bnrCallbacksStarted = true + AddCallback_UseEntGainFocus(BloodAndRustIL_Focus) + AddCallback_UseEntLoseFocus(BloodAndRustIL_LoseFocus) + RegisterConCommandTriggeredCallback("+use", BloodAndRustIL_OnUse) + } +} + +bool bnrButton1InFocus = false +bool bnrButton2InFocus = false +void function BloodAndRustIL_Focus(entity thingy) +{ + vector origin = thingy.GetOrigin() + if (thingy == GetEntByScriptName("SewerSplit_gate_switch")) { + bnrButton1InFocus = true + } + if (int(origin.x) == -2720 && int(origin.y) == -160 && int(origin.z) == 914) { // pos of button2 + bnrButton2InFocus = true + } +} +void function BloodAndRustIL_LoseFocus(entity thingy) +{ + vector origin = thingy.GetOrigin() + if (thingy == GetEntByScriptName("SewerSplit_gate_switch")) { + bnrButton1InFocus = false + } + if (int(origin.x) == -2720 && int(origin.y) == -160 && int(origin.z) == 914) { + bnrButton2InFocus = false + } +} + +void function BloodAndRustIL_OnUse(entity player) { + if (bnrButton1InFocus) { + // whatever command to split + RunUIScript("SplitWithName", "Button 1") + } + if (bnrButton2InFocus) { + RunUIScript("SplitWithName", "Button 2") + } +} + +bool bnrDoorTriggered = false +void function BloodAndRustIL_CheckDoorTrigger() { + if (!bnrDoorTriggered) { + entity player = GetLocalClientPlayer() + if (!IsValid( player ) || !IsAlive( player )) + return + + vector origin = player.GetOrigin() + + if (origin.y <= -226 && origin.x <= -827 && origin.z > 450) { + printt("Split Door Trigger please") + RunUIScript("SplitWithName", "Door Trigger") + bnrDoorTriggered = true + } + } + +} + +bool bnrHasEmbarked = false +void function BloodAndRustIL_CheckEmbark() { + if (!bnrHasEmbarked) { + entity player = GetLocalClientPlayer() + if (!IsValid( player ) || !IsAlive( player )) + return + + if(player.GetCinematicEventFlags() & CE_FLAG_EMBARK) { + RunUIScript("SplitWithName", "Embark") + bnrHasEmbarked = true + } + } +} + +// ITA 2 +bool ita3HasEmbarked = false +void function IntoTheAbyss3IL_CheckEmbark() { + if(!ita3HasEmbarked) { + entity player = GetLocalClientPlayer() + if (!IsValid( player ) || !IsAlive( player )) + return + + if(player.GetCinematicEventFlags() & CE_FLAG_EMBARK) { + RunUIScript("SplitWithName", "Embark") + ita3HasEmbarked = true + } + } +} + +// ENC 1 +bool enc1HasHelmet = false +void function EffectAndCause1IL_CheckHelmet() { + if(!enc1HasHelmet) { + entity player = GetLocalClientPlayer() + if (!IsValid( player ) || !IsAlive( player )) + return + + if (DistanceSqr(player.GetOrigin(), < 997, -2718, -860>) < 25000 && IsInCutscene()) { + enc1HasHelmet = true + thread void function(): (){ + wait 1.8 + RunUIScript("SplitWithName", "Helmet") + }() + } + } +} + +// ENC 2 +bool enc2Dialogue = false +void function EffectAndCause2IL_CheckDialogue() { + if(!enc2Dialogue) { + entity player = GetLocalClientPlayer() + if (!IsValid( player ) || !IsAlive( player )) + return + + vector origin = player.GetOrigin() + + if (origin.x > 8755 && origin.x < 9655 && origin.y < -4528 && origin.z > 5000) { + enc2Dialogue = true + thread void function(): (){ + wait 3 + RunUIScript("SplitWithName", "Anderson 1") + }() + } + + } +} + +bool enc2Hellroom = false +void function EffectAndCause2IL_CheckHellroom() { // start of hellroom, common split name is "Anderson 2" + if (!enc2Hellroom) { + entity player = GetLocalClientPlayer() + if (!IsValid( player ) || !IsAlive( player )) + return + + vector origin = player.GetOrigin() + + if (DistanceSqr(, <10708, -2263, 0>) < 15000) { //fzzycode ignores z here so I guess I will too, I don't want to have to search for the Z axis + enc2Hellroom = true + RunUIScript("SplitWithName", "Anderson 2") + } + } +} + +bool enc2Vent = false +void function EffectAndCause2IL_CheckVent() { // SUS. end of hellroom, bottom of vent. common split name is "hellroom" + if (!enc2Vent) { + entity player = GetLocalClientPlayer() + if (!IsValid( player ) || !IsAlive( player )) + return + + vector origin = player.GetOrigin() + + if (origin.z < -1200 && IsInCutscene()) { + enc2Vent = true + RunUIScript("SplitWithName", "Hellroom") + } + } +} + +bool enc2callbacks = false +void function EffectAndCause2IL_StartCallbacks() { + if (!enc2callbacks) { + enc2callbacks = true + AddCallback_UseEntGainFocus(EffectAndCause2IL_Focus) + AddCallback_UseEntLoseFocus(EffectAndCause2IL_LoseFocus) + RegisterConCommandTriggeredCallback("+use", EffectAndCause2IL_OnUse) + } +} + +bool enc2Button1InFocus = false +bool enc2Button2InFocus = false +void function EffectAndCause2IL_Focus(entity thingy) { + vector origin = thingy.GetOrigin() + + printt("In Focus Origin: ", origin) + if (int(origin.x) == 2845 && int(origin.y) == -3361 && int(origin.z) == 11015) { + enc2Button1InFocus = true + } + if (int(origin.x) == 6256 && int(origin.y) == -3552 && int(origin.z) == 11834) { + enc2Button2InFocus = true + } +} + +void function EffectAndCause2IL_LoseFocus(entity thingy) { + vector origin = thingy.GetOrigin() + + if (int(origin.x) == 2845 && int(origin.y) == -3361 && int(origin.z) == 11015) { + enc2Button1InFocus = false + } + if (int(origin.x) == 6256 && int(origin.y) == -3552 && int(origin.z) == 11834) { + enc2Button2InFocus = false + } + +} + +void function EffectAndCause2IL_OnUse(entity player) { + if (enc2Button1InFocus) { + RunUIScript("SplitWithName", "Button 1") + } + if (enc2Button2InFocus) { + RunUIScript("SplitWithName", "Button 2") + } +} + +// Beacon 2 +vector oldOrigin +bool beacon2Started = false +void function Beacon2IL_Init() { + if (!beacon2Started) { + + AddCallback_UseEntGainFocus(Beacon2IL_Focus) + AddCallback_UseEntLoseFocus(Beacon2IL_LoseFocus) + RegisterConCommandTriggeredCallback("+use", Beacon2IL_OnUse) + + beacon2Started = true + entity player = GetLocalClientPlayer() + if (!IsValid( player ) || !IsAlive( player )) + return + + vector origin = player.GetOrigin() + oldOrigin = origin // resetting it here because I need it in two places lol + } +} + +bool b2ButtonInFocus = false +bool b2ArcToolInFocus = false +void function Beacon2IL_Focus(entity thingy) { + vector origin = thingy.GetOrigin() + printt(string(thingy.GetModelName())) + if (int(origin.x) == 2688 && int(origin.y) == 10387 && int(origin.z) == 1059) { + b2ButtonInFocus = true + } + + if(string(thingy.GetModelName()) == "$\"models/weapons/arc_tool_sp/w_arc_tool_sp.mdl\"") { + b2ArcToolInFocus = true + } +} + +void function Beacon2IL_LoseFocus(entity thingy) { + vector origin = thingy.GetOrigin() + if (int(origin.x) == 2688 && int(origin.y) == 10387 && int(origin.z) == 1059) { + b2ButtonInFocus = false + } + + if(string(thingy.GetModelName()) == "$\"models/weapons/arc_tool_sp/w_arc_tool_sp.mdl\"") { + b2ArcToolInFocus = false + } + +} + +void function Beacon2IL_OnUse(entity player) { + if (b2ButtonInFocus) { + RunUIScript("SplitWithName", "Button") + } + + if (b2ArcToolInFocus) { + RunUIScript("SplitWithName", "Arc Tool Get") + } +} + +bool b2Heatsink = false +void function Beacon2IL_CheckHeatsink() { + entity player = GetLocalClientPlayer() + if (!IsValid( player ) || !IsAlive( player )) + return + + vector origin = player.GetOrigin() + + if (!b2Heatsink) { + + if (oldOrigin.x > -2113 && origin.x <= -2113 && origin.y < 11800 && origin.y > 10100) { + RunUIScript("SplitWithName", "Heatsink Trigger") + } + } + + oldOrigin = origin // resetting it here because I need it in two places lol +} + +bool b2DeathWarp = false +void function Beacon2IL_CheckDeathWarp() { + if (!b2DeathWarp) { + entity player = GetLocalClientPlayer() + if (!IsValid( player ) || !IsAlive( player )) + return + + vector origin = player.GetOrigin() + if (DistanceSqr(, <4019, 4233, 0>) < 500 && DistanceSqr(origin, oldOrigin) > 20000) { + b2DeathWarp = true + RunUIScript("SplitWithName", "Deathwarp") + } + } +} + +// Beacon 3 +bool b3Module = false +void function Beacon3IL_CheckModule() { + +} + +// Trial By Fire + + +// ================================ +// REST OF THE FUNCTIONS +// ================================ void function ResetStartPointValue( entity player ) { diff --git a/ronin/scripts/vscripts/timer/components/button_carousel.nut b/ronin/scripts/vscripts/timer/components/button_carousel.nut index 4620144..8f9c28f 100644 --- a/ronin/scripts/vscripts/timer/components/button_carousel.nut +++ b/ronin/scripts/vscripts/timer/components/button_carousel.nut @@ -54,7 +54,7 @@ void function Carousel_SetValueDisplay( var button, string val ) var rightButton = Hud_GetChild( button, "ButtonRight" ) printt(Hud_GetAbsX(valueLabel), Hud_GetWidth(valueLabel)) Hud_SetText( valueLabel, val ) - Squircle_SetSize(bg, Hud_GetWidth( valueLabel ) + 12, Hud_GetHeight(bg) ) + Squircle_SetSize(bg, Hud_GetWidth( valueLabel ) + int(ContentScaledX(12)), Hud_GetHeight(bg) ) printt(Hud_GetAbsX(valueLabel), Hud_GetWidth(valueLabel)) int labelCenter = Hud_GetAbsX(valueLabel) + (Hud_GetWidth(valueLabel) / 2) - Hud_GetAbsX( button ) diff --git a/ronin/scripts/vscripts/timer/components/run_panel.nut b/ronin/scripts/vscripts/timer/components/run_panel.nut index 7d070c2..8b066ad 100644 --- a/ronin/scripts/vscripts/timer/components/run_panel.nut +++ b/ronin/scripts/vscripts/timer/components/run_panel.nut @@ -43,7 +43,7 @@ void function RunPanel_DisplayRun( var panel, Run run ) int x = Hud_GetX(categoryBG) - Hud_GetX(categoryName) Hud_SetText(categoryName, categoryDisplayName.toupper()) - Squircle_SetSize(categoryBG, abs(x) * 2 + Hud_GetTextWidth(categoryName), 24) + Squircle_SetSize(categoryBG, abs(x) * 2 + Hud_GetTextWidth(categoryName), int(ContentScaledY(24))) Squircle_SetColor(categoryBG, int(color.x), int(color.y), int(color.z), 255) } diff --git a/ronin/scripts/vscripts/timer/run_saves.nut b/ronin/scripts/vscripts/timer/run_saves.nut index 4ff416d..458465e 100644 --- a/ronin/scripts/vscripts/timer/run_saves.nut +++ b/ronin/scripts/vscripts/timer/run_saves.nut @@ -15,6 +15,9 @@ global function SplitArrayToTableArray global function IsSplitBetter global function GetGoldSplitsForCategory global function SaveGoldSplits +global function GetPBRunByIndex +global function GetPBRunIndex +global function GetPBRunCount table defaultSplitNames = { sp_training = "The Gauntlet", @@ -41,6 +44,7 @@ table defaultSplitNames = { struct { array runs + array pbRuns table bestRuns table goldSplits table splitNames @@ -111,6 +115,12 @@ void function WaitForAllFilesToLoad( array runFiles ) file.runs.sort(RunCompareLatest) + foreach (Run run in file.runs) { + if (run.isPB) { + file.pbRuns.append(run) + } + } + PastRuns_RunsFinishedLoading() } @@ -204,6 +214,16 @@ Run function GetRunByIndex(int index) return file.runs[index] } +int function GetPBRunCount() +{ + return file.pbRuns.len() +} + +Run function GetPBRunByIndex(int index) +{ + return file.pbRuns[index] +} + void function SaveRunData( Duration time, array splits, table facts, bool isValid ) { int timestamp = GetUnixTimestamp() @@ -358,6 +378,11 @@ int function GetRunIndex(Run run) return file.runs.find(run) } +int function GetPBRunIndex(Run run) +{ + return file.pbRuns.find(run) +} + int function RunCompareLatest( Run a, Run b ) { if ( a.timestamp < b.timestamp ) diff --git a/ronin/scripts/vscripts/timer/time_measurement.nut b/ronin/scripts/vscripts/timer/time_measurement.nut index e5be081..c79b89f 100644 --- a/ronin/scripts/vscripts/timer/time_measurement.nut +++ b/ronin/scripts/vscripts/timer/time_measurement.nut @@ -15,6 +15,9 @@ global function GetTimeDelta global function GetSplitIndex global function PreviousDelta global function PreviousLevelDelta +global function SplitWithName + +const array CUSTOM_LEVEL_SPLITS = ["sp_sewers1"] struct { @@ -170,13 +173,16 @@ void function MeasureTime() // dont care } - if (GetConVarInt("sp_currentstartpoint") > file.curStartPoint && IsILCategory(GetRunCategory())) + /* + if (GetConVarInt("sp_currentstartpoint") > file.curStartPoint && IsILCategory(GetRunCategory()) + && !CUSTOM_LEVEL_SPLITS.contains(GetRunCurrentLevel())) { printt("split! ") Split() file.levelTime.name = "Startpoint " + GetConVarInt("sp_currentstartpoint") file.curStartPoint = GetConVarInt("sp_currentstartpoint") } + */ foreach (void functionref() callback in file.onTimerUpdatedCallbacks) callback() @@ -214,7 +220,13 @@ bool function ShouldStartCounting() { ResetTime() } + // only start if: + // exiting a loading screen and a run is not ended + // category is IL + // timer is not zero (mid run) + // timer is zero, and starting from new game (gauntlet, start point 0) bool result = !IsInLoadingScreen() && GetEngineTick() > 23 && GetActiveLevel() != "" && !file.runEnded + && ((file.time.microseconds != 0 && file.time.seconds != 0) || IsILCategory(GetRunCategory()) || (GetActiveLevel() == "sp_training" && GetConVarInt("sp_currentstartpoint") == 0)) if (result) { print("\n\n\nstart timer!!!") @@ -226,6 +238,7 @@ bool function ShouldStartCounting() { file.levelTime.name = "Startpoint " + file.curStartPoint } + RunClientScript("TimerStarted", file.time.seconds, file.time.microseconds ) } return result } @@ -308,10 +321,27 @@ void function Split() file.levelTime = levelTime } +void function SplitWithName(string name) { + file.levelTime.name = name + Split() + // kinda a hacky way to name the last split + file.levelTime.name = GetILLastSplitName() +} + +string function GetILLastSplitName() { + switch (GetRunCurrentLevel()) { + case "sp_crashsite": + return "No U" + case "sp_sewers1": + return "Kane" + } + return "idk" +} + void function ResetTime() { var stackInfos = getstackinfos( 2 ) - printt("reset! " + stackInfos["src"] + ":" + stackInfos["line"]) + //printt("reset! " + stackInfos["src"] + ":" + stackInfos["line"]) file.runInvalidated = false file.time.seconds = 0 file.time.microseconds = 0 @@ -453,4 +483,4 @@ string function PreviousDelta() { string function PreviousLevelDelta() { return file.previousLevelDelta -} \ No newline at end of file +} diff --git a/ronin/scripts/vscripts/ui/menu_past_runs.nut b/ronin/scripts/vscripts/ui/menu_past_runs.nut index 69c018b..cee46bd 100644 --- a/ronin/scripts/vscripts/ui/menu_past_runs.nut +++ b/ronin/scripts/vscripts/ui/menu_past_runs.nut @@ -235,7 +235,7 @@ void function PastRuns_OnRunPanelClick( var button ) { var panel = Hud_GetParent( button ) int index = expect int(panel.s.index) - PastRuns_DisplayRun(GetRunByIndex(index + file.runListScrollOffset)) + PastRuns_DisplayRun(GetPBRunByIndex(index + file.runListScrollOffset)) } void function RunList_Refresh() @@ -245,10 +245,18 @@ void function RunList_Refresh() for (int i = 0; i < 8; i++) { var runSquare = Hud_GetChild(file.menu, "RunPanel" + i) - Hud_SetVisible(runSquare, (i + file.runListScrollOffset) < GetRunCount()) - if ((i + file.runListScrollOffset) < GetRunCount()) - { - RunPanel_DisplayRun(runSquare, GetRunByIndex(i + file.runListScrollOffset)) + if (/*only sort if by PB*/ true) { + Hud_SetVisible(runSquare, (i + file.runListScrollOffset) < GetPBRunCount()) + if ((i + file.runListScrollOffset) < GetPBRunCount()) + { + RunPanel_DisplayRun(runSquare, GetPBRunByIndex(i + file.runListScrollOffset)) + } + } else { + Hud_SetVisible(runSquare, (i + file.runListScrollOffset) < GetRunCount()) + if ((i + file.runListScrollOffset) < GetRunCount()) + { + RunPanel_DisplayRun(runSquare, GetRunByIndex(i + file.runListScrollOffset)) + } } } @@ -324,7 +332,7 @@ string function GetTimeAsString(int timestamp) void function PastRuns_DisplayRun(Run run) { - file.selectedRunIndex = GetRunIndex(run) + file.selectedRunIndex = GetPBRunIndex(run) printt("run index", file.selectedRunIndex) var deleteButton = Hud_GetChild(file.menu, "DeleteRunButton") diff --git a/ronin/scripts/vscripts/ui/menu_timer_settings.nut b/ronin/scripts/vscripts/ui/menu_timer_settings.nut index 474ee1d..7a95fcf 100644 --- a/ronin/scripts/vscripts/ui/menu_timer_settings.nut +++ b/ronin/scripts/vscripts/ui/menu_timer_settings.nut @@ -25,12 +25,12 @@ void function TimerSettingsMenu_Init() }) Carousel_SetLabel( Hud_GetChild(file.menu, "ShowDeltas"), "Show Comparison" ) - Carousel_UpdateValue( Hud_GetChild(file.menu, "ShowDeltas"), ["NO", "YES", "ONLY IN LOAD SCREEN (NOT IMPLEMENTED)"][GetConVarInt("igt_show_deltas")], <128, 128, 128> ) + Carousel_UpdateValue( Hud_GetChild(file.menu, "ShowDeltas"), + ["NO", "YES", "ONLY IN LOAD SCREEN (NOT IMPLEMENTED)"][GetConVarInt("igt_show_deltas")], + [<255,64,64>, <64,255,64>, <128,128,128>][GetConVarInt("igt_show_deltas")] ) Carousel_AddClickedHandler( Hud_GetChild(file.menu, "ShowDeltas"), void function(var button, bool isRight) : (){ string convar = "igt_show_deltas" array categories = ["NO", "YES", "ONLY IN LOAD SCREEN (NOT IMPLEMENTED)"] - printt(convar, isRight) - string conVarValue = GetConVarString(convar) int currentRulesetIndex = GetConVarInt(convar) if (isRight) @@ -46,9 +46,9 @@ void function TimerSettingsMenu_Init() currentRulesetIndex = categories.len() - 1 } - conVarValue = categories[currentRulesetIndex] + string conVarValue = categories[currentRulesetIndex] SetConVarInt( convar, currentRulesetIndex ) - vector color = GetCategoryColor(conVarValue) + vector color = [<255,64,64>, <64,255,64>, <128,128,128>][currentRulesetIndex] Carousel_UpdateValue( button, conVarValue, color ) }) @@ -76,6 +76,18 @@ void function TimerSettingsMenu_Init() Carousel_UpdateValue( button, value ? "YES" : "NO", color ) }) + + Carousel_SetLabel( Hud_GetChild(file.menu, "Enable"), "Enable Timer" ) + Carousel_UpdateValue( Hud_GetChild(file.menu, "Enable"), GetConVarBool("igt_enable") ? "YES" : "NO", GetConVarBool("igt_enable") ? <64,255,64> : <255,64,64> ) + Carousel_AddClickedHandler( Hud_GetChild(file.menu, "Enable"), void function(var button, bool isRight) : (){ + bool value = GetConVarBool("igt_enable") + + value = !value + SetConVarBool( "igt_enable", value ) + vector color = value ? <64,255,64> : <255,64,64> + + Carousel_UpdateValue( button, value ? "YES" : "NO", color ) + }) SRM_SetupFooter() }