diff options
Diffstat (limited to 'Northstar.Client/mod/scripts')
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 |