aboutsummaryrefslogtreecommitdiff
path: root/Northstar.Client/mod/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'Northstar.Client/mod/scripts')
-rw-r--r--Northstar.Client/mod/scripts/vscripts/cl_northstar_client_init.nut13
-rw-r--r--Northstar.Client/mod/scripts/vscripts/client/cl_damage_indicator.gnut951
-rw-r--r--Northstar.Client/mod/scripts/vscripts/client/cl_screenfade.gnut9
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/menu_ns_modmenu.nut194
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut122
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/menu_ns_setversionlabel.nut2
-rw-r--r--Northstar.Client/mod/scripts/vscripts/ui/panel_mainmenu.nut4
7 files changed, 1149 insertions, 146 deletions
diff --git a/Northstar.Client/mod/scripts/vscripts/cl_northstar_client_init.nut b/Northstar.Client/mod/scripts/vscripts/cl_northstar_client_init.nut
index 9e683a86..e4e44d51 100644
--- a/Northstar.Client/mod/scripts/vscripts/cl_northstar_client_init.nut
+++ b/Northstar.Client/mod/scripts/vscripts/cl_northstar_client_init.nut
@@ -29,6 +29,19 @@ global struct UIPresenceStruct {
int gameState
}
+global struct ModInfo
+{
+ string name = ""
+ string description = ""
+ string version = ""
+ string downloadLink = ""
+ int loadPriority = 0
+ bool enabled = false
+ bool requiredOnClient = false
+ bool isRemote
+ array<string> conVars = []
+}
+
global struct RequiredModInfo
{
string name
diff --git a/Northstar.Client/mod/scripts/vscripts/client/cl_damage_indicator.gnut b/Northstar.Client/mod/scripts/vscripts/client/cl_damage_indicator.gnut
new file mode 100644
index 00000000..4857c109
--- /dev/null
+++ b/Northstar.Client/mod/scripts/vscripts/client/cl_damage_indicator.gnut
@@ -0,0 +1,951 @@
+untyped
+
+global function ClDamageIndicator_Init
+global function Create_DamageIndicatorHUD
+global function DamageIndicators
+global function GrenadeArrowThink
+global function RumbleForTitanDamage
+
+global function ServerCallback_TitanTookDamage
+global function ServerCallback_PilotTookDamage
+//global function ClientCodeCallback_OnMissileCreation
+global function ClientCodeCallback_CreateGrenadeIndicator
+
+global function DamageIndicatorRui
+
+global function ShowGrenadeArrow
+
+global function SCB_AddGrenadeIndicatorForEntity
+
+const DAMAGEARROW_FADEANIM = "damage_fade"
+const DAMAGEARROW_DURATION = 2.5
+const DAMAGEARROW_SMALL = 0
+const DAMAGEARROW_MEDIUM = 1
+const DAMAGEARROW_LARGE = 2
+
+const float DAMAGEHUD_GRENADE_DEBOUNCE_TIME = 0.4
+const float DAMAGEHUD_GRENADE_DEBOUNCE_TIME_LOWSPEED = 0.15
+const float DAMAGEHUD_GRENADE_DEBOUNCE_TIME_LOWSPEED_VELOCITYCUTOFF = 500.0
+
+
+struct {
+ array<table> damageArrows
+ int currentDamageArrow = 0
+ int numDamageArrows = 16
+ float damageArrowFadeDuration = 1.0
+ float damageArrowTime = 0.0
+ vector damageArrowAngles = < 0.0, 0.0, 0.0 >
+ vector damageArrowPointCenter = < 0.0, 0.0, 0.0 >
+
+ table whizByFX = {
+ small = null,
+ large = null,
+ titan = null,
+ }
+
+ array<table> arrowIncomingAnims = [
+ { anim = "damage_incoming_small", duration = 1.5 },
+ { anim = "damage_incoming", duration = 1.75 },
+ { anim = "damage_incoming_large", duration = 2.00 },
+ ]
+
+ int damageIndicatorCount = 0
+} file
+
+function ClDamageIndicator_Init()
+{
+ RegisterSignal( "CriticalHitReceived" )
+
+ AddCreateCallback( "titan_cockpit", DamageArrow_CockpitInit )
+
+ PrecacheParticleSystem( $"P_wpn_grenade_frag_icon" )
+ PrecacheParticleSystem( $"P_wpn_grenade_frag_blue_icon" )
+ PrecacheParticleSystem( $"P_wpn_grenade_smoke_icon" )
+
+ if ( !IsLobby() )
+ AddCallback_EntitiesDidLoad( InitDamageArrows )
+}
+
+function ServerCallback_TitanTookDamage( damage, x, y, z, damageType, damageSourceId, attackerEHandle, eModId, doomedNow, doomedDamage )
+{
+ expect float( damage )
+ expect int( damageType )
+ expect int( damageSourceId )
+ expect bool( doomedNow )
+ expect int( doomedDamage )
+
+ if ( IsWatchingThirdPersonKillReplay() )
+ return
+
+ if ( DebugVictimClientDamageFeedbackIsEnabled() && (damage > 0.0) )
+ {
+ entity attacker = attackerEHandle ? GetHeavyWeightEntityFromEncodedEHandle( attackerEHandle ) : null
+ entity localViewPlayer = GetLocalViewPlayer()
+ bool isHeadShot = (damageType & DF_HEADSHOT) ? true : false
+ bool isKillShot = (damageType & DF_KILLSHOT) ? true : false
+ bool isCritical = (damageType & DF_CRITICAL) ? true : false
+ bool isDoomProtected = (damageType & DF_DOOM_PROTECTED) ? true : false
+ bool isDoomFatality = (damageType & DF_DOOM_FATALITY) ? true : false
+
+ local weaponMods = []
+ if ( eModId != null && eModId in modNameStrings )
+ weaponMods.append( modNameStrings[eModId] )
+
+ string modDesc = ((eModId != null && eModId in modNameStrings) ? (expect string( modNameStrings[eModId] )) : "")
+ DebugTookDamagePrint( localViewPlayer, attacker, damage, damageSourceId, modDesc, isHeadShot, isKillShot, isCritical, doomedNow, doomedDamage, isDoomProtected, isDoomFatality )
+ }
+
+ // It appears to be faster here to create a new thread so other functions called can wait until the frame ends before running.
+ thread TitanTookDamageThread( damage, x, y, z, damageType, damageSourceId, attackerEHandle, eModId, doomedNow, doomedDamage )
+
+ vector damageOrigin = < x, y, z >
+ entity attacker = attackerEHandle ? GetHeavyWeightEntityFromEncodedEHandle( attackerEHandle ) : null
+
+ if ( damageSourceId in clGlobal.onLocalPlayerTookDamageCallback )
+ {
+ foreach ( callback in clGlobal.onLocalPlayerTookDamageCallback[ damageSourceId ] )
+ callback( damage, damageOrigin, damageType, damageSourceId, attacker )
+ }
+}
+
+function TitanTookDamageThread( float damage, x, y, z, int damageType, int damageSourceId, attackerEHandle, eModId, bool doomedNow, int doomedDamage )
+{
+ WaitEndFrame()
+
+ entity attacker = attackerEHandle ? GetHeavyWeightEntityFromEncodedEHandle( attackerEHandle ) : null
+ entity localViewPlayer = GetLocalViewPlayer()
+ entity cockpit = localViewPlayer.GetCockpit()
+
+ if ( cockpit && IsTitanCockpitModelName( cockpit.GetModelName() ) )
+ TitanCockpit_DamageFeedback( localViewPlayer, cockpit, damage, damageType, < x, y, z >, damageSourceId, doomedNow, doomedDamage )
+
+ if ( damage >= DAMAGE_BREAK_MELEE_ASSIST )
+ localViewPlayer.Lunge_ClearTarget()
+
+ if ( damageSourceId != eDamageSourceId.bubble_shield ) //Don't play Betty OS dialogue if we took damage by bubble shield. We don't have appropriate dialogue for it.
+ Tracker_PlayerAttackedByTarget( localViewPlayer, attacker )
+
+ array<string> weaponMods
+ if ( eModId != null && eModId in modNameStrings )
+ weaponMods.append( expect string( modNameStrings[ eModId ] ) )
+
+ if ( (damage > 0.0) || doomedDamage )
+ {
+ vector damageOrigin = < x, y, z >
+ DamageHistoryStruct damageHistory = StoreDamageHistoryAndUpdate( localViewPlayer, MAX_DAMAGE_HISTORY_TIME, damage, damageOrigin, damageType, damageSourceId, attacker, weaponMods )
+ DamageIndicators( damageHistory, true )
+ }
+
+ entity soul = localViewPlayer.GetTitanSoul()
+ if ( PlayerHasPassive( localViewPlayer, ePassives.PAS_AUTO_EJECT ) ) //TODO: Handle nuclear eject if we ever allow nuclear + auto eject combo again
+ {
+ if ( ShouldPlayAutoEjectAnim( localViewPlayer, soul, doomedNow ) )
+ thread PlayerEjects( localViewPlayer, cockpit )
+
+ }
+
+ if ( damageType & DF_CRITICAL )
+ {
+ localViewPlayer.Signal( "CriticalHitReceived" )
+ EmitSoundOnEntity( localViewPlayer, "titan_damage_crit_3p_vs_1p" )
+ }
+}
+
+bool function ShouldPlayAutoEjectAnim( entity player, entity titanSoul, bool doomedNow )
+{
+ if ( !titanSoul.IsDoomed() )
+ return false
+
+ if ( player.ContextAction_IsActive() && !player.ContextAction_IsBusy() ) //Some other context action, e.g. melee instead of eject. Then again
+ return false
+
+ return true
+}
+
+string function DevBuildAttackerDesc( entity localViewPlayer, entity ent )
+{
+ if ( ent == null )
+ return "<null>"
+
+ if ( localViewPlayer == ent )
+ return ("<self>")
+
+ if ( ent.IsPlayer() )
+ return ("'" + ent.GetPlayerName() + "' " + ent.GetPlayerSettings())
+
+ entity bossPlayer = ent.GetBossPlayer()
+ string ownerString = ((bossPlayer != null) ? (bossPlayer.GetPlayerName() + "'s ") : "")
+
+ var sigName = ent.GetSignifierName()
+ string debugName = (sigName != null) ? expect string( sigName ) : ent.GetClassName()
+ return (ownerString + debugName)
+}
+
+void function DebugTookDamagePrint( entity ornull localViewPlayer, entity attacker, float damage, int damageSourceId, string modDesc, bool isHeadShot, bool isKillShot, bool isCritical, bool isDoomShot, int doomShotDamage, bool isDoomProtected, bool isDoomFatality )
+{
+ Assert( localViewPlayer )
+ string attackerDesc = DevBuildAttackerDesc( expect entity( localViewPlayer ), attacker )
+ string timePrint = format( "%d:%.2f", FrameCount(), PlatformTime() )
+ printt(
+ "{"+timePrint+"} TOOK DAMAGE: " + damage +
+ (isHeadShot ? " (headshot)" : "") +
+ (isCritical ? " (critical)" : "") +
+ (isKillShot ? " (KILLED)" : "") +
+ (isDoomShot ? " (DOOMED dmg:" + doomShotDamage + ")" : "") +
+ (isDoomProtected ? " (DOOM PROTECTION)" : "") +
+ (isDoomFatality ? " (DOOM FATALITY)" : "") +
+ " " + attackerDesc +
+ " w/ " + GetObitFromDamageSourceID( damageSourceId ) + modDesc
+ )
+}
+
+void function PlayVictimHeadshotConfirmation( bool isKillShot )
+{
+ entity localViewPlayer = GetLocalViewPlayer()
+ if ( localViewPlayer == null )
+ return
+
+ if ( isKillShot )
+ EmitSoundOnEntity( localViewPlayer, "Player.Hitbeep_headshot.Kill.Human_3P_vs_1P" )
+ else
+ EmitSoundOnEntity( localViewPlayer, "Player.Hitbeep_headshot.Human_3P_vs_1P" )
+}
+
+void function RumbleForPilotDamage( float damageAmount )
+{
+ Rumble_Play( "rumble_pilot_hurt", {} )
+}
+
+void function RumbleForTitanDamage( float damageAmount )
+{
+ string rumbleName;
+ if ( damageAmount < 500 )
+ rumbleName = "titan_damaged_small"
+ else if ( damageAmount < 1000 )
+ rumbleName = "titan_damaged_med"
+ else
+ rumbleName = "titan_damaged_big"
+
+ Rumble_Play( rumbleName, {} )
+}
+
+function ServerCallback_PilotTookDamage( damage, x, y, z, damageType, damageSourceId, attackerEHandle, eModId )
+{
+ expect float( damage )
+ expect int( damageType )
+ expect int( damageSourceId )
+
+ if ( IsWatchingThirdPersonKillReplay() )
+ return
+
+ entity attacker = attackerEHandle ? GetHeavyWeightEntityFromEncodedEHandle( attackerEHandle ) : null
+ entity localViewPlayer = GetLocalViewPlayer()
+ vector damageOrigin = < x, y, z >
+
+ bool isHeadShot = (damageType & DF_HEADSHOT) ? true : false
+ bool isKillShot = (damageType & DF_KILLSHOT) ? true : false
+
+ if ( isHeadShot )
+ PlayVictimHeadshotConfirmation( isKillShot );
+
+ //Jolt view if player is getting meleed
+ if ( damageSourceId == eDamageSourceId.human_melee )
+ {
+ vector joltDir = Normalize( localViewPlayer.CameraPosition() - damageOrigin )
+ //clear melee assist when you get meleed
+ localViewPlayer.Lunge_ClearTarget()
+ }
+
+ array<string> weaponMods
+ if ( eModId != null && eModId in modNameStrings )
+ weaponMods.append( expect string( modNameStrings[ eModId ] ) )
+
+ if ( DebugVictimClientDamageFeedbackIsEnabled() && !IsWatchingReplay() )
+ {
+ string modDesc = (weaponMods.len() > 0 ? (" +" + weaponMods[0]) : "")
+ bool isCritical = (damageType & DF_CRITICAL) ? true : false
+
+ DebugTookDamagePrint( localViewPlayer, attacker, damage, damageSourceId, modDesc, isHeadShot, isKillShot, isCritical, false, 0, false, false )
+ }
+
+ RumbleForPilotDamage( damage )
+
+ DamageHistoryStruct damageTable = StoreDamageHistoryAndUpdate( localViewPlayer, MAX_DAMAGE_HISTORY_TIME, damage, damageOrigin, damageType, damageSourceId, attacker, weaponMods )
+
+ DamageIndicators( damageTable, false )
+
+ if ( damageSourceId in clGlobal.onLocalPlayerTookDamageCallback )
+ {
+ foreach ( callback in clGlobal.onLocalPlayerTookDamageCallback[ damageSourceId ] )
+ callback( damage, damageOrigin, damageType, damageSourceId, attacker )
+ }
+}
+
+/*
+void function ClientCodeCallback_OnMissileCreation( entity missileEnt, string weaponName, bool firstTime )
+{
+
+}
+*/
+
+void function ClientCodeCallback_CreateGrenadeIndicator( entity missileEnt, string weaponName )
+{
+ if ( !IsValid( missileEnt ) )
+ return
+
+ //Called for all projectiles, not just missiles.
+ TryAddGrenadeIndicator( missileEnt, weaponName )
+}
+
+
+void function DamageIndicators( DamageHistoryStruct damageHistory, bool playerIsTitan )
+{
+ if ( damageHistory.damageType & DF_NO_INDICATOR )
+ return
+ if ( !level.clientScriptInitialized )
+ return
+ if ( IsWatchingThirdPersonKillReplay() )
+ return
+
+ entity localViewPlayer = GetLocalViewPlayer()
+
+ int arrowType = DAMAGEARROW_MEDIUM
+
+ if ( IsValid( damageHistory.attacker ) )
+ {
+ if ( damageHistory.attacker == localViewPlayer )
+ return
+
+ if ( damageHistory.attacker.IsTitan() )
+ arrowType = DAMAGEARROW_MEDIUM
+ else if ( damageHistory.attacker.IsPlayer() )
+ arrowType = DAMAGEARROW_SMALL
+ else
+ arrowType = DAMAGEARROW_SMALL
+
+ //if ( damageHistory.attacker.IsTitan() )
+ // arrowType = DAMAGEARROW_LARGE
+ //else if ( damageHistory.attacker.IsPlayer() )
+ // arrowType = DAMAGEARROW_MEDIUM
+ //else
+ // arrowType = DAMAGEARROW_SMALL
+ }
+
+ if ( playerIsTitan )
+ {
+ entity cockpit = localViewPlayer.GetCockpit()
+
+ if ( !cockpit )
+ return
+
+ vector dirToDamage = damageHistory.origin - localViewPlayer.GetOrigin()
+ dirToDamage.z = 0
+ dirToDamage = Normalize( dirToDamage )
+
+ vector playerViewForward = localViewPlayer.GetViewVector()
+ playerViewForward.z = 0.0
+ playerViewForward = Normalize( playerViewForward )
+
+ float damageFrontDot = DotProduct( dirToDamage, playerViewForward )
+
+ if ( damageFrontDot >= 0.707107 )
+ cockpit.AddToTitanHudDamageHistory( COCKPIT_PANEL_TOP, damageHistory.damage )
+ else if ( damageFrontDot <= -0.707107 )
+ cockpit.AddToTitanHudDamageHistory( COCKPIT_PANEL_BOTTOM, damageHistory.damage )
+ else
+ {
+ vector playerViewRight = localViewPlayer.GetViewRight()
+ playerViewRight.z = 0.0
+ playerViewRight = Normalize( playerViewRight )
+
+ float damageRightDot = DotProduct( dirToDamage, playerViewRight )
+
+ if ( damageRightDot >= 0.707107 )
+ cockpit.AddToTitanHudDamageHistory( COCKPIT_PANEL_RIGHT, damageHistory.damage )
+ else
+ cockpit.AddToTitanHudDamageHistory( COCKPIT_PANEL_LEFT, damageHistory.damage )
+ }
+
+ if ( damageHistory.attacker && damageHistory.attacker.GetParent() == localViewPlayer )
+ {
+ damageHistory.rodeoDamage = true
+ return
+ }
+ }
+
+ #if SP
+ if ( IsValid( damageHistory.attacker ) && damageHistory.attacker.IsTitan() )
+ arrowType = DAMAGEARROW_LARGE
+ else if ( playerIsTitan && damageHistory.damage < 50 )
+ return
+ else if ( !playerIsTitan && damageHistory.damage < 15 )
+ arrowType = DAMAGEARROW_SMALL
+ else
+ arrowType = DAMAGEARROW_MEDIUM
+
+ thread DamageIndicatorRui( damageHistory.origin, arrowType, playerIsTitan )
+ #else
+ bool show2DIndicator = true
+ bool show3DIndicator = false
+
+ const int DAMAGE_INDICATOR_STYLE_2D_ONLY = 0
+ const int DAMAGE_INDICATOR_STYLE_BOTH = 1
+ const int DAMAGE_INDICATOR_STYLE_3D_ONLY = 2
+
+ if ( playerIsTitan )
+ {
+ show2DIndicator = GetConVarInt( "damage_indicator_style_titan" ) != DAMAGE_INDICATOR_STYLE_3D_ONLY
+ show3DIndicator = GetConVarInt( "damage_indicator_style_titan" ) != DAMAGE_INDICATOR_STYLE_2D_ONLY
+ }
+ else
+ {
+ show2DIndicator = GetConVarInt( "damage_indicator_style_pilot" ) != DAMAGE_INDICATOR_STYLE_3D_ONLY
+ show3DIndicator = GetConVarInt( "damage_indicator_style_pilot" ) != DAMAGE_INDICATOR_STYLE_2D_ONLY
+ }
+
+ if ( show2DIndicator )
+ thread DamageIndicatorRui( damageHistory.origin, arrowType, playerIsTitan )
+
+ if ( show3DIndicator )
+ ShowDamageArrow( localViewPlayer, damageHistory.origin, arrowType, playerIsTitan, damageHistory.attacker )
+ #endif
+}
+
+const float DAMAGE_INDICATOR_DURATION = 4.0
+
+void function DamageIndicatorRui( vector damageOrigin, int arrowType, bool playerIsTitan )
+{
+ clGlobal.levelEnt.EndSignal( "KillReplayStarted" )
+ clGlobal.levelEnt.EndSignal( "KillReplayEnded" )
+
+ // slop
+ float distance = Length( damageOrigin - GetLocalViewPlayer().CameraPosition() )
+ float randomRange = GraphCapped( distance, 0.0, 2048, 0.0, 256.0 )
+ damageOrigin = <damageOrigin.x + RandomFloatRange( randomRange, -randomRange ), damageOrigin.y + RandomFloatRange( randomRange, -randomRange ), damageOrigin.z>
+
+ float startTime = Time()
+
+ var rui = RuiCreate( $"ui/damage_indicator.rpak", clGlobal.topoFullScreen, RUI_DRAW_HUD, 0 )
+ RuiSetResolutionToScreenSize( rui )
+ RuiSetGameTime( rui, "startTime", startTime )
+ RuiSetFloat( rui, "duration", DAMAGE_INDICATOR_DURATION )
+ RuiSetInt( rui, "attackerType", arrowType )
+
+ file.damageIndicatorCount++
+ int damageIndicatorThreshold = file.damageIndicatorCount + 8
+
+ OnThreadEnd(
+ function() : ( rui )
+ {
+ RuiDestroy( rui )
+ }
+ )
+
+ while ( Time() - startTime < DAMAGE_INDICATOR_DURATION && file.damageIndicatorCount < damageIndicatorThreshold )
+ {
+ vector vecToDamage = damageOrigin - GetLocalViewPlayer().CameraPosition()
+ vecToDamage.z = 0
+ vecToDamage = Normalize( vecToDamage )
+ RuiSetFloat3( rui, "vecToDamage2D", vecToDamage )
+ RuiSetFloat3( rui, "camVec2D", Normalize( AnglesToForward( < 0, GetLocalViewPlayer().CameraAngles().y, 0 > ) ) )
+ RuiSetFloat( rui, "sideDot", vecToDamage.Dot( CrossProduct( <0, 0, 1>, Normalize( AnglesToForward( < 0, GetLocalViewPlayer().CameraAngles().y, 0 > ) ) ) ) )
+ WaitFrame()
+ }
+}
+
+void function ShowGrenadeArrow( entity player, entity grenade, float damageRadius, float startDelay, bool requireLos = true )
+{
+ thread GrenadeArrowThink( player, grenade, damageRadius, startDelay, requireLos )
+}
+
+vector function GetRandomOriginWithinBounds( entity ent )
+{
+ vector boundingMins = ent.GetBoundingMins()
+ vector boundingMaxs = ent.GetBoundingMaxs()
+
+ vector randomOffset = < RandomFloatRange( boundingMins.x, boundingMaxs.x ), RandomFloatRange( boundingMins.y, boundingMaxs.y ), RandomFloatRange( boundingMins.z, boundingMaxs.z ) >
+
+ return ent.GetOrigin() + randomOffset
+}
+
+void function GrenadeArrowThink( entity player, entity grenade, float damageRadius, float startDelay, bool requireLos, string requiredPlayerState = "any" )
+{
+ EndSignal( grenade, "OnDeath" ) //On death added primarily for frag_drones
+ EndSignal( grenade, "OnDestroy" )
+ EndSignal( player, "OnDeath" )
+
+ wait startDelay
+
+ asset grenadeModel = GRENADE_INDICATOR_FRAG_MODEL
+ vector grenadeOffset = < -5, 0, 0 >
+ if ( grenade instanceof C_Projectile )
+ {
+ if ( grenade.ProjectileGetWeaponClassName() == "mp_weapon_grenade_sonar" )
+ {
+ grenadeModel = GRENADE_INDICATOR_SONAR_MODEL
+ grenadeOffset = < -5, 0, 0 >
+ requireLos = false
+ }
+ }
+ else if ( grenade.IsNPC() )
+ {
+ switch ( grenade.GetSignifierName() )
+ {
+ #if MP
+ case "npc_stalker":
+ grenadeModel = GRENADE_INDICATOR_STALKER_MODEL
+ break
+ #endif
+
+ case "npc_frag_drone":
+ grenadeModel = GRENADE_INDICATOR_TICK_MODEL
+ break
+ }
+ }
+
+ entity arrow = CreateClientSidePropDynamic( < 0, 0, 0 >, < 0, 0, 0 >, GRENADE_INDICATOR_ARROW_MODEL )
+ entity mdl = CreateClientSidePropDynamic( < 0, 0, 0 >, < 0, 0, 0 >, grenadeModel )
+
+ OnThreadEnd(
+ function() : ( arrow, mdl )
+ {
+ if ( IsValid( arrow ) )
+ arrow.Destroy()
+ if ( IsValid( mdl ) )
+ mdl.Destroy()
+ }
+ )
+
+ entity cockpit = player.GetCockpit()
+ if ( !cockpit )
+ return
+
+ EndSignal( cockpit, "OnDestroy" )
+
+ arrow.SetParent( cockpit, "CAMERA_BASE" )
+ arrow.SetAttachOffsetOrigin( < 25.0, 0.0, -4.0 > )
+
+ mdl.SetParent( arrow, "BACK" )
+ mdl.SetAttachOffsetOrigin( grenadeOffset )
+
+ float lastVisibleTime = 0
+ bool shouldBeVisible = true
+
+ while ( true )
+ {
+ cockpit = player.GetCockpit()
+
+ switch ( requiredPlayerState )
+ {
+ case "any":
+ shouldBeVisible = true
+ break
+ case "pilot":
+ shouldBeVisible = !player.IsTitan()
+ break
+ case "titan":
+ shouldBeVisible = player.IsTitan()
+ break
+ default:
+ Assert( false, "Invalid player state! Allower states: 'any' 'pilot' 'titan'" )
+
+ }
+
+ if ( shouldBeVisible )
+ {
+ if ( Distance( player.GetOrigin(), grenade.GetOrigin() ) > damageRadius || !cockpit )
+ {
+ shouldBeVisible = false
+ }
+ else
+ {
+ bool tracePassed = false
+
+ if ( requireLos )
+ {
+ TraceResults result = TraceLine( grenade.GetOrigin(), GetRandomOriginWithinBounds( player ), grenade, TRACE_MASK_SHOT, TRACE_COLLISION_GROUP_NONE )
+
+ if ( result.fraction == 1.0 )
+ tracePassed = true
+ }
+
+ if ( requireLos && !tracePassed )
+ {
+ shouldBeVisible = false
+ }
+ else
+ {
+ shouldBeVisible = true
+ lastVisibleTime = Time()
+ }
+ }
+ }
+
+ if ( shouldBeVisible || Time() - lastVisibleTime < 0.25 )
+ {
+ arrow.EnableDraw()
+ mdl.EnableDraw()
+
+ arrow.DisableRenderWithViewModelsNoZoom()
+ arrow.EnableRenderWithCockpit()
+ arrow.EnableRenderWithHud()
+ mdl.DisableRenderWithViewModelsNoZoom()
+ mdl.EnableRenderWithCockpit()
+ mdl.EnableRenderWithHud()
+
+ vector damageArrowAngles = AnglesInverse( player.EyeAngles() )
+ vector vecToDamage = grenade.GetOrigin() - (player.EyePosition() + (player.GetViewVector() * 20.0))
+
+ // reparent for embark/disembark
+ if ( arrow.GetParent() == null )
+ arrow.SetParent( cockpit, "CAMERA_BASE", true )
+
+ arrow.SetAttachOffsetAngles( damageArrowAngles.AnglesCompose( vecToDamage.VectorToAngles() ) )
+ }
+ else
+ {
+ mdl.DisableDraw()
+ arrow.DisableDraw()
+ }
+
+ WaitFrame()
+ }
+
+}
+
+
+function Create_DamageIndicatorHUD()
+{
+}
+
+
+void function SCB_AddGrenadeIndicatorForEntity( int team, int ownerHandle, int eHandle, float damageRadius )
+{
+ if ( !level.grenadeIndicatorEnabled )
+ return
+
+ #if DEV
+ if ( !level.clientScriptInitialized )
+ return
+ #endif
+
+ entity player = GetLocalViewPlayer()
+ entity owner = GetEntityFromEncodedEHandle( ownerHandle )
+
+ entity ent = GetEntityFromEncodedEHandle( eHandle )
+ if ( !IsValid( ent ) )
+ return
+
+ if ( team == player.GetTeam() && owner != player )
+ return
+
+ //TryAddGrenadeIndicator( ent, "" ) // TODO: make function handle non-grenade ents
+}
+
+
+function TryAddGrenadeIndicator( grenade, weaponName )
+{
+ #if DEV
+ if ( !level.clientScriptInitialized )
+ return
+ #endif
+
+ if ( !level.grenadeIndicatorEnabled )
+ return
+
+ expect entity( grenade )
+ entity player = GetLocalViewPlayer()
+
+ // view player may be null when dead
+ if ( !IsValid( player ) )
+ return
+
+ var className = grenade.GetClassName()
+ float damageRadius = 0.0
+
+ if ( className == "grenade" )
+ {
+ damageRadius = grenade.GetDamageRadius()
+ }
+ else if ( grenade.ProjectileGetWeaponClassName() == "mp_titanweapon_arc_ball" )
+ {
+ // arc ball doesn't arc to pilots so no need to show the warning
+ if ( !player.IsTitan() )
+ return
+
+ damageRadius = BALL_LIGHTNING_ZAP_RADIUS
+ }
+ else
+ {
+ return
+ }
+
+ float radius = grenade.GetExplosionRadius()
+
+ if ( player.IsPhaseShifted() )
+ return
+
+
+ float startDelay = 0.0
+ if ( grenade.GetOwner() == player )
+ {
+ if ( !grenade.GetProjectileWeaponSettingBool( eWeaponVar.projectile_damages_owner ) && !grenade.GetProjectileWeaponSettingBool( eWeaponVar.explosion_damages_owner ) )
+ return
+
+ float relVelocity = Length( grenade.GetVelocity() - player.GetVelocity() )
+ if ( relVelocity < DAMAGEHUD_GRENADE_DEBOUNCE_TIME_LOWSPEED_VELOCITYCUTOFF )
+ startDelay = DAMAGEHUD_GRENADE_DEBOUNCE_TIME_LOWSPEED
+ else
+ startDelay = DAMAGEHUD_GRENADE_DEBOUNCE_TIME
+ }
+ else if ( grenade.GetTeam() == player.GetTeam() )
+ {
+ return
+ }
+
+ float padding = player.IsTitan() ? 204.0 : 65.0
+
+ //AddGrenadeIndicator( grenade, radius + padding, startDelay, false )
+ ShowGrenadeArrow( player, grenade, radius + padding, startDelay )
+
+ //thread ShowRuiGrenadeThreatIndicator( grenade, float( radius ) + padding )
+}
+
+void function ShowRuiGrenadeThreatIndicator( entity grenade, float radius )
+{
+ var rui = RuiCreate( $"ui/grenade_threat_indicator.rpak", clGlobal.topoCockpitHudPermanent, RUI_DRAW_COCKPIT, 0 )
+ //var rui = CreateCockpitRui( $"ui/grenade_threat_indicator.rpak", 0 )
+ RuiSetGameTime( rui, "startTime", Time() )
+ RuiSetFloat( rui, "damageRadius", radius )
+ //RuiTrackFloat3( rui, "pos", grenade, RUI_TRACK_ABSORIGIN_FOLLOW )`
+ RuiTrackFloat3( rui, "pos", grenade, RUI_TRACK_POINT_FOLLOW, grenade.LookupAttachment( "BACK" ) )
+
+ OnThreadEnd(
+ function() : ( rui )
+ {
+ RuiDestroy( rui )
+ }
+ )
+
+ grenade.WaitSignal( "OnDestroy" )
+}
+
+
+
+void function InitDamageArrows()
+{
+ for ( int i = 0; i < file.numDamageArrows; i++ )
+ {
+ table arrowData = {
+ grenade = null
+ grenadeRadius = 0.0
+ damageOrigin = < 0.0, 0.0, 0.0 >,
+ damageDirection = < 0.0, 0.0, 0.0 >,
+ endTime = -99.0 + DAMAGEARROW_DURATION,
+ startTime = -99.0,
+ isDying = false,
+ isVisible = false,
+ whizby = false, // hack until we get a new model/shader for the whizby indicator - Roger
+ attacker = null,
+ randomAngle = 0 // Repeated shots from the same attacker randomize the angle of the arrow.
+ }
+
+ entity arrow = CreateClientSidePropDynamic( < 0, 0, 0 >, < 0, 0, 0 >, DAMAGEARROW_MODEL )
+ arrow.SetCanCloak( false )
+ arrow.SetVisibleForLocalPlayer( 0 )
+ arrow.DisableDraw()
+
+ arrowData.arrow <- arrow
+ arrow.s.arrowData <- arrowData
+
+ file.damageArrows.append( arrowData )
+ }
+
+ entity arrow = CreateClientSidePropDynamic( < 0, 0, 0 >, < 0, 0, 0 >, DAMAGEARROW_MODEL )
+ file.damageArrowFadeDuration = arrow.GetSequenceDuration( DAMAGEARROW_FADEANIM ) // 0.266
+ arrow.Destroy()
+}
+
+
+void function DamageArrow_CockpitInit( entity cockpit )
+{
+ entity localViewPlayer = GetLocalViewPlayer()
+ thread UpdateDamageArrows( localViewPlayer, cockpit )
+}
+
+function RefreshExistingDamageArrow( entity player, arrowData, int arrowType, damageOrigin )
+{
+ //Hack - 10 tick rate is making damage feedback bunch up. If we improve that then shouldn't be threaded.
+ player.EndSignal( "OnDestroy" )
+ entity cockpit = player.GetCockpit()
+ if ( IsValid( cockpit ) )
+ cockpit.EndSignal( "OnDestroy" )
+
+ float time = Time()
+
+ if ( arrowData.startTime == time )
+ wait 0.05
+
+ if ( !arrowData.isVisible || arrowData.isDying )
+ return
+
+ time = Time()
+ arrowData.endTime = time + file.arrowIncomingAnims[ arrowType ].duration
+ arrowData.startTime = time
+ arrowData.damageOrigin = damageOrigin
+ arrowData.randomAngle = RandomIntRange( -3, 3 )
+ PulseDamageArrow( expect entity( arrowData.arrow ), arrowType )
+ UpdateDamageArrowVars( player )
+ UpdateDamageArrowAngle( arrowData )
+}
+
+function ShowDamageArrow( entity player, damageOrigin, int arrowType, playerIsTitan, attacker )
+{
+ if ( file.damageArrows.len() == 0 ) // not yet initialized
+ return
+
+ table arrowData = file.damageArrows[file.currentDamageArrow]
+ entity arrow = expect entity( arrowData.arrow )
+
+ file.currentDamageArrow++
+ if ( file.currentDamageArrow >= file.numDamageArrows )
+ file.currentDamageArrow = 0
+
+ float time = Time()
+
+ arrow.s.arrowData.damageOrigin = damageOrigin
+ arrow.s.arrowData.grenade = null
+ arrow.s.arrowData.grenadeRadius = 0.0
+ arrow.s.arrowData.endTime = time + file.arrowIncomingAnims[ arrowType ].duration
+ arrow.s.arrowData.startTime = time
+ arrow.s.arrowData.isDying = false
+ arrow.s.arrowData.whizby = false // hack until we get a new model/shader for the whizby indicator
+ arrow.s.arrowData.attacker = attacker
+
+ if ( !arrow.s.arrowData.isVisible )
+ {
+ entity cockpit = player.GetCockpit()
+
+ if ( !cockpit )
+ return
+
+ arrow.s.arrowData.isVisible = true
+ arrow.EnableDraw()
+
+ arrow.DisableRenderWithViewModelsNoZoom()
+ arrow.EnableRenderWithCockpit()
+
+ arrow.EnableRenderWithHud()
+
+ arrow.SetParent( cockpit, "CAMERA_BASE" )
+ arrow.SetAttachOffsetOrigin( < 20.0, 0.0, -2.0 > )
+ }
+
+
+ PulseDamageArrow( arrow, arrowType )
+ UpdateDamageArrowVars( player )
+ UpdateDamageArrowAngle( arrowData )
+}
+
+
+function PulseDamageArrow( entity arrow, int arrowType )
+{
+ arrow.Anim_NonScriptedPlay( file.arrowIncomingAnims[ arrowType ].anim )
+}
+
+function UpdateDamageArrowVars( entity localViewPlayer )
+{
+ file.damageArrowTime = Time()
+ file.damageArrowAngles = AnglesInverse( localViewPlayer.EyeAngles() )
+ file.damageArrowPointCenter = localViewPlayer.EyePosition() + ( localViewPlayer.GetViewVector() * 20.0 )
+}
+
+function UpdateDamageArrowAngle( arrowData )
+{
+ if ( IsValid( arrowData.grenade ) )
+ arrowData.damageOrigin = arrowData.grenade.GetOrigin()
+
+ vector vecToDamage = expect vector( arrowData.damageOrigin ) - file.damageArrowPointCenter
+ vector anglesToDamage = VectorToAngles( vecToDamage )
+ vector eyeAngles = GetLocalViewPlayer().EyeAngles()
+
+ float roll = sin( DegToRad( eyeAngles.y - anglesToDamage.y ) )
+
+ arrowData.arrow.SetAttachOffsetAngles( AnglesCompose( file.damageArrowAngles, anglesToDamage ) + < arrowData.randomAngle, 0, roll * 90.0 > )
+ arrowData.damageDirection = Normalize( vecToDamage )
+}
+
+function UpdateDamageArrows( entity localViewPlayer, entity cockpit )
+{
+ localViewPlayer.EndSignal( "OnDestroy" )
+ cockpit.EndSignal( "OnDestroy" )
+
+ OnThreadEnd(
+ function() : ( localViewPlayer )
+ {
+ foreach ( arrowData in file.damageArrows )
+ {
+ if ( IsValid( arrowData.arrow ) )
+ {
+ arrowData.arrow.DisableDraw()
+ arrowData.arrow.ClearParent()
+ arrowData.attacker = null
+ arrowData.isVisible = false
+ arrowData.randomAngle = 0
+ }
+ }
+ }
+ )
+
+ bool varsUpdated = false
+
+ while ( true )
+ {
+ WaitEndFrame()
+
+ vector playerOrigin = localViewPlayer.GetOrigin()
+
+ varsUpdated = false
+ bool inPhaseShift = localViewPlayer.IsPhaseShifted()
+
+ foreach ( arrowData in file.damageArrows )
+ {
+ if ( !arrowData.isVisible )
+ {
+ continue
+ }
+
+ if ( arrowData.grenade != null )
+ {
+ if ( !IsValid( arrowData.grenade ) )
+ arrowData.endTime = 0.0
+ }
+
+ if ( (file.damageArrowTime >= arrowData.endTime) || inPhaseShift )
+ {
+ arrowData.arrow.DisableDraw()
+ arrowData.arrow.ClearParent()
+ arrowData.attacker = null
+ arrowData.isVisible = false
+ arrowData.randomAngle = 0
+ continue
+ }
+
+ if ( !varsUpdated ) // only call UpdateDamageArrowVars if one or more of the file.damageArrows is visible
+ {
+ varsUpdated = true
+ UpdateDamageArrowVars( localViewPlayer )
+ }
+
+ UpdateDamageArrowAngle( arrowData )
+
+ if ( !arrowData.isDying && ( ( arrowData.endTime - file.damageArrowTime ) <= file.damageArrowFadeDuration ) )
+ {
+ arrowData.isDying = true
+ arrowData.arrow.Anim_NonScriptedPlay( DAMAGEARROW_FADEANIM )
+ }
+ }
+
+ wait( 0.0 )
+ }
+}
diff --git a/Northstar.Client/mod/scripts/vscripts/client/cl_screenfade.gnut b/Northstar.Client/mod/scripts/vscripts/client/cl_screenfade.gnut
index 9844b65b..deccbac2 100644
--- a/Northstar.Client/mod/scripts/vscripts/client/cl_screenfade.gnut
+++ b/Northstar.Client/mod/scripts/vscripts/client/cl_screenfade.gnut
@@ -247,8 +247,10 @@ void function PlayerPainSoundThread()
ourPlayer = localViewPlayer;
soundLayer2Start = GetPainSound( ourPlayer, "sound_pain_layer2_start" )
soundLayer2Loop = GetPainSound( ourPlayer, "sound_pain_layer2_loop" )
- EmitSoundOnEntity( ourPlayer, soundLayer2Start )
- EmitSoundOnEntity( ourPlayer, soundLayer2Loop )
+ if ( soundLayer2Start != "" )
+ EmitSoundOnEntity( ourPlayer, soundLayer2Start )
+ if ( soundLayer2Loop != "" )
+ EmitSoundOnEntity( ourPlayer, soundLayer2Loop )
}
else
{
@@ -274,7 +276,8 @@ void function PlayerPainSoundThread()
ourPlayer = localViewPlayer
soundLayer3Loop = GetPainSound( ourPlayer, "sound_pain_layer3_loop" )
soundLayer3End = GetPainSound( ourPlayer, "sound_pain_layer3_end" )
- EmitSoundOnEntity( ourPlayer, soundLayer3Loop )
+ if ( soundLayer3Loop != "" )
+ EmitSoundOnEntity( ourPlayer, soundLayer3Loop )
}
else
{
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_modmenu.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_modmenu.nut
index f08d69a7..67a18431 100644
--- a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_modmenu.nut
+++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_modmenu.nut
@@ -5,17 +5,8 @@ global function AddNorthstarModMenu_MainMenuFooter
global function ReloadMods
-struct modData {
- string name = ""
- string version = ""
- string link = ""
- int loadPriority = 0
- bool enabled = false
- array<string> conVars = []
-}
-
struct panelContent {
- modData& mod
+ ModInfo& mod
bool isHeader = false
}
@@ -37,10 +28,10 @@ struct {
var menu
array<var> panels
int scrollOffset = 0
- array<string> enabledMods
+ array<ModInfo> enabledMods
var currentButton
string searchTerm
- modData& lastMod
+ ModInfo& lastMod
} file
const int PANELS_LEN = 15
@@ -150,11 +141,22 @@ void function OnModMenuClosed()
}
catch ( ex ) {}
- array<string> current = GetEnabledModsArray()
+ array<ModInfo> current = GetEnabledModsArray()
bool reload
- foreach ( string mod in current )
+ foreach ( ModInfo mod in current )
{
- if ( file.enabledMods.find(mod) == -1 )
+ bool notFound = true
+
+ foreach ( ModInfo enMod in file.enabledMods )
+ {
+ if ( mod.name == enMod.name )
+ {
+ notFound = false
+ break
+ }
+ }
+
+ if ( notFound )
{
reload = true
break
@@ -176,10 +178,10 @@ void function OnModButtonFocused( var button )
RuiSetGameTime( rui, "startTime", -99999.99 ) // make sure it skips the whole animation for showing this
RuiSetString( rui, "headerText", modName )
- RuiSetString( rui, "messageText", FormatModDescription( modName ) )
+ RuiSetString( rui, "messageText", FormatModDescription() )
// Add a button to open the link with if required
- string link = NSGetModDownloadLinkByModName( modName )
+ string link = file.lastMod.downloadLink
var linkButton = Hud_GetChild( file.menu, "ModPageButton" )
if ( link.len() )
{
@@ -193,28 +195,44 @@ void function OnModButtonFocused( var button )
Hud_SetVisible( linkButton, false )
}
- SetControlBarColor( modName )
+ SetControlBarColor( file.lastMod )
- bool required = NSIsModRequiredOnClient( modName )
+ bool required = file.lastMod.requiredOnClient
Hud_SetVisible( Hud_GetChild( file.menu, "WarningLegendLabel" ), required )
Hud_SetVisible( Hud_GetChild( file.menu, "WarningLegendImage" ), required )
}
void function OnModButtonPressed( var button )
{
- string modName = file.mods[ int ( Hud_GetScriptID( Hud_GetParent( button ) ) ) + file.scrollOffset - 1 ].mod.name
- if ( StaticFind( modName ) && NSIsModEnabled( modName ) )
+ ModInfo mod = file.mods[ int ( Hud_GetScriptID( Hud_GetParent( button ) ) ) + file.scrollOffset - 1 ].mod
+ string modName = mod.name
+ if ( StaticFind( modName ) && mod.enabled )
CoreModToggleDialog( modName )
else
{
- NSSetModEnabled( modName, !NSIsModEnabled( modName ) )
- var panel = file.panels[ int ( Hud_GetScriptID( Hud_GetParent( button ) ) ) - 1 ]
- SetControlBoxColor( Hud_GetChild( panel, "ControlBox" ), modName )
- SetControlBarColor( modName )
- SetModEnabledHelperImageAsset( Hud_GetChild( panel, "EnabledImage" ), modName )
- // RefreshMods()
- UpdateListSliderPosition()
- UpdateListSliderHeight()
+ NSSetModEnabled( modName, !mod.enabled )
+
+ // retrieve state of the mod that just got toggled
+ array<ModInfo> infos = NSGetModInformation( mod.name )
+ foreach ( modInfo in infos )
+ {
+ if ( modInfo.name != modName || modInfo.version != mod.version )
+ {
+ continue
+ }
+
+ // Update UI mod state
+ file.mods[ int ( Hud_GetScriptID( Hud_GetParent( button ) ) ) + file.scrollOffset - 1 ].mod = modInfo
+
+ var panel = file.panels[ int ( Hud_GetScriptID( Hud_GetParent( button ) ) ) - 1 ]
+ SetControlBoxColor( Hud_GetChild( panel, "ControlBox" ), modInfo )
+ SetControlBarColor( modInfo )
+ SetModEnabledHelperImageAsset( Hud_GetChild( panel, "EnabledImage" ), modInfo )
+ // RefreshMods()
+ UpdateListSliderPosition()
+ UpdateListSliderHeight()
+ break
+ }
}
}
@@ -230,8 +248,8 @@ void function OnAuthenticationAgreementButtonPressed( var button )
void function OnModLinkButtonPressed( var button )
{
- string modName = file.mods[ int ( Hud_GetScriptID( Hud_GetParent( file.currentButton ) ) ) + file.scrollOffset - 1 ].mod.name
- string link = NSGetModDownloadLinkByModName( modName )
+ ModInfo mod = file.mods[ int ( Hud_GetScriptID( Hud_GetParent( file.currentButton ) ) ) + file.scrollOffset - 1 ].mod
+ string link = mod.downloadLink
if ( link.find("http://") != 0 && link.find("https://") != 0 )
link = "http://" + link // links without the http or https protocol get opened in the internal browser
LaunchExternalWebBrowser( link, WEBBROWSER_FLAG_FORCEEXTERNAL )
@@ -261,7 +279,7 @@ void function OnHideConVarsChange( var n )
if ( modName == "" )
return
var rui = Hud_GetRui( Hud_GetChild( file.menu, "LabelDetails" ) )
- RuiSetString( rui, "messageText", FormatModDescription( modName ) )
+ RuiSetString( rui, "messageText", FormatModDescription() )
}
// LIST LOGIC
@@ -284,23 +302,35 @@ void function CoreModToggleDialog( string mod )
void function DisableMod()
{
- string modName = file.mods[ int ( Hud_GetScriptID( Hud_GetParent( file.currentButton ) ) ) + file.scrollOffset - 1 ].mod.name
+ ModInfo mod = file.mods[ int ( Hud_GetScriptID( Hud_GetParent( file.currentButton ) ) ) + file.scrollOffset - 1 ].mod
+ string modName = mod.name
NSSetModEnabled( modName, false )
- var panel = file.panels[ int ( Hud_GetScriptID( Hud_GetParent( file.currentButton ) ) ) - 1]
- SetControlBoxColor( Hud_GetChild( panel, "ControlBox" ), modName )
- SetControlBarColor( modName )
- SetModEnabledHelperImageAsset( Hud_GetChild( panel, "EnabledImage" ), modName )
+ // retrieve state of the mod that just got toggled
+ array<ModInfo> infos = NSGetModInformation( mod.name )
+ foreach ( modInfo in infos )
+ {
+ if ( modInfo.name != modName || modInfo.version != mod.version )
+ {
+ continue
+ }
+
+ var panel = file.panels[ int ( Hud_GetScriptID( Hud_GetParent( file.currentButton ) ) ) - 1]
+ SetControlBoxColor( Hud_GetChild( panel, "ControlBox" ), modInfo )
+ SetControlBarColor( modInfo )
+ SetModEnabledHelperImageAsset( Hud_GetChild( panel, "EnabledImage" ), modInfo )
- RefreshMods()
+ RefreshMods()
+ break
+ }
}
-array<string> function GetEnabledModsArray()
+array<ModInfo> function GetEnabledModsArray()
{
- array<string> enabledMods
- foreach ( string mod in NSGetModNames() )
+ array<ModInfo> enabledMods
+ foreach ( ModInfo mod in NSGetModsInformation() )
{
- if ( NSIsModEnabled( mod ) )
+ if ( mod.enabled )
enabledMods.append( mod )
}
return enabledMods
@@ -324,29 +354,30 @@ void function UpdateList()
void function RefreshMods()
{
- array<string> modNames = NSGetModNames()
+ array<ModInfo> mods = NSGetModsInformation()
file.mods.clear()
bool reverse = GetConVarBool( "modlist_reverse" )
- int lastLoadPriority = reverse ? NSGetModLoadPriority( modNames[ modNames.len() - 1 ] ) + 1 : -1
+ int lastLoadPriority = reverse ? mods.top().loadPriority + 1 : -1
string searchTerm = Hud_GetUTF8Text( Hud_GetChild( file.menu, "BtnModsSearch" ) ).tolower()
- for ( int i = reverse ? modNames.len() - 1 : 0;
- reverse ? ( i >= 0 ) : ( i < modNames.len() );
+ for ( int i = reverse ? mods.len() - 1 : 0;
+ reverse ? ( i >= 0 ) : ( i < mods.len() );
i += ( reverse ? -1 : 1) )
{
- string mod = modNames[i]
+ ModInfo mod = mods[i]
+ string modName = mod.name
// Do not display remote mods
- if ( NSIsModRemote( mod ) )
+ if ( mod.isRemote )
continue
- if ( searchTerm.len() && mod.tolower().find( searchTerm ) == null )
+ if ( searchTerm.len() && modName.tolower().find( searchTerm ) == null )
continue
- bool enabled = NSIsModEnabled( mod )
- bool required = NSIsModRequiredOnClient( mod )
+ bool enabled = mod.enabled
+ bool required = mod.requiredOnClient
switch ( GetConVarInt( "filter_mods" ) )
{
case filterShow.ONLY_ENABLED:
@@ -367,11 +398,11 @@ void function RefreshMods()
break
}
- int pr = NSGetModLoadPriority( mod )
+ int pr = mod.loadPriority
if ( reverse ? pr < lastLoadPriority : pr > lastLoadPriority )
{
- modData m
+ ModInfo m
m.name = pr.tostring()
panelContent c
@@ -381,16 +412,8 @@ void function RefreshMods()
lastLoadPriority = pr
}
- modData m
- m.name = mod
- m.version = NSGetModVersionByModName( mod )
- m.link = NSGetModDownloadLinkByModName( mod )
- m.loadPriority = NSGetModLoadPriority( mod )
- m.enabled = enabled
- m.conVars = NSGetModConvarsByModName( mod )
-
panelContent c
- c.mod = m
+ c.mod = mod
file.mods.append( c )
}
@@ -404,7 +427,7 @@ void function DisplayModPanels()
break
panelContent c = file.mods[ file.scrollOffset + i ]
- modData mod = c.mod
+ ModInfo mod = c.mod
var btn = Hud_GetChild( panel, "BtnMod" )
var headerLabel = Hud_GetChild( panel, "Header" )
var box = Hud_GetChild( panel, "ControlBox" )
@@ -434,53 +457,45 @@ void function DisplayModPanels()
Hud_SetVisible( headerLabel, false )
- SetControlBoxColor( box, mod.name )
+ SetControlBoxColor( box, mod )
Hud_SetVisible( box, true )
Hud_SetVisible( line, false )
- Hud_SetVisible( warning, NSIsModRequiredOnClient( c.mod.name ) )
+ Hud_SetVisible( warning, mod.requiredOnClient )
- SetModEnabledHelperImageAsset( enabledImage, c.mod.name )
+ SetModEnabledHelperImageAsset( enabledImage, c.mod )
}
Hud_SetVisible( panel, true )
}
}
-void function SetModEnabledHelperImageAsset( var panel, string modName )
+void function SetModEnabledHelperImageAsset( var panel, ModInfo mod )
{
- if( NSIsModEnabled( modName ) )
+ if( mod.enabled )
RuiSetImage( Hud_GetRui( panel ), "basicImage", $"rui/menu/common/merit_state_success" )
else
RuiSetImage( Hud_GetRui( panel ), "basicImage", $"rui/menu/common/merit_state_failure" )
- RuiSetFloat3(Hud_GetRui( panel ), "basicImageColor", GetControlColorForMod( modName ) )
+ RuiSetFloat3(Hud_GetRui( panel ), "basicImageColor", GetControlColorForMod( mod ) )
Hud_SetVisible( panel, true )
}
-void function SetControlBoxColor( var box, string modName )
+void function SetControlBoxColor( var box, ModInfo mod )
{
var rui = Hud_GetRui( box )
- // if ( NSIsModEnabled( modName ) )
- // RuiSetFloat3(rui, "basicImageColor", <0,1,0>)
- // else
- // RuiSetFloat3(rui, "basicImageColor", <1,0,0>)
- RuiSetFloat3(rui, "basicImageColor", GetControlColorForMod( modName ) )
+ RuiSetFloat3(rui, "basicImageColor", GetControlColorForMod( mod ) )
}
-void function SetControlBarColor( string modName )
+void function SetControlBarColor( ModInfo mod )
{
var bar_element = Hud_GetChild( file.menu, "ModEnabledBar" )
var bar = Hud_GetRui( bar_element )
- // if ( NSIsModEnabled( modName ) )
- // RuiSetFloat3(bar, "basicImageColor", <0,1,0>)
- // else
- // RuiSetFloat3(bar, "basicImageColor", <1,0,0>)
- RuiSetFloat3(bar, "basicImageColor", GetControlColorForMod( modName ) )
+ RuiSetFloat3(bar, "basicImageColor", GetControlColorForMod( mod ) )
Hud_SetVisible( bar_element, true )
}
-vector function GetControlColorForMod( string modName )
+vector function GetControlColorForMod( ModInfo mod )
{
- if ( NSIsModEnabled( modName ) )
+ if ( mod.enabled )
switch ( GetConVarInt( "colorblind_mode" ) )
{
case 1:
@@ -502,17 +517,20 @@ vector function GetControlColorForMod( string modName )
unreachable
}
-string function FormatModDescription( string modName )
+string function FormatModDescription()
{
+ ModInfo mod = file.lastMod
+ string modName = mod.name
+
string ret
// version
- ret += format( "Version %s\n", NSGetModVersionByModName( modName ) )
+ ret += format( "Version %s\n", mod.version )
// load priority
- ret += format( "Load Priority: %i\n", NSGetModLoadPriority( modName ) )
+ ret += format( "Load Priority: %i\n", mod.loadPriority )
// convars
- array<string> modCvars = NSGetModConvarsByModName( modName )
+ array<string> modCvars = mod.conVars
if ( modCvars.len() != 0 && GetConVarBool( "modlist_show_convars" ) )
{
ret += "ConVars: "
@@ -529,7 +547,7 @@ string function FormatModDescription( string modName )
}
// description
- ret += format( "\n%s\n", NSGetModDescriptionByModName( modName ) )
+ ret += format( "\n%s\n", mod.description )
return ret
}
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut
index cdeb8b3e..f2effd12 100644
--- a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut
+++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_serverbrowser.nut
@@ -973,18 +973,27 @@ void function OnServerSelected_Threaded( var button )
foreach ( requiredModInfo in server.requiredMods )
{
// Tolerate core mods having different versions
- if ( requiredModInfo.name.len() > 10 && requiredModInfo.name.slice(0, 10) == "Northstar." )
+ if ( IsCoreMod( requiredModInfo.name ) )
continue
if ( !modNames.contains( requiredModInfo.name ) )
{
- print( format ( "\"%s\" was not found locally, triggering manifesto fetching.", requiredModInfo.name ) )
- uninstalledModFound = true
- break
- } else if ( NSGetModVersionByModName( requiredModInfo.name ) != requiredModInfo.version ) {
- print( format ( "\"%s\" was found locally but has version \"%s\" while server requires \"%s\", triggering manifesto fetching.", requiredModInfo.name, NSGetModVersionByModName( requiredModInfo.name ), requiredModInfo.version ) )
+ print( format ( "\"%s\" was not found locally" + ( autoDownloadAllowed ? ", triggering manifesto fetching." : "." ), requiredModInfo.name ) )
uninstalledModFound = true
break
+ } else {
+ array<string> modVersions = GetModVersions( requiredModInfo.name )
+
+ if ( !modVersions.contains( requiredModInfo.version ) ) {
+ print( format ( "\"%s\" was found locally but has versions:", requiredModInfo.name ) )
+ foreach ( string version in modVersions )
+ {
+ print(" - " + version)
+ }
+ print( format ( "while server requires \"%s\"" + ( autoDownloadAllowed ? ", triggering manifesto fetching." : "." ), requiredModInfo.version ) )
+ uninstalledModFound = true
+ break
+ }
}
}
@@ -992,17 +1001,16 @@ void function OnServerSelected_Threaded( var button )
// mods can be installed through auto-download
if ( uninstalledModFound && autoDownloadAllowed )
{
- print("Auto-download is allowed, checking if missing mods can be installed automatically.")
FetchVerifiedModsManifesto()
}
foreach ( RequiredModInfo mod in server.requiredMods )
{
// Tolerate core mods having different versions
- if ( mod.name.len() > 10 && mod.name.slice(0, 10) == "Northstar." )
+ if ( IsCoreMod( mod.name ) )
continue
- if ( !NSGetModNames().contains( mod.name ) || NSGetModVersionByModName( mod.name ) != mod.version )
+ if ( !NSGetModNames().contains( mod.name ) || !GetModVersions( mod.name ).contains( mod.version ) )
{
// Auto-download mod
if ( autoDownloadAllowed )
@@ -1055,43 +1063,8 @@ void function OnServerSelected_Threaded( var button )
return
}
}
- else
- {
- // this uses semver https://semver.org
- array<string> serverModVersion = split( mod.name, "." )
- array<string> clientModVersion = split( NSGetModVersionByModName( mod.name ), "." )
-
- bool semverFail = false
- // if server has invalid semver don't bother checking
- if ( serverModVersion.len() == 3 )
- {
- // bad client semver
- if ( clientModVersion.len() != serverModVersion.len() )
- semverFail = true
- // major version, don't think we should need to check other versions
- else if ( clientModVersion[ 0 ] != serverModVersion[ 0 ] )
- semverFail = true
- }
-
- if ( semverFail )
- {
- DialogData dialogData
- dialogData.header = "#ERROR"
- dialogData.message = Localize( "#WRONG_MOD_VERSION", mod.name, mod.version, NSGetModVersionByModName( mod.name ) )
- dialogData.image = $"ui/menu/common/dialog_error"
-
- #if PC_PROG
- AddDialogButton( dialogData, "#DISMISS" )
- AddDialogFooter( dialogData, "#A_BUTTON_SELECT" )
- #endif // PC_PROG
- AddDialogFooter( dialogData, "#B_BUTTON_DISMISS_RUI" )
-
- OpenDialog( dialogData )
-
- return
- }
- }
+ // If we get here, means that mod version exists locally => we good
}
if ( server.requiresPassword )
@@ -1136,25 +1109,31 @@ void function ThreadedAuthAndConnectToServer( string password = "", bool modsCha
if ( NSWasAuthSuccessful() )
{
// disable all RequiredOnClient mods that are not required by the server and are currently enabled
- foreach ( string modName in NSGetModNames() )
+ foreach ( ModInfo mod in NSGetModsInformation() )
{
- if ( NSIsModRequiredOnClient( modName ) && NSIsModEnabled( modName ) )
+ string modName = mod.name
+ string modVersion = mod.version
+
+ if ( mod.requiredOnClient && mod.enabled )
{
// find the mod name in the list of server required mods
bool found = false
foreach ( RequiredModInfo mod in file.lastSelectedServer.requiredMods )
{
- if (mod.name == modName)
+ // this tolerates a version difference for requiredOnClient core mods (only Northstar.Custom for now)
+ if (mod.name == modName && ( IsCoreMod( modName ) || mod.version == modVersion ))
{
found = true
+ print(format("\"%s\" (v%s) is required and already enabled.", modName, modVersion))
break
}
}
- // if we didnt find the mod name, disable the mod
+ // if we didn't find the mod name, disable the mod
if (!found)
{
modsChanged = true
NSSetModEnabled( modName, false )
+ print(format("Disabled \"%s\" (v%s) since it's not required on server.", modName, modVersion))
}
}
}
@@ -1162,10 +1141,33 @@ void function ThreadedAuthAndConnectToServer( string password = "", bool modsCha
// enable all RequiredOnClient mods that are required by the server and are currently disabled
foreach ( RequiredModInfo mod in file.lastSelectedServer.requiredMods )
{
- if ( NSIsModRequiredOnClient( mod.name ) && !NSIsModEnabled( mod.name ))
+ string modName = mod.name
+ string modVersion = mod.version
+ array<ModInfo> localModInfos = NSGetModInformation( modName )
+
+ // Tolerate core mods (only Northstar.Custom for now) having a different version than server
+ if ( IsCoreMod(modName) )
+ {
+ if ( !localModInfos[0].enabled )
+ {
+ modsChanged = true
+ NSSetModEnabled( modName, true )
+ print(format("Enabled \"%s\" (v%s) to join server.", modName, localModInfos[0].version))
+ }
+ }
+
+ else
{
- modsChanged = true
- NSSetModEnabled( mod.name, true )
+ foreach( localMod in localModInfos )
+ {
+ if ( localMod.version == mod.version )
+ {
+ modsChanged = true
+ NSSetModEnabled( mod.name, true )
+ print(format("Enabled \"%s\" (v%s) to join server.", modName, modVersion))
+ break
+ }
+ }
}
}
@@ -1361,3 +1363,19 @@ void function TriggerConnectToServerCallbacks( ServerInfo ornull targetServer =
callback( expect ServerInfo( targetServer ) )
}
}
+
+const array<string> CORE_MODS = ["Northstar.Client", "Northstar.Coop", "Northstar.CustomServers", "Northstar.Custom"]
+bool function IsCoreMod( string modName )
+{
+ return CORE_MODS.find( modName ) != -1
+}
+
+array<string> function GetModVersions( string modName )
+{
+ array<string> versions = []
+ foreach ( ModInfo mod in NSGetModInformation( modName ) )
+ {
+ versions.append( mod.version )
+ }
+ return versions
+}
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_setversionlabel.nut b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_setversionlabel.nut
index 6dbafde9..afba8a70 100644
--- a/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_setversionlabel.nut
+++ b/Northstar.Client/mod/scripts/vscripts/ui/menu_ns_setversionlabel.nut
@@ -5,6 +5,6 @@ void function NS_SetVersionLabel()
{
var mainMenu = GetMenu( "MainMenu" ) //Gets main menu element
var versionLabel = GetElementsByClassname( mainMenu, "nsVersionClass" )[0] //Gets the label from the mainMenu element.
- Hud_SetText( versionLabel, "v" + NSGetModVersionByModName("Northstar.Client")) //Sets the label text (Getting Northstar version from Northstar.Client)
+ Hud_SetText( versionLabel, "v" + NSGetModInformation( "Northstar.Client" )[0].version ) //Sets the label text (Getting Northstar version from Northstar.Client)
}
diff --git a/Northstar.Client/mod/scripts/vscripts/ui/panel_mainmenu.nut b/Northstar.Client/mod/scripts/vscripts/ui/panel_mainmenu.nut
index 2f1bcf02..330cd0d6 100644
--- a/Northstar.Client/mod/scripts/vscripts/ui/panel_mainmenu.nut
+++ b/Northstar.Client/mod/scripts/vscripts/ui/panel_mainmenu.nut
@@ -431,9 +431,9 @@ void function UpdatePlayButton( var button )
{
// restrict non-vanilla players from accessing official servers
bool hasNonVanillaMods = false
- foreach ( string modName in NSGetModNames() )
+ foreach ( ModInfo mod in NSGetModsInformation() )
{
- if ( NSIsModEnabled( modName ) && NSIsModRequiredOnClient( modName ) )
+ if ( mod.enabled && mod.requiredOnClient )
{
hasNonVanillaMods = true
break