diff options
author | BobTheBob <32057864+BobTheBob9@users.noreply.github.com> | 2021-08-31 23:14:58 +0100 |
---|---|---|
committer | BobTheBob <32057864+BobTheBob9@users.noreply.github.com> | 2021-08-31 23:14:58 +0100 |
commit | 9a96d0bff56f1969c68bb52a2f33296095bdc67d (patch) | |
tree | 4175928e488632705692e3cccafa1a38dd854615 /Northstar.Custom/mod/scripts/vscripts | |
parent | 27bd240871b7c0f2f49fef137718b2e3c208e3b4 (diff) | |
download | NorthstarMods-9a96d0bff56f1969c68bb52a2f33296095bdc67d.tar.gz NorthstarMods-9a96d0bff56f1969c68bb52a2f33296095bdc67d.zip |
move to new mod format
Diffstat (limited to 'Northstar.Custom/mod/scripts/vscripts')
44 files changed, 8001 insertions, 0 deletions
diff --git a/Northstar.Custom/mod/scripts/vscripts/_custom_air_accel.gnut b/Northstar.Custom/mod/scripts/vscripts/_custom_air_accel.gnut new file mode 100644 index 00000000..5c228af4 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/_custom_air_accel.gnut @@ -0,0 +1,23 @@ +global function CustomAirAccelVars_Init + +void function CustomAirAccelVars_Init() +{ + AddPrivateMatchModeSettingArbitrary( "Pilot", "custom_air_accel_pilot", "500" ) // 500 is the default airaccel + + #if SERVER + AddCallback_OnPlayerRespawned( ApplyCustomPlayerAirAccel ) + AddCallback_OnPilotBecomesTitan( ApplyCustomPlayerAirAccelFromTitan ) // not sure if necessary but assuming it is + #endif +} + +#if SERVER +void function ApplyCustomPlayerAirAccel( entity player ) +{ + player.kv.airAcceleration = GetCurrentPlaylistVarInt( "custom_air_accel_pilot", int( player.GetPlayerSettingsField( "airAcceleration" ) ) ) +} + +void function ApplyCustomPlayerAirAccelFromTitan( entity player, entity titan ) +{ + player.kv.airAcceleration = GetCurrentPlaylistVarInt( "custom_air_accel_pilot", int( player.GetPlayerSettingsField( "airAcceleration" ) ) ) +} +#endif
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/_droppod_spawn.gnut b/Northstar.Custom/mod/scripts/vscripts/_droppod_spawn.gnut new file mode 100644 index 00000000..7447fc59 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/_droppod_spawn.gnut @@ -0,0 +1,79 @@ +untyped +global function DropPodSpawn_Init +global function SpawnPlayersInDropPod + +struct { + array< entity > droppods +} file + +void function DropPodSpawn_Init() +{ + AddCallback_OnRoundEndCleanup( CleanupSpawningDropPods ) +} + +void function CleanupSpawningDropPods() +{ + foreach ( entity pod in file.droppods ) + pod.Destroy() + + file.droppods.clear() +} + +void function SpawnPlayersInDropPod( array< entity > players, vector targetOrigin, vector angles, float destructionTime = -1 ) +{ + entity pod = CreateDropPod( targetOrigin, angles ) + + file.droppods.append( pod ) + svGlobal.levelEnt.EndSignal( "CleanUpEntitiesForRoundEnd" ) + + // TODO: we need to make a door for this, CreateDropPodDoor in _droppod_fireteam is just busted for some reason tho + + entity camera = CreateEntity( "point_viewcontrol" ) + camera.SetParent( pod, "ATTACH", false ) + camera.SetLocalOrigin( < 0, 150, 450 > ) + camera.SetAngles( < 60, -90, 0 > ) + + foreach ( entity player in players ) + { + if ( !IsAlive( player ) ) + player.RespawnPlayer( null ) + + player.SetOrigin( pod.GetOrigin() ) + player.SetAngles( pod.GetAngles() ) + player.SetParent( pod ) + player.FreezeControlsOnServer() + AddCinematicFlag( player, CE_FLAG_HIDE_MAIN_HUD ) + player.SetViewEntity( camera, true ) + } + + // wait for this + LaunchAnimDropPod( pod, "pod_testpath", targetOrigin, angles ) + + foreach ( entity player in players ) + { + player.ClearParent() + player.ClearViewEntity() + player.UnfreezeControlsOnServer() + RemoveCinematicFlag( player, CE_FLAG_HIDE_MAIN_HUD ) + } + + // wait a frame, otherwise this won't properly work + WaitFrame() + vector doorPos = pod.GetAttachmentOrigin( pod.LookupAttachment( "hatch" ) ) + + foreach ( entity player in players ) + { + vector viewAngles = doorPos - player.GetOrigin() + viewAngles.x = 3.0 + + player.SetAngles( viewAngles ) + } + + if ( destructionTime != -1 ) + { + wait destructionTime + pod.Dissolve( ENTITY_DISSOLVE_NORMAL, < 0, 0, 0 >, 0 ) + + file.droppods.remove( file.droppods.find( pod ) ) + } +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/_northstar_devcommands.gnut b/Northstar.Custom/mod/scripts/vscripts/_northstar_devcommands.gnut new file mode 100644 index 00000000..960e619e --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/_northstar_devcommands.gnut @@ -0,0 +1,54 @@ +untyped +global function NorthstarDevCommands_Init + +void function NorthstarDevCommands_Init() +{ + AddClientCommandCallback( "noclip", ClientCommandCallbackToggleNoclip ) + AddClientCommandCallback( "script", ClientCommandCallbackEvalScript ) + AddClientCommandCallback( "kill", ClientCommandCallbackKill ) +} + +bool function ClientCommandCallbackToggleNoclip( entity player, array<string> args ) +{ + if ( GetConVarInt( "sv_cheats" ) != 1 ) + return true + + if ( player.IsNoclipping() ) + player.SetPhysics( MOVETYPE_WALK ) + else + player.SetPhysics( MOVETYPE_NOCLIP ) + + return true +} + +bool function ClientCommandCallbackEvalScript( entity player, array<string> args ) +{ + if ( args.len() < 1 || GetConVarInt( "sv_cheats" ) != 1 ) + return true + + // todo: rewrite this at some point to use a concommand because clientcommands can't just take in a single string with spaces, quotes etc + // should just have the concommand call a clientcommand manually with properly formatted args + string joinedArgs = args[0] + for ( int i = 1; i < args.len(); i++ ) + joinedArgs += " " + args[i] + + try + { + compilestring( joinedArgs )() + } + catch (exception) + { + // should probably send this to the client at some point + // no need to log here because compilestring errors already do that + } + + return true +} + +bool function ClientCommandCallbackKill( entity player, array<string> args ) +{ + if ( IsAlive( player ) && GetConVarInt( "sv_cheats" ) == 1 ) + player.Die() + + return true +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/_promode.gnut b/Northstar.Custom/mod/scripts/vscripts/_promode.gnut new file mode 100644 index 00000000..3e6d6d65 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/_promode.gnut @@ -0,0 +1,25 @@ +untyped +global function Promode_Init +global function PromodeEnabled + +void function Promode_Init() +{ + AddPrivateMatchModeSettingEnum( "Promode", "promode_enable", [ "Disabled", "Enabled" ], "0" ) + + #if SERVER + AddCallback_OnPlayerRespawned( GivePromodeWeaponMod ) + #endif +} + +bool function PromodeEnabled() +{ + return GetCurrentPlaylistVarInt( "promode_enable", 0 ) == 1 +} + +#if SERVER +void function GivePromodeWeaponMod( entity player ) +{ + if ( PromodeEnabled() ) + player.GiveExtraWeaponMod( "promode" ) +} +#endif
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_boost_store.gnut b/Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_boost_store.gnut new file mode 100644 index 00000000..515ffd9d --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_boost_store.gnut @@ -0,0 +1,762 @@ +global function ShInitBoostStore +global function ShInitBoostStoreForMode +global function GetAvailableBoosts +global function BoostStoreEnabled +global function CanPurchaseBoost +global function CanAffordBoost +global function GetPlaylistBoostCost + +#if CLIENT || SERVER +global function BoostStoreOpen +#endif + +#if SERVER +global function SetBoostPurchaseCallback +global function SetBoostRefundCallback +global function SetTeamReserveInteractCallback +global function AddMoneyToPlayer +global function SetMoneyForPlayer +global function CreateBoostStoreLocation +global function OpenBoostStores +global function CloseBoostStores +global function GetBoostStores +global function BurnRewardRefundThink +global function SetJoinInProgressBonus +#endif + +#if CLIENT +global function ServerCallback_EnableDropshipBoostStore +global function ServerCallback_DisableDropshipBoostStore +global function ServerCallback_BoostStoreTitanHint +#endif + +#if UI +global function UpdatePlayerMoney +global function ServerCallback_UpdateTurretCount +global function ServerCallback_UpdatePlayerHasBattery +global function ServerCallback_UpdateAmpedWeaponState +#endif + +global function GetPlayerMoney +global function GetTeamReserve +global function UpdateTeamReserve + +const int MAX_MONEY = 5000 +const float BOOST_STORE_TRIGGER_RADIUS = 300 +const int BOOST_STORE_DEFAULT_EXCHANGE = 100 + +global struct BoostStoreData +{ + string itemRef + string modesAllowed + string additionalDesc + int cost + bool autoActivate + asset storeIcon + asset lockedStoreIcon + string additionalDescFail +} + +struct +{ + array<BoostStoreData> allBoosts + array<BoostStoreData> availableBoosts + table<entity,int> playerBonusData + int joinInProgressBonus = 0 + array<string> joinInProgressGivenToPlayers + + int teamReserveAmount = 0 + + var boostStoreHintRui + + #if UI + int playerMoney = 0 + int turretCount = 0 + int maxTurretCount = 5 + bool playerHasBattery = false + bool playerHasAmpedWeapons = false + #endif + + array<entity> storeLocations + table<string,bool functionref(entity)> boostPurchaseExtraChecks + void functionref(entity,BoostStoreData) boostPurchaseCallback + void functionref( entity, string ) boostRefundCallback + void functionref( entity, string, int ) teamReserveDepositOrWithdrawCallback +} file + +void function ShInitBoostStore() +{ + var dataTable = GetDataTable( $"datatable/burn_meter_store.rpak" ) + int len = GetDatatableRowCount( dataTable ) + for ( int i=0; i<len; i++ ) + { + string allowedModes = GetDataTableString( dataTable, i, GetDataTableColumnByName( dataTable, "modes" ) ) + string itemRef = GetDataTableString( dataTable, i, GetDataTableColumnByName( dataTable, "itemRef" ) ) + string additionalDesc = GetDataTableString( dataTable, i, GetDataTableColumnByName( dataTable, "extraDesc" ) ) + int cost = GetDataTableInt( dataTable, i, GetDataTableColumnByName( dataTable, "cost" ) ) + bool autoActivate = GetDataTableBool( dataTable, i, GetDataTableColumnByName( dataTable, "autoActivate" ) ) + asset storeIcon = GetDataTableAsset( dataTable, i, GetDataTableColumnByName( dataTable, "storeIcon" ) ) + asset lockedStoreIcon = GetDataTableAsset( dataTable, i, GetDataTableColumnByName( dataTable, "lockedStoreIcon" ) ) + string additionalDescFail = GetDataTableString( dataTable, i, GetDataTableColumnByName( dataTable, "extraDescFail" ) ) + BoostStoreData data + data.itemRef = itemRef + data.cost = cost + data.modesAllowed = allowedModes + data.additionalDesc = additionalDesc + data.autoActivate = autoActivate + data.storeIcon = storeIcon + data.lockedStoreIcon = lockedStoreIcon + data.additionalDescFail = additionalDescFail + file.allBoosts.append( data ) + } + + foreach ( turretType in GetAllBoostTurretTypes() ) + { + file.boostPurchaseExtraChecks[ turretType ] <- CheckTooManyTurrets + } + + file.boostPurchaseExtraChecks[ "burnmeter_instant_battery" ] <- CheckHasNoBattery + file.boostPurchaseExtraChecks[ "burnmeter_amped_weapons_permanent" ] <- CheckHasNoAmpedWeapons + + #if UI + AddUICallback_OnLevelInit( ShInitBoostStoreForMode ) + #else + if ( !IsLobby() ) + ShInitBoostStoreForMode() + #endif + + #if SERVER + if ( !BoostStoreEnabled() ) + return + + AddCallback_OnClientConnected( Store_OnClientConnected ) + AddClientCommandCallback( "PurchaseBoost", ClientCommand_PurchaseBoost ) + AddClientCommandCallback( "TeamReserveDeposit", ClientCommand_Deposit ) + AddClientCommandCallback( "TeamReserveWithdraw", ClientCommand_Withdraw ) + RegisterSignal( "BoostRefunded" ) + RegisterSignal( "CancelRefund" ) + #endif +} + +#if SERVER +void function SetJoinInProgressBonus( int moneyToAdd ) +{ + file.joinInProgressBonus = file.joinInProgressBonus + moneyToAdd +} + +void function SetBoostPurchaseCallback( void functionref( entity, BoostStoreData ) func ) +{ + file.boostPurchaseCallback = func +} + +void function SetBoostRefundCallback( void functionref( entity, string ) func ) +{ + file.boostRefundCallback = func +} + +void function SetTeamReserveInteractCallback( void functionref( entity, string, int ) func ) +{ + file.teamReserveDepositOrWithdrawCallback = func +} + +void function Store_OnClientConnected( entity player ) +{ + file.playerBonusData[ player ] <- 0 + string playerUID = player.GetUID() + if ( !file.joinInProgressGivenToPlayers.contains( playerUID ) ) + { + AddMoneyToPlayer( player, file.joinInProgressBonus ) + file.joinInProgressGivenToPlayers.append( playerUID ) + } +} +#endif + +void function ShInitBoostStoreForMode() +{ + string boostStoreMode = GetCurrentPlaylistVarString( "boost_store_mode", "off" ) + file.availableBoosts.clear() + + if ( boostStoreMode == "off" ) + return + + foreach ( data in file.allBoosts ) + { + array<string> tokens = split( data.modesAllowed, " " ) + if ( tokens.contains( boostStoreMode ) ) + { + file.availableBoosts.append( data ) + } + } + file.availableBoosts.sort( SortCompareBoostCost ) + printt( file.availableBoosts.len() ) +} + +int function SortCompareBoostCost( BoostStoreData a, BoostStoreData b ) +{ + int acost = GetPlaylistBoostCost( a.itemRef, a.cost ) + int bcost = GetPlaylistBoostCost( b.itemRef, b.cost ) + + if ( acost > bcost ) + return 1 + + if ( bcost > acost ) + return -1 + + return 0 +} + +array<BoostStoreData> function GetAvailableBoosts() +{ + // custom stuff for arena + string boostStoreMode = GetCurrentPlaylistVarString( "boost_store_mode", "off" ) + if ( boostStoreMode == "arena" ) + { + array<BoostStoreData> arenaLoadouts = PopulateArenaLoadouts() + + #if SERVER + // bit of a hack, but assign weapon checks to weapon refs + foreach ( BoostStoreData data in arenaLoadouts ) + if ( data.itemRef in eDamageSourceId ) + file.boostPurchaseExtraChecks[ data.itemRef ] <- bool function( entity player ) { return player.GetMainWeapons().len() != 3 } + #endif + + return arenaLoadouts + } + + return file.availableBoosts +} + +bool function CanAffordBoost( entity player, BoostStoreData data ) +{ + int money = GetPlayerMoney( player ) + return money >= GetPlaylistBoostCost( data.itemRef, data.cost ) +} + +bool function CanPurchaseBoost( entity player, BoostStoreData data, bool checkMoney = true ) +{ + bool canBuy = true + + if ( checkMoney ) + canBuy = canBuy && CanAffordBoost( player, data ) + + if ( data.itemRef in file.boostPurchaseExtraChecks ) + { + canBuy = canBuy && file.boostPurchaseExtraChecks[ data.itemRef ]( player ) + } + + return canBuy +} + +#if SERVER +bool function ClientCommand_PurchaseBoost( entity player, array<string> args ) +{ + // bob note: readded this check, why the fuck is it not a check in vanilla + if ( !BoostStoreOpen() ) + return true + + if ( player.IsPhaseShifted() ) + return false + + if ( !IsAlive( player ) ) + return false + + if ( player.IsTitan() ) + return false + + array<BoostStoreData> availableBoosts = GetAvailableBoosts() + + if ( args.len() == 0 ) + return false + + foreach ( data in availableBoosts ) + { + if ( data.itemRef == args[0] ) + { + if ( IsItemLocked( player, data.itemRef ) ) + break + + if ( CanPurchaseBoost( player, data ) ) + { + if ( file.boostPurchaseCallback != null ) + file.boostPurchaseCallback( player, data ) + + AddMoneyToPlayer( player, -1*GetPlaylistBoostCost( data.itemRef, data.cost ) ) + BurnReward burnReward = BurnReward_GetByRef( data.itemRef ) + if ( !data.autoActivate ) + { + BurnMeter_GiveRewardDirect( player, data.itemRef ) + } + else + { + RunBurnCardUseFunc( player, data.itemRef ) + Remote_CallFunction_NonReplay( player, "ServerCallback_RewardUsed", burnReward.id ) + EmitSoundOnEntityOnlyToPlayer( player, player, "HUD_Boost_Card_Earned_1P" ) + } + EmitSoundOnEntityOnlyToPlayer( player, player, "UI_InGame_FD_ArmoryPurchase" ) + MessageToTeam( player.GetTeam(), eEventNotifications.FD_BoughtItem, null, player, burnReward.id ) + break + } + } + } + + return true +} + +bool function ClientCommand_Deposit( entity player, array<string> args ) +{ + // if ( !BoostStoreOpen() ) + // return true + + int depositAmount = minint( file.playerBonusData[ player ], BOOST_STORE_DEFAULT_EXCHANGE ) + file.teamReserveAmount += depositAmount + AddMoneyToPlayer( player, -1*depositAmount ) + + if ( IsValid( file.teamReserveDepositOrWithdrawCallback ) ) + file.teamReserveDepositOrWithdrawCallback( player, "deposit", depositAmount ) + + foreach ( player in GetPlayerArray() ) + { + Remote_CallFunction_UI( player, "ServerCallback_UpdateTeamReserve", file.teamReserveAmount ) + } + + return true +} + + +bool function ClientCommand_Withdraw( entity player, array<string> args ) +{ + // if ( !BoostStoreOpen() ) + // return true + + if ( GetPlayerMoney( player ) >= MAX_MONEY ) + return true + + int withdrawAmount = minint( file.teamReserveAmount, BOOST_STORE_DEFAULT_EXCHANGE ) + file.teamReserveAmount -= withdrawAmount + AddMoneyToPlayer( player, withdrawAmount ) + + if ( IsValid( file.teamReserveDepositOrWithdrawCallback ) ) + file.teamReserveDepositOrWithdrawCallback( player, "withdraw", withdrawAmount ) + + foreach ( player in GetPlayerArray() ) + { + Remote_CallFunction_UI( player, "ServerCallback_UpdateTeamReserve", file.teamReserveAmount ) + } + + return true +} + +void function AddMoneyToPlayer( entity player, int points ) +{ + SetMoneyForPlayer( player, file.playerBonusData[ player ] + points ) +} + +void function SetMoneyForPlayer( entity player, int points ) +{ + file.playerBonusData[ player ] = int( Clamp( float( points ), 0, MAX_MONEY ) ) + + int bonusPoints = file.playerBonusData[ player ] + int bonusPointStack = int( max( ( bonusPoints - ( bonusPoints % 256 ) ) / 256, 0 ) ) + int bonusPointRemainder = ( bonusPoints % 256 ) + + player.SetPlayerNetInt( "FD_money", bonusPointRemainder ) + player.SetPlayerNetInt( "FD_money256", bonusPointStack ) + player.SetTitle( "$" + bonusPoints ) + + Remote_CallFunction_UI( player, "ServerCallback_UpdateMoney", file.playerBonusData[ player ] ) +} + +void function CreateBoostStoreLocation( int team, vector origin, vector angles, bool showOnMinimap = true ) +{ + if ( !BoostStoreEnabled() ) + return + + entity crate = CreatePropDynamic( MODEL_ATTRITION_BANK, origin, angles, 6 ) + + entity invisibleCrate = CreatePropScript( MODEL_ATTRITION_BANK, origin, angles, 6 ) + SetTargetName( invisibleCrate, "boostStore" ) + SetTeam( invisibleCrate, team ) + invisibleCrate.Hide() + invisibleCrate.NotSolid() + + // just to create an association + crate.SetParent( invisibleCrate ) + + thread PlayAnim( crate, "mh_inactive_idle", crate.GetParent() ) + if ( BoostStoreOpen() ) + thread EnableBoostStoreCrate( crate ) + + crate.SetUsePrompts( "#BOOST_STORE_HOLD_USE", "#BOOST_STORE_PRESS_USE" ) + crate.SetForceVisibleInPhaseShift( true ) + + file.storeLocations.append( crate ) + + if ( showOnMinimap ) + { + invisibleCrate.Minimap_SetObjectScale( MINIMAP_LOADOUT_CRATE_SCALE ) + invisibleCrate.Minimap_SetAlignUpright( true ) + invisibleCrate.Minimap_AlwaysShow( TEAM_IMC, null ) + invisibleCrate.Minimap_AlwaysShow( TEAM_MILITIA, null ) + invisibleCrate.Minimap_SetHeightTracking( true ) + invisibleCrate.Minimap_SetZOrder( MINIMAP_Z_OBJECT ) + invisibleCrate.Minimap_SetCustomState( eMinimapObject_prop_script.BOOST_STORE ) + } + if ( !GetGlobalNetBool("boostStoreOpen") ) + { + invisibleCrate.Minimap_Hide( TEAM_IMC, null ) + invisibleCrate.Minimap_Hide( TEAM_MILITIA, null ) + } + + thread CreateBoostStoreHintTrigger( crate ) + thread BoostStoreThink( crate ) +} + +void function EnableBoostStoreCrate( entity crate ) +{ + crate.EndSignal( "OnDestroy" ) + crate.SetUsable() + + entity parentCrate = crate.GetParent() + int team = parentCrate.GetTeam() + if ( team == TEAM_MILITIA || team == TEAM_IMC ) + crate.SetUsableByGroup( "friendlies pilot" ) + else + crate.SetUsableByGroup( "pilot" ) + + SetTeam( crate, team ) + + waitthread PlayAnim( crate, "mh_inactive_2_active", crate.GetParent() ) + thread PlayAnim( crate, "mh_active_idle", crate.GetParent() ) + EmitSoundOnEntity( crate, "Mobile_Hardpoint_Idle" ) +} + +void function DisableBoostStoreCrate( entity crate ) +{ + crate.EndSignal( "OnDestroy" ) + crate.UnsetUsable() + SetTeam( crate, TEAM_UNASSIGNED ) + FadeOutSoundOnEntity( crate, "Mobile_Hardpoint_Idle", 1.0 ) + waitthread PlayAnim( crate, "mh_active_2_inactive", crate.GetParent() ) + thread PlayAnim( crate, "mh_inactive_idle", crate.GetParent() ) +} + +void function CreateBoostStoreHintTrigger( entity crate ) +{ + entity trig = CreateEntity( "trigger_cylinder" ) + trig.SetRadius( BOOST_STORE_TRIGGER_RADIUS ) + trig.SetAboveHeight( 200 ) + trig.SetBelowHeight( 0) + trig.SetOrigin( crate.GetOrigin() ) + trig.kv.triggerFilterNpc = "none" + trig.kv.triggerFilterPlayer = "all" + DispatchSpawn( trig ) + + trig.EndSignal( "OnDestroy" ) + crate.EndSignal( "OnDestroy" ) + + OnThreadEnd( + function() : ( trig ) + { + if ( IsValid( trig ) ) + trig.Destroy() + } + ) + + trig.SetEnterCallback( BoostHintTrigEnter ) + + WaitForever() +} + +void function BoostHintTrigEnter( entity trigger, entity ent ) +{ + if ( !ent.IsPlayer() ) + return + if ( !ent.IsTitan() ) + return + if ( !GetGlobalNetBool( "boostStoreOpen" ) ) + return + vector org = trigger.GetOrigin() + Remote_CallFunction_NonReplay( ent, "ServerCallback_BoostStoreTitanHint", org.x, org.y, org.z ) +} + +void function CloseBoostStores() +{ + foreach ( crate in file.storeLocations ) + thread DisableBoostStoreCrate( crate ) + SetGlobalNetBool("boostStoreOpen", false) +} + +void function OpenBoostStores() +{ + foreach ( crate in file.storeLocations ) + thread EnableBoostStoreCrate( crate ) + SetGlobalNetBool("boostStoreOpen", true) +} + +array<entity> function GetBoostStores() +{ + return file.storeLocations +} + +void function BurnRewardRefundThink( entity useEnt, entity ent ) +{ + useEnt.EndSignal( "OnDestroy" ) + ent.EndSignal( "OnDestroy" ) + ent.EndSignal( "OnDeath" ) + ent.EndSignal( "CancelRefund" ) + useEnt.SetUsable() + useEnt.SetUsableByGroup( "owner pilot" ) + useEnt.SetUsePrompts( "#REFUND_HOLD_USE", "#REFUND_PRESS_USE" ) + + entity player = expect entity( useEnt.WaitSignal( "OnPlayerUse" ).player ) + { + if ( ent.e.burnReward == "" ) + return + + BurnMeter_GiveRewardDirect( player, ent.e.burnReward ) + entity weapon = player.GetOffhandWeapon( OFFHAND_INVENTORY ) + + // Defensive: meaning the boost didn't make it to the inventory for some reason + if ( weapon == null ) + return + + if ( IsTurret( ent ) ) + { + weapon.w.savedKillCount = int( ent.kv.killCount ) + ent.DisableTurret() + ent.Signal( "StopTurretLaser" ) + } + weapon.e.fd_roundDeployed = ent.e.fd_roundDeployed + + EmitSoundAtPosition( TEAM_UNASSIGNED, ent.GetOrigin(), "Emplacement_Move_Dissolve" ) + ent.Signal( "BoostRefunded" ) + ent.UnsetUsable() + ent.SetInvulnerable() + ent.Dissolve( ENTITY_DISSOLVE_CORE, Vector( 0, 0, 0 ), 100 ) + + if ( file.boostRefundCallback != null ) + file.boostRefundCallback( player, ent.e.burnReward ) + } +} + +void function BoostStoreThink( entity crate ) +{ + while( IsValid( crate ) ) + { + entity player = expect entity( crate.WaitSignal( "OnPlayerUse" ).player ) + if ( IsValid( player ) ) + OpenBoostStoreMenu( player ) + } +} + +void function OpenBoostStoreMenu( entity player ) +{ + if ( !BoostStoreEnabled() ) + return + + CalculatePlayerTurretCount( player ) + Remote_CallFunction_UI( player, "ServerCallback_UpdateMoney", file.playerBonusData[ player ] ) + Remote_CallFunction_UI( player, "ServerCallback_UpdateTeamReserve", file.teamReserveAmount ) + Remote_CallFunction_UI( player, "ServerCallback_UpdatePlayerHasBattery", PlayerHasBattery( player ) ) + Remote_CallFunction_UI( player, "ServerCallback_UpdateAmpedWeaponState", PlayerHasAmpedWeapons( player ) ) + Remote_CallFunction_UI( player, "ServerCallback_OpenBoostStore" ) +} +#endif + +#if CLIENT +void function ServerCallback_BoostStoreTitanHint( float x, float y, float z ) +{ + vector org = <x,y,z> + thread ServerCallback_BoostStoreTitanHint_Internal( org ) +} + +void function ServerCallback_BoostStoreTitanHint_Internal( vector org ) +{ + entity player = GetLocalViewPlayer() + + player.EndSignal( "OnDeath" ) + + float titanHullCompensate = 75.0 + float maxDist = ((BOOST_STORE_TRIGGER_RADIUS+titanHullCompensate) * (BOOST_STORE_TRIGGER_RADIUS+titanHullCompensate)) + float minDot = 0.8 + float fadeTime = 0.25 + float hintTime = 3.5 + float showHintTime = -999.0 + bool hintShowing = false + + OnThreadEnd( + function() : ( ) + { + HidePlayerHint( "#BOOST_STORE_NEED_PILOT_HINT" ) + } + ) + + while ( player.IsTitan() ) + { + float dist = DistanceSqr( org, player.GetOrigin() ) + + bool showHint = false + + if ( dist <= maxDist ) + { + if ( PlayerCanSeePos( player, org, false, 35.0 ) ) + { + if ( Time() - showHintTime + fadeTime >= hintTime ) + { + AddPlayerHint( hintTime, fadeTime, $"", "#BOOST_STORE_NEED_PILOT_HINT" ) + showHintTime = Time() + } + showHint = true + } + } + else + { + return + } + + if ( !showHint && Time() - showHintTime + fadeTime < hintTime ) + { + showHintTime = -999.0 + HidePlayerHint( "#BOOST_STORE_NEED_PILOT_HINT" ) + } + + WaitFrame() + } +} + + +void function OpenBoostStoreMenu( entity player ) +{ + if ( !BoostStoreEnabled() ) + return + + RunUIScript( "OpenBoostStore" ) +} + +void function ServerCallback_EnableDropshipBoostStore() +{ + if ( !BoostStoreEnabled() ) + return + + RegisterButtonPressedCallback( BUTTON_A, OpenBoostStoreMenu ) + RegisterButtonPressedCallback( KEY_SPACE, OpenBoostStoreMenu ) + if ( file.boostStoreHintRui != null ) + RuiDestroyIfAlive( file.boostStoreHintRui ) + file.boostStoreHintRui = CreatePermanentCockpitRui( $"ui/hint_display.rpak" ) + RuiSetString( file.boostStoreHintRui, "hintText", Localize( "#BOOST_STORE_DROPSHIP_PRESS_USE" ) ) +} + +void function ServerCallback_DisableDropshipBoostStore() +{ + if ( !BoostStoreEnabled() ) + return + + if ( file.boostStoreHintRui != null ) + { + RuiDestroyIfAlive( file.boostStoreHintRui ) + DeregisterButtonPressedCallback( BUTTON_A, OpenBoostStoreMenu ) + DeregisterButtonPressedCallback( KEY_SPACE, OpenBoostStoreMenu ) + file.boostStoreHintRui = null + } +} +#endif + +bool function BoostStoreEnabled() +{ + return ( GetCurrentPlaylistVarString( "boost_store_mode", "off" ) != "off" ) +} + +#if CLIENT || SERVER +bool function BoostStoreOpen() +{ + return GetGlobalNetBool("boostStoreOpen" ) +} +#endif + +int function GetTeamReserve() +{ + return file.teamReserveAmount +} + +void function UpdateTeamReserve( int reserveMoney ) +{ + file.teamReserveAmount = reserveMoney +} + + +int function GetPlayerMoney( entity player ) +{ + #if SERVER + return file.playerBonusData[ player ] + #endif + + #if CLIENT + int money = player.GetPlayerNetInt( "FD_money" ) + int money256 = player.GetPlayerNetInt( "FD_money256" ) + return ( money256 * 256 ) + money + #endif + + #if UI + return file.playerMoney + #endif +} + +bool function CheckTooManyTurrets( entity player ) +{ + #if SERVER || CLIENT + return player.GetPlayerNetInt( "burn_numTurrets" ) < GetGlobalNetInt( "burn_turretLimit" ) + #elseif UI + return file.turretCount < file.maxTurretCount + #endif +} + +bool function CheckHasNoBattery( entity player ) +{ + #if SERVER || CLIENT + return !PlayerHasBattery( player ) + #elseif UI + return !file.playerHasBattery + #endif +} + +bool function CheckHasNoAmpedWeapons( entity player ) +{ + #if SERVER || CLIENT + return !PlayerHasAmpedWeapons( player ) + #elseif UI + return !file.playerHasAmpedWeapons + #endif +} + +#if UI +void function ServerCallback_UpdatePlayerHasBattery( bool hasBattery ) +{ + file.playerHasBattery = hasBattery +} + +void function ServerCallback_UpdateAmpedWeaponState( bool hasAmpedWeapons ) +{ + file.playerHasAmpedWeapons = hasAmpedWeapons +} + +void function ServerCallback_UpdateTurretCount( int count, int max ) +{ + file.turretCount = count + file.maxTurretCount = max +} + +void function UpdatePlayerMoney( int money ) +{ + file.playerMoney = money +} +#endif + +int function GetPlaylistBoostCost( string ref, int originalCost ) +{ + int cost = GetCurrentPlaylistVarInt( "override_boost_cost_" + ref, -1 ) + + if ( cost > 0 ) + return cost + + return originalCost +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_burnmeter.gnut b/Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_burnmeter.gnut new file mode 100644 index 00000000..3f914745 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/burnmeter/sh_burnmeter.gnut @@ -0,0 +1,488 @@ +global function ShBurnMeter_Init +global function BurnReward_GetById +global function BurnReward_GetByRef +global function BurnReward_GetRandom +global function GetSelectedBurnCardRef +global function GetBoostSkin +#if SERVER || CLIENT +global function BurnMeterEnabled +global function CanUseWeaponAsBurnCard +global function TryUsingBurnCardWeapon +global function TryUsingBurnCardWeaponInCriticalSection +global function BurnMeterPlayer_CanUseEquipped +global function OnWeaponAttemptOffhandSwitch_burncardweapon +global function BurnMeterPlayer_CanUseReward +global function BurnMeterPlayer_GetRewardOrGoal +global function BurnReward_GetTopInventoryItemBurnCard +global function OnWeaponPrimaryAttack_nuke_eject +global function BurnMeter_HarvesterShieldCanUse +#endif + +global function GetAllBoostTurretTypes + +global const BURN_METER_SMALL_POINT_VALUE = 1 +global const BURN_METER_MID_POINT_VALUE = 2 +global const BURN_METER_LARGE_POINT_VALUE = 5 +global const BURN_METER_EXTRA_LARGE_POINT_VALUE = 10 + +global const BURN_METER_RADAR_JAMMER_PULSE_DURATION = 6.0 +global const BURN_METER_RADAR_JAMMER_EASE_OFF_TIME = 1.0 + +global enum eBurnMeterRewardAvailableFor +{ + PILOT_ONLY, + TITAN_ONLY, + PILOT_AND_TITAN, + SPECIAL_PLAYERS_ONLY, +} + +global struct BurnReward +{ + int id = -1 + string ref = "" + string localizedName = "" + string description = "" + asset image = $"" + float cost + int userType = -1 + int skinIndex = 0 + string weaponName = "" + string extraWeaponMod = "" + string activationText = "" + + void functionref( entity ) ornull rewardAvailableCallback = null +} + +global struct BurnStruct +{ + table< string, BurnReward > burnRewards + array< BurnReward > allCards //Kinda inefficient to store the same burn cards 3 times (once in burnRewards, once in foil/normal, once in allcards. Prefer speed over memory? ) + array< BurnReward > allowedCards + +} + +global BurnStruct burn + +global table< string, bool functionref( entity ) > burnMeterCanUseFuncTable + +struct +{ + + bool shouldCycleOnRelease = false + +} file + +array<string> turretBurnCards = [ + "burnmeter_ap_turret_weapon", + "burnmeter_at_turret_weapon", + "burnmeter_ap_turret_weapon_infinite", + "burnmeter_at_turret_weapon_infinite", +] + +void function ShBurnMeter_Init() +{ + + #if SERVER || CLIENT + //burnMeterCanUseFuncTable[ "burnmeter_random_foil" ] <- BurnMeter_RandomFoilCanUse + burnMeterCanUseFuncTable[ "burnmeter_emergency_titan" ] <- BurnMeter_EmergencyTitanCanUse + burnMeterCanUseFuncTable[ "burnmeter_nuke_titan" ] <- BurnMeter_NukeTitanCanUse + // burnMeterCanUseFuncTable[ "burnmeter_harvester_shield" ] <- BurnMeter_HarvesterShieldCanUse + #endif + + + + var dataTable = GetDataTable( $"datatable/burn_meter_rewards.rpak" ) + + for ( int row = 0; row < GetDatatableRowCount( dataTable ); row++ ) + { + BurnReward burnReward + burnReward.id = row + burnReward.ref = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, BURN_REF_COLUMN_NAME ) ) + burnReward.localizedName = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, BURN_NAME_COLUMN_NAME ) ) + burnReward.description = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, BURN_DESCRIPTION_COLUMN_NAME ) ) + burnReward.image = GetDataTableAsset( dataTable, row, GetDataTableColumnByName( dataTable, BURN_IMAGE_COLUMN_NAME ) ) + burnReward.cost = GetDataTableFloat( dataTable, row, GetDataTableColumnByName( dataTable, BURN_COST_COLUMN_NAME ) ) + string userType = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, BURN_AVAILABLE_COLUMN_NAME ) ) + burnReward.userType = eBurnMeterRewardAvailableFor[userType] + burnReward.weaponName = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, BURN_WEAPON_COLUMN_NAME ) ) + burnReward.extraWeaponMod = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, BURN_EXTRA_WEAPON_MOD_NAME ) ) + burnReward.activationText = GetDataTableString( dataTable, row, GetDataTableColumnByName( dataTable, "activationText" ) ) + burnReward.skinIndex = GetDataTableInt( dataTable, row, GetDataTableColumnByName( dataTable, "skinIndex" ) ) + + if ( IsDisabledRef( burnReward.ref ) ) + continue + +#if SERVER || CLIENT + if ( burnReward.weaponName != "" ) + PrecacheWeapon( burnReward.weaponName ) + #endif + + burn.burnRewards[burnReward.ref] <- burnReward + + bool selectable = GetDataTableBool( dataTable, row, GetDataTableColumnByName( dataTable, "selectable" ) ) + burn.allCards.append( burnReward ) + if ( selectable ) + burn.allowedCards.append( burnReward ) + } + + #if CLIENT + //Registering here instead of in CreateWeaponData() in _items.nut to remove dependence on HUD script with items. If more weapons other than smart pistol run into this problem, just add a column in burn_meter_rewards to specify we need to do this. ( ticks are fine because they have a damagedef associated with them ) + RegisterWeaponDamageSourceName( "mp_weapon_smart_pistol", expect string( GetWeaponInfoFileKeyField_GlobalNotNull( "mp_weapon_smart_pistol", "shortprintname" ) ) ) + + RegisterConCommandTriggeredCallback( "-offhand4", CycleOnRelease ) + #endif +} + +#if SERVER || CLIENT +bool function BurnMeterEnabled() +{ + return Riff_BoostAvailability() != eBoostAvailability.Disabled +} +#endif + +BurnReward function BurnReward_GetById( int id ) +{ + return burn.allCards[ id ] +} + + +BurnReward function BurnReward_GetByRef( string ref ) +{ + Assert( ref in burn.burnRewards ) + + // more hacks for arena + if ( !( ref in burn.burnRewards ) && GetCurrentPlaylistVarString( "boost_store_mode", "off" ) == "arena" ) + return GetArenaLoadoutItemAsBurnReward( ref ) + + return burn.burnRewards[ref] +} + +int function GetBoostSkin( string ref ) +{ + BurnReward reward = BurnReward_GetByRef( ref ) + //printt( "ref: " + ref + ", Boost skin: " + reward.skinIndex ) + return reward.skinIndex +} + + +BurnReward function BurnReward_GetRandom() +{ + return burn.allowedCards.getrandom() +} + +string function GetSelectedBurnCardRef( entity player ) +{ + int burnCardID = expect int ( player.GetPersistentVar( "burnmeterSlot" ) ) + BurnReward burnCard = burn.allCards[ burnCardID ] + string ref = burnCard.ref + + #if SERVER + if ( GetItemDisplayData( ref ).hidden ) + ClientCommand( player, "disconnect" ) + #endif + + #if SERVER || CLIENT + + if ( Riff_TitanAvailability() == eTitanAvailability.Never ) + { + //JFS: Batteries and Titan turrets are useless in PvP, turn them into amped weapons instead. Not an elegant solution at all. Fix next game. (Make boosts editable mid-match? ) + if ( ref == "burnmeter_emergency_battery" || ref == "burnmeter_at_turret_weapon" ) + ref = "burnmeter_amped_weapons" + } + + if ( GetCurrentPlaylistVarInt( "featured_mode_all_ticks", 0 ) >= 1 ) + { + if ( ref == "burnmeter_ticks" || ref == "burnmeter_random_foil" ) + ref = "burnmeter_amped_weapons" + } + #endif + + return ref +} + + +#if SERVER || CLIENT +bool function CanUseWeaponAsBurnCard( entity weapon, entity ownerPlayer ) +{ + Assert( weapon.HasMod( "burn_card_weapon_mod" ) ) + + if ( !BurnMeterPlayer_CanUseEquipped( ownerPlayer ) ) + { + weapon.EmitWeaponSound( "CoOp_SentryGun_DeploymentDeniedBeep" ) + return false + } + + return true +} + +bool function TryUsingBurnCardWeapon( entity weapon, entity ownerPlayer ) +{ + Assert( weapon.HasMod( "burn_card_weapon_mod" ) ) + + if ( !CanUseWeaponAsBurnCard( weapon, ownerPlayer ) ) + return false + + #if SERVER + UseBurnCardWeapon( weapon, ownerPlayer ) + #endif + + return true +} + +bool function TryUsingBurnCardWeaponInCriticalSection( entity weapon, entity ownerPlayer ) +{ + Assert( weapon.HasMod( "burn_card_weapon_mod" ) ) + + if ( !CanUseWeaponAsBurnCard( weapon, ownerPlayer ) ) + return false + + #if SERVER + UseBurnCardWeaponInCriticalSection( weapon, ownerPlayer ) + #endif + + return true +} + + + +bool function BurnMeterPlayer_CanUseReward( entity player, BurnReward burnReward ) +{ + if ( !BurnMeterPlayer_CanUseGlobal( player ) ) + return false + + switch( burnReward.userType ) + { + case eBurnMeterRewardAvailableFor.PILOT_ONLY: + return !player.IsTitan() + + case eBurnMeterRewardAvailableFor.TITAN_ONLY: + return player.IsTitan() + + case eBurnMeterRewardAvailableFor.PILOT_AND_TITAN: + return true + + case eBurnMeterRewardAvailableFor.SPECIAL_PLAYERS_ONLY: + return BurnMeterPlayer_CanUseSpecial( player, burnReward ) //TODO: Note that for the case of reaperfall we also send the message to the client if they can't summon reaper in the validation function. Not ideal... + } + + unreachable + +} + + +bool function BurnMeterPlayer_CanUseEquipped( entity player ) +{ + BurnReward burnReward = BurnReward_GetTopInventoryItemBurnCard( player ) + return BurnMeterPlayer_CanUseReward( player, burnReward ) +} + + +EarnObject function BurnMeterPlayer_GetRewardOrGoal( entity player ) +{ + if ( PlayerEarnMeter_IsRewardEnabled( player ) ) // TODO: more robust + return PlayerEarnMeter_GetReward( player ) + else + return PlayerEarnMeter_GetGoal( player ) + + unreachable +} + + +bool function BurnMeterPlayer_CanUseGlobal( entity player ) +{ + if ( player.IsBot() ) + return false + + if ( !IsAlive( player ) ) + return false + + if ( player.IsPhaseShifted() ) + return false + + if ( player.ContextAction_IsInVehicle() ) //no rewards when in dropship + return false + + if ( !IsBurnMeterRewardAvailableForGameState() ) + return false + + return true +} + + +bool function IsBurnMeterRewardAvailableForGameState() //Based off IsReplacementTitanAvailable +{ + int currentGameState = GetGameState() + + switch ( currentGameState ) //need to add a new entry in here for every new game state we make + { + case eGameState.WaitingForCustomStart: + case eGameState.WaitingForPlayers: + case eGameState.PickLoadout: + case eGameState.Prematch: + case eGameState.SwitchingSides: + case eGameState.Postmatch: + return false + + case eGameState.Playing: + case eGameState.SuddenDeath: + return true + + case eGameState.WinnerDetermined: + case eGameState.Epilogue: + { + if ( IsRoundBased() ) + { + // TODO: make this work on the client + #if SERVER + if ( !IsRoundBasedGameOver() ) + return false + + if ( !ShouldRunEvac() ) + return false + #endif + } + + return true + } + + default: + Assert( false, "Unknown Game State: " + currentGameState ) + return false + } + + unreachable +} + + +bool function OnWeaponAttemptOffhandSwitch_burncardweapon( entity weapon ) +{ + entity ownerPlayer = weapon.GetWeaponOwner() + Assert( ownerPlayer.IsPlayer() ) + + if ( ownerPlayer.IsUsingOffhandWeapon() ) + return false + + if ( weapon.HasMod( "burn_card_weapon_mod" ) ) + { + bool canUse = BurnMeterPlayer_CanUseEquipped( ownerPlayer ) + + return canUse + } + else + return true + + unreachable +} + +BurnReward function BurnReward_GetTopInventoryItemBurnCard( entity player ) +{ + int burnRewardID = player.GetPlayerNetInt( TOP_INVENTORY_ITEM_BURN_CARD_ID ) + return BurnReward_GetById( burnRewardID ) +} + +bool function BurnMeterPlayer_CanUseSpecial( entity player, BurnReward burnReward ) +{ + Assert( burnReward.ref in burnMeterCanUseFuncTable ) + return burnMeterCanUseFuncTable[ burnReward.ref ]( player ) +} + +bool function BurnMeter_HarvesterShieldCanUse( entity player ) +{ +// entity harvester = GetGlobalNetEnt( "FD_activeHarvester" ) + bool canUse = GetGlobalNetTime( "FD_harvesterInvulTime" ) < Time() + // harvester.GetShieldHealth() < harvester.GetShieldHealthMax() + + if ( !canUse ) + { + #if CLIENT + AddPlayerHint( 3.0, 0.5, $"", "#HINT_HARVESTER_BOOST_CANT_USE" ) + #endif + } + + return canUse +} + +bool function BurnMeter_NukeTitanCanUse( entity player ) +{ + bool canUse = BurnMeter_EmergencyTitanCanUse( player ) + + if ( !canUse ) + { + #if CLIENT + AddPlayerHint( 5.0, 0.5, $"", "#HINT_NUKE_TITAN_CANT_USE" ) + #endif + } + + return canUse +} + +bool function BurnMeter_EmergencyTitanCanUse( entity player ) +{ + if ( IsAlive( player.GetPetTitan() ) ) + return false + + #if SERVER + if ( !IsReplacementTitanAvailableForGameState() ) + return false + + if ( player.isSpawning ) + return false + + return true + #endif + + #if CLIENT + return true + #endif +} + +bool function BurnMeter_SummonReaperCanUse( entity player ) +{ + array< entity > allReapers = GetNPCArrayByClass( "npc_super_spectre" ) + + int playerTeam = player.GetTeam() + + foreach( reaper in allReapers ) + { + if ( reaper.GetTeam() == playerTeam ) + { + #if SERVER + MessageToPlayer( player, eEventNotifications.BurnMeter_CantSummonReaper ) + #endif + + return false + } + } + return true +} + +var function OnWeaponPrimaryAttack_nuke_eject( entity weapon, WeaponPrimaryAttackParams attackParams ) +{ + #if CLIENT + entity weaponOwner = weapon.GetWeaponOwner() + if ( weaponOwner.IsPlayer() && IsValid( weaponOwner.GetCockpit() ) ) + { + weaponOwner.ClientCommand( "TitanEject " + 3 ) + PlayerEjects( weaponOwner, weaponOwner.GetCockpit() ) //Note that this can be run multiple times in a frame, e.g. get damaged by 4 pellets of a shotgun that brings the Titan into a doomed state with auto eject. Not ideal + } + #endif + return 0 +} + +#endif + +#if CLIENT +void function CycleOnRelease( entity player ) +{ + if ( file.shouldCycleOnRelease ) + { + CycleInventory( player ) + file.shouldCycleOnRelease = false + } +} +#endif + +array<string> function GetAllBoostTurretTypes() +{ + return turretBurnCards +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/client/cl_gamestate.gnut b/Northstar.Custom/mod/scripts/vscripts/client/cl_gamestate.gnut new file mode 100644 index 00000000..93be01ea --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/client/cl_gamestate.gnut @@ -0,0 +1,1119 @@ +untyped + +global function ClGameState_Init +global function ClGameState_Think +global function ClGameState_GetRui +global function ClGameState_RegisterNetworkFunctions +global function ClGameState_SetInfoStatusText + +global function ServerCallback_AnnounceWinner +global function ServerCallback_AnnounceRoundWinner + +global function ShowRoundScoresInAnnouncement + +global function PlayRoundWonConversationWithAnnouncementDelay + +global function UpdatePilotTitanStatusDisplay +global function UpdatePilotTitanStatusDisplayWithCount + +global function SetGameModeScoreBarUpdateRules +global function SetGameModeScoreBarUpdateRulesWithFlags + +global function DisplayPostMatchTop3 + +global function GetGameStartTime + +global function ClGameState_RegisterGameStateAsset + +global function ClGameState_SetPilotTitanStatusCallback +global function GetPilotTitanStatusForPlayer + +global enum ePlayerStatusType +{ + PTS_TYPE_NONE, + PTS_TYPE_DEAD, + PTS_TYPE_DEAD_PILOT_TITAN, + PTS_TYPE_DEAD_READY, + PTS_TYPE_PILOT, + PTS_TYPE_READY, + PTS_TYPE_PILOT_TITAN, + PTS_TYPE_EVAC, + PTS_TYPE_ION, + PTS_TYPE_SCORCH, + PTS_TYPE_RONIN, + PTS_TYPE_TONE, + PTS_TYPE_LEGION, + PTS_TYPE_NORTHSTAR, + PTS_TYPE_VANGUARD, + PTS_TYPE_WAVE_READY, + PTS_TYPE_WAVE_NOT_READY, + PTS_TYPE_COUNT, +} + +struct PilotTitanStatus +{ + int playerCount + int[8] slotTypes +} + +global enum sbflag +{ + SKIP_STANDARD_UPDATE = (1<<0), + _count = 1 +} + +struct +{ + var gamestate_info + var letterBoxRui = null + bool thirtySecondWarningDone = false + + void functionref( var ) gameModeScoreBarUpdateRules + int gameModeScoreBarUpdateFlags = 0 + + table< int, PilotTitanStatus> pilotTitanStatus + int functionref( entity,int ) pilotTitanStatusCallback + + asset gameStateRuiAsset = $"" +} file + + +const DEV_COUNTDOWNTIMER = 0//turn on to see the countdown timer to match starts - helps with lining up intros + +function ClGameState_Init() +{ + RegisterSignal( "GameStateChanged" ) + RegisterServerVarChangeCallback( "gameState", ClGameState_Changed ) + RegisterServerVarChangeCallback( "badRepPresent", UpdateScoreboardBadRepPresentMessage ) + + AddCallback_OnClientScriptInit( ClGameState_AddClient ) + + var rui = CreatePermanentCockpitRui( GetGameStateAsset(), MINIMAP_Z_BASE - 1 ) + file.gamestate_info = rui + + if ( file.gameModeScoreBarUpdateRules == null ) + SetGameModeScoreBarUpdateRules( GameModeScoreBarUpdateRules_Default ) +} + +void function GameModeScoreBarUpdateRules_Default( var rui ) +{ +} + +void function SetGameModeScoreBarUpdateRulesWithFlags( void functionref( var ) rules, int sbflags ) +{ + file.gameModeScoreBarUpdateRules = rules + file.gameModeScoreBarUpdateFlags = sbflags +} + +void function SetGameModeScoreBarUpdateRules( void functionref( var ) rules ) +{ + SetGameModeScoreBarUpdateRulesWithFlags( rules, 0 ) +} + +void function ClGameState_RegisterNetworkFunctions() +{ + RegisterNetworkedVariableChangeCallback_int( "gameInfoStatusText", ClGameState_UpdateInfoStatusText ) +} + +void function ClGameState_UpdateInfoStatusText( entity player, int oldValue, int newValue, bool actuallyChanged ) +{ + printt( "UPDATE", player, oldValue, newValue ) + + if ( newValue == -1 ) + { + RuiSetString( file.gamestate_info, "statusText", "" ) + return + } + + string statusText = GetStringFromNetworkableID( newValue ) + printt( "SET", player, statusText, Localize( statusText ) ) + RuiSetString( file.gamestate_info, "statusText", Localize( statusText ) ) +} + + +void function ClGameState_SetInfoStatusText( string statusText ) +{ + RuiSetString( file.gamestate_info, "statusText", Localize( statusText ) ) +} + + +asset function GetGameStateAsset() +{ + if ( GAMETYPE == ATTRITION ) + return $"ui/gamestate_info_at.rpak" + + if ( GAMETYPE == FREE_AGENCY ) + return $"ui/gamestate_info_fra.rpak" + + if ( GAMETYPE == FFA ) + return $"ui/gamestate_info_ffa.rpak" + + if ( GAMETYPE == FORT_WAR ) + return $"ui/gamestate_info_fw.rpak" + + if ( GAMETYPE == LAST_TITAN_STANDING ) + return $"ui/gamestate_info_lts.rpak" + + if ( GAMETYPE == LTS_BOMB ) + return $"ui/gamestate_info_lts_bomb.rpak" + + if ( GAMETYPE == CAPTURE_POINT ) + return $"ui/gamestate_info_cp.rpak" + + if ( GAMETYPE == PILOT_SKIRMISH ) + return $"ui/gamestate_info_ps.rpak" + + if ( GAMETYPE == COLISEUM ) + return $"ui/gamestate_info_coliseum.rpak" + + if ( GAMETYPE == SPEEDBALL ) + return $"ui/gamestate_info_speedball.rpak" + + if ( GAMETYPE == MARKED_FOR_DEATH ) + return $"ui/gamestate_info_mfd.rpak" + + if ( file.gameStateRuiAsset != $"" ) + return file.gameStateRuiAsset + + return $"ui/gamestate_info.rpak" +} + +void function ClGameState_RegisterGameStateAsset( asset gameStateAsset ) +{ + file.gameStateRuiAsset = gameStateAsset +} + +void function ClGameState_AddClient( entity player ) +{ + player.cv.scoreboardBadRepPresentMessage <- HudElement( "ScoreboardBadRepPresentMessage", HudElement( "Scoreboard" ) ) + + #if PC_PROG + player.cv.scoreboardBadRepPresentMessage.SetText( "#ASTERISK_FAIRFIGHT_CHEATER" ) + #else + player.cv.scoreboardBadRepPresentMessage.SetText( "#ASTERISK_BAD_REPUTATION" ) + #endif + + player.cv.hudCheaterMessage <- HudElement( "HudCheaterMessage" ) + if ( !Durango_IsDurango() && !IsLobby() && player.HasBadReputation() ) + player.cv.hudCheaterMessage.Show() + else + player.cv.hudCheaterMessage.Hide() +} + + +var function ClGameState_GetRui() +{ + return file.gamestate_info +} + + +void function ClGameState_Think() +{ + entity player = GetLocalClientPlayer() + + TEMP_UpdateRuiScores( player ) + switch( GetGameState() ) + { + case eGameState.WaitingForPlayers: + ClGameStateThink_WaitingForPlayers( player ) + break + + case eGameState.PickLoadout: + ClGameStateThink_PickLoadOut( player ) + break + + case eGameState.Prematch: + ClGameStateThink_Prematch( player ) + break + + case eGameState.Playing: + ClGameStateThink_Playing( player ) + break + + case eGameState.SwitchingSides: + ClGameStateThink_SwitchingSides( player ) + break + + case eGameState.Postmatch: + ClGameStateThink_Postmatch( player ) + break + } +} + + +void function ClGameStateThink_Prematch( entity player ) +{ + string factionChoice = GetFactionChoice( player ) + ItemDisplayData displayData = GetItemDisplayData( factionChoice ) + asset factionLogo = displayData.image + string factionTitle = displayData.name + + RuiSetImage( ClGameState_GetRui(), "factionImage", factionLogo ) + + //removing countdown timer all together + if ( !DEV_COUNTDOWNTIMER ) + return + + local timeRemaining = ceil( level.nv.gameStartTime - Time() ) + + player.cv.prematchTimer.Show() + player.cv.prematchTimerGlow.Show() + player.cv.prematchDesc.Show() + + player.cv.prematchTimer.SetText( string( timeRemaining ) ) + player.cv.prematchTimerGlow.SetText( string( timeRemaining ) ) +} + +void function ClGameStateThink_WaitingForPlayers( player ) +{ + int reservedCount + int connectingCount + int loadingCount + if ( IsFFAGame() ) + { + reservedCount = GetTotalPendingPlayersReserved() + connectingCount = GetTotalPendingPlayersConnecting() + loadingCount = GetTotalPendingPlayersLoading() + } + else + { + reservedCount = GetTeamPendingPlayersReserved( TEAM_MILITIA ) + GetTeamPendingPlayersReserved( TEAM_IMC ) + connectingCount = GetTeamPendingPlayersConnecting( TEAM_MILITIA ) + GetTeamPendingPlayersConnecting( TEAM_IMC ) + loadingCount = GetTeamPendingPlayersLoading( TEAM_MILITIA ) + GetTeamPendingPlayersLoading( TEAM_IMC ) + } + + int connectedCount = GetPlayerArray().len() + int allKnownPlayersCount = reservedCount + connectingCount + loadingCount + connectedCount + int minPlayers = GetCurrentPlaylistVarInt( "min_players", 0 ) + int expectedPlayers = maxint( minPlayers, allKnownPlayersCount ) + + if ( !("lastTimeRemaining" in player.cv) ) + player.cv.lastTimeRemaining <- null + + if ( Time() <= level.nv.connectionTimeout ) + { + local timeRemainingFloat = level.nv.connectionTimeout - Time() + local timeRemaining = ceil( timeRemainingFloat ) + + //player.cv.waitingForPlayersDesc.SetText( "#HUD_WAITING_FOR_PLAYERS", connectedCount, expectedPlayers, timeRemaining ) + //player.cv.waitingForPlayersDesc.Show() + + if ( player.cv.lastTimeRemaining && timeRemaining != player.cv.lastTimeRemaining ) + EmitSoundOnEntity( player, WAITING_FOR_PLAYERS_COUNTDOWN_SOUND ) + + player.cv.lastTimeRemaining = timeRemaining + } + else + { + //player.cv.waitingForPlayersDesc.SetText( "#HUD_WAITING_FOR_PLAYERS", connectedCount, expectedPlayers, "" ) + //player.cv.waitingForPlayersDesc.Show() + } +} + +void function ClGameStateThink_PickLoadOut( entity player ) +{ + if ( IsMultiplayerPlaylist() && ClassicMP_Client_GetGameStateThinkFunc_PickLoadOut() != null ) + ClassicMP_Client_GetGameStateThinkFunc_PickLoadOut()( player ) +} + +entity function GetTopCompetitor( int team ) +{ + array<entity> players = GetPlayerArrayOfEnemies( team ) + + entity topCompetitor + foreach ( player in players ) + { + if ( !topCompetitor || (GameRules_GetTeamScore( player.GetTeam()) ) > GameRules_GetTeamScore( topCompetitor.GetTeam()) ) + topCompetitor = player + } + + return topCompetitor +} + + +string function GetTopCompetitorName( int team ) +{ + entity topCompetitor = GetTopCompetitor( team ) + return topCompetitor != null ? topCompetitor.GetPlayerName() : "None" +} + + +int function GetTopCompetitorTeam( int team ) +{ + entity topCompetitor = GetTopCompetitor( team ) + return topCompetitor != null ? topCompetitor.GetTeam() : 0 +} + + +void function TEMP_UpdateRuiScores( entity player ) +{ + float endTime + if ( IsRoundBased() ) + { + endTime = expect float( level.nv.roundEndTime ) + if ( + GetGameState() == eGameState.Prematch || + GetGameState() == eGameState.Playing + ) + RuiSetString( file.gamestate_info, "roundText", Localize( "#GAMESTATE_ROUND_N", GetRoundsPlayed() + 1 ) ) + } + else + { + endTime = expect float( level.nv.gameEndTime ) + } + + RuiSetInt( file.gamestate_info, "maxTeamScore", GetScoreLimit_FromPlaylist() ) + int maxPlayers = GetCurrentPlaylistVarInt( "max_players", 12 ) + RuiSetInt( file.gamestate_info, "maxTeamPlayers", maxPlayers / 2 ) + + int friendlyTeam = player.GetTeam() + RuiSetFloat( file.gamestate_info, "leftTeamScore", float( GameRules_GetTeamScore( friendlyTeam ) ) ) + + UpdatePilotTitanStatusDisplay( friendlyTeam ) + + int enemyTeam + if ( IsFFAGame() ) + { + enemyTeam = GetTopCompetitorTeam( player.GetTeam() ) + RuiSetImage( file.gamestate_info, "friendlyPlayerCardImage", CallsignIcon_GetImage( PlayerCallsignIcon_GetActive( player ) ) ) + array<entity> enemyPlayers = GetPlayerArrayOfTeam( enemyTeam ) + if ( enemyPlayers.len() > 0 ) + RuiSetImage( file.gamestate_info, "enemyPlayerCardImage", CallsignIcon_GetImage( PlayerCallsignIcon_GetActive( enemyPlayers[0] ) ) ) + } + else + { + enemyTeam = friendlyTeam == TEAM_IMC ? TEAM_MILITIA : TEAM_IMC + } + + RuiSetFloat( file.gamestate_info, "rightTeamScore", float( GameRules_GetTeamScore( enemyTeam ) ) ) + + //Run any gamemode specific score bar logic (e.g. the bonus segment on the scorebar in bounty hunt ). No special logic is run by default. + file.gameModeScoreBarUpdateRules( file.gamestate_info ) + + if ( !endTime ) + return + + if ( Time() > endTime ) + { + RuiSetGameTime( file.gamestate_info, "endTime", RUI_BADGAMETIME ) + return + } + + RuiSetGameTime( file.gamestate_info, "endTime", endTime ) +} + + +void function ClGameStateThink_Playing( entity player ) +{ + //TEMP_UpdateRuiScores( player ) + + local endTime + if ( IsRoundBased() ) + endTime = level.nv.roundEndTime + else + endTime = level.nv.gameEndTime + + if ( !endTime ) + return + + if ( Time() > endTime ) + return + + if ( endTime - Time() > 30.0 ) + return + + if ( file.thirtySecondWarningDone ) + return + + thread ThirtySecondWarning() + file.thirtySecondWarningDone = true +} + + +void function ThirtySecondWarning() +{ + string conversation = GameMode_GetGameEndingConversation( GameRules_GetGameMode() ) + if ( conversation != "" ) + PlayConversationToLocalClient( conversation ) + + int lastSecond = -1 + while ( Time() < level.nv.gameEndTime && GetGameState() == eGameState.Playing ) + { + int second = int( floor( level.nv.gameEndTime - Time() ) ) + if ( lastSecond == -1 ) + { + lastSecond = second + } + else if ( second != lastSecond && second < 30 ) + { + if ( second > 5 ) + { + EmitSoundOnEntity( GetLocalClientPlayer(), "HUD_match_start_timer_tick_1P" ) + } + else if ( second >= 0 ) + { + EmitSoundOnEntity( GetLocalClientPlayer(), "HUD_match_start_timer_5_seconds_1P" ) + } + } + + lastSecond = second + + WaitFrame() + } + + EmitSoundOnEntity( GetLocalClientPlayer(), "HUD_match_start_timer_0_seconds_1P" ) + +} + + +string function GetTitanClass( entity titan ) +{ + entity soul = titan.GetTitanSoul() + string settingsName = PlayerSettingsIndexToName( soul.GetPlayerSettingsNum() ) + + return expect string( Dev_GetPlayerSettingByKeyField_Global( settingsName, "titanCharacterName" ) ) +} + + +array<int> function GetPilotTitanStatusForTeam( int team ) +{ + array<int> statusTypes = [ ePlayerStatusType.PTS_TYPE_NONE, ePlayerStatusType.PTS_TYPE_NONE, ePlayerStatusType.PTS_TYPE_NONE, ePlayerStatusType.PTS_TYPE_NONE, ePlayerStatusType.PTS_TYPE_NONE, ePlayerStatusType.PTS_TYPE_NONE, ePlayerStatusType.PTS_TYPE_NONE, ePlayerStatusType.PTS_TYPE_NONE ] + + int playerIndex = 0 + foreach ( teamPlayer in GetPlayerArrayOfTeam( team ) ) + { + // fix for situations where number of players > 8, which happens in infection + // could be good to move this to northstar.client at some point, unsure + if ( playerIndex == statusTypes.len() ) + break + + statusTypes[playerIndex] = GetPilotTitanStatusForPlayer( teamPlayer ) + playerIndex++ + } + + statusTypes.sort( PilotTitanStatusSort ) + + return statusTypes +} + +int function GetPilotTitanStatusForPlayer( entity teamPlayer ) +{ + int statusIndex = ePlayerStatusType.PTS_TYPE_NONE + entity titan + if ( teamPlayer.GetPetTitan() ) + titan = teamPlayer.GetPetTitan() + else if ( teamPlayer.IsTitan() ) + titan = teamPlayer + + entity playerParent = teamPlayer.GetParent() + bool playerIsInDropship = playerParent != null && IsDropship( playerParent ) + + if ( playerIsInDropship && ( GetWaveSpawnType() == eWaveSpawnType.DROPSHIP || GetGameState() == eGameState.Epilogue ) ) + { + statusIndex = ePlayerStatusType.PTS_TYPE_EVAC + } + else if ( titan && titan.GetTitanSoul() ) + { + if ( !teamPlayer.IsTitan() ) + { + if ( IsAlive( teamPlayer ) ) + statusIndex = ePlayerStatusType.PTS_TYPE_PILOT_TITAN + else + statusIndex = ePlayerStatusType.PTS_TYPE_DEAD_PILOT_TITAN + } + else + { + if ( !IsAlive( teamPlayer ) ) + { + statusIndex = ePlayerStatusType.PTS_TYPE_DEAD + } + else + { + switch ( GetTitanClass( titan ) ) + { + case "ion": + statusIndex = ePlayerStatusType.PTS_TYPE_ION + break + case "scorch": + statusIndex = ePlayerStatusType.PTS_TYPE_SCORCH + break + case "ronin": + statusIndex = ePlayerStatusType.PTS_TYPE_RONIN + break + case "tone": + statusIndex = ePlayerStatusType.PTS_TYPE_TONE + break + case "legion": + statusIndex = ePlayerStatusType.PTS_TYPE_LEGION + break + case "northstar": + statusIndex = ePlayerStatusType.PTS_TYPE_NORTHSTAR + break + case "vanguard": + statusIndex = ePlayerStatusType.PTS_TYPE_VANGUARD + break + } + } + } + } + else + { + if ( IsAlive( teamPlayer ) ) + statusIndex = IsTitanAvailable( teamPlayer ) ? ePlayerStatusType.PTS_TYPE_READY : ePlayerStatusType.PTS_TYPE_PILOT + else + statusIndex = IsTitanAvailable( teamPlayer ) ? ePlayerStatusType.PTS_TYPE_DEAD_READY : ePlayerStatusType.PTS_TYPE_DEAD + } + + if ( file.pilotTitanStatusCallback != null ) + statusIndex = file.pilotTitanStatusCallback( teamPlayer, statusIndex ) + + return statusIndex +} + +int function PilotTitanStatusSort( int a, int b ) +{ + if ( a > b ) + return -1 + else if ( a < b ) + return 1 + + return 0 +} + + +void function UpdatePilotTitanStatusDisplayWithCount( int team, int friendlyCount, int enemyCount ) +{ + if ( IsFFAGame() ) + return + var statusRui = ClGameState_GetRui() + + array<int> playerStatus = GetPilotTitanStatusForTeam( team ) + for ( int index = 0; index < friendlyCount; index++ ) + RuiSetInt( statusRui, "friendlyPlayerStatusType" + (index + 1), playerStatus[index] ) + + array<int> enemyPlayerStatus = GetPilotTitanStatusForTeam( GetOtherTeam( team ) ) + for ( int index = 0; index < enemyCount; index++ ) + RuiSetInt( statusRui, "enemyPlayerStatusType" + (index + 1), enemyPlayerStatus[index] ) +} + +void function UpdatePilotTitanStatusDisplay( int team ) +{ + if ( GAMETYPE == "fd" ) + UpdatePilotTitanStatusDisplayWithCount( team, 4, 0 ) + else + UpdatePilotTitanStatusDisplayWithCount( team, 8, 8 ) +} + +void function ClGameStateThink_Postmatch( player ) +{ +} + +void function ClGameStateThink_SwitchingSides( player ) +{ + //player.cv.vignette.SetColor( 0, 0, 0, 255 ) + //player.cv.vignette.Show() +} + + +void function ClGameState_Changed() +{ + entity player = GetLocalClientPlayer() + player.Signal( "GameStateChanged" ) + + foreach ( callbackFunc in clGlobal.gameStateEnterCallbacks[ GetGameState() ] ) + { + callbackFunc() + } + + switch ( GetGameState() ) + { + case eGameState.WaitingForCustomStart: + //player.cv.gamescomWaitTillReady.Show() + break + + case eGameState.WaitingForPlayers: + if ( IsMultiplayerPlaylist() && ClassicMP_Client_GetGameStateEnterFunc_WaitingForPlayers() != null ) + ClassicMP_Client_GetGameStateEnterFunc_WaitingForPlayers()( player ) + //player.cv.gamescomWaitTillReady.Hide() + + //player.cv.waitingForPlayersDesc.SetText( "#HUD_WAITING_FOR_PLAYERS_BASIC" ) + //player.cv.waitingForPlayersDesc.Show() + //player.cv.waitingForPlayersLine.Show() + //player.cv.waitingForPlayersTimer.Show() + break + + case eGameState.PickLoadout: + if ( IsMultiplayerPlaylist() && ClassicMP_Client_GetGameStateEnterFunc_PickLoadOut() != null ) + ClassicMP_Client_GetGameStateEnterFunc_PickLoadOut()( player ) + + break + + case eGameState.Prematch: + RemoveAllRagdolls() + HideEventNotification() + + SetCrosshairPriorityState( crosshairPriorityLevel.PREMATCH, CROSSHAIR_STATE_HIDE_ALL ) + + //player.cv.waitingForPlayersDesc.HideOverTime( 0.25 ) + //player.cv.waitingForPlayersLine.HideOverTime( 0.25 ) + //player.cv.waitingForPlayersTimer.HideOverTime( 0.25 ) + + file.letterBoxRui = RuiCreate( $"ui/letter_box.rpak", clGlobal.topoFullScreen, RUI_DRAW_HUD, 0 ) + + //player.cv.prematchTimer.SetAlpha( 255 ) + //player.cv.prematchTimerGlow.SetAlpha( 255 ) + //player.cv.prematchDesc.SetAlpha( 255 ) + + file.thirtySecondWarningDone = false + + StopPlayerDeathSound( player ) //If you were watching your own kill replay, don't want this playing through till next round + + clGlobal.levelEnt.Signal( "AnnoucementPurge" ) + + break + + case eGameState.Playing: + ClearCrosshairPriority( crosshairPriorityLevel.PREMATCH ) + //player.cv.vignette.HideOverTime( 0.25 ) + if ( file.letterBoxRui != null ) + RuiSetGameTime( file.letterBoxRui, "hideStartTime", Time() ) + //player.cv.prematchTimer.HideOverTime( 0.25 ) + //player.cv.prematchTimerGlow.HideOverTime( 0.25 ) + //player.cv.prematchDesc.HideOverTime( 0.25 ) + + ShowScriptHUD( player ) + break + + case eGameState.SuddenDeath: + AnnouncementData announcement = Announcement_Create( "#GAMEMODE_ANNOUNCEMENT_SUDDEN_DEATH" ) + + switch ( GAMETYPE ) + { + case CAPTURE_THE_FLAG: + case RAID: + Announcement_SetSubText( announcement, "#GAMEMODE_ANNOUNCEMENT_SUDDEN_DEATH_CTF" ) + break + + case TEAM_DEATHMATCH: + case TITAN_BRAWL: + case HARDCORE_TDM: + Announcement_SetSubText( announcement, "#GAMEMODE_ANNOUNCEMENT_SUDDEN_DEATH_TDM" ) + break + + default: + Announcement_SetSubText( announcement, "" ) + } + + Announcement_SetHideOnDeath( announcement, false ) + Announcement_SetDuration( announcement, 7.0 ) + Announcement_SetPurge( announcement, true ) + AnnouncementFromClass( player, announcement ) + break + + case eGameState.WinnerDetermined: + player.cv.roundSpawnCount = 0 + break + + case eGameState.Epilogue: + thread MainHud_Outro( level.nv.winningTeam ) + break + + case eGameState.SwitchingSides: + + float announcementDuration = 7.0 + if ( IsRoundWinningKillReplayEnabled() == true && ( !IsRoundBased() && IsSwitchSidesBased() ) ) //Ideally this should be a call to WillShowRoundWinningKillReplay(), but that's only available on the server + announcementDuration = SWITCHING_SIDES_DELAY + ROUND_WINNING_KILL_REPLAY_TOTAL_LENGTH + + AnnouncementData announcement = Announcement_Create( "#GameState_HALFTIME" ) + announcement.sortKey = RUI_SORT_SCREENFADE + 1 // Draw over screen fade + announcement.drawOverScreenFade = true + Announcement_SetSubText( announcement, "#GameState_SWITCHING_SIDES" ) + Announcement_SetHideOnDeath( announcement, false ) + Announcement_SetDuration( announcement, announcementDuration ) + Announcement_SetPurge( announcement, true ) + + EmitSoundOnEntity( player, "UI_InGame_HalftimeText_Enter" ) + EmitSoundOnEntityAfterDelay( player, "UI_InGame_HalftimeText_Exit", announcementDuration ) + + local friendlyTeam = player.GetTeam() + local enemyTeam = friendlyTeam == TEAM_IMC ? TEAM_MILITIA : TEAM_IMC + + if ( friendlyTeam == TEAM_IMC ) + { + Announcement_SetLeftIcon( announcement, TEAM_ICON_IMC ) + Announcement_SetRightIcon( announcement, TEAM_ICON_MILITIA ) + } + else + { + Announcement_SetLeftIcon( announcement, TEAM_ICON_MILITIA ) + Announcement_SetRightIcon( announcement, TEAM_ICON_IMC ) + } + + if ( IsRoundBased() ) + { + Announcement_SetSubText2( announcement, "#GAMEMODE_ROUND_WIN_CONDITION", GetRoundScoreLimit_FromPlaylist() ) + Announcement_SetLeftText( announcement, "#GAMEMODE_JUST_THE_SCORE", GameRules_GetTeamScore2( friendlyTeam ) ) + Announcement_SetRightText( announcement, "#GAMEMODE_JUST_THE_SCORE", GameRules_GetTeamScore2( enemyTeam ) ) + } + else + { + Announcement_SetLeftText( announcement, "#GAMEMODE_JUST_THE_SCORE", GameRules_GetTeamScore( friendlyTeam ) ) + Announcement_SetRightText( announcement, "#GAMEMODE_JUST_THE_SCORE", GameRules_GetTeamScore( enemyTeam ) ) + } + + AnnouncementFromClass( player, announcement ) + + break + + case eGameState.Postmatch: + ShowScoreboard() + //wait for scoreboard to be up + delaythread( 1.5 ) ServerCallback_ResetMapSettings() + break + } +} + +void function SwitchingSides_Changed() +{ + if ( IsMenuLevel() ) + return + + thread SwitchingSides_Changed_threaded() +} + +void function SwitchingSides_Changed_threaded() +{ + //entity player = GetLocalViewPlayer() + // + //if ( level.nv.switchingSides ) + //{ + // player.cv.halfTimeText.SetText( "Test - Switching Sides" ) + // player.cv.halfTimeText.Show() + // wait 1.5 + //} + //else + //{ + // player.cv.halfTimeText.Hide() + // + //} +} + +void function ServerCallback_AnnounceWinner( int teamIndex, int subStringIndex, float winnerDeterminedWait ) +{ + entity player = GetLocalClientPlayer() + + string subString = "" + if ( subStringIndex ) + subString = GetStringFromID( subStringIndex ) + + if ( !level.nv.winningTeam ) + { + AnnouncementData outcomeAnnouncement = Announcement_Create( "#GAMEMODE_DRAW" ) + outcomeAnnouncement.drawOverScreenFade = true + Announcement_SetSubText( outcomeAnnouncement, subString ) + //Announcement_SetTitleColor( outcomeAnnouncement, TEAM_COLOR_YOU ) + Announcement_SetHideOnDeath( outcomeAnnouncement, false ) + Announcement_SetDuration( outcomeAnnouncement, winnerDeterminedWait ) + Announcement_SetPriority( outcomeAnnouncement, 500 ) + Announcement_SetPurge( outcomeAnnouncement, true ) + AnnouncementFromClass( player, outcomeAnnouncement ) + } + else if ( IsFFAGame() ) + { + array<entity> players = GetPlayerArrayOfTeam( level.nv.winningTeam ) + if ( players.len() ) + { + string winText = ((players[0] == player) ? "#GAMEMODE_MATCH_WON_BY_SELF" : "#GAMEMODE_MATCH_WON_BY_ENEMY_TEAM") + AnnouncementData outcomeAnnouncement = Announcement_Create( winText ) + outcomeAnnouncement.drawOverScreenFade = true + Announcement_SetSubText( outcomeAnnouncement, subString ) + Announcement_SetOptionalTextArgsArray( outcomeAnnouncement, [players[0].GetPlayerName()] ) + Announcement_SetTitleColor( outcomeAnnouncement, TEAM_COLOR_YOU ) + Announcement_SetHideOnDeath( outcomeAnnouncement, false ) + Announcement_SetDuration( outcomeAnnouncement, winnerDeterminedWait ) + Announcement_SetPriority( outcomeAnnouncement, 500 ) + Announcement_SetPurge( outcomeAnnouncement, true ) + AnnouncementFromClass( player, outcomeAnnouncement ) + } + } + else if ( player.GetTeam() == level.nv.winningTeam ) + { + + AnnouncementData outcomeAnnouncement = Announcement_Create( "#GAMEMODE_VICTORY" ) + outcomeAnnouncement.announcementStyle = ANNOUNCEMENT_STYLE_RESULTS + outcomeAnnouncement.drawOverScreenFade = true + outcomeAnnouncement.soundAlias = "HUD_MP_Match_End_WinLoss_UI_Sweep_1P" + Announcement_SetSubText( outcomeAnnouncement, subString ) + Announcement_SetTitleColor( outcomeAnnouncement, TEAM_COLOR_PARTY ) + Announcement_SetHideOnDeath( outcomeAnnouncement, false ) + Announcement_SetDuration( outcomeAnnouncement, winnerDeterminedWait ) + Announcement_SetPriority( outcomeAnnouncement, 500 ) + Announcement_SetPurge( outcomeAnnouncement, true ) + AnnouncementFromClass( player, outcomeAnnouncement ) + } + else if ( level.nv.winningTeam != TEAM_UNASSIGNED ) + { + AnnouncementData outcomeAnnouncement = Announcement_Create( "#GAMEMODE_DEFEATED" ) + outcomeAnnouncement.announcementStyle = ANNOUNCEMENT_STYLE_RESULTS + outcomeAnnouncement.drawOverScreenFade = true + outcomeAnnouncement.soundAlias = "HUD_MP_Match_End_WinLoss_UI_Sweep_1P" + Announcement_SetSubText( outcomeAnnouncement, subString ) + Announcement_SetTitleColor( outcomeAnnouncement, TEAM_COLOR_ENEMY ) + Announcement_SetHideOnDeath( outcomeAnnouncement, false ) + Announcement_SetDuration( outcomeAnnouncement, winnerDeterminedWait ) + Announcement_SetPriority( outcomeAnnouncement, 500 ) + Announcement_SetPurge( outcomeAnnouncement, true ) + AnnouncementFromClass( player, outcomeAnnouncement ) + } +} + +void function ServerCallback_AnnounceRoundWinner( int teamIndex, int subStringIndex, float winnerDeterminedWait, int imcTeamScore2, int militiaTeamScore2 ) +{ + string subString = "" + if ( subStringIndex ) + subString = GetStringFromID( subStringIndex ) + + entity player = GetLocalClientPlayer() + + // ANNOUNCEMENT_FLICKER_BUFFER Ensures that the INTERPOLATOR_FLICKER animation is played before the message is hidden in + // AnnouncementMessage_DisplayOnHud(). I'm not certain why this is needed. There may be a delay before + // ServerCallback_AnnounceRoundWinner is actually received and/or the hud fades may be framerate + // dependent (they don't seem to play nice with timescale), or it could be something much more simple that I'm missing. + const ANNOUNCEMENT_FLICKER_BUFFER = 0.2 + + float announcementDuration = winnerDeterminedWait - ANNOUNCEMENT_FLICKER_BUFFER + float subtext2IconDelay = winnerDeterminedWait - 4.5 + float conversationDelay = subtext2IconDelay + 1.5 // Bit of time to give it to breathe, and to let RoundWinningKillReplay be fully over before starting the conversation. + + if ( !level.nv.winningTeam ) + { + AnnouncementData announcement = Announcement_Create( "#GAMEMODE_ROUND_DRAW" ) + announcement.drawOverScreenFade = true + Announcement_SetSubText( announcement, subString ) + //Announcement_SetTitleColor( announcement, TEAM_COLOR_YOU ) + Announcement_SetHideOnDeath( announcement, false ) + Announcement_SetPurge( announcement, true ) + Announcement_SetDuration( announcement, announcementDuration) + + ShowRoundScoresInAnnouncement( announcement, subtext2IconDelay, imcTeamScore2, militiaTeamScore2 ) + AnnouncementFromClass( player, announcement ) + } + else if ( player.GetTeam() == level.nv.winningTeam ) + { + AnnouncementData announcement = Announcement_Create( "#GAMEMODE_ROUND_WIN" ) + announcement.drawOverScreenFade = true + Announcement_SetSubText( announcement, subString ) + Announcement_SetTitleColor(announcement, TEAM_COLOR_FRIENDLY ) + Announcement_SetHideOnDeath( announcement, false ) + Announcement_SetPurge( announcement, true ) + Announcement_SetDuration( announcement, announcementDuration) + + ShowRoundScoresInAnnouncement( announcement, subtext2IconDelay, imcTeamScore2, militiaTeamScore2 ) + thread PlayRoundWonConversationWithAnnouncementDelay( conversationDelay ) + + AnnouncementFromClass( player, announcement ) + } + else if ( level.nv.winningTeam != TEAM_UNASSIGNED ) + { + AnnouncementData announcement = Announcement_Create( "#GAMEMODE_ROUND_LOSS" ) + announcement.drawOverScreenFade = true + Announcement_SetSubText( announcement, subString ) + Announcement_SetTitleColor( announcement, TEAM_COLOR_ENEMY ) + Announcement_SetHideOnDeath( announcement, false ) + Announcement_SetPurge( announcement, true ) + Announcement_SetDuration( announcement, announcementDuration) + + ShowRoundScoresInAnnouncement( announcement, subtext2IconDelay, imcTeamScore2, militiaTeamScore2 ) + thread PlayRoundWonConversationWithAnnouncementDelay( conversationDelay ) + + AnnouncementFromClass( player, announcement ) + } +} + +//Note that RoundWinningKillReplay doesn't send imcTeamScore2 and militiaTeamScore2 overrides. +void function ShowRoundScoresInAnnouncement( AnnouncementData announcement, float subtext2IconDelay, int ornull imcTeamScore2 = null, int ornull militiaTeamScore2 = null ) +{ + entity player = GetLocalClientPlayer() + + local friendlyTeam = player.GetTeam() + local enemyTeam = friendlyTeam == TEAM_IMC ? TEAM_MILITIA : TEAM_IMC + + asset leftIcon + asset rightIcon + + if ( friendlyTeam == TEAM_IMC ) + { + leftIcon = TEAM_ICON_IMC + rightIcon = TEAM_ICON_MILITIA + } + else + { + leftIcon = TEAM_ICON_MILITIA + rightIcon = TEAM_ICON_IMC + } + + if ( level.nv.roundScoreLimitComplete == true ) //Generally this is never true except for modes with RoundWinningKillReplay enabled + { + if ( friendlyTeam == level.nv.winningTeam ) + { + Announcement_SetSubText( announcement, "#GAMEMODE_MATCH_WON_BY_FRIENDLY_TEAM" ) + string friendlyTeamString = friendlyTeam == TEAM_IMC ? "#TEAM_IMC" : "#TEAM_MCOR" + Announcement_SetOptionalSubTextArgsArray( announcement, [ friendlyTeamString ] ) + } + else if ( enemyTeam == level.nv.winningTeam ) + { + Announcement_SetSubText( announcement, "#GAMEMODE_MATCH_WON_BY_ENEMY_TEAM" ) + string enemyTeamString = enemyTeam == TEAM_IMC ? "#TEAM_IMC" : "#TEAM_MCOR" + Announcement_SetOptionalSubTextArgsArray( announcement, [ enemyTeamString ] ) + } + + } + else + { + Announcement_SetSubText2( announcement, "#GAMEMODE_ROUND_WIN_CONDITION", GetRoundScoreLimit_FromPlaylist() ) + Announcement_SetSubText2AndIconDelay( announcement, subtext2IconDelay ) + } + + //Hack: GetTeamScore2 doesn't work mid-kill replay because we get the rewound values as opposed to the current values. + //Fix for R2 when we get the ability to flag certain values as "use current value instead of rewound value" + if ( imcTeamScore2 == null && militiaTeamScore2 == null ) + { + Announcement_SetLeftText( announcement, "#GAMEMODE_JUST_THE_SCORE", GameRules_GetTeamScore2( friendlyTeam ) ) + Announcement_SetRightText( announcement, "#GAMEMODE_JUST_THE_SCORE", GameRules_GetTeamScore2( enemyTeam ) ) + } + else + { + Assert( imcTeamScore2 != null && militiaTeamScore2 != null ) //Don't have only one team with teamScore2 override + if ( friendlyTeam == TEAM_IMC ) + { + Announcement_SetLeftText( announcement, "#GAMEMODE_JUST_THE_SCORE", imcTeamScore2 ) + Announcement_SetRightText( announcement, "#GAMEMODE_JUST_THE_SCORE", militiaTeamScore2 ) + + } + else + { + Announcement_SetLeftText( announcement, "#GAMEMODE_JUST_THE_SCORE", militiaTeamScore2 ) + Announcement_SetRightText( announcement, "#GAMEMODE_JUST_THE_SCORE", imcTeamScore2 ) + } + } + + Announcement_SetLeftIcon( announcement, leftIcon ) + Announcement_SetRightIcon( announcement, rightIcon ) +} + +void function PlayRoundWonConversationWithAnnouncementDelay( conversationDelay ) +{ + WaitEndFrame() //Necessary so we don't get the AnnouncementPurge signal from the same announcement we are originating from + clGlobal.levelEnt.EndSignal( "AnnoucementPurge" ) + + if ( conversationDelay != 0 ) + wait conversationDelay + + if ( level.nv.winningTeam == null ) + return + + entity player = GetLocalClientPlayer() + if ( player.GetTeam() == level.nv.winningTeam ) + PlayConversationToLocalClient( "RoundWonAnnouncement" ) + else if ( level.nv.winningTeam != TEAM_UNASSIGNED ) + PlayConversationToLocalClient( "RoundLostAnnouncement" ) +} + +void function UpdateScoreboardBadRepPresentMessage() +{ + if ( IsLobby() ) + return + + entity player = GetLocalClientPlayer() + + if ( level.nv.badRepPresent ) + player.cv.scoreboardBadRepPresentMessage.Show() + else + player.cv.scoreboardBadRepPresentMessage.Hide() +} + +void function UpdateChatHudLocationForTop3() +{ + var hudElement = HudElement( "IngameTextChat" ) + var height = hudElement.GetHeight() + var screenSize = Hud.GetScreenSize() + var position = hudElement.GetPos() + HudElement( "IngameTextChat" ).SetPos( position[0], -1 * ( screenSize[1] - ( height + screenSize[1] * 0.10 ) ) ) +} + +void function DisplayPostMatchTop3() +{ + array<entity> players = GetPlayerArray() + + for ( int i = players.len() - 1; i >= 0; i-- ) + { + if ( IsPrivateMatchSpectator( players[ i ] ) ) + players.remove( i ) + } + + if ( players.len() >= 1 ) + { + int localTeam = GetLocalClientPlayer().GetTeam() + #if PC_PROG + var rui = RuiCreate( $"ui/scoreboard_postmatch_top3_pc.rpak", clGlobal.topoFullScreen, RUI_DRAW_HUD, 1001 ) + UpdateChatHudLocationForTop3() + #else + var rui = RuiCreate( $"ui/scoreboard_postmatch_top3.rpak", clGlobal.topoFullScreen, RUI_DRAW_HUD, 1001 ) + #endif + string gamemode = GameRules_GetGameMode() + int functionref( entity, entity ) compareFunc = GameMode_GetScoreCompareFunc( gamemode ) + if ( compareFunc == null ) + { + printt( "gamemode doesn't have a compare func to display the top 3") + return + } + players.sort( compareFunc ) + int playerCount = players.len() + int currentPlace = 1 + for ( int i = 0; i < 3; i++ ) + { + if ( i >= playerCount ) + continue + + string playerRank + if ( i > 0 && compareFunc( players[i - 1], players[i] ) != 0 ) + currentPlace += 1 + + switch( currentPlace ) + { + case 1: + playerRank = "#GENERATION_NUMERIC_1" + break + case 2: + playerRank = "#GENERATION_NUMERIC_2" + break + case 3: + playerRank = "#GENERATION_NUMERIC_3" + break + } + + float cardScale = currentPlace == 1 ? 1.0 : 0.9 + + RuiSetString( rui, "playerName" + i, players[i].GetPlayerName() ) + RuiSetString( rui, "playerRank" + i, Localize( playerRank ) ) + RuiSetFloat( rui, "cardScale" + i, cardScale ) + RuiSetImage( rui, "cardImage" + i, CallingCard_GetImage( PlayerCallingCard_GetActive( players[i] ) ) ) + RuiSetImage( rui, "iconImage" + i, CallsignIcon_GetImage( PlayerCallsignIcon_GetActive( players[i] ) ) ) + RuiSetInt( rui, "layoutType" + i, CallingCard_GetLayout( PlayerCallingCard_GetActive( players[i] ) ) ) + RuiSetImage( rui, "cardGenImage" + i, PlayerXPGetGenIcon( players[i] ) ) + RuiSetString( rui, "playerLevel" + i, PlayerXPDisplayGenAndLevel( players[i].GetGen(), players[i].GetLevel() ) ) + RuiSetBool( rui, "isFriendly" + i, localTeam == players[i].GetTeam() ) + } + } +} + + +float function GetGameStartTime() +{ + return expect float( level.nv.gameStartTime.tofloat() ) +} + +void function ClGameState_SetPilotTitanStatusCallback( int functionref(entity,int) func ) +{ + file.pilotTitanStatusCallback = func +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_arena.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_arena.gnut new file mode 100644 index 00000000..1765fd9b --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_arena.gnut @@ -0,0 +1,185 @@ +global function GameModeArena_Init + +struct { + bool inBuyPhase + + entity imcBoostStore + entity militiaBoostStore + + entity imcShield + entity militiaShield +} file + +void function GameModeArena_Init() +{ + SetSpawnpointGamemodeOverride( TEAM_DEATHMATCH ) + + SetShouldUseRoundWinningKillReplay( true ) + SetRoundBased( true ) + SetRespawnsEnabled( false ) + SetLoadoutGracePeriodEnabled( false ) // prevent modifying loadouts with grace period + Riff_ForceTitanAvailability( eTitanAvailability.Never ) + Riff_ForceBoostAvailability( eBoostAvailability.Disabled ) + Riff_ForceSetEliminationMode( eEliminationMode.Pilots ) + + ClassicMP_SetCustomIntro( GameModeArena_BuyPhaseSetup, 30.0 ) + AddCallback_EntitiesDidLoad( CreateBoostStores ) + AddCallback_OnPlayerRespawned( SetupArenaLoadoutForPlayer ) +} + +// intro / buy phase + +void function GameModeArena_BuyPhaseSetup() +{ + AddCallback_OnClientConnected( SpawnPlayerIntoArenasIntro ) + AddCallback_GameStateEnter( eGameState.Prematch, void function() { thread BuyPhase() } ) +} + +void function SpawnPlayerIntoArenasIntro( entity player ) +{ + if ( GetGameState() == eGameState.Prematch ) + RespawnAsPilot( player ) +} + +void function CreateBoostStores() +{ + array<entity> startspawns = GetEntArrayByClass_Expensive( "info_spawnpoint_human_start" ) // easier to do this than use a spawn callback imo + + vector imcAverageOrigin + float imcAverageAngle + int imcNumSpawns + + vector militiaAverageOrigin + float militiaAverageAngle + int militiaNumSpawns + + foreach ( entity startspawn in startspawns ) + { + if ( !startspawn.HasKey( "gamemode_tdm" ) || startspawn.kv.gamemode_tdm == "0" ) + continue + + if ( startspawn.GetTeam() == TEAM_IMC ) + { + imcAverageOrigin += startspawn.GetOrigin() + imcAverageAngle += startspawn.GetAngles().y + imcNumSpawns++ + } + else + { + militiaAverageOrigin += startspawn.GetOrigin() + militiaAverageAngle += startspawn.GetAngles().y + militiaNumSpawns++ + } + } + + // create imc boost store + vector finalPositionImc = < imcAverageOrigin.x / imcNumSpawns, imcAverageOrigin.y / imcNumSpawns, imcAverageOrigin.z / imcNumSpawns > + finalPositionImc += ( 200 * AnglesToForward( < 0, imcAverageAngle / imcNumSpawns, 0 > ) ) + CreateBoostStoreLocation( TEAM_IMC, finalPositionImc, < 0, 0, 0 >, true ) + + vector finalPositionMilitia = < militiaAverageOrigin.x / militiaNumSpawns, militiaAverageOrigin.y / militiaNumSpawns, militiaAverageOrigin.z / militiaNumSpawns > + finalPositionMilitia += ( 200 * AnglesToForward( < 0, militiaAverageAngle / militiaNumSpawns, 0 > ) ) + CreateBoostStoreLocation( TEAM_MILITIA, finalPositionMilitia, < 0, 0, 0 >, true ) + + // createbooststorelocation is void so have to do this + // also boost store code is just fully fucked lol, teams only get set on open so can't compare teams at this point + // sorry if someone else makes their own boost stores lol this'll just break + // if there's some way to get the invisible crates used for boost stores i will be very happy + + if ( GetBoostStores().len() != 2 ) + print( "_gamemode_arena.gnut: there are more than 2 boost stores, very bad no good" ) + + file.imcBoostStore = GetBoostStores()[0] + file.militiaBoostStore = GetBoostStores()[1] +} + +void function BuyPhase() +{ + ClassicMP_OnIntroStarted() + + foreach ( entity player in GetPlayerArray() ) + { + ScreenFadeFromBlack( player ) + RespawnAsPilot( player ) + + AddMoneyToPlayer( player, GetCashBoostForRoundCount( GetRoundsPlayed() ) ) + } + + SetJoinInProgressBonus( GetCashBoostForRoundCount( GetRoundsPlayed() ) ) + + // sort of a hack, set up a new intro here, so dropship intro only ever plays once + + //file.imcShield = CreateBubbleShieldWithSettings( TEAM_IMC, file.imcBoostStore.GetOrigin(), <0,0,0>, null, 15.0 ) + //file.militiaShield = CreateBubbleShieldWithSettings( TEAM_MILITIA, file.militiaBoostStore.GetOrigin(), <0,0,0>, null, 15.0 ) + + entity imcShield = CreateEntity( "prop_dynamic" ) + imcShield.SetValueForModelKey( $"models/fx/xo_shield.mdl" ) + imcShield.kv.solid = 0 + imcShield.kv.rendercolor = "255 255 255" // white + imcShield.kv.modelscale = 2.25 + imcShield.SetOrigin( file.imcBoostStore.GetOrigin() ) + DispatchSpawn( imcShield ) + + entity militiaShield = CreateEntity( "prop_dynamic" ) + militiaShield.SetValueForModelKey( $"models/fx/xo_shield.mdl" ) + militiaShield.kv.solid = 0 + militiaShield.kv.rendercolor = "255 255 255" // white + militiaShield.kv.modelscale = 2.25 + militiaShield.SetOrigin( file.militiaBoostStore.GetOrigin() ) + DispatchSpawn( militiaShield ) + + // current problem, there is seemingly no way of getting a shield we can resize which actually resizes the collision + // could probably just damage players that try to leave lol + + OpenBoostStores() + + thread DamageLeavingPlayers( imcShield.GetOrigin(), militiaShield.GetOrigin() ) + + wait 30.0 // intro length + + CloseBoostStores() + imcShield.Destroy() + militiaShield.Destroy() + + foreach ( entity player in GetPlayerArray() ) + if ( player.GetMainWeapons().len() != 3 ) + player.GiveWeapon( "mp_weapon_semipistol" ) + + ClassicMP_OnIntroFinished() +} + +void function DamageLeavingPlayers( vector imcOrigin, vector militiaOrigin ) +{ + while ( GetGameState() == eGameState.Prematch ) + { + wait 0.5 + foreach ( entity player in GetPlayerArray() ) + { + vector pos = imcOrigin + if ( player.GetTeam() == TEAM_MILITIA ) + pos = militiaOrigin + + if ( Distance( player.GetOrigin(), pos ) > 510.0 ) // roughly the size of the shield + player.TakeDamage( 25, svGlobal.worldspawn, svGlobal.worldspawn, {} ) + } + } +} + +void function SetupArenaLoadoutForPlayer( entity player ) +{ + PilotLoadoutDef playerLoadout = clone GetActivePilotLoadout( player ) + + if ( GetGameState() == eGameState.Prematch ) // buy phase + { + playerLoadout.primary = "" + playerLoadout.primaryMods = [] + playerLoadout.secondary = "" + playerLoadout.secondaryMods = [] + playerLoadout.weapon3 = "" + playerLoadout.weapon3Mods = [] + playerLoadout.ordnance = "" + playerLoadout.special = "" + } + + GivePilotLoadout( player, playerLoadout ) +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fastball.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fastball.gnut new file mode 100644 index 00000000..b59cd2dd --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fastball.gnut @@ -0,0 +1,195 @@ +untyped +global function GamemodeFastball_Init + +struct { + // first panel is a, second is b, third is c + array<Point> panelSpawns +} file + +void function GamemodeFastball_Init() +{ + // used for intro + PrecacheModel( $"models/titans/buddy/titan_buddy.mdl" ) + PrecacheParticleSystem( $"P_BT_eye_SM" ) + + // used for respawn + PrecacheParticleSystem( $"P_pod_screen_lasers_OUT" ) + + SetShouldUseRoundWinningKillReplay( true ) + SetRoundBased( true ) + SetRespawnsEnabled( false ) + Riff_ForceSetEliminationMode( eEliminationMode.Pilots ) + + // implementing intro in a different file because it'll likely be big + ClassicMP_SetCustomIntro( GamemodeFastballIntroSetup, 14.5 ) // bit of a guess number + AddCallback_EntitiesDidLoad( SpawnPanelsForLevel ) + AddCallback_GameStateEnter( eGameState.Prematch, ResetPanels ) + SetTimeoutWinnerDecisionFunc( FastballDecideWinner ) + + AddCallback_OnClientConnected( FastballInitPlayer ) + AddCallback_OnPlayerKilled( FastballOnPlayerKilled ) // move this to a system in _gamestate soon!! + + // setup spawns + // first is a, second is b, third is c + + FastballAddBuddySpawnForLevel( "mp_angel_city", TEAM_IMC, < 2281.39, -3333.06, 200.031 >, < 0, 91.23, 0 > ) + FastballAddBuddySpawnForLevel( "mp_angel_city", TEAM_MILITIA, < -4139.57, 4684.4, 41.0313 >, <0, -14.326, 0 > ) + FastballAddPanelSpawnsForLevel( "mp_angel_city", [ + < 2055.94, 2040.03, 128.031 >, < 0, -180, 0 >, + < -274.757, 2255.22, 400.031 >, < 0, -176.251, 0 >, + < -3208.28, 2741.17, 128.031 >, < 0, -0.0821686, 0 > + ]) + + FastballAddBuddySpawnForLevel( "mp_thaw", TEAM_MILITIA, < 2049.29, -4085.22, -274.839 >, < 0, 89.2991, 0 > ) + FastballAddBuddySpawnForLevel( "mp_thaw", TEAM_IMC, < 834.484, 2664.28, -380.515 >, < 0.532141, -90.875, -0.542593 > ) + FastballAddPanelSpawnsForLevel( "mp_thaw", [ + < -1026.71, -1691.93, -319.969 >, < 0, 90, 0 >, + < 1836.07, -538.823, -64.1245 >, < 0, -135, 0 >, + < 2840.15, 1321.17, -63.9688 >, < 0, 0, 0 > + ]) + + FastballAddBuddySpawnForLevel( "mp_wargames", TEAM_MILITIA, < -4848.87, 682.17, -127.969 >, < 0, 0, 0 > ) + FastballAddBuddySpawnForLevel( "mp_wargames", TEAM_IMC, < 2960.78, 1229.36, -127.969 >, < 7.89891e-005, 146.505, 1.38387e-005 > ) + FastballAddPanelSpawnsForLevel( "mp_wargames", [ + < -691.961, 1888.56, 112.031 >, < 0, 90, 0 >, + < -1072.03, -508.229, -127.969 >, < 0, 180, 0 >, + < -24.291, -1403.69, -119.969 >, < 0, -90, 0 > + ]) + + FastballAddBuddySpawnForLevel( "mp_eden", TEAM_MILITIA, < -2404.4, 1738.07, 167.767 >, <0, -40.0894, 0> ) + FastballAddBuddySpawnForLevel( "mp_eden", TEAM_IMC, < 5248.01, 414.698, 77.1051 >, < 0, 180, 0 > ) + FastballAddPanelSpawnsForLevel( "mp_eden", [ + < 704.333, 1530.18, 144.031 >, < 0, 90, 0 >, + < -108.43, 272.638, 72.0313 >, < 0, -90, 0 >, + <1044.25, -1145.68, 68.0313>, < 0, 180, 0 > + ]) + + FastballAddBuddySpawnForLevel( "mp_black_water_canal", TEAM_MILITIA, < 1222.88, -5050.63, -187.763 >, < 0, 45, 0 > ) + FastballAddBuddySpawnForLevel( "mp_black_water_canal", TEAM_IMC, < -235.375, 4736.75, -255.969 >, < 0, -90, 0 > ) + FastballAddPanelSpawnsForLevel( "mp_black_water_canal", [ + < 1566.13, -731.539, -63.9688 >, < 0, -90, 0 >, + < 502.603, 1102.92, 260.031 >, < 0, 152.328, 0 >, + < 2337.37, 2099.91, -26.9688 >, < 0, 0, 0 > + ]) + + FastballAddBuddySpawnForLevel( "mp_grave", TEAM_MILITIA, < 11026.8, -5163.18, 1885.64 >, < 0, 155.05, 0 > ) + FastballAddBuddySpawnForLevel( "mp_grave", TEAM_IMC, < -1952, -3120, 1993.33 >, < 0, 0, 0 > ) + FastballAddPanelSpawnsForLevel( "mp_grave", [ + < 5204.54, -2726.54, 2376.03 >, < 0, -90, 0 >, + < 6001.58, -4126.61, 2252.03 >, < 0, -135, 0 >, + < 3595.96, -4568.04, 2376.03 >, < 0, -135, 0 > + ]) +} + +void function SpawnPanelsForLevel() +{ + int panelId + foreach ( Point panelSpawn in file.panelSpawns ) + { + entity panel = CreatePanel( panelSpawn.origin, panelSpawn.angles ) + panel.s.panelId <- panelId++ + } +} + +void function ResetPanels() +{ + foreach ( entity panel in GetAllControlPanels() ) + SetTeam( panel, TEAM_UNASSIGNED ) +} + +void function FastballInitPlayer( entity player ) +{ + foreach ( entity panel in GetAllControlPanels() ) + Remote_CallFunction_NonReplay( player, "ServerCallback_FastballUpdatePanelRui", panel.GetEncodedEHandle(), panel.s.panelId ) +} + +void function FastballAddPanelSpawnsForLevel( string level, array<vector> positionsAndOrigins ) +{ + if ( GetMapName() != level ) + return + + for ( int i = 0; i < positionsAndOrigins.len(); i += 2 ) + { + Point spawnPoint + spawnPoint.origin = positionsAndOrigins[ i ] + spawnPoint.angles = positionsAndOrigins[ i + 1 ] + + file.panelSpawns.append( spawnPoint ) + } +} + +entity function CreatePanel( vector origin, vector angles ) +{ + entity panel = CreateEntity( "prop_control_panel" ) + panel.SetValueForModelKey( $"models/communication/terminal_usable_imc_01.mdl" ) + panel.SetOrigin( origin ) + panel.SetAngles( angles ) + panel.kv.solid = SOLID_VPHYSICS + DispatchSpawn( panel ) + + panel.SetModel( $"models/communication/terminal_usable_imc_01.mdl" ) + panel.s.onPlayerFinishesUsing_func = FastballOnPanelHacked + + Highlight_SetNeutralHighlight( panel, "sp_enemy_pilot" ) + + return panel +} + +// control panel code isn't very statically typed, pain +function FastballOnPanelHacked( panel, player, success ) +{ + expect entity( panel ) + expect entity( player ) + expect bool( success ) + + if ( !success ) + return + + print( panel + " was hacked by " + player ) + PanelFlipsToPlayerTeamAndUsableByEnemies( panel, player ) + player.SetPlayerGameStat( PGS_ASSAULT_SCORE, player.GetPlayerGameStat( PGS_ASSAULT_SCORE ) + 1 ) + + foreach ( entity otherPlayer in GetPlayerArray() ) + Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_FastballPanelHacked", panel.GetEncodedEHandle(), panel.s.panelId, player.GetEncodedEHandle() ) + + // respawn dead players + foreach ( entity deadPlayer in GetPlayerArrayOfTeam( player.GetTeam() ) ) + { + if ( !IsAlive( deadPlayer ) ) + { + deadPlayer.SetOrigin( player.GetOrigin() ) + deadPlayer.RespawnPlayer( null ) + Remote_CallFunction_NonReplay( deadPlayer, "ServerCallback_FastballRespawnPlayer" ) + } + } +} + +void function FastballOnPlayerKilled( entity victim, entity attacker, var damageInfo ) +{ + if ( !victim.IsPlayer() || GetGameState() != eGameState.Playing ) + return + + if ( GetPlayerArrayOfTeam_Alive( victim.GetTeam() ).len() == 0 ) + SetWinner( GetOtherTeam( victim.GetTeam() ) ) +} + +int function FastballDecideWinner() +{ + int militiaPanels + int imcPanels + + foreach ( entity panel in GetAllControlPanels() ) + { + if ( panel.GetTeam() == TEAM_MILITIA ) + militiaPanels++ + else if ( panel.GetTeam() == TEAM_IMC ) + imcPanels++ + } + + if ( militiaPanels > imcPanels ) + return TEAM_MILITIA + else if ( imcPanels > militiaPanels ) + return TEAM_IMC + + return TEAM_UNASSIGNED +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fastball_intro.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fastball_intro.gnut new file mode 100644 index 00000000..75482558 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_fastball_intro.gnut @@ -0,0 +1,201 @@ +untyped +global function GamemodeFastballIntroSetup +global function FastballAddBuddySpawnForLevel + +struct { + float introStartTime + + table< int, Point > buddySpawns + entity militiaBuddy + entity imcBuddy + + table<entity, bool> playersHoldingJump +} file + +void function GamemodeFastballIntroSetup() +{ + RegisterSignal( "fastball_start_throw" ) + RegisterSignal( "fastball_release" ) + + AddCallback_GameStateEnter( eGameState.Prematch, OnPrematchStart ) + AddCallback_OnClientConnected( AddPlayerToFastballIntro ) +} + +void function FastballAddBuddySpawnForLevel( string level, int team, vector origin, vector angles ) +{ + if ( GetMapName() != level ) + return + + Point spawnPoint + spawnPoint.origin = origin + spawnPoint.angles = angles + + file.buddySpawns[ team ] <- spawnPoint +} + +void function OnPrematchStart() +{ + ClassicMP_OnIntroStarted() + + file.introStartTime = Time() + file.playersHoldingJump = {} // clear it + + Point militiaBuddySpawn + Point imcBuddySpawn + + // figure out positions if there's none manually specified + if ( file.buddySpawns.len() == 0 ) + { + array<entity> militiaSpawns + array<entity> imcSpawns + + foreach ( entity spawnpoint in GetEntArrayByClass_Expensive( "info_spawnpoint_titan_start" ) ) + { + // trace from top to bottom + float result = TraceHullSimple( spawnpoint.GetOrigin() + < 0, 0, 250 >, spawnpoint.GetOrigin(), < -200, -200, 0 >, < 200, 200, 400 >, null ) + + // don't need to trace much, as long as it's over 0 that means it fits + // cases where it's over 0 but less than 1 are usually caused by terrain + if ( result > 0 ) + { + if ( spawnpoint.GetTeam() == TEAM_MILITIA ) + militiaSpawns.append( spawnpoint ) + else + imcSpawns.append( spawnpoint ) + } + } + + entity milititaSpawnEnt = militiaSpawns[ RandomInt( militiaSpawns.len() ) ] + militiaBuddySpawn.origin = milititaSpawnEnt.GetOrigin() + militiaBuddySpawn.angles = milititaSpawnEnt.GetAngles() + + entity imcSpawnEnt = imcSpawns[ RandomInt( imcSpawns.len() ) ] + imcBuddySpawn.origin = imcSpawnEnt.GetOrigin() + imcBuddySpawn.angles = imcSpawnEnt.GetAngles() + } + else + { + militiaBuddySpawn = file.buddySpawns[ TEAM_MILITIA ] + imcBuddySpawn = file.buddySpawns[ TEAM_IMC ] + } + + file.militiaBuddy = CreatePropDynamic( $"models/titans/buddy/titan_buddy.mdl" ) + file.militiaBuddy.SetOrigin( militiaBuddySpawn.origin ) + file.militiaBuddy.SetAngles( militiaBuddySpawn.angles ) + thread AnimateBuddy( file.militiaBuddy ) + + file.imcBuddy = CreatePropDynamic( $"models/titans/buddy/titan_buddy.mdl" ) + file.imcBuddy.SetOrigin( imcBuddySpawn.origin ) + file.imcBuddy.SetAngles( imcBuddySpawn.angles ) + thread AnimateBuddy( file.imcBuddy ) + + foreach ( entity player in GetPlayerArray() ) + thread FastballPlayer( player ) +} + +void function AnimateBuddy( entity buddy ) +{ + print( "buddy spawn at " + buddy.GetOrigin() + " " + buddy.GetAngles() ) + + thread PlayAnim( buddy, "bt_beacon_fastball_throw_end" ) + + // play dialogue at the right time + buddy.WaitSignal( "fastball_start_throw" ) + float diagDuration = EmitSoundOnEntity( buddy, "diag_sp_spoke1_BE117_04_01_mcor_bt" ) // trust me + StartParticleEffectOnEntity( buddy, GetParticleSystemIndex( $"P_BT_eye_SM" ), FX_PATTACH_POINT_FOLLOW, buddy.LookupAttachment( "EYEGLOW" ) ) + + wait diagDuration + if ( GetGameState() != eGameState.Playing ) + ClassicMP_OnIntroFinished() + + buddy.WaitSignal( "fastball_release" ) + wait 5.0 + buddy.Destroy() +} + +void function AddPlayerToFastballIntro( entity player ) +{ + if ( GetGameState() == eGameState.Prematch ) + thread FastballPlayer( player ) +} + +void function FastballPlayer( entity player ) +{ + player.EndSignal( "OnDeath" ) + player.EndSignal( "OnDestroy" ) + + OnThreadEnd( function() : ( player ) + { + RemoveCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING ) + player.ClearParent() + ClearPlayerAnimViewEntity( player ) + player.DeployWeapon() + player.PlayerCone_Disable() + }) + + FirstPersonSequenceStruct throwSequence + throwSequence.attachment = "REF" + throwSequence.useAnimatedRefAttachment = true + throwSequence.hideProxy = true + throwSequence.viewConeFunction = ViewConeFastball // this seemingly does not trigger for some reason + throwSequence.firstPersonAnim = "ptpov_beacon_fastball_throw_end" + // mp models seemingly have no 3p animation for this + throwSequence.firstPersonBlendOutTime = 0.0 + throwSequence.teleport = true + throwSequence.setInitialTime = Time() - file.introStartTime + + // get our buddy + entity buddy + if ( player.GetTeam() == TEAM_MILITIA ) + buddy = file.militiaBuddy + else + buddy = file.imcBuddy + + // respawn the player + player.SetOrigin( buddy.GetOrigin() ) + player.RespawnPlayer( null ) + player.Hide() + player.HolsterWeapon() + + // hide hud, fade screen out from black + AddCinematicFlag( player, CE_FLAG_CLASSIC_MP_SPAWNING ) + ScreenFadeFromBlack( player, 0.5, 0.5 ) + + // start fp sequence + thread FirstPersonSequence( throwSequence, player, buddy ) + + // manually do this because i can't get viewconefastball to work + player.PlayerCone_FromAnim() + player.PlayerCone_SetMinYaw( -50 ) + player.PlayerCone_SetMaxYaw( 25 ) + player.PlayerCone_SetMinPitch( -15 ) + player.PlayerCone_SetMaxPitch( 15 ) + + buddy.WaitSignal( "fastball_start_throw" ) + // lock in their final angles at this point + vector throwVel = AnglesToForward( player.EyeAngles() ) * 950 + throwVel.z = 675.0 + + // wait for it to finish + buddy.WaitSignal( "fastball_release" ) + + if ( player.IsInputCommandHeld( IN_JUMP ) ) + throwVel.z = 850.0 + + // have to correct this manually here since due to no 3p animation our position isn't set right during this sequence + player.SetOrigin( buddy.GetAttachmentOrigin( buddy.LookupAttachment( "FASTBALL_R" ) ) ) + player.Show() + player.SetVelocity( throwVel ) + + TryGameModeAnnouncement( player ) +} + +void function PlayerHoldingJumpInIntro( entity player ) +{ + file.playersHoldingJump[ player ] <- true +} + +void function PlayerNoLongerHoldingJumpInIntro( entity player ) +{ + file.playersHoldingJump[ player ] <- false +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_gg.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_gg.gnut new file mode 100644 index 00000000..b9ee40f1 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_gg.gnut @@ -0,0 +1,114 @@ +global function GamemodeGG_Init + +void function GamemodeGG_Init() +{ + SetSpawnpointGamemodeOverride( FFA ) + + SetShouldUseRoundWinningKillReplay( true ) + Evac_SetEnabled( false ) + SetLoadoutGracePeriodEnabled( false ) // prevent modifying loadouts with grace period + SetWeaponDropsEnabled( false ) + Riff_ForceTitanAvailability( eTitanAvailability.Never ) + Riff_ForceBoostAvailability( eBoostAvailability.Disabled ) + + AddCallback_OnPlayerRespawned( OnPlayerRespawned ) + AddCallback_OnPlayerKilled( OnPlayerKilled ) + + AddCallback_GameStateEnter( eGameState.WinnerDetermined, OnWinnerDetermined ) +} + +void function OnPlayerRespawned( entity player ) +{ + UpdateLoadout( player ) + thread OnPlayerRespawned_Threaded( player ) +} + +void function OnPlayerRespawned_Threaded( entity player ) +{ + // bit of a hack, need to rework earnmeter code to have better support for completely disabling it + // rn though this just waits for earnmeter code to set the mode before we set it back + WaitFrame() + PlayerEarnMeter_SetMode( player, eEarnMeterMode.DISABLED ) +} + +void function OnPlayerKilled( entity victim, entity attacker, var damageInfo ) +{ + if ( !victim.IsPlayer() || !attacker.IsPlayer() || GetGameState() != eGameState.Playing ) + return + + if ( attacker == victim ) // suicide + { + string message = victim.GetPlayerName() + " committed suicide." + foreach ( entity player in GetPlayerArray() ) + SendHudMessage( player, message, -1, 0.4, 255, 0, 0, 0, 0, 3, 0.15 ) + + if ( GameRules_GetTeamScore( victim.GetTeam() ) != 0 ) + { + AddTeamScore( victim.GetTeam(), -1 ) // get absolutely fucking destroyed lol + victim.AddToPlayerGameStat( PGS_ASSAULT_SCORE, -1 ) + } + } + else + { + if ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) != eDamageSourceId.melee_pilot_emptyhanded ) + { + AddTeamScore( attacker.GetTeam(), 1 ) + attacker.AddToPlayerGameStat( PGS_ASSAULT_SCORE, 1 ) + UpdateLoadout( attacker ) + } + + if ( DamageInfo_GetDamageSourceIdentifier( damageInfo ) == eDamageSourceId.human_execution ) + { + string message = victim.GetPlayerName() + " got executed." + foreach ( entity player in GetPlayerArray() ) + SendHudMessage( player, message, -1, 0.4, 255, 0, 0, 0, 0, 3, 0.15 ) + + if ( GameRules_GetTeamScore( victim.GetTeam() ) != 0 ) + { + AddTeamScore( victim.GetTeam(), -1 ) // get absolutely fucking destroyed lol + victim.AddToPlayerGameStat( PGS_ASSAULT_SCORE, -1 ) + } + } + } +} + +void function UpdateLoadout( entity player ) +{ + // todo: honestly, this should be reworked to use PilotLoadoutDefs instead of directly modifying weapons and shit + + int currentWeaponIndex = GameRules_GetTeamScore( player.GetTeam() ) + array<GunGameWeapon> weapons = GetGunGameWeapons() + + if ( currentWeaponIndex >= weapons.len() ) + currentWeaponIndex = weapons.len() - 1 + + if ( currentWeaponIndex > 18 ) // play end of game music for special weapons + PlayMusicToAll( eMusicPieceID.LEVEL_LAST_MINUTE ) // this *shouldn't* overlap if done multiple times + + GunGameWeapon weapon = weapons[ currentWeaponIndex ] + + foreach ( entity weapon in player.GetMainWeapons() ) + player.TakeWeaponNow( weapon.GetWeaponClassName() ) + + foreach ( entity weapon in player.GetOffhandWeapons() ) + player.TakeWeaponNow( weapon.GetWeaponClassName() ) + + if ( weapon.offhandSlot != -1 ) + { + // TEMP: give archer so player so player has a weapon which lets them use offhands + // need to replace this with a custom empty weapon at some point + player.GiveWeapon( "mp_weapon_rocket_launcher" ) + + player.GiveOffhandWeapon( weapon.weapon, weapon.offhandSlot, weapon.mods ) + } + else + player.GiveWeapon( weapon.weapon, weapon.mods ) + + player.GiveOffhandWeapon( "melee_pilot_emptyhanded", OFFHAND_MELEE ) +} + +void function OnWinnerDetermined() +{ + SetRespawnsEnabled( false ) + SetKillcamsEnabled( false ) +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_inf.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_inf.gnut new file mode 100644 index 00000000..8706d53b --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_inf.gnut @@ -0,0 +1,179 @@ +global function GamemodeInfection_Init + +struct { + bool hasHadFirstInfection = false + array<entity> playersToNotifyOfInfection +} file + +void function GamemodeInfection_Init() +{ + SetSpawnpointGamemodeOverride( FFA ) + SetLoadoutGracePeriodEnabled( false ) // prevent modifying loadouts with grace period + SetWeaponDropsEnabled( false ) + Riff_ForceTitanAvailability( eTitanAvailability.Never ) + Riff_ForceBoostAvailability( eBoostAvailability.Disabled ) + + AddCallback_OnClientConnected( InfectionInitPlayer ) + AddCallback_OnPlayerKilled( InfectionOnPlayerKilled ) + AddCallback_OnPlayerRespawned( RespawnInfected ) + AddCallback_GameStateEnter( eGameState.Playing, SelectFirstInfected ) + + SetTimeoutWinnerDecisionFunc( TimeoutCheckSurvivors ) +} + +void function InfectionInitPlayer( entity player ) +{ + if ( GetGameState() < eGameState.Playing ) + SetTeam( player, INFECTION_TEAM_SURVIVOR ) + else + InfectPlayer( player ) +} + +void function SelectFirstInfected() +{ + thread SelectFirstInfectedDelayed() +} + +void function SelectFirstInfectedDelayed() +{ + wait 10.0 + RandomFloat( 5.0 ) + + array<entity> players = GetPlayerArray() + entity infected = players[ RandomInt( players.len() ) ] + + InfectPlayer( infected ) + RespawnInfected( infected ) +} + +void function InfectionOnPlayerKilled( entity victim, entity attacker, var damageInfo ) +{ + if ( !victim.IsPlayer() || GetGameState() != eGameState.Playing ) + return + + if ( victim.GetTeam() == INFECTION_TEAM_SURVIVOR ) + InfectPlayer( victim ) + + if ( attacker.IsPlayer() ) + attacker.SetPlayerGameStat( PGS_ASSAULT_SCORE, attacker.GetPlayerGameStat( PGS_ASSAULT_SCORE ) + 1 ) +} + +void function InfectPlayer( entity player ) +{ + SetTeam( player, INFECTION_TEAM_INFECTED ) + player.SetPlayerGameStat( PGS_ASSAULT_SCORE, 0 ) // reset kills + file.playersToNotifyOfInfection.append( player ) + + // check how many survivors there are + array<entity> survivors = GetPlayerArrayOfTeam( INFECTION_TEAM_SURVIVOR ) + if ( survivors.len() == 0 ) + SetWinner( INFECTION_TEAM_INFECTED ) + else if ( survivors.len() == 1 ) + SetLastSurvivor( survivors[ 0 ] ) + + if ( !file.hasHadFirstInfection ) + { + file.hasHadFirstInfection = true + + foreach ( entity otherPlayer in GetPlayerArray() ) + if ( player != otherPlayer ) + Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_AnnounceFirstInfected", player.GetEncodedEHandle() ) + + PlayMusicToAll( eMusicPieceID.GAMEMODE_1 ) + } +} + +void function RespawnInfected( entity player ) +{ + if ( player.GetTeam() != INFECTION_TEAM_INFECTED ) + return + + // notify newly infected players of infection + if ( file.playersToNotifyOfInfection.contains( player ) ) + { + Remote_CallFunction_NonReplay( player, "ServerCallback_YouAreInfected" ) + file.playersToNotifyOfInfection.remove( file.playersToNotifyOfInfection.find( player ) ) + } + + // set camo to pond scum + player.SetSkin( 1 ) + player.SetCamo( 110 ) + + // stats for infected + StimPlayer( player, 9999.9 ) // can't do endless since we don't get the visual effect in endless + player.kv.airAcceleration = 2500 + + // scale health with num of infected, with 50 as base health + player.SetMaxHealth( ( GetPlayerArrayOfTeam( INFECTION_TEAM_SURVIVOR ).len() + 1 ) * 10 ) + + // set loadout + foreach ( entity weapon in player.GetMainWeapons() ) + player.TakeWeaponNow( weapon.GetWeaponClassName() ) + + foreach ( entity weapon in player.GetOffhandWeapons() ) + player.TakeWeaponNow( weapon.GetWeaponClassName() ) + + // TEMP: give archer so player so player has a weapon which lets them use offhands + // need to replace this with a custom empty weapon at some point + //player.GiveWeapon( "mp_weapon_rocket_launcher" ) + player.GiveWeapon( "mp_weapon_mgl" ) + player.GiveOffhandWeapon( "melee_pilot_emptyhanded", OFFHAND_MELEE ) + + thread PlayInfectedSounds( player ) +} + +void function PlayInfectedSounds( entity player ) +{ + player.EndSignal( "OnDeath" ) + player.EndSignal( "OnDestroy" ) + + float nextRandomSound + while ( true ) + { + WaitFrame() + + int meleeState = player.PlayerMelee_GetState() + if ( nextRandomSound < Time() || meleeState != 0 ) + { + string selectedSound + if ( CoinFlip() ) + selectedSound = "prowler_vocal_attack" + else + selectedSound = "prowler_vocal_attackmiss" + + bool canSeeSurvivor + foreach ( entity survivor in GetPlayerArrayOfTeam( INFECTION_TEAM_SURVIVOR ) ) + if ( TraceLineSimple( player.GetOrigin(), survivor.GetOrigin(), survivor ) == 1.0 ) + canSeeSurvivor = true + + // _int sounds are less agressive so only play them if we aren't in some sorta fight + if ( player.GetHealth() == player.GetMaxHealth() || !canSeeSurvivor || meleeState != 0 ) + selectedSound += "_int" + + EmitSoundOnEntity( player, selectedSound ) + + nextRandomSound = Time() + max( 2.5, RandomFloat( 12.0 ) ) + while ( player.PlayerMelee_GetState() != 0 ) // need to ensure this is updated + WaitFrame() + } + } +} + +void function SetLastSurvivor( entity player ) +{ + foreach ( entity otherPlayer in GetPlayerArray() ) + Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_AnnounceLastSurvivor", player.GetEncodedEHandle() ) + + Highlight_SetEnemyHighlight( player, "enemy_sonar" ) + thread CreateTitanForPlayerAndHotdrop( player, GetTitanReplacementPoint( player, false ) ) + + if ( GameTime_TimeLeftSeconds() > 45 ) + SetServerVar( "gameEndTime", Time() + 45.0 ) +} + +int function TimeoutCheckSurvivors() +{ + if ( GetPlayerArrayOfTeam( INFECTION_TEAM_SURVIVOR ).len() > 0 ) + return INFECTION_TEAM_SURVIVOR + + return INFECTION_TEAM_INFECTED +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_kr.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_kr.gnut new file mode 100644 index 00000000..cf9d6bc5 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_kr.gnut @@ -0,0 +1,126 @@ +global function GamemodeKR_Init + +struct { + float currentHighestKillraceAmount + int currentKillraceScore + entity currentRacer + array<vector> flagSpawnPoints +} file + +void function GamemodeKR_Init() +{ + PrecacheModel( CTF_FLAG_MODEL ) + + SetSpawnpointGamemodeOverride( FFA ) + + Evac_SetEnabled( false ) + Riff_ForceTitanAvailability( eTitanAvailability.Never ) + Riff_ForceBoostAvailability( eBoostAvailability.Disabled ) + + AddSpawnCallback( "info_hardpoint", AddFlagSpawnPoint ) + + AddCallback_OnPlayerKilled( OnPlayerKilled ) + AddCallback_OnTouchHealthKit( "item_flag", StartPlayerKillrace ) + + AddCallback_GameStateEnter( eGameState.Playing, StartKillraceSpawnThink ) +} + +void function AddFlagSpawnPoint( entity hardpoint ) +{ + if ( hardpoint.HasKey( "hardpointGroup" ) && ( hardpoint.kv.hardpointGroup == "A" || hardpoint.kv.hardpointGroup == "B" || hardpoint.kv.hardpointGroup == "C" ) ) + file.flagSpawnPoints.append( hardpoint.GetOrigin() ) +} + +void function OnPlayerKilled( entity victim, entity attacker, var damageInfo ) +{ + if ( !victim.IsPlayer() || !attacker.IsPlayer() || attacker == victim ) + return + + float killRaceTime = attacker.GetPlayerNetTime( "killRaceTime" ) + 5.0 + attacker.SetPlayerNetTime( "killRaceTime", killRaceTime ) + if ( killRaceTime > file.currentHighestKillraceAmount ) + file.currentHighestKillraceAmount = killRaceTime + if ( file.currentRacer != null ) + file.currentKillraceScore++ +} + +bool function StartPlayerKillrace( entity player, entity flag ) +{ + float killRaceTime = player.GetPlayerNetTime( "killRaceTime" ) + if ( killRaceTime > 0.0 ) + { + thread PlayerKillrace( player, killRaceTime ) + return true // delete the flag entity + } + + return false // keep it alive +} + +void function PlayerKillrace( entity player, float raceTime ) +{ + file.currentKillraceScore = 0 + file.currentRacer = player + int oldMaxHealth = player.GetMaxHealth() + + player.SetMaxHealth( oldMaxHealth * 10 ) + player.SetHealth( player.GetMaxHealth() ) + + foreach ( entity weapon in player.GetMainWeapons() ) + foreach ( string mod in GetWeaponBurnMods( weapon.GetWeaponClassName() ) ) + weapon.AddMod( mod ) + + foreach ( entity otherPlayer in GetPlayerArray() ) + Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_NewKillRacer", player.GetEncodedEHandle(), Time() + raceTime ) + + float raceEnd = Time() + raceTime + while ( raceEnd > Time() && IsAlive( player ) ) + WaitFrame() + + player.SetPlayerNetTime( "killRaceTime", 0.0 ) + player.SetMaxHealth( oldMaxHealth ) + + foreach ( entity weapon in player.GetMainWeapons() ) + foreach ( string mod in GetWeaponBurnMods( weapon.GetWeaponClassName() ) ) + weapon.RemoveMod( mod ) + + foreach ( entity otherPlayer in GetPlayerArray() ) + Remote_CallFunction_NonReplay( otherPlayer, "ServerCallback_EndKillrace", player.GetEncodedEHandle(), file.currentKillraceScore ) + + if ( GameRules_GetTeamScore( player.GetTeam() ) < file.currentKillraceScore ) + { + GameRules_SetTeamScore( player.GetTeam(), file.currentKillraceScore ) + player.SetPlayerGameStat( PGS_ASSAULT_SCORE, file.currentKillraceScore ) + } + + thread KillraceSpawnThink() // go to spawn next flag +} + +void function StartKillraceSpawnThink() +{ + thread KillraceSpawnThink() +} + +void function KillraceSpawnThink() +{ + file.currentHighestKillraceAmount = 0 + file.currentRacer = null + file.currentKillraceScore = 0 + float time = Time() + while ( time + 20.0 > Time() && file.currentHighestKillraceAmount < 25 ) + WaitFrame() + + vector spawnpos = file.flagSpawnPoints[ RandomInt( file.flagSpawnPoints.len() ) ] + foreach ( entity player in GetPlayerArray() ) + Remote_CallFunction_NonReplay( player, "ServerCallback_FlagSpawnIncoming", spawnpos.x, spawnpos.y, spawnpos.z, Time() + 15 ) + + wait 15.0 + + // create a flag + entity flag = CreateEntity( "item_flag" ) + flag.SetValueForModelKey( CTF_FLAG_MODEL ) + SetTargetName( flag, "krflag" ) + DispatchSpawn( flag ) + flag.SetModel( CTF_FLAG_MODEL ) + flag.SetOrigin( spawnpos + < 0, 0, flag.GetBoundingMaxs().z / 2 > ) // get it out of the ground + flag.SetVelocity( < 0, 0, 1 > ) // make it do gravity again +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_sbox.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_sbox.gnut new file mode 100644 index 00000000..27581aea --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_sbox.gnut @@ -0,0 +1,49 @@ +untyped +global function GamemodeSbox_Init + +struct { + array<entity> spawnpoints +} file + +void function GamemodeSbox_Init() +{ + SetConVarInt( "sv_cheats", 1 ) // cheats on by default + + // cache spawnpoints + //file.spawnpoints = GetEntArrayByClass_Expensive( "info_spawnpoint_human" ) + // todo just use a spawn callback for this rather than weird late cache in spawn + + AddCallback_OnClientConnected( SboxSpawnPlayer ) + AddDeathCallback( "player", SboxRespawnPlayer ) +} + +void function SboxSpawnPlayer( entity player ) +{ + if ( player.GetPlayerSettings() == "spectator" ) // if they haven't spawned yet + player.SetPlayerSettings( "pilot_grapple_male" ) + + if ( file.spawnpoints.len() == 0 ) // have to cache late rather than on init due to spawnpoints not existing in init + file.spawnpoints = GetEntArrayByClass_Expensive( "info_spawnpoint_human" ) + + if ( GetGameState() != eGameState.Playing ) // hacky but can't set this in init either + SetGameState( eGameState.Playing ) + + entity spawnpoint = file.spawnpoints[ RandomInt( file.spawnpoints.len() ) ] + + ScreenFadeFromBlack( player, 0.0, 0.0 ) // HACK before non-classicmp intros are ready, remove the blackscreen we get from waitingforplayers + player.RespawnPlayer( spawnpoint ) + player.GiveWeapon( "mp_weapon_toolgun" ) +} + +void function SboxRespawnPlayer( entity player, var damageInfo ) +{ + thread SboxRespawnPlayerThreaded( player ) +} + +void function SboxRespawnPlayerThreaded( entity player ) +{ + // todo: replace this with real respawn logic when that's ready + + wait 2.5 + SboxSpawnPlayer( player ) +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_tt.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_tt.gnut new file mode 100644 index 00000000..6a53ef87 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_gamemode_tt.gnut @@ -0,0 +1,71 @@ +global function GamemodeTT_Init + +struct { + entity lastPlayerDropped +} file + +void function GamemodeTT_Init() +{ + SetSpawnpointGamemodeOverride( TEAM_DEATHMATCH ) + Riff_ForceTitanAvailability( eTitanAvailability.Never ) + + AddCallback_GameStateEnter( eGameState.Playing, OnEnterPlaying ) + + AddCallback_OnPlayerKilled( OnPlayerKilled ) + AddDeathCallback( "npc_titan", OnTitanKilled ) +} + +void function OnEnterPlaying() +{ + thread DropRandomTitan() +} + +void function DropRandomTitan() +{ + array<entity> players = GetPlayerArray() + + if ( players.len() == 1 ) + file.lastPlayerDropped = null // don't wanna loop forever if only 1 player + + entity titanPlayer + do { + titanPlayer = players[ RandomInt( players.len() ) ] + } while ( titanPlayer == file.lastPlayerDropped ) + + DropTitanForPlayer( titanPlayer, 5.0 ) +} + +void function DropTitanForPlayer( entity player, float delay ) +{ + wait delay + + file.lastPlayerDropped = player + CreateTitanForPlayerAndHotdrop( player, GetTitanReplacementPoint( player, false ) ) +} + +void function AttemptToDropTitanForKill( entity victim, entity attacker, var damageInfo ) +{ + if ( !victim.IsTitan() ) + return + + if ( !attacker.IsPlayer() || victim == attacker ) + thread DropRandomTitan() + else + thread DropTitanForPlayer( attacker, 2.0 ) +} + +void function OnPlayerKilled( entity victim, entity attacker, var damageInfo ) +{ + if ( victim.IsTitan() ) + AttemptToDropTitanForKill( victim, attacker, damageInfo ) + else if ( attacker.IsTitan() ) + { + AddTeamScore( attacker.GetTeam(), 1 ) + } +} + +void function OnTitanKilled( entity victim, var damageInfo ) +{ + if ( IsPetTitan( victim ) ) + AttemptToDropTitanForKill( victim, DamageInfo_GetAttacker( damageInfo ), damageInfo ) +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/_riff_instagib.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_riff_instagib.gnut new file mode 100644 index 00000000..b3868359 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/_riff_instagib.gnut @@ -0,0 +1,65 @@ +global function RiffInstagib_Init + +struct { + table<entity, int> playerWeapons + array<string> instagibWeapons +} file + +void function RiffInstagib_Init() +{ + if ( GetCurrentPlaylistVarInt( "riff_instagib", 0 ) == 0 ) + return + + SetLoadoutGracePeriodEnabled( false ) + SetWeaponDropsEnabled( false ) + + file.instagibWeapons = [ + "mp_weapon_sniper", + "mp_weapon_wingman", + "mp_weapon_defender", + "mp_weapon_arena3", + "mp_weapon_wingman_n", + "mp_weapon_doubletake", + ] + file.instagibWeapons.randomize() + + AddCallback_OnPlayerRespawned( InstagibSetWeapons ) + AddCallback_OnPlayerKilled( InstagibCycleWeaponsForKill ) + AddCallback_OnClientDisconnected( InstagibCleanupClient ) +} + +void function InstagibSetWeapons( entity player ) +{ + if ( !( player in file.playerWeapons ) ) + file.playerWeapons[ player ] <- 0 + + player.SetMaxHealth( 1 ) + InstagibUpdateWeapons( player ) +} + +void function InstagibUpdateWeapons( entity player ) +{ + foreach( entity weapon in player.GetMainWeapons() ) + player.TakeWeaponNow( weapon.GetWeaponClassName() ) + + player.TakeWeaponNow( player.GetOffhandWeapon( OFFHAND_RIGHT ).GetWeaponClassName() ) + if ( !HasOffhandWeapon( player, "mp_weapon_grenade_sonar" ) ) + player.GiveOffhandWeapon( "mp_weapon_grenade_sonar", OFFHAND_RIGHT ) + + player.GiveWeapon( file.instagibWeapons[ file.playerWeapons[ player ] ] ) +} + +void function InstagibCycleWeaponsForKill( entity victim, entity attacker, var damageInfo ) +{ + if ( !victim.IsPlayer() || !attacker.IsPlayer() || victim == attacker ) + return + + file.playerWeapons[ attacker ] = ( file.playerWeapons[ attacker ] + 1 ) % file.instagibWeapons.len() + InstagibUpdateWeapons( attacker ) +} + +void function InstagibCleanupClient( entity player ) +{ + if ( player in file.playerWeapons ) + delete file.playerWeapons[ player ] +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_arena.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_arena.gnut new file mode 100644 index 00000000..632a7efc --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_arena.gnut @@ -0,0 +1,40 @@ +global function ClGameModeArena_Init +global function ServerCallback_CreateMoneyParticles + +struct { + var moneyRui +} file + +void function ClGameModeArena_Init() +{ + AddCallback_OnClientScriptInit( CreateArenaUI ) + + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_fd_intro_easy", TEAM_MILITIA ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_fd_intro_easy", TEAM_IMC ) +} + +void function CreateArenaUI( entity player ) +{ + AddEventNotificationCallback( eEventNotifications.FD_BoughtItem, void function( entity e, var v ) {} ) + + var rui = CreateCockpitRui( $"ui/fd_score_splash.rpak", 500 ) + RuiTrackInt( rui, "pointValue", player, RUI_TRACK_SCRIPT_NETWORK_VAR_INT, GetNetworkedVariableIndex( "FD_money" ) ) + RuiTrackInt( rui, "pointStack", player, RUI_TRACK_SCRIPT_NETWORK_VAR_INT, GetNetworkedVariableIndex( "FD_money256" ) ) + file.moneyRui = rui +} + +void function ServerCallback_CreateMoneyParticles( int playerHandle, int amount ) +{ + // largely taken from cl_gamemode_fd + entity player = GetEntityFromEncodedEHandle( playerHandle ) + + vector randDir2D = < RandomFloatRange( -1, 1 ), 1, 0 > + randDir2D = Normalize( randDir2D ) + + var rui = RuiCreate( $"ui/at_score_popup.rpak", clGlobal.topoFullScreen, RUI_DRAW_HUD, 100 ) + RuiSetInt( rui, "scoreVal", amount ) + RuiSetGameTime( rui, "startTime", Time() ) + RuiSetFloat3( rui, "pos", player.EyePosition() ) + RuiSetFloat2( rui, "driftDir", randDir2D ) + RuiSetBool( rui, "showNormalPoints", false ) +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_fastball.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_fastball.gnut new file mode 100644 index 00000000..80dc548a --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_fastball.gnut @@ -0,0 +1,96 @@ +global function ClGamemodeFastball_Init +global function ServerCallback_FastballUpdatePanelRui +global function ServerCallback_FastballPanelHacked +global function ServerCallback_FastballRespawnPlayer + +struct { + var panelARui + var panelBRui + var panelCRui +} file + +void function ClGamemodeFastball_Init() +{ + ClGameState_RegisterGameStateAsset( $"ui/gamestate_info_lts.rpak" ) + + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "Music_Beacon_14_BTThrowThruFirstCrane", TEAM_MILITIA ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "Music_Beacon_14_BTThrowThruFirstCrane", TEAM_MILITIA ) + + AddCallback_OnClientScriptInit( FastballCreateRui ) +} + +void function FastballCreateRui( entity player ) +{ + file.panelARui = CreateCockpitRui( $"ui/cp_hardpoint_marker.rpak", 200 ) + file.panelBRui = CreateCockpitRui( $"ui/cp_hardpoint_marker.rpak", 200 ) + file.panelCRui = CreateCockpitRui( $"ui/cp_hardpoint_marker.rpak", 200 ) + +} + +void function ServerCallback_FastballUpdatePanelRui( int panelHandle, int id ) +{ + entity panel = GetEntityFromEncodedEHandle( panelHandle ) + var rui + if ( id == 0 ) + rui = file.panelARui + else if ( id == 1 ) + rui = file.panelBRui + else if ( id == 2 ) + rui = file.panelCRui + + RuiSetInt( rui, "hardpointId", id ) + RuiTrackFloat3( rui, "pos", panel, RUI_TRACK_OVERHEAD_FOLLOW ) + RuiSetInt( rui, "viewerTeam", GetLocalClientPlayer().GetTeam() ) + ////RuiTrackInt( rui, "cappingTeam", null, RUI_TRACK_SCRIPT_NETWORK_VAR_GLOBAL_INT, GetNetworkedVariableIndex( "panel" + id + "progress" ) ) + RuiTrackInt( rui, "hardpointTeamRelation", panel, RUI_TRACK_TEAM_RELATION_VIEWPLAYER ) + + RuiSetBool( rui, "isVisible", true ) +} + +void function ServerCallback_FastballPanelHacked( int panelHandle, int id, int capturingPlayerHandle ) +{ + ServerCallback_FastballUpdatePanelRui( panelHandle, id ) // may not be necessary, just wanna ensure this is always right + + entity panel = GetEntityFromEncodedEHandle( panelHandle ) + entity capturingPlayer = GetEntityFromEncodedEHandle( capturingPlayerHandle ) + + if ( capturingPlayer == GetLocalViewPlayer() ) + return + + string panelIdString + if ( id == 0 ) + panelIdString = "A" + if ( id == 1 ) + panelIdString = "B" + else if ( id == 2 ) + panelIdString = "C" + + AnnouncementData announcement = Announcement_Create( Localize( "#FASTBALL_PANEL_CAPTURED", capturingPlayer.GetPlayerName(), panelIdString ) ) + + if ( capturingPlayer.GetTeam() == GetLocalViewPlayer().GetTeam() ) + Announcement_SetTitleColor( announcement, < 0, 0, 1 > ) + else + Announcement_SetTitleColor( announcement, < 1, 0, 0 > ) + + Announcement_SetPurge( announcement, true ) + Announcement_SetPriority( announcement, 200 ) //Be higher priority than Titanfall ready indicator etc + Announcement_SetSoundAlias( announcement, SFX_HUD_ANNOUNCE_QUICK ) + Announcement_SetStyle( announcement, ANNOUNCEMENT_STYLE_QUICK ) + AnnouncementFromClass( GetLocalViewPlayer(), announcement ) +} + +void function ServerCallback_FastballRespawnPlayer() +{ + thread FastballRespawnPlayerEffects_Threaded() +} + +void function FastballRespawnPlayerEffects_Threaded() +{ + // sometimes this seems to get called before the player has respawned clientside, so we just wait until the client thinks they're alive + entity player = GetLocalViewPlayer() + + while ( !IsAlive( player ) ) + WaitFrame() + + StartParticleEffectOnEntity( player.GetCockpit(), GetParticleSystemIndex( $"P_pod_screen_lasers_OUT" ), FX_PATTACH_ABSORIGIN_FOLLOW, -1 ) +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_gg.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_gg.gnut new file mode 100644 index 00000000..de8a3449 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_gg.gnut @@ -0,0 +1,26 @@ +global function ClGamemodeGG_Init + +void function ClGamemodeGG_Init() +{ + // add ffa gamestate asset + ClGameState_RegisterGameStateAsset( $"ui/gamestate_info_ffa.rpak" ) + + // add music for mode, this is copied directly from the ffa/fra music registered in cl_music.gnut + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_freeagents_intro", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_freeagents_intro", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_freeagents_outro_win", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_freeagents_outro_win", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_freeagents_outro_lose", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_freeagents_outro_lose", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_freeagents_outro_lose", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_freeagents_outro_lose", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_THREE_MINUTE, "music_mp_freeagents_almostdone", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_THREE_MINUTE, "music_mp_freeagents_almostdone", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_freeagents_lastminute", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_freeagents_lastminute", TEAM_MILITIA ) +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_inf.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_inf.gnut new file mode 100644 index 00000000..56763bd4 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_inf.gnut @@ -0,0 +1,75 @@ +global function ClGamemodeInfection_Init +global function ServerCallback_YouAreInfected +global function ServerCallback_AnnounceFirstInfected +global function ServerCallback_AnnounceLastSurvivor + +void function ClGamemodeInfection_Init() +{ + //ClGameState_RegisterGameStateAsset( $"ui/gamestate_info_ffa.rpak" ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_beacon_8a_jumpingsuccess", TEAM_MILITIA ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_beacon_8a_jumpingsuccess", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.GAMEMODE_1, "Music_Beacon_24_BTLob", TEAM_MILITIA ) + RegisterLevelMusicForTeam( eMusicPieceID.GAMEMODE_1, "Music_Beacon_24_BTLob", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_titanwar_lastminute", TEAM_MILITIA ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_titanwar_lastminute", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_freeagents_outro_lose", TEAM_MILITIA ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_freeagents_outro_lose", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_freeagents_outro_lose", TEAM_MILITIA ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_freeagents_outro_lose", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_freeagents_outro_lose", TEAM_MILITIA ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_freeagents_outro_lose", TEAM_IMC ) +} + +void function ServerCallback_YouAreInfected() +{ + // heavily based on mfd code + entity localPlayer = GetLocalViewPlayer() + + StartParticleEffectOnEntity( localPlayer.GetCockpit(), GetParticleSystemIndex( $"P_MFD" ), FX_PATTACH_ABSORIGIN_FOLLOW, -1 ) + EmitSoundOnEntity( localPlayer, "UI_InGame_MarkedForDeath_PlayerMarked" ) + HideEventNotification() + AnnouncementData announcement = Announcement_Create( "#INFECTION_YOU_ARE_INFECTED" ) + Announcement_SetSubText( announcement, "#INFECTION_KILL_SURVIVORS" ) + Announcement_SetTitleColor( announcement, <1,0,0> ) + Announcement_SetPurge( announcement, true ) + Announcement_SetPriority( announcement, 200 ) //Be higher priority than Titanfall ready indicator etc + Announcement_SetSoundAlias( announcement, SFX_HUD_ANNOUNCE_QUICK ) + Announcement_SetStyle( announcement, ANNOUNCEMENT_STYLE_QUICK ) + AnnouncementFromClass( localPlayer, announcement ) +} + +void function ServerCallback_AnnounceFirstInfected( int survivorEHandle ) +{ + entity player = GetEntityFromEncodedEHandle( survivorEHandle ) + + AnnouncementData announcement = Announcement_Create( Localize( "#INFECTION_FIRST_INFECTED", player.GetPlayerName() ) ) + //Announcement_SetSubText( announcement, "#INFECTION_KILL_LAST_SURVIVOR" ) + Announcement_SetTitleColor( announcement, <1,0,0> ) + Announcement_SetPurge( announcement, true ) + Announcement_SetPriority( announcement, 200 ) //Be higher priority than Titanfall ready indicator etc + Announcement_SetSoundAlias( announcement, SFX_HUD_ANNOUNCE_QUICK ) + Announcement_SetStyle( announcement, ANNOUNCEMENT_STYLE_QUICK ) + AnnouncementFromClass( GetLocalViewPlayer(), announcement ) +} + +void function ServerCallback_AnnounceLastSurvivor( int survivorEHandle ) +{ + entity player = GetEntityFromEncodedEHandle( survivorEHandle ) + + string announcementString = Localize( "#INFECTION_LAST_SURVIVOR", player.GetPlayerName() ) + string announcementSubString = "#INFECTION_KILL_LAST_SURVIVOR" + if ( player == GetLocalViewPlayer() ) + { + announcementString = "#INFECTION_YOU_ARE_LAST_SURVIVOR" + announcementSubString = "#INFECTION_SURVIVE_LAST_SURVIVOR" + } + + AnnouncementData announcement = Announcement_Create( announcementString ) + Announcement_SetSubText( announcement, announcementSubString ) + Announcement_SetTitleColor( announcement, <1,0,0> ) + Announcement_SetPurge( announcement, true ) + Announcement_SetPriority( announcement, 200 ) //Be higher priority than Titanfall ready indicator etc + Announcement_SetSoundAlias( announcement, SFX_HUD_ANNOUNCE_QUICK ) + Announcement_SetStyle( announcement, ANNOUNCEMENT_STYLE_QUICK ) + AnnouncementFromClass( GetLocalViewPlayer(), announcement ) +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_kr.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_kr.gnut new file mode 100644 index 00000000..269057c7 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_kr.gnut @@ -0,0 +1,241 @@ +global function ClGamemodeKR_Init + +global function ShowTimeGainOnKill +global function ServerCallback_FlagSpawnIncoming +global function ServerCallback_NewKillRacer +global function ServerCallback_EndKillrace + +struct { + var currentTimeRui + var currentTimeAdditionRui + var flagRui + var flagIncomingRui + var killRacerRui + + bool isCurrentlyInRace = false + float currentTimeAmount + float currentTimeLastAdditionTime + float currentTimeAdditionCombined +} file + +void function ClGamemodeKR_Init() +{ + // add ffa gamestate asset + ClGameState_RegisterGameStateAsset( $"ui/gamestate_info_ffa.rpak" ) + + // add music for mode, this is copied directly from the ffa/fra music registered in cl_music.gnut + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_freeagents_intro", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_freeagents_intro", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_freeagents_outro_win", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_freeagents_outro_win", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_freeagents_outro_lose", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_freeagents_outro_lose", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_freeagents_outro_lose", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_freeagents_outro_lose", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_THREE_MINUTE, "music_mp_freeagents_almostdone", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_THREE_MINUTE, "music_mp_freeagents_almostdone", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_freeagents_lastminute", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_freeagents_lastminute", TEAM_MILITIA ) + + AddCallback_OnClientScriptInit( CreateKRUI ) + AddCreateCallback( "item_flag", OnFlagCreated ) + AddDestroyCallback( "item_flag", OnFlagDestroyed ) +} + +void function CreateKRUI( entity player ) +{ + file.currentTimeRui = CreateCockpitRui( $"ui/titan_protocol_text_center.rpak", 200 ) + RuiSetInt( file.currentTimeRui, "lineNum", 1 ) + RuiSetGameTime( file.currentTimeRui, "startTime", 0.0 ) + UpdateCurrentTimeAmount() + + file.flagRui = CreateCockpitRui( $"ui/speedball_flag_marker.rpak", 200 ) + RuiSetBool( file.flagRui, "playerIsCarrying", false ) + RuiSetBool( file.flagRui, "isCarried", false ) + + file.killRacerRui = CreateCockpitRui( $"ui/mfd_target_marker.rpak", 200 ) + RuiSetBool( file.killRacerRui, "isVisible", false ) + RuiSetImage( file.killRacerRui, "markedIcon", $"rui/hud/gametype_icons/mfd/mfd_enemy" ) + RuiSetBool( file.killRacerRui, "isMarked", true ) +} + +void function OnFlagCreated( entity flag ) +{ + if ( IsValid( file.flagIncomingRui ) ) + RuiDestroy( file.flagIncomingRui ) + + RuiSetBool( file.flagRui, "isVisible", true ) + RuiTrackFloat3( file.flagRui, "pos", flag, RUI_TRACK_OVERHEAD_FOLLOW ) +} + +void function OnFlagDestroyed( entity flag ) +{ + RuiSetBool( file.flagRui, "isVisible", false ) +} + +void function ShowTimeGainOnKill( entity player, float oldVal, float newVal, bool actuallyChanged ) +{ + if ( file.isCurrentlyInRace || player != GetLocalViewPlayer() || !actuallyChanged ) + return + + if ( newVal > oldVal ) // time increase: likely given on kill + { + float amount = newVal - oldVal + + // show a combined number on the addition rui if last addition was recent enough + float additionShowAmount = amount + file.currentTimeAdditionCombined += amount + if ( Time() - file.currentTimeLastAdditionTime < 1.25 ) + amount = file.currentTimeAdditionCombined + else + { + file.currentTimeAdditionRui = CreateCockpitRui( $"ui/titan_protocol_text_center.rpak", 200 ) + RuiSetInt( file.currentTimeAdditionRui, "lineNum", 2 ) + file.currentTimeAdditionCombined = amount + } + + RuiSetString( file.currentTimeAdditionRui, "displayString", "+" + amount + "s 00ms " ) // formatted so that it lines up with other rui + RuiSetGameTime( file.currentTimeAdditionRui, "startTime", Time() ) + RuiSetGameTime( file.currentTimeAdditionRui, "endTime", Time() + 1.5 ) + + file.currentTimeLastAdditionTime = Time() + + thread UpdateFullTimeAmountAfterAdditionDone( file.currentTimeLastAdditionTime ) + } + else // time decrease either a reset or + UpdateCurrentTimeAmount() +} + +void function UpdateFullTimeAmountAfterAdditionDone( float previousAdditionTime ) +{ + wait 1.25 + + if ( previousAdditionTime == file.currentTimeLastAdditionTime ) // if not, there's been another addition since this was last updated and we'll wait for that instead + UpdateCurrentTimeAmount() +} + +void function UpdateCurrentTimeAmount( float overrideTime = -1 ) +{ + if ( overrideTime == -1 ) + file.currentTimeAmount = GetLocalViewPlayer().GetPlayerNetTime( "killRaceTime" ) + else + file.currentTimeAmount = overrideTime + + string currentTimeString + int seconds = file.currentTimeAmount.tointeger() + + string secondsString = seconds.tostring() + if ( secondsString.len() < 2 ) // pad to 2 chars + secondsString = "0" + secondsString + currentTimeString += secondsString + "s " + + string msString = ( ( file.currentTimeAmount - file.currentTimeAmount.tointeger() ) * 100 ).tostring() + if ( msString.len() < 2 ) // pad to 2 chars + msString = "0" + msString + currentTimeString += msString.slice( 0, 2 ) + "ms " + + RuiSetString( file.currentTimeRui, "displayString", currentTimeString ) + RuiSetGameTime( file.currentTimeRui, "endTime", Time() + 99999.0 ) // arbitrarily large number so this doesn't disappear +} + +void function ServerCallback_FlagSpawnIncoming( float x, float y, float z , float spawnTime ) +{ + print( "flagspawn: < " + x + ", " + y + ", " + z + " > in " + ( spawnTime - Time() ) + " seconds" ) + + AnnouncementData announcement = Announcement_Create( Localize( "#KR_FLAG_INCOMING", spawnTime.tostring() ) ) + Announcement_SetSubText( announcement, "#KR_COLLECT_FLAG" ) + Announcement_SetTitleColor( announcement, < 0, 0, 1 > ) + Announcement_SetPurge( announcement, true ) + Announcement_SetPriority( announcement, 200 ) + Announcement_SetSoundAlias( announcement, SFX_HUD_ANNOUNCE_QUICK ) + Announcement_SetStyle( announcement, ANNOUNCEMENT_STYLE_QUICK ) + AnnouncementFromClass( GetLocalViewPlayer(), announcement ) + + RuiSetFloat3( file.flagRui, "pos", < x, y, z > ) + RuiSetBool( file.flagRui, "isVisible", true ) + + file.flagIncomingRui = RuiCreate( $"ui/titanfall_timer.rpak", clGlobal.topoFullScreen, RUI_DRAW_HUD, 0 ) + RuiTrackFloat3( file.flagIncomingRui, "playerPos", GetLocalViewPlayer(), RUI_TRACK_ABSORIGIN_FOLLOW ) + RuiSetFloat3( file.flagIncomingRui, "pos", < x, y, z > + < 0, 0, 48 > ) + RuiSetGameTime( file.flagIncomingRui, "impactTime", spawnTime ) +} + +void function ServerCallback_NewKillRacer( int playerHandle, float endTime ) +{ + entity player = GetEntityFromEncodedEHandle( playerHandle ) + + string announcementMessage = Localize( "#KR_NEW_RACER", player.GetPlayerName() ) + string announcementSubMessage + if ( player == GetLocalViewPlayer() ) + { + file.isCurrentlyInRace = true + thread LerpTimeDuringRace( endTime ) + + StartParticleEffectOnEntity( player.GetCockpit(), GetParticleSystemIndex( $"P_MFD" ), FX_PATTACH_ABSORIGIN_FOLLOW, -1 ) + EmitSoundOnEntity( player, "UI_InGame_MarkedForDeath_PlayerMarked" ) + HideEventNotification() + + announcementMessage = "#KR_YOU_ARE_NEW_RACER" + announcementSubMessage = "#KR_YOU_SET_NEW_RECORD" + } + else + { + // mark the player + RuiTrackFloat3( file.killRacerRui, "pos", player, RUI_TRACK_OVERHEAD_FOLLOW ) + RuiSetBool( file.killRacerRui, "isVisible", true ) + } + + AnnouncementData announcement = Announcement_Create( announcementMessage ) + Announcement_SetSubText( announcement, announcementSubMessage ) + Announcement_SetTitleColor( announcement, < 1, 0, 0 > ) + Announcement_SetPurge( announcement, true ) + Announcement_SetPriority( announcement, 200 ) + Announcement_SetSoundAlias( announcement, SFX_HUD_ANNOUNCE_QUICK ) + Announcement_SetStyle( announcement, ANNOUNCEMENT_STYLE_QUICK ) + AnnouncementFromClass( GetLocalViewPlayer(), announcement ) +} + +void function LerpTimeDuringRace( float endTime ) +{ + while ( Time() < endTime ) + { + // manually update this here so we can get more frequent updates than what we'd get with networked vars + UpdateCurrentTimeAmount( endTime - Time() ) + WaitFrame() + } + + UpdateCurrentTimeAmount( 0.0 ) + file.isCurrentlyInRace = false +} + +void function ServerCallback_EndKillrace( int playerHandle, int score ) +{ + entity player = GetEntityFromEncodedEHandle( playerHandle ) + + vector colour = < 0, 0, 1 > + string announcementMessage = Localize( "#KR_ENEMY_KILLRACE_OVER", player.GetPlayerName() ) + string announcementSubMessage + if ( player == GetLocalViewPlayer() ) + { + StartParticleEffectOnEntity( GetLocalViewPlayer().GetCockpit(), GetParticleSystemIndex( $"P_MFD_unmark" ), FX_PATTACH_ABSORIGIN_FOLLOW, -1 ) + colour = < 1, 0, 0 > + announcementMessage = "#KR_YOUR_KILLRACE_OVER" + announcementSubMessage = Localize( "#KR_YOUR_KILLRACE_SCORE", score ) + } + + AnnouncementData announcement = Announcement_Create( announcementMessage ) + Announcement_SetSubText( announcement, announcementSubMessage ) + Announcement_SetTitleColor( announcement, colour ) + Announcement_SetPurge( announcement, true ) + Announcement_SetPriority( announcement, 200 ) + Announcement_SetSoundAlias( announcement, SFX_HUD_ANNOUNCE_QUICK ) + Announcement_SetStyle( announcement, ANNOUNCEMENT_STYLE_QUICK ) + AnnouncementFromClass( GetLocalViewPlayer(), announcement ) + + RuiSetBool( file.killRacerRui, "isVisible", false ) +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_tt.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_tt.gnut new file mode 100644 index 00000000..b73ed958 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/cl_gamemode_tt.gnut @@ -0,0 +1,26 @@ +global function ClGamemodeTT_Init + +void function ClGamemodeTT_Init() +{ + // register gamestate asset, this is default so not necessary but doing it anyway + ClGameState_RegisterGameStateAsset( $"ui/gamestate_info_ps.rpak" ) + + // add music for mode, this is copied directly from the attrition/tdm music registered in cl_music.gnut + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_pilothunt_intro_flyin", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_pilothunt_intro_flyin", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_pilothunt_epilogue_win", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_pilothunt_epilogue_win", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_pilothunt_epilogue_win", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_pilothunt_epilogue_win", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_pilothunt_epilogue_lose", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_pilothunt_epilogue_lose", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.GAMEMODE_1, "music_mp_pilothunt_almostdone", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.GAMEMODE_1, "music_mp_pilothunt_almostdone", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_pilothunt_lastminute", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_pilothunt_lastminute", TEAM_MILITIA ) +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_arena_loadouts.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_arena_loadouts.gnut new file mode 100644 index 00000000..ae0fa7d6 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_arena_loadouts.gnut @@ -0,0 +1,98 @@ +// the purpose of this script is basically just to provide methods that are used in modified burnmeter and boost_store scripts to get custom items to work + +global function InitialiseArenaLoadouts +global function PopulateArenaLoadouts +global function GetArenaLoadoutForRoundCount +global function GetArenaLoadoutItemAsBurnReward +global function GetCashBoostForRoundCount + +#if SERVER + global function GivePlayerArenaLoadoutItem +#endif + +const int ABILITY_ROUND = 2 +const int CRATE_ROUND = 4 +const int TITAN_ROUND = 7 + +struct { + array<BoostStoreData> tier0Weapons + array<BoostStoreData> tier1Weapons + array<BoostStoreData> antiTitanWeapons + + BoostStoreData droppodRespawn + BoostStoreData titanBattery + BoostStoreData titanfall +} file + +void function InitialiseArenaLoadouts() +{ + BoostStoreData g2Data = { itemRef="mp_weapon_g2", modesAllowed="arena", cost=75, ... } + file.tier0Weapons.append( g2Data ) + + BoostStoreData flatlineData = { itemRef="mp_weapon_vinson", modesAllowed="arena", cost=125, ... } + file.tier0Weapons.append( flatlineData ) + + BoostStoreData wingmanData = { itemRef="mp_weapon_wingman", modesAllowed="arena", cost=50, ... } + file.tier0Weapons.append( wingmanData ) + + BoostStoreData respawnData = { itemRef="droppodRespawn", modesAllowed="arena", cost=50, ... } + file.tier0Weapons.append( respawnData ) + + #if SERVER + SetBoostPurchaseCallback( GivePlayerArenaLoadoutItem ) + #endif +} + +array<BoostStoreData> function PopulateArenaLoadouts() +{ + return GetArenaLoadoutForRoundCount( 0 ) +} + +array<BoostStoreData> function GetArenaLoadoutForRoundCount( int round ) +{ + return file.tier0Weapons +} + +BurnReward function GetArenaLoadoutItemAsBurnReward( string itemRef ) +{ + BurnReward reward + reward.ref = itemRef + reward.localizedName = itemRef + + return reward +} + +bool function ArenaLoadoutItemIsWeapon( string item ) +{ + if ( item.find( "mp_weapon" ) == 0 ) + return true + + return false +} + +int function GetCashBoostForRoundCount( int round ) +{ + if ( round == 0 ) + return 150 + + if ( round < 4 ) + return 250 + + return 350 +} + +#if SERVER + void function GivePlayerArenaLoadoutItem( entity player, BoostStoreData item ) + { + if ( ArenaLoadoutItemIsWeapon( item.itemRef ) ) + { + array<string> mods + // apply mods + //if ( item.itemRef = "mp_weapon_wingman" ) + + player.GiveWeapon( item.itemRef, mods ) + player.SetActiveWeaponByName( item.itemRef ) + return + } + } +#endif
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_arena.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_arena.gnut new file mode 100644 index 00000000..b634f1d3 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_arena.gnut @@ -0,0 +1,58 @@ +global function Sh_GamemodeArena_Init + +global const string GAMEMODE_ARENA = "arena" + +void function Sh_GamemodeArena_Init() +{ + // create custom gamemode + AddCallback_OnCustomGamemodesInit( CreateGamemodeArena ) + AddCallback_OnRegisteringCustomNetworkVars( ArenaRegisterNetworkVars ) +} + +void function CreateGamemodeArena() +{ + GameMode_Create( GAMEMODE_ARENA ) + GameMode_SetName( GAMEMODE_ARENA, "#GAMEMODE_arena" ) + GameMode_SetDesc( GAMEMODE_ARENA, "#PL_arena_desc" ) + GameMode_SetGameModeAnnouncement( GAMEMODE_ARENA, "gnrc_modeDesc" ) + GameMode_SetDefaultTimeLimits( GAMEMODE_ARENA, 5, 0.0 ) + GameMode_AddScoreboardColumnData( GAMEMODE_ARENA, "#SCOREBOARD_PILOT_KILLS", PGS_PILOT_KILLS, 2 ) + GameMode_SetColor( GAMEMODE_ARENA, [147, 204, 57, 255] ) + + #if SERVER + GameMode_AddServerInit( GAMEMODE_ARENA, GameModeArena_Init ) + GameMode_SetPilotSpawnpointsRatingFunc( GAMEMODE_ARENA, RateSpawnpoints_Generic ) + GameMode_SetTitanSpawnpointsRatingFunc( GAMEMODE_ARENA, RateSpawnpoints_Generic ) + #elseif CLIENT + GameMode_AddClientInit( GAMEMODE_ARENA, ClGameModeArena_Init ) + #endif + #if !UI + GameMode_SetScoreCompareFunc( GAMEMODE_ARENA, CompareAssaultScore ) + #endif +} + +void function ArenaRegisterNetworkVars() +{ + if ( GAMETYPE != GAMEMODE_ARENA ) + return + + // boost store stuff + Remote_RegisterFunction( "ServerCallback_OpenBoostStore" ) + Remote_RegisterFunction( "ServerCallback_UpdateMoney" ) + Remote_RegisterFunction( "ServerCallback_UpdateTeamReserve" ) + Remote_RegisterFunction( "ServerCallback_UpdatePlayerHasBattery" ) + Remote_RegisterFunction( "ServerCallback_UpdateAmpedWeaponState" ) + Remote_RegisterFunction( "ServerCallback_BoostStoreTitanHint" ) + Remote_RegisterFunction( "ServerCallback_UpdateTurretCount" ) + + RegisterNetworkedVariable( "boostStoreOpen", SNDC_GLOBAL, SNVT_BOOL, false ) + RegisterNetworkedVariable( "FD_money", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 ) + RegisterNetworkedVariable( "FD_money256", SNDC_PLAYER_GLOBAL, SNVT_UNSIGNED_INT, 0 ) + + // these are required to prevent crashes in fd code that's called from menu_boost_store + RegisterNetworkedVariable( "numSuperRodeoGrenades", SNDC_PLAYER_GLOBAL, SNVT_INT, 0 ) + RegisterNetworkedVariable( "FD_waveActive", SNDC_GLOBAL, SNVT_BOOL, false ) + + // arena-exclusive stuff + Remote_RegisterFunction( "ServerCallback_CreateMoneyParticles" ) +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_bomb.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_bomb.gnut new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_bomb.gnut diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_ctf_comp.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_ctf_comp.gnut new file mode 100644 index 00000000..1a1ce645 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_ctf_comp.gnut @@ -0,0 +1,114 @@ +global function ShGamemodeCTFComp_Init + +global const string GAMEMODE_CTF_COMP = "ctf_comp" + +void function ShGamemodeCTFComp_Init() +{ + // create custom gamemode + AddCallback_OnCustomGamemodesInit( CreateGamemodeCTFComp ) + AddCallback_OnRegisteringCustomNetworkVars( CTFCompRegisterNetworkVars ) +} + +void function CreateGamemodeCTFComp() +{ + GameMode_Create( GAMEMODE_CTF_COMP ) + GameMode_SetName( GAMEMODE_CTF_COMP, "#GAMEMODE_ctf_comp" ) + GameMode_SetGameModeAnnouncement( GAMEMODE_CTF_COMP, "ctf_modeDesc" ) + GameMode_SetDesc( GAMEMODE_CTF_COMP, "#PL_capture_the_flag_hint" ) + GameMode_SetIcon( GAMEMODE_CTF_COMP, $"ui/menu/playlist/ctf" ) + GameMode_SetSuddenDeath( GAMEMODE_CTF_COMP, true ) + GameMode_SetDefaultScoreLimits( GAMEMODE_CTF_COMP, 0, 5 ) + GameMode_SetDefaultTimeLimits( GAMEMODE_CTF_COMP, 0, 3.0 ) + GameMode_AddScoreboardColumnData( GAMEMODE_CTF_COMP, "#SCOREBOARD_CAPTURES", PGS_ASSAULT_SCORE, 2 ) + GameMode_AddScoreboardColumnData( GAMEMODE_CTF_COMP, "#SCOREBOARD_RETURNS", PGS_DEFENSE_SCORE, 2 ) + GameMode_AddScoreboardColumnData( GAMEMODE_CTF_COMP, "#SCOREBOARD_KILLS", PGS_KILLS, 2 ) + GameMode_AddScoreboardColumnData( GAMEMODE_CTF_COMP, "#SCOREBOARD_TITAN_DAMAGE", PGS_DISTANCE_SCORE, 6 ) // gotta use a weird pgs here since we're running out of them lol + GameMode_SetColor( GAMEMODE_CTF_COMP, [61, 117, 193, 255] ) + + AddPrivateMatchMode( GAMEMODE_CTF_COMP ) // add to private lobby modes + + // this gamemode is literally just normal ctf + a few extra settings + // as such we do all the inits in this file, not enough logic to be worth splitting it up + + #if SERVER + GameMode_AddServerInit( GAMEMODE_CTF_COMP, InitCTFCompSpecificSettings ) + GameMode_AddServerInit( GAMEMODE_CTF_COMP, CaptureTheFlag_Init ) + GameMode_SetPilotSpawnpointsRatingFunc( GAMEMODE_CTF_COMP, RateSpawnpoints_CTF ) + GameMode_SetTitanSpawnpointsRatingFunc( GAMEMODE_CTF_COMP, RateSpawnpoints_CTF ) + #elseif CLIENT + GameMode_AddClientInit( GAMEMODE_CTF_COMP, InitCTFCompSpecificSettings ) + GameMode_AddClientInit( GAMEMODE_CTF_COMP, ClCaptureTheFlag_Init ) + #endif + #if !UI + GameMode_SetScoreCompareFunc( GAMEMODE_CTF_COMP, CompareCTF ) + GameMode_AddSharedInit( GAMEMODE_CTF_COMP, GamemodeCtfDialogue_Init ) + GameMode_AddSharedInit( GAMEMODE_CTF_COMP, CaptureTheFlagShared_Init ) + #endif +} + +void function CTFCompRegisterNetworkVars() +{ + if ( GAMETYPE != GAMEMODE_CTF_COMP ) + return + + // copied from the vanilla ctf remote functions + RegisterNetworkedVariable( "imcFlag", SNDC_GLOBAL, SNVT_ENTITY ) + RegisterNetworkedVariable( "milFlag", SNDC_GLOBAL, SNVT_ENTITY ) + + RegisterNetworkedVariable( "imcFlagHome", SNDC_GLOBAL, SNVT_ENTITY ) + RegisterNetworkedVariable( "milFlagHome", SNDC_GLOBAL, SNVT_ENTITY ) + + RegisterNetworkedVariable( "imcFlagState", SNDC_GLOBAL, SNVT_INT, 0 ) + RegisterNetworkedVariable( "milFlagState", SNDC_GLOBAL, SNVT_INT, 0 ) + + RegisterNetworkedVariable( "flagReturnProgress", SNDC_GLOBAL, SNVT_FLOAT_RANGE_OVER_TIME, 0.0, 0.0, 1.0 ) + RegisterNetworkedVariable( "returningFlag", SNDC_PLAYER_EXCLUSIVE, SNVT_BOOL, false ) + + Remote_RegisterFunction( "ServerCallback_CTF_PlayMatchNearEndMusic" ) + Remote_RegisterFunction( "ServerCallback_CTF_StartReturnFlagProgressBar" ) + Remote_RegisterFunction( "ServerCallback_CTF_StopReturnFlagProgressBar" ) + + #if CLIENT + CLCaptureTheFlag_RegisterNetworkFunctions() + #endif +} + +void function InitCTFCompSpecificSettings() +{ + #if SERVER + SetShouldUsePickLoadoutScreen( true ) + TrackTitanDamageInPlayerGameStat( PGS_DISTANCE_SCORE ) + SetSpawnpointGamemodeOverride( CAPTURE_THE_FLAG ) + TeamTitanSelectMenu_Init() + #elseif CLIENT + ClTeamTitanSelectMenu_Init() + + // gotta register the music here because this is done hardcoded to ctf in cl_music + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_ctf_intro_flyin", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_ctf_intro_flyin", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_ctf_epilogue_win", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_ctf_epilogue_win", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_ctf_halftime_losing", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_ctf_halftime_losing", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_SUDDEN_DEATH, "music_mp_ctf_draw", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_SUDDEN_DEATH, "music_mp_ctf_draw", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_ctf_epilogue_lose", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_ctf_epilogue_lose", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.ROUND_BASED_GAME_WON, "music_mp_ctf_halftime_winning", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.ROUND_BASED_GAME_WON, "music_mp_ctf_halftime_winning", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.ROUND_BASED_GAME_LOST, "music_mp_ctf_halftime_losing", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.ROUND_BASED_GAME_LOST, "music_mp_ctf_halftime_losing", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.GAMEMODE_1, "music_mp_ctf_flag_4", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.GAMEMODE_1, "music_mp_ctf_flag_4", TEAM_MILITIA ) + + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_ctf_lastminute", TEAM_IMC ) + RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_ctf_lastminute", TEAM_MILITIA ) + #endif +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_fastball.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_fastball.gnut new file mode 100644 index 00000000..2462d537 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_fastball.gnut @@ -0,0 +1,47 @@ +global function Sh_GamemodeFastball_Init + +global const string GAMEMODE_FASTBALL = "fastball" + +void function Sh_GamemodeFastball_Init() +{ + // create custom gamemode + AddCallback_OnCustomGamemodesInit( CreateGamemodeFastball ) + AddCallback_OnRegisteringCustomNetworkVars( FastballRegisterNetworkVars ) +} + +void function CreateGamemodeFastball() +{ + GameMode_Create( GAMEMODE_FASTBALL ) + GameMode_SetName( GAMEMODE_FASTBALL, "#GAMEMODE_FASTBALL" ) + GameMode_SetDesc( GAMEMODE_FASTBALL, "#PL_fastball_desc" ) + GameMode_SetGameModeAnnouncement( GAMEMODE_FASTBALL, "grnc_modeDesc" ) + GameMode_SetDefaultTimeLimits( GAMEMODE_FASTBALL, 1, 0 ) + GameMode_SetDefaultScoreLimits( GAMEMODE_FASTBALL, 5, 0 ) + GameMode_AddScoreboardColumnData( GAMEMODE_FASTBALL, "#SCOREBOARD_FASTBALL_HACKS", PGS_ASSAULT_SCORE, 2 ) + GameMode_AddScoreboardColumnData( GAMEMODE_FASTBALL, "#SCOREBOARD_PILOT_KILLS", PGS_PILOT_KILLS, 2 ) + GameMode_AddScoreboardColumnData( GAMEMODE_FASTBALL, "#SCOREBOARD_DEATHS", PGS_DEATHS, 2 ) + GameMode_SetColor( GAMEMODE_FASTBALL, [147, 204, 57, 255] ) + + AddPrivateMatchMode( GAMEMODE_FASTBALL ) // add to private lobby modes + + #if SERVER + GameMode_AddServerInit( GAMEMODE_FASTBALL, GamemodeFastball_Init ) + GameMode_SetPilotSpawnpointsRatingFunc( GAMEMODE_FASTBALL, RateSpawnpoints_Generic ) + GameMode_SetTitanSpawnpointsRatingFunc( GAMEMODE_FASTBALL, RateSpawnpoints_Generic ) + #elseif CLIENT + GameMode_AddClientInit( GAMEMODE_FASTBALL, ClGamemodeFastball_Init ) + #endif + #if !UI + GameMode_SetScoreCompareFunc( GAMEMODE_FASTBALL, CompareAssaultScore ) + #endif +} + +void function FastballRegisterNetworkVars() +{ + if ( GAMETYPE != GAMEMODE_FASTBALL ) + return + + Remote_RegisterFunction( "ServerCallback_FastballUpdatePanelRui" ) + Remote_RegisterFunction( "ServerCallback_FastballPanelHacked" ) + Remote_RegisterFunction( "ServerCallback_FastballRespawnPlayer" ) +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_fw_custom.nut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_fw_custom.nut new file mode 100644 index 00000000..8c6e3f63 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_fw_custom.nut @@ -0,0 +1,73 @@ +// this script only exists to create the fw gamemode +// all client/shared gamelogic is still done in the gamemode's respective client and shared scripts +// these scripts are shipped with the game's official build so no need to recreate these +// their paths are gamemodes/cl_gamemode_fw.nut and gamemodes/sh_gamemode_fw.nut, respectively + +global function SHCreateGamemodeFW_Init + +void function SHCreateGamemodeFW_Init() +{ + AddCallback_OnCustomGamemodesInit( CreateGamemodeFW ) + AddCallback_OnRegisteringCustomNetworkVars( FWOnRegisteringNetworkVars ) +} + +void function CreateGamemodeFW() +{ + //entity e = CreateEntity("npc_turret_mega"); SetAISettingsWrapper( e, "npc_turret_mega_fortwar" ); e.SetOrigin(GetPlayerArray()[0].GetOrigin()); SetTeam(e,3); DispatchSpawn(e) + + // we have to manually add the client/shared scripts to scripts.rson atm so we need to prevent compile errors when they aren't included + // best way to do this is to just ignore this whole block for now and wait until we don't have to add them manually + + GameMode_Create( FORT_WAR ) + GameMode_SetName( FORT_WAR, "#GAMEMODE_fw" ) + GameMode_SetDesc( FORT_WAR, "#PL_fw_desc" ) + GameMode_SetGameModeAnnouncement( FORT_WAR, "ffa_modeDesc" ) // fw lines are unfortunately not registered to faction dialogue + + #if SERVER + //GameMode_AddServerInit( FORT_WAR, GamemodeFW_Init ) // doesn't exist yet lol + #elseif CLIENT + GameMode_AddClientInit( FORT_WAR, CLGamemodeFW_Init ) + #endif + #if !UI + GameMode_AddSharedInit( FORT_WAR, SHGamemodeFW_Init ) + #endif +} + +void function FWOnRegisteringNetworkVars() +{ + if ( GAMETYPE != FORT_WAR ) + return + + RegisterNetworkedVariable( "turretSite1", SNDC_GLOBAL, SNVT_ENTITY ) + RegisterNetworkedVariable( "turretSite2", SNDC_GLOBAL, SNVT_ENTITY ) + RegisterNetworkedVariable( "turretSite3", SNDC_GLOBAL, SNVT_ENTITY ) + RegisterNetworkedVariable( "turretSite4", SNDC_GLOBAL, SNVT_ENTITY ) + RegisterNetworkedVariable( "turretSite5", SNDC_GLOBAL, SNVT_ENTITY ) + RegisterNetworkedVariable( "turretSite6", SNDC_GLOBAL, SNVT_ENTITY ) + RegisterNetworkedVariable( "turretSite7", SNDC_GLOBAL, SNVT_ENTITY ) + RegisterNetworkedVariable( "turretSite8", SNDC_GLOBAL, SNVT_ENTITY ) + RegisterNetworkedVariable( "turretSite9", SNDC_GLOBAL, SNVT_ENTITY ) + + RegisterNetworkedVariable( "turretStateFlags1", SNDC_GLOBAL, SNVT_INT ) + RegisterNetworkedVariable( "turretStateFlags2", SNDC_GLOBAL, SNVT_INT ) + RegisterNetworkedVariable( "turretStateFlags3", SNDC_GLOBAL, SNVT_INT ) + RegisterNetworkedVariable( "turretStateFlags4", SNDC_GLOBAL, SNVT_INT ) + RegisterNetworkedVariable( "turretStateFlags5", SNDC_GLOBAL, SNVT_INT ) + RegisterNetworkedVariable( "turretStateFlags6", SNDC_GLOBAL, SNVT_INT ) + RegisterNetworkedVariable( "turretStateFlags7", SNDC_GLOBAL, SNVT_INT ) + RegisterNetworkedVariable( "turretStateFlags8", SNDC_GLOBAL, SNVT_INT ) + RegisterNetworkedVariable( "turretStateFlags9", SNDC_GLOBAL, SNVT_INT ) + + RegisterNetworkedVariable( "imcTowerThreatLevel", SNDC_GLOBAL, SNVT_INT ) + RegisterNetworkedVariable( "milTowerThreatLevel", SNDC_GLOBAL, SNVT_INT ) + RegisterNetworkedVariable( "fwCampAlertA", SNDC_GLOBAL, SNVT_INT ) + RegisterNetworkedVariable( "fwCampStressA", SNDC_GLOBAL, SNVT_INT ) + RegisterNetworkedVariable( "fwCampAlertB", SNDC_GLOBAL, SNVT_INT ) + RegisterNetworkedVariable( "fwCampStressB", SNDC_GLOBAL, SNVT_INT ) + RegisterNetworkedVariable( "fwCampAlertC", SNDC_GLOBAL, SNVT_INT ) + RegisterNetworkedVariable( "fwCampStressC", SNDC_GLOBAL, SNVT_INT ) + + #if CLIENT + CLFortWar_RegisterNetworkFunctions() + #endif +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_gg.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_gg.gnut new file mode 100644 index 00000000..c4021a3c --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_gg.gnut @@ -0,0 +1,165 @@ +global function Sh_GamemodeGG_Init +global function GetGunGameWeapons + +global const string GAMEMODE_GG = "gg" + +global struct GunGameWeapon +{ + string weapon + array<string> mods + int offhandSlot = -1 +} + +struct { + array<GunGameWeapon> weapons +} file + +void function Sh_GamemodeGG_Init() +{ + // create custom gamemode + AddCallback_OnCustomGamemodesInit( CreateGamemodeGG ) +} + +void function CreateGamemodeGG() +{ + GameMode_Create( GAMEMODE_GG ) + GameMode_SetName( GAMEMODE_GG, "#GAMEMODE_GG" ) + GameMode_SetDesc( GAMEMODE_GG, "#PL_gg_desc" ) + GameMode_SetGameModeAnnouncement( GAMEMODE_GG, "ffa_modeDesc" ) + GameMode_SetDefaultTimeLimits( GAMEMODE_GG, 10, 0.0 ) + GameMode_AddScoreboardColumnData( GAMEMODE_GG, "#SCOREBOARD_SCORE", PGS_ASSAULT_SCORE, 2 ) + GameMode_AddScoreboardColumnData( GAMEMODE_GG, "#SCOREBOARD_PILOT_KILLS", PGS_PILOT_KILLS, 2 ) + GameMode_SetColor( GAMEMODE_GG, [147, 204, 57, 255] ) + + AddPrivateMatchMode( GAMEMODE_GG ) // add to private lobby modes + + // setup guns + + // smgs + // car + GunGameWeapon ggCar = { weapon = "mp_weapon_car", mods = [ "pas_run_and_gun" ], ... } + file.weapons.append( ggCar ) + + // alternator + GunGameWeapon ggAlternator = { weapon = "mp_weapon_alternator_smg", mods = [ "pas_run_and_gun" ], ... } + file.weapons.append( ggAlternator ) + + // volt + GunGameWeapon ggVolt = { weapon = "mp_weapon_hemlok_smg", ... } + file.weapons.append( ggVolt ) + + + // rifles + // hemlok + GunGameWeapon ggHemlok = { weapon = "mp_weapon_hemlok", mods = [ ], ... } + file.weapons.append( ggHemlok ) + + // flatline + GunGameWeapon ggFlatline = { weapon = "mp_weapon_vinson", mods = [ "hcog" ], ... } + file.weapons.append( ggFlatline ) + + // r201 + GunGameWeapon ggR101 = { weapon = "mp_weapon_rspn101", ... } + file.weapons.append( ggR101 ) + + + // lmgs + // devotion + GunGameWeapon ggDevotion = { weapon = "mp_weapon_esaw", ... } + file.weapons.append( ggDevotion ) + + // l-star + GunGameWeapon ggLstar = { weapon = "mp_weapon_lstar", mods = [ "pas_run_and_gun" ], ... } + if ( RandomInt( 100 ) <= 5 ) + ggLstar.mods.append( "rcee" ) // easter egg mod that changes the screen of the lstar + + file.weapons.append( ggLstar ) + + + // shotguns + // eva-8 + GunGameWeapon ggEva = { weapon = "mp_weapon_shotgun", ... } + file.weapons.append( ggEva ) + + // mastiff + GunGameWeapon ggMastiff = { weapon = "mp_weapon_mastiff", ... } + file.weapons.append( ggMastiff ) + + + // grenadiers + // softball + GunGameWeapon ggSoftball = { weapon = "mp_weapon_softball", ... } + file.weapons.append( ggSoftball ) + + // epg + GunGameWeapon ggEpg = { weapon = "mp_weapon_epg", mods = [ "jump_kit" ], ... } + file.weapons.append( ggEpg ) + + + // primary pistols + // mozambique + GunGameWeapon ggMozam = { weapon = "mp_weapon_shotgun_pistol", mods = [ "pas_run_and_gun" ], ... } + file.weapons.append( ggMozam ) + + // wingman elite + GunGameWeapon ggWme = { weapon = "mp_weapon_wingman_n", mods = [ "pas_run_and_gun", "ricochet" ], ... } + file.weapons.append( ggWme ) + + + // snipers + // double take + GunGameWeapon ggTaketake = { weapon = "mp_weapon_doubletake", ... } + file.weapons.append( ggTaketake ) + + // kraber + GunGameWeapon ggKraber = { weapon = "mp_weapon_sniper", mods = [ "pas_fast_ads", "ricochet" ], ... } + file.weapons.append( ggKraber ) + + + // secondary pistols + // re-45 + GunGameWeapon ggRe45 = { weapon = "mp_weapon_autopistol", mods = [ "pas_run_and_gun", "temp_sight" ], ... } + file.weapons.append( ggRe45 ) + + // p2016 + GunGameWeapon ggP2016 = { weapon = "mp_weapon_semipistol", mods = [ "pas_run_and_gun" ], ... } + file.weapons.append( ggP2016 ) + + // wingman + GunGameWeapon ggWingman = { weapon = "mp_weapon_wingman", mods = [ "pas_run_and_gun" ], ... } + file.weapons.append( ggWingman ) + + + // final/special weapons + // charge rifle + GunGameWeapon ggChargeRifle = { weapon = "mp_weapon_defender", ... } + file.weapons.append( ggChargeRifle ) + + // pulse blade + GunGameWeapon ggPulseBlade = { weapon = "mp_weapon_grenade_sonar", mods = [ "pas_power_cell", "amped_tacticals" ], offhandSlot = 0 } + file.weapons.append( ggPulseBlade ) + + + // set this to the number of guns + GameMode_SetDefaultScoreLimits( GAMEMODE_GG, file.weapons.len(), 0 ) + + #if SERVER + GameMode_AddServerInit( GAMEMODE_GG, GamemodeGG_Init ) + GameMode_AddServerInit( GAMEMODE_GG, GamemodeFFAShared_Init ) + GameMode_SetPilotSpawnpointsRatingFunc( GAMEMODE_GG, RateSpawnpoints_Generic ) + GameMode_SetTitanSpawnpointsRatingFunc( GAMEMODE_GG, RateSpawnpoints_Generic ) + #elseif CLIENT + GameMode_AddClientInit( GAMEMODE_GG, ClGamemodeGG_Init ) + GameMode_AddClientInit( GAMEMODE_GG, GamemodeFFAShared_Init ) + GameMode_AddClientInit( GAMEMODE_GG, ClGamemodeFFA_Init ) + #endif + #if !UI + GameMode_SetScoreCompareFunc( GAMEMODE_GG, CompareAssaultScore ) + GameMode_AddSharedInit( GAMEMODE_GG, GamemodeFFA_Dialogue_Init ) + #endif +} + +array<GunGameWeapon> function GetGunGameWeapons() +{ + return file.weapons +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_inf.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_inf.gnut new file mode 100644 index 00000000..bcd86378 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_inf.gnut @@ -0,0 +1,58 @@ +global function Sh_GamemodeInfection_Init + +global const string GAMEMODE_INFECTION = "inf" +global const int INFECTION_TEAM_SURVIVOR = TEAM_MILITIA +global const int INFECTION_TEAM_INFECTED = TEAM_IMC + +void function Sh_GamemodeInfection_Init() +{ + // create custom gamemode + AddCallback_OnCustomGamemodesInit( CreateGamemodeInfection ) + AddCallback_OnRegisteringCustomNetworkVars( InfectionRegisterNetworkVars ) +} + +void function CreateGamemodeInfection() +{ + GameMode_Create( GAMEMODE_INFECTION ) + GameMode_SetName( GAMEMODE_INFECTION, "#GAMEMODE_inf" ) + GameMode_SetDesc( GAMEMODE_INFECTION, "#PL_inf_desc" ) + GameMode_SetGameModeAnnouncement( GAMEMODE_INFECTION, "ffa_modeDesc" ) + GameMode_SetDefaultTimeLimits( GAMEMODE_INFECTION, 5, 0.0 ) + GameMode_AddScoreboardColumnData( GAMEMODE_INFECTION, "#SCOREBOARD_KILLS", PGS_ASSAULT_SCORE, 2 ) + GameMode_SetColor( GAMEMODE_INFECTION, [147, 204, 57, 255] ) + + AddPrivateMatchMode( GAMEMODE_INFECTION ) // add to private lobby modes + + #if SERVER + GameMode_AddServerInit( GAMEMODE_INFECTION, GamemodeInfection_Init ) + GameMode_SetPilotSpawnpointsRatingFunc( GAMEMODE_INFECTION, RateSpawnpoints_Generic ) + GameMode_SetTitanSpawnpointsRatingFunc( GAMEMODE_INFECTION, RateSpawnpoints_Generic ) + #elseif CLIENT + GameMode_AddClientInit( GAMEMODE_INFECTION, ClGamemodeInfection_Init ) + #endif + #if !UI + GameMode_SetScoreCompareFunc( GAMEMODE_INFECTION, CompareAssaultScoreAndInfection ) + #endif +} + +void function InfectionRegisterNetworkVars() +{ + if ( GAMETYPE != GAMEMODE_INFECTION ) + return + + Remote_RegisterFunction( "ServerCallback_YouAreInfected" ) + Remote_RegisterFunction( "ServerCallback_AnnounceFirstInfected" ) + Remote_RegisterFunction( "ServerCallback_AnnounceLastSurvivor" ) +} + +int function CompareAssaultScoreAndInfection( entity a, entity b ) +{ + // survivors should be on top, then sort by assault score + + if ( a.GetTeam() == INFECTION_TEAM_INFECTED && b.GetTeam() == INFECTION_TEAM_SURVIVOR ) + return 1 + else if ( a.GetTeam() == INFECTION_TEAM_SURVIVOR && b.GetTeam() == INFECTION_TEAM_INFECTED ) + return -1 + + return CompareAssaultScore( a, b ) +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_kr.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_kr.gnut new file mode 100644 index 00000000..7cd91de9 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_kr.gnut @@ -0,0 +1,55 @@ +global function Sh_GamemodeKR_Init + +global const string GAMEMODE_KR = "kr" + +void function Sh_GamemodeKR_Init() +{ + // create custom gamemode + AddCallback_OnCustomGamemodesInit( CreateGamemodeKR ) + AddCallback_OnRegisteringCustomNetworkVars( KRRegisterNetworkVars ) +} + +void function CreateGamemodeKR() +{ + GameMode_Create( GAMEMODE_KR ) + GameMode_SetName( GAMEMODE_KR, "#GAMEMODE_kr" ) + GameMode_SetDesc( GAMEMODE_KR, "#PL_kr_desc" ) + GameMode_SetGameModeAnnouncement( GAMEMODE_KR, "ffa_modeDesc" ) + GameMode_SetDefaultTimeLimits( GAMEMODE_KR, 10, 0.0 ) + GameMode_AddScoreboardColumnData( GAMEMODE_KR, "#SCOREBOARD_KR_RECORD", PGS_ASSAULT_SCORE, 2 ) + GameMode_AddScoreboardColumnData( GAMEMODE_KR, "#SCOREBOARD_PILOT_KILLS", PGS_PILOT_KILLS, 2 ) + GameMode_SetColor( GAMEMODE_KR, [147, 204, 57, 255] ) + + AddPrivateMatchMode( GAMEMODE_KR ) // add to private lobby modes + + #if SERVER + GameMode_AddServerInit( GAMEMODE_KR, GamemodeKR_Init ) + GameMode_AddServerInit( GAMEMODE_KR, GamemodeFFAShared_Init ) + GameMode_SetPilotSpawnpointsRatingFunc( GAMEMODE_KR, RateSpawnpoints_Generic ) + GameMode_SetTitanSpawnpointsRatingFunc( GAMEMODE_KR, RateSpawnpoints_Generic ) + #elseif CLIENT + GameMode_AddClientInit( GAMEMODE_KR, ClGamemodeKR_Init ) + GameMode_AddClientInit( GAMEMODE_KR, GamemodeFFAShared_Init ) + GameMode_AddClientInit( GAMEMODE_KR, ClGamemodeFFA_Init ) + #endif + #if !UI + GameMode_SetScoreCompareFunc( GAMEMODE_KR, CompareAssaultScore ) + GameMode_AddSharedInit( GAMEMODE_KR, GamemodeFFA_Dialogue_Init ) + #endif +} + +void function KRRegisterNetworkVars() +{ + if ( GAMETYPE != GAMEMODE_KR ) + return + + Remote_RegisterFunction( "ServerCallback_FlagSpawnIncoming" ) + Remote_RegisterFunction( "ServerCallback_NewKillRacer" ) + Remote_RegisterFunction( "ServerCallback_EndKillrace" ) + + RegisterNetworkedVariable( "killRaceTime", SNDC_PLAYER_EXCLUSIVE, SNVT_TIME, 0.0 ) + + #if CLIENT + RegisterNetworkedVariableChangeCallback_time( "killRaceTime", ShowTimeGainOnKill ) + #endif +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_sbox.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_sbox.gnut new file mode 100644 index 00000000..893d9410 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_sbox.gnut @@ -0,0 +1,20 @@ +global function Sh_GamemodeSbox_Init + +global const string GAMEMODE_SBOX = "sbox" + +void function Sh_GamemodeSbox_Init() +{ + // create custom gametype + AddCallback_OnCustomGamemodesInit( CreateGamemodeSbox ) +} + +void function CreateGamemodeSbox() +{ + GameMode_Create( GAMEMODE_SBOX ) + GameMode_SetName( GAMEMODE_SBOX, "#PL_sbox" ) + GameMode_SetDesc( GAMEMODE_SBOX, "#PL_sbox_desc" ) + + #if SERVER + GameMode_AddServerInit( GAMEMODE_SBOX, GamemodeSbox_Init ) + #endif +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_tt.gnut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_tt.gnut new file mode 100644 index 00000000..f3fbff28 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_gamemode_tt.gnut @@ -0,0 +1,36 @@ +global function Sh_GamemodeTT_Init + +global const string GAMEMODE_TT = "tt" + +void function Sh_GamemodeTT_Init() +{ + // create custom gamemode + AddCallback_OnCustomGamemodesInit( CreateGamemodeTT ) +} + +void function CreateGamemodeTT() +{ + GameMode_Create( GAMEMODE_TT ) + GameMode_SetName( GAMEMODE_TT, "#GAMEMODE_TT" ) + GameMode_SetDesc( GAMEMODE_TT, "#PL_tt_desc" ) + GameMode_SetGameModeAnnouncement( GAMEMODE_TT, "gnrc_modeDesc" ) + GameMode_SetDefaultScoreLimits( GAMEMODE_TT, 20, 0 ) + GameMode_SetDefaultTimeLimits( GAMEMODE_TT, 15, 0.0 ) + GameMode_AddScoreboardColumnData( GAMEMODE_TT, "#SCOREBOARD_SCORE", PGS_ASSAULT_SCORE, 2 ) + GameMode_AddScoreboardColumnData( GAMEMODE_TT, "#SCOREBOARD_TITAN_KILLS", PGS_TITAN_KILLS, 1 ) + GameMode_AddScoreboardColumnData( GAMEMODE_TT, "#SCOREBOARD_PILOT_KILLS", PGS_PILOT_KILLS, 2 ) + GameMode_SetColor( GAMEMODE_TT, [200, 40, 40, 255] ) + + AddPrivateMatchMode( GAMEMODE_TT ) // add to private lobby modes + + #if SERVER + GameMode_AddServerInit( GAMEMODE_TT, GamemodeTT_Init ) + GameMode_SetPilotSpawnpointsRatingFunc( GAMEMODE_TT, RateSpawnpoints_Generic ) + GameMode_SetTitanSpawnpointsRatingFunc( GAMEMODE_TT, RateSpawnpoints_Generic ) + #elseif CLIENT + GameMode_AddClientInit( GAMEMODE_TT, ClGamemodeTT_Init ) + #endif + #if !UI + GameMode_SetScoreCompareFunc( GAMEMODE_TT, CompareAssaultScore ) + #endif +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_riff_floor_is_lava.nut b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_riff_floor_is_lava.nut new file mode 100644 index 00000000..3a8ac8e7 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/gamemodes/sh_riff_floor_is_lava.nut @@ -0,0 +1,262 @@ +untyped + +global function RiffFloorIsLavaShared_Init + +global function GetFogHeight +global function GetLethalFogTopTitan +global function GetLethalFogTop +global function GetLethalFogBottom +global function GetVisibleFogTop +global function GetVisibleFogBottom +global function GetMaxTitanSpawnFogHeight + +global function IsEntInSafeVolume +global function IsEntInLethalVolume + +struct +{ + float fogDepth = 64.0 + float maxTitanSpawnFogDepth = 170.0 + array lethalTitanVolumes + array lethalPilotVolumes + array safePilotVolumes + bool volumesDebug = false + table< string, float > lethalFogHeights +} file + +function RiffFloorIsLavaShared_Init() +{ + switch ( GetMapName() ) + { + case "mp_lagoon": + AddLethalTitanVolume( Vector( -45.656845, 3555.449463, 40.422455 ), Vector( 1209.944092, 5599.152832, 234.813217 ) ) + AddLethalTitanVolume( Vector( -5232.395020, 205.406250, 0.031250 ), Vector( -777.285400, 4075.119385, 300.634771 ) ) + AddLethalTitanVolume( Vector( -4686.448730, 4190.655273, 20.642021 ), Vector( -41.171387, 9072.019043, 200.697632 ) ) + AddLethalTitanVolume( Vector( -7586.861328, 4072.843994, 0.031254 ), Vector( -7012.854004, 4614.723145, 302.714966 ) ) + break + + case "mp_nexus": + AddLethalTitanVolume( Vector( 1567.173523, -27.374023, 209.422455 ), Vector( 2516.944092, 2585.152832, 500.813217 ) ) + AddLethalTitanVolume( Vector( -2825.766113, 5056.203125, 243.706253 ), Vector( -2255.893555, 5688.334961, 400.251160 ) ) + AddLethalTitanVolume( Vector( -5717.068359, -349.599976, 189.669785 ), Vector( -4960.125000, 758.196350, 400.268097 ) ) + AddLethalTitanVolume( Vector( -3292.942139, 1713.916626, 233.749817 ), Vector( -2322.137695, 3091.497070, 477.462799 ) ) + AddLethalTitanVolume( Vector( -878.712769, -5878.528809, 71.145332 ), Vector( 338.741943, -5014.183594, 443.146179 ) ) + AddLethalTitanVolume( Vector( -6930.957031, -1277.388550, 107.619537 ), Vector( -6574.779297, -779.338013, 685.485901 ) ) + break + + case "mp_outpost_207": + AddSafePilotVolume( Vector( 2359.524658, -631.065918, -256.714142 ), Vector( 2623.051270, -182.453323, -220.125641 ) ) + + AddLethalTitanVolume( Vector( -100.349350, 2218.763916, -330.968750 ), Vector( 2561.511230, 4030.028320, -133.065369 ) ) + AddLethalTitanVolume( Vector( -452.031647, 282.244629, -255.968750 ), Vector( 2241.971069, 1594.146851, -100.212967 ) ) + break + + case "mp_training_ground": + AddSafePilotVolume( Vector( -2618.053223, -3435.505615, 40.215054 ), Vector( -2309.167236, -3321.788330, 146.218491 ) ) + AddSafePilotVolume( Vector( -3187.767090, -2886.333496, 45.746925 ), Vector( -2865.753174, -2681.679443, 109.089279 ) ) + AddSafePilotVolume( Vector( -3717.815674, -2350.831543, 47.694588 ), Vector( -3431.980957, -2145.194092, 120.640717 ) ) + + AddLethalTitanVolume( Vector( -3439.702179, -2227.359741, -8.036909 ), Vector( 2185.765076, 2384.459412, 225.199013 ) ) + AddLethalTitanVolume( Vector( -3200.747681, -4456.148926, 0.0 ), Vector( -1261.621826, -3000.667480, 160.689011 ) ) + AddLethalTitanVolume( Vector( 1261.621826, 3000.667480, 0.0 ), Vector( 2700.747681, 4456.148926, 160.689011 ) ) + AddLethalTitanVolume( Vector( -3291.510986, 3483.724609, 4.031250 ), Vector( -2018.871826, 4463.995850, 122.675621 ) ) + AddLethalTitanVolume( Vector( 2018.871826, -3638.995850, 4.031250 ), Vector( 2241.510986, -3483.724609, 122.675621 ) ) + AddLethalTitanVolume( Vector( -2798.816528, -2302.519897, -30.285933 ), Vector( -1561.589355, -791.616699, 300.917297 ) ) + AddLethalTitanVolume( Vector( 3809.276123, 1639.001587, 11.272846 ), Vector( 4056.847412, 1862.587036, 100.205643 ) ) + AddLethalTitanVolume( Vector( -4189.979492, -3298.505127, -5.597572 ), Vector( -3398.622803, -560.027344, 147.054291 ) ) + break + + case "mp_runoff": + AddLethalPilotVolume( Vector( -621.502319, -5743.472656, 299.838928 ), Vector( -397.317047, -5578.512207, 425.437927 ) ) + break + } +} + +float function GetFogHeight() +{ + string mapName = GetMapName() + + file.lethalFogHeights = {} + file.lethalFogHeights[ "mp_angel_city" ] <- 216.0 // cp ctf mfd + file.lethalFogHeights[ "mp_lagoon" ] <- 98.0 // cp mfd + file.lethalFogHeights[ "mp_nexus" ] <- 310.0 // mfd + file.lethalFogHeights[ "mp_o2" ] <- 40.0 // mfd + file.lethalFogHeights[ "mp_outpost_207" ] <- -225.0 // mfd + file.lethalFogHeights[ "mp_training_ground" ] <- 80.0 // cp mfd + file.lethalFogHeights[ "mp_harmony_mines" ] <- 260.0 // cp ctf mfd + file.lethalFogHeights[ "mp_haven" ] <- 128.0 // mfd + + // good map, needs spawns, etc... + file.lethalFogHeights[ "mp_rise" ] <- 420.0 // mfd + file.lethalFogHeights[ "mp_runoff" ] <- 340.0 // mfd + file.lethalFogHeights[ "mp_zone_18" ] <- 460.0 // mfd + file.lethalFogHeights[ "mp_sandtrap" ] <- 64.0 + + // these don't work as well + file.lethalFogHeights[ "mp_swampland" ] <- 350.0 // mfd + file.lethalFogHeights[ "mp_backwater" ] <- 320.0 // mfd + file.lethalFogHeights[ "mp_airbase" ] <- 450.0 + file.lethalFogHeights[ "mp_boneyard" ] <- 64.0 + file.lethalFogHeights[ "mp_colony" ] <- 270.0 + file.lethalFogHeights[ "mp_corporate" ] <- -765.0 + file.lethalFogHeights[ "mp_fracture" ] <- 270.0 + file.lethalFogHeights[ "mp_overlook" ] <- 16.0 + file.lethalFogHeights[ "mp_relic" ] <- 475.0 + file.lethalFogHeights[ "mp_smugglers_cove" ] <- 400.0 + file.lethalFogHeights[ "mp_wargames" ] <- 64.0 + file.lethalFogHeights[ "mp_switchback" ] <- 840.0 + + file.lethalFogHeights[ "mp_chin_rodeo_express" ] <- 1580.0 + + // custom: titanfall 2 maps + // TODO: really need a modular system here + file.lethalFogHeights[ "mp_colony02" ] <- 270.0 // map changed name from tf1 => tf2 + file.lethalFogHeights[ "mp_glitch" ] <- 200.0 + file.lethalFogHeights[ "mp_grave" ] <- 2350.0 + file.lethalFogHeights[ "mp_homestead" ] <- 64.0 + file.lethalFogHeights[ "mp_forwardbase_kodai" ] <- 930.0 + file.lethalFogHeights[ "mp_thaw" ] <- 32.0 + file.lethalFogHeights[ "mp_black_water_canal" ] <- 32.0 + file.lethalFogHeights[ "mp_eden" ] <- 175.0 + file.lethalFogHeights[ "mp_drydock" ] <- 300.0 + file.lethalFogHeights[ "mp_crashsite3" ] <- 800.0 // crashsite is just as awful for this as it is for anything else + file.lethalFogHeights[ "mp_complex3" ] <- 630.0 + file.lethalFogHeights[ "mp_relic02" ] <- 250.0 // not great, tf1's would honestly be worse though imo + + // lf maps: overall a bit hit or miss, many likely have spawn issues + file.lethalFogHeights[ "mp_lf_stacks" ] <- -9999.0 // entirely nonworking, breaks spawns no matter what from what i can tell, could potentially use safe zones for this? + file.lethalFogHeights[ "mp_lf_deck" ] <- -9999.0 // nonworking fogcontroller so fog is invisible + file.lethalFogHeights[ "mp_lf_uma" ] <- 64.0 + file.lethalFogHeights[ "mp_lf_meadow" ] <- 64.0 + file.lethalFogHeights[ "mp_lf_traffic" ] <- 50.0 + file.lethalFogHeights[ "mp_lf_township" ] <- 64.0 + + if ( mapName in file.lethalFogHeights ) + return file.lethalFogHeights[ mapName ] + + return 64.0 +} + +float function GetLethalFogTopTitan() +{ + float fogTop = GetLethalFogTop() + + switch ( GetMapName() ) + { + case "mp_lagoon": + case "mp_nexus": + case "mp_outpost_207": + case "mp_training_ground": + case "mp_chin_rodeo_express": + return fogTop + } + + return fogTop + 256.0 +} + +float function GetLethalFogTop() +{ + return GetFogHeight() - file.fogDepth * 0.2 +} + +float function GetLethalFogBottom() +{ + return GetFogHeight() - file.fogDepth * 0.7 +} + +float function GetVisibleFogTop() +{ + return GetFogHeight() + file.fogDepth * 0.5 +} + +float function GetVisibleFogBottom() +{ + return GetFogHeight() - file.fogDepth * 0.5 +} + +float function GetMaxTitanSpawnFogHeight() +{ + return GetFogHeight() - file.maxTitanSpawnFogDepth +} + +function AddLethalTitanVolume( vector volumeMins, vector volumeMaxs ) +{ + Assert( volumeMins.x < volumeMaxs.x ) + Assert( volumeMins.y < volumeMaxs.y ) + Assert( volumeMins.z < volumeMaxs.z ) + + file.lethalTitanVolumes.append( { mins = volumeMins, maxs = volumeMaxs } ) +} + +function AddLethalPilotVolume( vector volumeMins, vector volumeMaxs ) +{ + Assert( volumeMins.x < volumeMaxs.x ) + Assert( volumeMins.y < volumeMaxs.y ) + Assert( volumeMins.z < volumeMaxs.z ) + + file.lethalPilotVolumes.append( { mins = volumeMins, maxs = volumeMaxs } ) +} + +function AddSafePilotVolume( vector volumeMins, vector volumeMaxs ) +{ + Assert( volumeMins.x < volumeMaxs.x ) + Assert( volumeMins.y < volumeMaxs.y ) + Assert( volumeMins.z < volumeMaxs.z ) + + file.safePilotVolumes.append( { mins = volumeMins, maxs = volumeMaxs } ) +} + +function IsEntInSafeVolume( entity ent ) +{ + if ( ent.IsPlayer() ) + { + foreach ( volume in file.safePilotVolumes ) + { + vector entOrg = ent.GetOrigin() + + #if SERVER + if ( file.volumesDebug ) + DebugDrawBox( Vector( 0.0, 0.0, 0.0 ), volume.mins, volume.maxs, 0, 0, 255, 1, 0.1 ) + #endif + + if ( PointIsWithinBounds( entOrg, expect vector( volume.mins ), expect vector( volume.maxs ) ) ) + return true + } + } +} + +function IsEntInLethalVolume( entity ent ) +{ + if ( ent.IsTitan() ) + { + foreach ( volume in file.lethalTitanVolumes ) + { + vector entOrg = ent.GetOrigin() + + #if SERVER + if ( file.volumesDebug ) + DebugDrawBox( Vector( 0.0, 0.0, 0.0 ), volume.mins, volume.maxs, 255, 255, 0, 1, 0.1 ) + #endif + + if ( PointIsWithinBounds( entOrg, expect vector( volume.mins ), expect vector( volume.maxs ) ) ) + return true + } + } + else if ( ent.IsPlayer() ) + { + foreach ( volume in file.lethalPilotVolumes ) + { + vector entOrg = ent.GetOrigin() + + #if SERVER + if ( file.volumesDebug ) + DebugDrawBox( Vector( 0.0, 0.0, 0.0 ), volume.mins, volume.maxs, 255, 255, 0, 1, 0.1 ) + #endif + + if ( PointIsWithinBounds( entOrg, expect vector( volume.mins ), expect vector( volume.maxs ) ) ) + return true + } + } + + return false +} diff --git a/Northstar.Custom/mod/scripts/vscripts/lobby/sh_private_lobby_custom_modes_init.gnut b/Northstar.Custom/mod/scripts/vscripts/lobby/sh_private_lobby_custom_modes_init.gnut new file mode 100644 index 00000000..81c08f22 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/lobby/sh_private_lobby_custom_modes_init.gnut @@ -0,0 +1,11 @@ +global function CustomPrivateMatchModesInit + +void function CustomPrivateMatchModesInit() +{ + // modes + AddPrivateMatchMode( "gg" ) + AddPrivateMatchMode( "inf" ) + AddPrivateMatchMode( "kr" ) + AddPrivateMatchMode( "tt" ) + AddPrivateMatchMode( "ctf_comp" ) +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/mp/levels/mp_box.nut b/Northstar.Custom/mod/scripts/vscripts/mp/levels/mp_box.nut new file mode 100644 index 00000000..a1828009 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/mp/levels/mp_box.nut @@ -0,0 +1,6 @@ +global function CodeCallback_MapInit + +void function CodeCallback_MapInit() +{ + +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/northstar_custom_autoprecache.gnut b/Northstar.Custom/mod/scripts/vscripts/northstar_custom_autoprecache.gnut new file mode 100644 index 00000000..1d5b7ba9 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/northstar_custom_autoprecache.gnut @@ -0,0 +1,14 @@ +untyped +global function NorthstarCustomAutoprecache + +void function NorthstarCustomAutoprecache() +{ + PrecacheWeapon( "mp_weapon_peacekraber" ) + PrecacheWeapon( "melee_pilot_kunai" ) + + if ( GAMETYPE == GAMEMODE_SBOX ) + PrecacheWeapon( "mp_weapon_toolgun" ) + + // will include this as a custom asset when mod v2 is written + PrecacheModel( $"models/SPOILER_w_titan_particle_accelerator.mdl" ) +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/titan/sh_first_person_embark.gnut b/Northstar.Custom/mod/scripts/vscripts/titan/sh_first_person_embark.gnut new file mode 100644 index 00000000..0c95ae4c --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/titan/sh_first_person_embark.gnut @@ -0,0 +1,41 @@ +global function FirstPersonEmbark_Init + +#if CLIENT + global function ServerCallback_HideHudForFPEmbark +#endif + +void function FirstPersonEmbark_Init() +{ + // atm do this no matter what playlist we're on since playlist overrides seem to get sent to clients after networkvar registration + // not nice but whatever lol + AddCallback_OnRegisteringCustomNetworkVars( FirstPersonEmbark_RegisterCustomNetworkFunctions ) + + // busted rn lol + if ( GetCurrentPlaylistVarInt( "fp_embark_enabled", 0 ) == 0 ) + return + + #if CLIENT + AddCallback_PlayerClassChanged( ShowHudOnEmbarkFinished ) + #endif +} + +void function FirstPersonEmbark_RegisterCustomNetworkFunctions() +{ + Remote_RegisterFunction( "ServerCallback_HideHudForFPEmbark" ) +} + +#if CLIENT +void function ServerCallback_HideHudForFPEmbark() +{ + thread MainHud_TurnOff_RUI( true ) + HidePermanentCockpitRui() +} + +void function ShowHudOnEmbarkFinished( entity player ) +{ + if ( !player.IsTitan() ) + return + + ShowPermanentCockpitRui() +} +#endif
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/titan/sh_titan_embark.gnut b/Northstar.Custom/mod/scripts/vscripts/titan/sh_titan_embark.gnut new file mode 100644 index 00000000..f9df2730 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/titan/sh_titan_embark.gnut @@ -0,0 +1,2253 @@ +untyped + +global function TitanEmbark_Init + +global function TitanCanStand +global function DebugEmbarkTimes +global function TitanIsCurrentlyEmbarkableForPlayer +global function PlayerCanEmbarkTitan +global function PlayerCanImmediatelyEmbarkTitan +global function FindBestEmbark +global function GenerateEmbarkActionTable +global function FindEmbarkActionForCriteria +global function GetRandomEmbarkAction +global function PlayerCanDisembarkTitan +global function IsPlayerDisembarking + +#if SERVER + global function ForceScriptedEmbark + global function PlayerCanEmbarkIntoTitan + global function IsPlayerEmbarking + global function PlayerEmbarksTitan + global function PlayerLungesToEmbark + global function FindBestEmbarkForNpcAnim + global function PlayerDisembarksTitan + global function ForcedTitanDisembark + global function ForcedTitanDisembarkCustomAnims + global function PhaseEmbarkPhaseStart + global function PhaseEmbarkPhaseStop + global function OverrideCockpitLightFX + global function StartCockpitLightThink + global function CockpitLightStop + global function Embark_DelayedFadeOut + global function PlayerIsFarOffTheGround + + global function SetSmallDisembarkFailSafeTeleportVector //TODO: Re-examine this for next game, probably should have different values for SP versus MP + global function SetLargeDisembarkFailSafeTeleportVector //TODO: Re-examine this for next game, probably should have different values for SP versus MP +#endif + +#if DEV + global function SetEmbarkDebugPrint +#endif + +const FAST_EMBARK = 1 +const SKIP_AHEAD_TIME = 2.0 + +#if MP +const EMBARK_FADE_TIME = 0.2 +#else +const EMBARK_FADE_TIME = 0.3 +#endif + +struct +{ + asset cockpitLightFX = $"xo_cockpit_dlight" + bool embarkDebugPrint = false + vector smallDisembarkFailSafeTeleportVector = < 400, 400, 200 > + vector largeDisembarkFailSafeTeleportVector = < 600, 600, 600 > +} file + +function TitanEmbark_Init() +{ + if ( reloadingScripts ) + return + + level.pilotDisembarkBounds <- {} + local end = {} + end.up <- 50.363811 + end.forward <- 110.146927 + end.right <- 13.045869 + end.yaw <- -8.381051 + + local start = {} + start.up <- 156.750015 + start.forward <- -13.429688 + start.right <- -11.374998 + start.yaw <- 0.409042 + + RefreshTitanEmbarkActions() + + level.pilotDisembarkBounds.end <- end + level.pilotDisembarkBounds.start <- start + + RegisterSignal( "OnComplete" ) + RegisterSignal( "startembark" ) // temp + + RegisterSignal( "DisembarkingTitan" ) + RegisterSignal( "player_embarks_titan" ) + + #if SERVER + // add all the embark anims with this suffix + AddEmbarkAnims( "titan_atlas", "atlas", true ) + AddEmbarkAnims( "titan_buddy", "buddy", true ) + AddEmbarkAnims( "titan_ogre", "ogre", true ) + AddEmbarkAnims( "titan_stryder", "stryder", true ) + + // AddEmbarkAudio( "titan_atlas", "atlas" ) + // AddEmbarkAudio( "titan_buddy", "buddy" ) + // AddEmbarkAudio( "titan_ogre", "ogre" ) + // AddEmbarkAudio( "titan_stryder", "stryder" ) + + RegisterSignal( "titanKneel" ) + RegisterSignal( "titanStand" ) + RegisterSignal( "titanEmbark" ) + RegisterSignal( "PhaseEmbarkPhaseStop" ) + + RegisterSignal( "CockpitLightStop" ) + PrecacheParticleSystem( $"xo_cockpit_dlight" ) + + AddClientCommandCallback( "TitanDisembark", ClientCommand_TitanDisembark ) // + AddClientCommandCallback( "TitanKneel", ClientCommand_TitanKneel ) // + //AddClientCommandCallback( "TitanStand", ClientCommand_TitanStand ) // + AddClientCommandCallback( "TitanNextMode", ClientCommand_TitanNextMode ) // + + AddCallback_OnTitanBecomesPilot( TitanBecomesPilot_UpdateRodeoRiderHud ) + AddCallback_OnPilotBecomesTitan( PilotBecomesTitan_UpdateRodeoRiderHud ) + #endif +} + +void function OverrideCockpitLightFX( asset fx ) +{ + file.cockpitLightFX = fx +} + +void function AddEmbarkAnims( string titan, string titanSubClass, bool thirdPersonOnly = false ) +{ + // anims are string-constructed from these types: + local Array = + [ + "kneel_front", + "kneel_behind", + "kneel_right", + "kneel_left", + "kneel_airgrab", + + "stand_front", + "stand_right", + "stand_left", + "stand_behind", + "stand_airgrab", + + "above_right", + "above_left", + "kneel_above_right", + "kneel_above_left", + ] + + + // force consistency in animation names + foreach ( item in Array ) + { + array<string> suffixes = [ "", "_fast" ] + foreach ( suffix in suffixes ) + { + //printt( "Adding base " + item + " to " + titan ) + local thirdPersonAlias = "pt_mount_" + item + suffix + local firstPersonAlias = "ptpov_mount_" + item + suffix + local thirdPersonAnim = "pt_mount_" + titanSubClass + "_" + item + suffix + local firstPersonAnim = "ptpov_mount_" + titanSubClass + "_" + item + suffix + + if ( thirdPersonOnly ) + firstPersonAnim = "" + + AddAnimAlias( titanSubClass, thirdPersonAlias, thirdPersonAnim ) + AddAnimAlias( titanSubClass, firstPersonAlias, firstPersonAnim ) + } + } +} + +function AddEmbarkAudio( titan, titanSubClass ) +{ + // audio files are string-constructed from these types: + local Array = + [ + "Kneeling_Front", + "Kneeling_Behind", + "Kneeling_Right", + "Kneeling_Left", + "Kneeling_AboveRight", + "Kneeling_AboveLeft", + + "Standing_Front", + "Standing_Behind", + "Standing_Airgrab", + "Standing_AboveRight", + "Standing_AboveLeft" + ] + + + // force consistency in audio file names + foreach ( item in Array ) + { + //printt( "Adding base " + item + " to " + titan ) + local thirdPersonAlias = "Embark_" + item + "_3P" + local firstPersonAlias = "Embark_" + item + "_1P" + local thirdPersonAnim = titanSubClass + "_Embark_" + item + "_3P" + local firstPersonAnim = titanSubClass + "_Embark_" + item + "_1P" + + AddAudioAlias( titanSubClass, thirdPersonAlias, thirdPersonAnim ) + AddAudioAlias( titanSubClass, firstPersonAlias, firstPersonAnim ) + } +} + +function RefreshTitanEmbarkActions() +{ + if ( "titanEmbarkActions" in level ) + { + delete level.titanEmbarkActions + delete level.titanEmbarkFarthestDistance + } + + local groundDist = 260 + + level.titanEmbarkActions <- [] + level.titanEmbarkFarthestDistance <- 0 + local action + + action = + { + direction = Vector( 1, 0, 0 ) + distance = groundDist + embark = "front" + minDot = 0.4 + priority = 1 // tried after priority 0 actions + titanCanStandRequired = false + //onGround = null // either + useAnimatedRefAttachment = true + alignFrontEnabled = true + canSkipAhead = true + + animSet = + { + firstPersonKneelingAlias = "ptpov_mount_kneel_front" + thirdPersonKneelingAlias = "pt_mount_kneel_front" + firstPersonStandingAlias = "ptpov_mount_stand_front" + thirdPersonStandingAlias = "pt_mount_stand_front" + titanKneelingAnim = "at_mount_kneel_front" + titanStandingAnim = "at_mount_stand_front" + + } + + audioSet = + { + firstPersonKneelingAudioAlias = "Embark_Kneeling_Front_1P" + thirdPersonKneelingAudioAlias = "Embark_Kneeling_Front_3P" + firstPersonStandingAudioAlias = "Embark_Standing_Front_1P" + thirdPersonStandingAudioAlias = "Embark_Standing_Front_3P" + + } + } + level.titanEmbarkActions.append( action ) + + action = + { + direction = Vector( 1, 0, 0 ) + distance = groundDist + embark = "front" + minDot = -1 + priority = 2 // tried after priority 1 actions + titanCanStandRequired = false + //onGround = null // either + useAnimatedRefAttachment = true + alignFrontEnabled = true + canSkipAhead = true + + animSet = + { + firstPersonKneelingAlias = "ptpov_mount_kneel_front" + thirdPersonKneelingAlias = "pt_mount_kneel_front" + firstPersonStandingAlias = "ptpov_mount_stand_front" + thirdPersonStandingAlias = "pt_mount_stand_front" + titanKneelingAnim = "at_mount_kneel_front" + titanStandingAnim = "at_mount_stand_front" + } + + audioSet = + { + firstPersonKneelingAudioAlias = "Embark_Kneeling_Front_1P" + thirdPersonKneelingAudioAlias = "Embark_Kneeling_Front_3P" + firstPersonStandingAudioAlias = "Embark_Standing_Front_1P" + thirdPersonStandingAudioAlias = "Embark_Standing_Front_3P" + } + } + level.titanEmbarkActions.append( action ) + + action = + { + direction = Vector( 0, 1, 0 ) + distance = groundDist + embark = "left" + minDot = 0.4 + priority = 1 // tried after priority 0 actions + titanCanStandRequired = true + useAnimatedRefAttachment = true + alignFrontEnabled = true + canSkipAhead = true + //onGround = null // either + + animSet = + { + firstPersonKneelingAlias = "ptpov_mount_kneel_left" + thirdPersonKneelingAlias = "pt_mount_kneel_left" + firstPersonStandingAlias = "ptpov_mount_stand_front" + thirdPersonStandingAlias = "pt_mount_stand_left" + titanKneelingAnim = "at_mount_kneel_left" + titanStandingAnim = "at_mount_stand_left" + } + + audioSet = + { + firstPersonKneelingAudioAlias = "Embark_Kneeling_Left_1P" + thirdPersonKneelingAudioAlias = "Embark_Kneeling_Left_3P" + firstPersonStandingAudioAlias = "Embark_Standing_Front_1P" + thirdPersonStandingAudioAlias = "Embark_Standing_Front_3P" + } + } + level.titanEmbarkActions.append( action ) + + action = + { + direction = Vector( 0, -1, 0 ) + distance = groundDist + embark = "right" + minDot = 0.4 + priority = 1 // tried after priority 0 actions + titanCanStandRequired = true + useAnimatedRefAttachment = true + alignFrontEnabled = true + canSkipAhead = true + //onGround = null // either + animSet = + { + firstPersonKneelingAlias = "ptpov_mount_kneel_right" + thirdPersonKneelingAlias = "pt_mount_kneel_right" + firstPersonStandingAlias = "ptpov_mount_stand_front" + thirdPersonStandingAlias = "pt_mount_stand_right" + titanKneelingAnim = "at_mount_kneel_right" + titanStandingAnim = "at_mount_stand_right" + } + + audioSet = + { + firstPersonKneelingAudioAlias = "Embark_Kneeling_Right_1P" + thirdPersonKneelingAudioAlias = "Embark_Kneeling_Right_3P" + firstPersonStandingAudioAlias = "Embark_Standing_Front_1P" + thirdPersonStandingAudioAlias = "Embark_Standing_Front_3P" + } + } + level.titanEmbarkActions.append( action ) + + + action = + { + direction = Vector( -1, 0, 0 ) + distance = groundDist + embark = "behind" + minDot = 0.4 + priority = 1 // tried after priority 0 actions + titanCanStandRequired = true + useAnimatedRefAttachment = true + canSkipAhead = false + //onGround = null // either + + animSet = + { + firstPersonKneelingAlias = "ptpov_mount_kneel_behind" + thirdPersonKneelingAlias = "pt_mount_kneel_behind" + firstPersonStandingAlias = "ptpov_mount_stand_behind" + thirdPersonStandingAlias = "pt_mount_stand_behind" + titanKneelingAnim = "at_mount_kneel_behind" + titanStandingAnim = "at_mount_stand_behind" + } + + audioSet = + { + firstPersonKneelingAudioAlias = "Embark_Kneeling_Behind_1P" + thirdPersonKneelingAudioAlias = "Embark_Kneeling_Behind_3P" + firstPersonStandingAudioAlias = "Embark_Standing_Behind_1P" + thirdPersonStandingAudioAlias = "Embark_Standing_Behind_3P" + } + } + level.titanEmbarkActions.append( action ) + + action = + { + direction = Vector( 0, 0, 1 ) // 0 -1 1 + distance = 350 + embark = "above_close" + minDot = 0.88 + canSkipAhead = false + priority = 0 // priority actions are checked first + + titanCanStandRequired = true + useAnimatedRefAttachment = true + //onGround = false // must be in air + + animSets = + { + right = + { + direction = Vector( 0, -1, 0 ) // 0 -1 1 + firstPersonKneelingAlias = "ptpov_mount_kneel_above_right" + thirdPersonKneelingAlias = "pt_mount_kneel_above_right" + firstPersonStandingAlias = "ptpov_mount_above_right" + thirdPersonStandingAlias = "pt_mount_above_right" + titanKneelingAnim = "at_mount_kneel_above" + titanStandingAnim = "at_mount_above_right" + + audioSet = //An annoying exception to how the audioSet is organized, better this than the alternative and having to find the "best audio set" + { + firstPersonKneelingAudioAlias = "Embark_Kneeling_AboveRight_1P" + thirdPersonKneelingAudioAlias = "Embark_Kneeling_AboveRight_3P" + firstPersonStandingAudioAlias = "Embark_Standing_AboveRight_1P" + thirdPersonStandingAudioAlias = "Embark_Standing_AboveRight_3P" + } + } + + left = + { + direction = Vector( 0, 1, 0 ) // 0 -1 1 + firstPersonKneelingAlias = "ptpov_mount_kneel_above_left" + thirdPersonKneelingAlias = "pt_mount_kneel_above_left" + firstPersonStandingAlias = "ptpov_mount_above_left" + thirdPersonStandingAlias = "pt_mount_above_left" + titanKneelingAnim = "at_mount_kneel_above" + titanStandingAnim = "at_mount_above_left" + + audioSet = //An annoying exception to how the audioSet is organized, better this than the alternative and having to find the "best audio set" + { + firstPersonKneelingAudioAlias = "Embark_Kneeling_AboveLeft_1P" + thirdPersonKneelingAudioAlias = "Embark_Kneeling_AboveLeft_3P" + firstPersonStandingAudioAlias = "Embark_Standing_AboveLeft_1P" + thirdPersonStandingAudioAlias = "Embark_Standing_AboveLeft_3P" + } + } + } + } + level.titanEmbarkActions.append( action ) + + action = + { + direction = Vector( 0, 0, 1 ) + distance = 275 + embark = "above_grab" + minDot = 0.3 + titanCanStandRequired = true + //onGround = null // false // must be in air + useAnimatedRefAttachment = true + //lungeCheck = true + canSkipAhead = true + + alignFrontEnabled = true + + priority = 0 // priority actions are checked first + + animSet = + { + firstPersonKneelingAlias = "ptpov_mount_kneel_airgrab" + thirdPersonKneelingAlias = "pt_mount_kneel_airgrab" + titanKneelingAnim = "at_mount_kneel_airgrab" + + firstPersonStandingAlias = "ptpov_mount_stand_airgrab" + thirdPersonStandingAlias = "pt_mount_stand_airgrab" + titanStandingAnim = "at_mount_stand_airgrab" + } + + audioSet = + { + firstPersonKneelingAudioAlias = "Embark_Kneeling_Front_1P" + thirdPersonKneelingAudioAlias = "Embark_Kneeling_Front_3P" + firstPersonStandingAudioAlias = "Embark_Standing_Front_1P" + thirdPersonStandingAudioAlias = "Embark_Standing_Front_3P" + } + } + level.titanEmbarkActions.append( action ) + + local autoParms = + [ + "lungeCheck" + "alignFrontEnabled" + ] + + foreach ( action in level.titanEmbarkActions ) + { + if ( action.distance > level.titanEmbarkFarthestDistance ) + { + level.titanEmbarkFarthestDistance = action.distance + } + foreach ( parm in autoParms ) + { + if ( !( parm in action ) ) + action[ parm ] <- false + } + } +} + +function DebugEmbarkTimes() +{ + local settings = [ "atlas", "ogre", "stryder" ] + + array< asset > models = [ $"models/Humans/imc_pilot/male_cq/imc_pilot_male_cq.mdl", $"models/humans/pilot/female_cq/pilot_female_cq.mdl" ] + local times = {} + + foreach ( model in models ) + { + times[ model ] <- [] + entity prop = CreatePropDynamic( model, Vector(0,0,0), Vector(0,0,0) ) + printt( "Human model: " + model ) + + foreach ( setting in settings ) + { + printt( "Titan: " + setting ) + foreach ( action in level.titanEmbarkActions ) + { + printt( "Embark Direction: " + action.embark ) + if ( "animSet" in action ) + { + local animation = GetAnimFromAlias( setting, action.animSet.thirdPersonKneelingAlias ) + local time = prop.GetSequenceDuration( animation ) + times[ model ].append( { time = time, animation = animation } ) + printt( "Kneeling: " + time ) + + animation = GetAnimFromAlias( setting, action.animSet.thirdPersonStandingAlias ) + time = prop.GetSequenceDuration( animation ) + times[ model ].append( { time = time, animation = animation } ) + printt( "Standing: " + time ) + } + + if ( "animSets" in action ) + { + local animation = GetAnimFromAlias( setting, action.animSets.left.thirdPersonKneelingAlias ) + local time = prop.GetSequenceDuration( animation ) + times[ model ].append( { time = time, animation = animation } ) + printt( "Kneeling Left: " + time ) + + animation = GetAnimFromAlias( setting, action.animSets.left.thirdPersonStandingAlias ) + time = prop.GetSequenceDuration( animation ) + times[ model ].append( { time = time, animation = animation } ) + printt( "Standing Left: " + time ) + + animation = GetAnimFromAlias( setting, action.animSets.right.thirdPersonKneelingAlias ) + time = prop.GetSequenceDuration( animation ) + times[ model ].append( { time = time, animation = animation } ) + printt( "Kneeling Right: " + time ) + + animation = GetAnimFromAlias( setting, action.animSets.right.thirdPersonStandingAlias ) + time = prop.GetSequenceDuration( animation ) + times[ model ].append( { time = time, animation = animation } ) + printt( "Standing Right: " + time ) + } + + printt( " " ) + } + } + + prop.Kill_Deprecated_UseDestroyInstead() + } + + printt( "Time comparison: " ) + bool wrong = false + for ( int i = 0; i < times[ models[0] ].len(); i++ ) + { + if ( times[models[0]][i].time == times[models[1]][i].time ) + { + printt( "MATCH: " + ( i + 1 ) + " times: " + times[models[0]][i].time + " " + times[models[1]][i].time + " " + times[models[1]][i].animation ) + } + else + { + printt( "MISMATCH: " + ( i + 1 ) + " times: " + times[models[0]][i].time + " " + times[models[1]][i].time + " " + times[models[1]][i].animation ) + wrong = true + } + } +// Assert( !wrong, "Times did not match between male and female, see above" ) +} + + +#if SERVER +bool function ClientCommand_TitanKneel( entity player, array<string> args ) +{ + entity titan = player.GetPetTitan() + if ( !IsAlive( titan ) ) + return true + + titan.Signal( "titanKneel" ) + titan.s.standQueued = false + return true +} + +/*bool function ClientCommand_TitanStand( entity player, array<string> args ) +{ + entity titan = player.GetPetTitan() + if ( !IsAlive( titan ) ) + return true + + titan.Signal( "titanStand" ) + titan.s.standQueued = true + titan.s.kneelQueued = false + return true +}*/ + +bool function ClientCommand_TitanNextMode( entity player, array<string> args ) +{ + if ( !IsAlive( player ) ) + return true + + entity titan = player.GetPetTitan() + if ( IsAlive( titan ) ) + NPCTitanNextMode( titan, player ) + + return true +} +#endif // SERVER + + +function EmbarkLine( player, titan ) +{ + player.EndSignal( "startembark" ) + local ref = player.LookupAttachment( "ref" ) + local hijack = titan.LookupAttachment( "hijack" ) + local origin + for ( ;; ) + { + origin = titan.GetAttachmentOrigin( hijack ) + DebugDrawLine( player.GetOrigin(), origin, 255, 0, 0, true, 0.15 ) + + origin = player.GetAttachmentOrigin( ref ) + DebugDrawLine( player.GetOrigin(), origin, 0, 255, 0, true, 0.15 ) + WaitFrame() + } +} + + +#if SERVER +function PlayerEmbarksTitan( entity player, entity titan, table embark ) +{ + //player.SetOrigin( Vector(314.971405, -1826.728638, 116.031250)) + //player.SetAngles( Vector(0.000000, 133.945892, 0.000000)) + //titan.SetOrigin( Vector(284.887970, -1622.180542, 112.093750)) + //titan.SetAngles(Vector(0.000000, 112.264252, 0)) + + entity soul = titan.GetTitanSoul() + string settings = GetSoulTitanSubClass( soul ) + printt( "TitanEmbarkDebug: Player ", player.GetOrigin(), player.GetAngles(), " Titan ", titan.GetOrigin(), titan.GetAngles(), settings, GetMapName() ) + + + Assert( IsAlive( titan ) ) + Assert( IsAlive( player ) ) + + player.SetInvulnerable() + + player.EndSignal( "OnDeath" ) + player.EndSignal( "TitanEjectionStarted" ) + titan.EndSignal( "OnDeath" ) + + titan.Signal( "player_embarks_titan" ) + player.Signal( "player_embarks_titan" ) + +// Assert( !InSolid( titan ), titan + " is in solid" ) + + DisableCloak( player ) + + entity groundEntity = titan.GetGroundEntity() + vector startOrigin = titan.GetGroundRelativePos() + vector startAngles = titan.GetAngles() + + OnThreadEnd( + function() : ( player, groundEntity, startOrigin, startAngles ) + { + if ( IsValid( player ) ) + { + player.SetCinematicEventFlags( player.GetCinematicEventFlags() & (~CE_FLAG_EMBARK) ) + TitanEmbark_PlayerCleanup( player ) + player.p.isEmbarking = false + + if ( IsAlive( player ) ) + { + if ( player.IsTitan() ) //Defensive fix, sometimes in SP (when game is shutting down etc ) this can run without the player being alive + { + TitanEmbarkFailsafe( player, null, groundEntity, startOrigin ) + Remote_CallFunction_Replay( player, "ServerCallback_TitanEmbark" ) + LetTitanPlayerShootThroughBubbleShield( player ) + } + else + { + player.Die() //Defensive fix, sometimes in SP (when game is shutting down etc ) this can run without the player being alive + } + + } + } + } + ) + + // track the embarking player so we can kill him if the titan dies + titan.s.embarkingPlayer <- player + player.p.isEmbarking = true + + #if HAS_STATS + UpdatePlayerStat( player, "misc_stats", "titanEmbarks", 1 ) + #endif + #if SERVER && MP + PIN_AddToPlayerCountStat( player, "embarks" ) + PIN_PlayerAbility( player, "", "embark", {}, 0 ) + #endif + + player.SetCinematicEventFlags( player.GetCinematicEventFlags() | CE_FLAG_EMBARK ) + + waitthread PlayerEmbarksTitan_PlayerBecomesTitan( player, titan, embark ) +} + +function PlayerEmbarksTitan_PlayerBecomesTitan( entity player, entity titan, table embark ) +{ + // a place to store the player finish time + table e + + e.threads <- 0 + e.embarkAction <- embark.action + e.animSet <- embark.animSet + e.audioSet <- embark.audioSet + + e.canStand <- TitanCanStand( titan ) + + e.shouldDoRegularEmbark <- ShouldDoRegularEmbark( titan ) + + // player and titan do their anims and wait for each other to finish + thread TitanEmbark_TitanEmbarks( player, titan, e ) + waitthread TitanEmbark_PlayerEmbarks( player, titan, e ) +} + +bool function ShouldDoRegularEmbark( entity titan ) +{ + entity soul = titan.GetTitanSoul() + + if ( IsSingleplayer() && GetSoulPlayerSettings( soul ) == "titan_buddy" ) + { + return titan.GetNPCState() == "combat" || titan.GetNPCState() == "alert" + } + + return true +} + +bool function TitanHasLeftAndRightEmbarkAnims( entity titan ) +{ + entity soul = titan.GetTitanSoul() + string settings = GetSoulPlayerSettings( soul ) + var hasAnims = Dev_GetPlayerSettingByKeyField_Global( settings, "hasLeftRightEmbarks" ) + if ( hasAnims != null && hasAnims == 1 ) + { + return true + } + + return false +} + +function TitanEmbark_PlayerCleanup( player ) +{ + expect entity( player ) + + player.SetSyncedEntity( null ) + DeployViewModelAndEnableWeapons( player ) + player.UnforceStand() + player.UnforceCrouch() + //printt("Clearing invulnerable") + player.ClearInvulnerable() + player.ClearParent() + //Let player jump in air after getting out if he wants to + player.TouchGround() + player.Anim_Stop() +} + + +void function ForceScriptedEmbark( entity player, entity titan ) +{ + HolsterViewModelAndDisableWeapons( player ) + ClearPlayerAnimViewEntity( player ) + player.ClearParent() + PilotBecomesTitan( player, titan ) + + thread PlayAnim( player, "cqb_idle_mp" ) + player.Anim_Stop() + + player.SetOrigin( titan.GetOrigin() ) + vector angles = titan.GetAngles() + angles.z = 0 + angles.x = 0 + player.SetAngles( angles ) + player.SnapEyeAngles( angles ) + + SetStanceStand( player.GetTitanSoul() ) + + TitanEmbark_PlayerCleanup( player ) + if ( player.ContextAction_IsBusy() ) + player.ContextAction_ClearBusy() +} + + +function TitanEmbarkFailsafe( entity player, entity titan, entity groundEnt, vector startOrigin ) +{ + if ( GetCurrentPlaylistVarInt( "player_embark_in_solid_checks", 0 ) != 1 ) + return + + #if DEV + if ( file.embarkDebugPrint ) + { + if ( IsValid( titan ) ) + printt( "TitanEmbarkFailsafe, before PutEntityInSafeSpot: player origin: " + player.GetOrigin() + ", titan origin: " + titan.GetOrigin() + " safeStartPoint: " + startOrigin ) + else + printt( "TitanEmbarkFailsafe, before PutEntityInSafeSpot: player origin: " + player.GetOrigin() + ", null titan, safeStartPoint: " + startOrigin ) + } + #endif + + if ( !PutEntityInSafeSpot( player, titan, groundEnt, startOrigin, player.GetOrigin() ) ) + player.SetOrigin( startOrigin ) + + #if DEV + if ( file.embarkDebugPrint ) + { + if ( IsValid( titan ) ) + printt( "TitanEmbarkFailsafe, after PutEntityInSafeSpot: player origin: " + player.GetOrigin() + ", titan origin: " + titan.GetOrigin() + " safeStartPoint: " + startOrigin ) + else + printt( "TitanEmbarkFailsafe, after PutEntityInSafeSpot: player origin: " + player.GetOrigin() + ", null titan, safeStartPoint: " + startOrigin ) + } + #endif +} + +function TitanEmbark_PlayerEmbarks( entity player, entity titan, table e ) +{ + player.EndSignal( "OnDeath" ) + player.EndSignal( "TitanEjectionStarted" ) + titan.EndSignal( "OnDeath" ) + + e.threads++ + OnThreadEnd( + function() : ( player, e ) + { + if ( IsValid( player ) ) + { + // ensure these are cleared regardless + ClearPlayerAnimViewEntity( player ) + + if ( player.ContextAction_IsBusy() ) + player.ContextAction_ClearBusy() + } + + e.threads-- + if ( !e.threads ) + { + Signal( e, "OnComplete" ) + } + } + ) + + player.ContextAction_SetBusy() + + bool standing = false + + if ( e.canStand ) + { + if ( e.shouldDoRegularEmbark ) + { + player.ForceStand() + switch ( titan.GetTitanSoul().GetStance() ) + { + case STANCE_KNEELING: + case STANCE_KNEEL: + standing = false + break + + default: + standing = true + break + } + } + else + { + standing = false + } + } + else + { + player.ForceCrouch() + } + + HolsterViewModelAndDisableWeapons( player ) + + FirstPersonSequenceStruct sequence + sequence.attachment = "hijack" + sequence.useAnimatedRefAttachment = expect bool ( e.embarkAction.useAnimatedRefAttachment ) + sequence.blendTime = 0.5 + + entity soul = titan.GetTitanSoul() + string settings = GetSoulTitanSubClass( soul ) + + local hasViewCone + + // string thirdPersonAudio + // string firstPersonAudio + + if ( standing ) + { + sequence.firstPersonAnim = GetAnimFromAlias( settings, e.animSet.firstPersonStandingAlias ) + sequence.thirdPersonAnim = GetAnimFromAlias( settings, e.animSet.thirdPersonStandingAlias ) + hasViewCone = false + // thirdPersonAudio = GetAudioFromAlias( settings, e.audioSet.thirdPersonStandingAudioAlias ) + // firstPersonAudio = GetAudioFromAlias( settings, e.audioSet.firstPersonStandingAudioAlias ) + + } + else + { + sequence.firstPersonAnim = GetAnimFromAlias( settings, e.animSet.firstPersonKneelingAlias ) + sequence.thirdPersonAnim = GetAnimFromAlias( settings, e.animSet.thirdPersonKneelingAlias ) + hasViewCone = true + // thirdPersonAudio = GetAudioFromAlias( settings, e.audioSet.thirdPersonKneelingAudioAlias ) + // firstPersonAudio = GetAudioFromAlias( settings, e.audioSet.firstPersonKneelingAudioAlias ) + } + + sequence.thirdPersonAnimIdle = "pt_mount_idle" + + bool doFirstPersonAnim = true + + if ( sequence.firstPersonAnim == "" ) + {// if there is no first person anim, then there must be a third person camera + sequence.thirdPersonCameraAttachments.append( "VDU" ) + sequence.thirdPersonCameraVisibilityChecks = true + doFirstPersonAnim = false + } + + if ( hasViewCone ) + { + sequence.viewConeFunction = EmbarkViewCone + thread DelayedClearViewCone( player ) + } + + //thread DelayedDisableEmbarkPlayerHud( player, sequence ) + + AddAnimEvent( player, "phase_shift_start", PhaseEmbarkPhaseStart ) + AddAnimEvent( player, "phase_shift_stop", PhaseEmbarkPhaseStop ) + AddAnimEvent( titan, "cockpit_light_start", CockpitLightStart ) + AddAnimEvent( titan, "cockpit_light_stop", CockpitLightStop ) + + OnThreadEnd( + function() : ( player, titan ) + { + if ( IsValid( player ) ) + { + Signal( player, "PhaseEmbarkPhaseStop" ) + DeleteAnimEvent( player, "phase_shift_start" ) + DeleteAnimEvent( player, "phase_shift_stop" ) + } + + if ( IsAlive( titan ) ) //Consider clearing titan.s.embarkingPlayer here? + titan.Die() + } + ) + + if ( GetCurrentPlaylistVarInt( "fp_embark_enabled", 0 ) == 1 ) + { + Remote_CallFunction_NonReplay( player, "ServerCallback_HideHudForFPEmbark" ) + + // fp embark hacks + entity viewControl = CreateEntity( "point_viewcontrol" ) + viewControl.kv.spawnflags = 56 + DispatchSpawn( viewControl ) + + viewControl.SetParent( player, "headshot" ) + viewControl.SetOrigin( < 4, 0, 0 > ) + viewControl.SetAngles( < 0, 0, 0 > ) + player.SetViewEntity( viewControl, false ) + } + + thread FirstPersonSequence( sequence, player, titan ) + // EmitDifferentSoundsOnEntityForPlayerAndWorld( firstPersonAudio, thirdPersonAudio, titan, player ) + + float animDuration = player.GetSequenceDuration( sequence.thirdPersonAnim ) + + if ( ShouldSkipAheadIntoEmbark( standing, player, titan, e ) ) + { + local duration = player.GetSequenceDuration( sequence.thirdPersonAnim ) + if ( duration >= SKIP_AHEAD_TIME ) + { + player.Anim_SetInitialTime( duration - SKIP_AHEAD_TIME ) + entity viewModel = player.GetFirstPersonProxy() + + if ( IsValid( viewModel ) && EntHasModelSet( viewModel ) && doFirstPersonAnim ) //JFS: Defensive fix for player not having view models sometimes + viewModel.Anim_SetInitialTime( duration - SKIP_AHEAD_TIME ) + + animDuration = SKIP_AHEAD_TIME + } + } + + thread Embark_DelayedFadeOut( player, titan, animDuration ) + + WaittillAnimDone( player ) + + Signal( player, "PhaseEmbarkPhaseStop" ) + + ClearPlayerAnimViewEntity( player ) + PilotBecomesTitan( player, titan ) + + thread PlayAnim( player, "cqb_idle_mp" ) + player.Anim_Stop() + player.SetVelocity( <0,0,0> ) + + player.SetOrigin( titan.GetOrigin() ) + local angles = titan.GetAngles() + angles.z = 0 + angles.x = 0 + player.SetAngles( angles ) + player.SnapEyeAngles( angles ) + + // soul stuff should be from anim event + Assert( IsServer() ) + SetStanceStand( player.GetTitanSoul() ) + titan.Destroy() +} + +void function Embark_DelayedFadeOut( entity player, entity titan, float delay ) +{ + if ( !IsAlive( player ) ) + return + + if ( !IsValid( titan ) ) + return + + player.EndSignal( "OnDeath" ) + titan.EndSignal( "OnDestroy" ) + + wait delay - EMBARK_FADE_TIME + + if ( GetCurrentPlaylistVarInt( "fp_embark_enabled", 0 ) == 0 ) + { + ScreenFadeToBlack( player, EMBARK_FADE_TIME, EMBARK_FADE_TIME + 0.2 ) // a little extra so we stay black + wait EMBARK_FADE_TIME + } + else + { + OnThreadEnd( function() : ( player ) + { + player.ClearViewEntity() + }) + + wait EMBARK_FADE_TIME - 0.2 + ScreenFadeToBlack( player, 0.2, 0.4 ) + wait 0.2 + player.ClearViewEntity() // make sure player is in normal first person again + } + + ScreenFadeFromBlack( player, EMBARK_FADE_TIME, EMBARK_FADE_TIME ) +} + +void function PlayStartupSounds( entity titan ) +{ + entity soul = titan.GetTitanSoul() + if ( !IsValid( soul ) ) + return + entity player = soul.GetBossPlayer() + if ( !IsValid( player ) ) + return + + player.EndSignal( "OnDeath" ) + + string titanSettings = GetSoulPlayerSettings( soul ) + var startupSound = Dev_GetPlayerSettingByKeyField_Global( titanSettings, "startup_sound" ) + + if ( startupSound != null ) + EmitSoundOnEntityOnlyToPlayer( player, player, expect string(startupSound) ) +} + + +void function CockpitLightStart( entity titan ) +{ + thread StartCockpitLightThink( titan, 5.0 ) +} + +void function StartCockpitLightThink( entity titan, float timeout ) +{ + titan.EndSignal( "OnDestroy" ) + titan.EndSignal( "CockpitLightStop" ) + + int attachID = titan.LookupAttachment( "HIJACK" ) + int fxID = GetParticleSystemIndex( file.cockpitLightFX ) + entity fx = StartParticleEffectOnEntity_ReturnEntity( titan, fxID, FX_PATTACH_POINT_FOLLOW, attachID ) + + OnThreadEnd( + function() : ( fx ) + { + if ( IsValid( fx ) ) + EffectStop( fx ) + } + ) + + if ( timeout < 0 ) + WaitForever() + else + wait timeout +} + +void function CockpitLightStop( entity titan ) +{ + titan.Signal( "CockpitLightStop" ) +} + +void function PhaseEmbarkPhaseStart( entity player ) +{ + player.MakeInvisible() + PlayPhaseShiftDisappearFX( player ) + EmitSoundOnEntity( player, "pilot_phaseembark_activate_3p" ) + + if ( GetCurrentPlaylistVarInt( "fp_embark_enabled", 0 ) == 1 ) + { + player.PhaseShiftBegin( 0.0, 0.2 ) + player.GetPetTitan().SetForceVisibleInPhaseShift( true ) // doesn't work for some reason + } + + thread PhaseEmbarkPhaseCleanup( player ) +} + +void function PhaseEmbarkPhaseCleanup( player ) +{ + EndSignal( player, "OnDeath" ) + + OnThreadEnd( + function() : ( player ) + { + if ( IsValid( player ) ) + { + player.MakeVisible() + } + } + ) + + WaitSignal( player, "PhaseEmbarkPhaseStop" ) +} + +void function PhaseEmbarkPhaseStop( entity player ) +{ + Signal( player, "PhaseEmbarkPhaseStop" ) + PlayPhaseShiftDisappearFX( player ) + EmitSoundOnEntity( player, "pilot_phaseembark_end_3p" ) + + if ( GetCurrentPlaylistVarInt( "fp_embark_enabled", 0 ) == 1 ) + player.PhaseShiftCancel() +} + +function ShouldSkipAheadIntoEmbark( standing, player, titan, e ) +{ + if ( !standing ) + return false + + if ( !e.embarkAction.canSkipAhead ) + return false + + local playerEye = player.EyePosition() + local titanOrg = titan.GetOrigin() + local vec = playerEye - titanOrg + vec.Norm() + vec.z = 0 + local start = playerEye + local end = playerEye + vec * 24 + + if ( Distance( player.GetOrigin(), titan.GetOrigin() ) >= 145 ) + return false + + local mask = TRACE_MASK_PLAYERSOLID + TraceResults result = TraceLine( start, end, [ titan, player ], mask, TRACE_COLLISION_GROUP_NONE ) + //DebugDrawLine( start, result.endPos, 0, 255, 0, true, 10.0 ) + //DebugDrawLine( result.endPos, end, 255, 0, 0, true, 10.0 ) + return result.fraction < 1.0 +} +#endif // SERVER + +function DelayedDisableEmbarkPlayerHud( player, sequence ) +{ + player.EndSignal( "OnDeath" ) + + local duration = player.GetSequenceDuration( sequence.thirdPersonAnim ) + wait duration - 1.0 + player.SetCinematicEventFlags( player.GetCinematicEventFlags() | CE_FLAG_EMBARK ) +} + +#if SERVER +function DelayedClearViewCone( player ) +{ + player.EndSignal( "OnDeath" ) + wait 1.0 + player.PlayerCone_SetLerpTime( 0.5 ) + player.PlayerCone_FromAnim() + player.PlayerCone_SetMinYaw( 0 ) + player.PlayerCone_SetMaxYaw( 0 ) + player.PlayerCone_SetMinPitch( 0 ) + player.PlayerCone_SetMaxPitch( 0 ) +} + +void function EmbarkViewCone( entity player ) +{ + player.PlayerCone_FromAnim() + player.PlayerCone_SetMinYaw( -70 ) + player.PlayerCone_SetMaxYaw( 60 ) + player.PlayerCone_SetMinPitch( -80 ) + player.PlayerCone_SetMaxPitch( 30 ) +} + +function TitanEmbark_TitanEmbarks( player, titan, e ) +{ + expect entity( player ) + expect entity( titan ) + + player.EndSignal( "OnDeath" ) + player.EndSignal( "TitanEjectionStarted" ) + titan.EndSignal( "OnDeath" ) + + titan.ContextAction_SetBusy() + + AddAnimEvent( titan, "play_startup_sound", PlayStartupSounds ) + + e.threads++ + OnThreadEnd( + function() : ( e, player, titan ) + { + e.threads-- + if ( !e.threads ) + { + Signal( e, "OnComplete" ) + } + + if ( !IsAlive( player ) && IsAlive( titan ) ) + { + titan.Anim_Stop() + titan.ContextAction_ClearBusy() + DeleteAnimEvent( titan, "play_startup_sound" ) + } + } + ) + + local soul = titan.GetTitanSoul() + + // dont let other players get in + + local animation +// local waittillAnimDone + local alignFront + bool standing = false + + if ( e.canStand ) + { + if ( e.shouldDoRegularEmbark ) + { + // default + switch ( titan.GetTitanSoul().GetStance() ) + { + case STANCE_KNEELING: + case STANCE_KNEEL: + animation = e.animSet.titanKneelingAnim + alignFront = false + break + + default: + animation = e.animSet.titanStandingAnim + if ( TitanHasLeftAndRightEmbarkAnims( titan ) ) + alignFront = false + else + alignFront = true + standing = true + break + } + } + else + { + // special for BT if he is in casual mode + animation = e.animSet.titanKneelingAnim + alignFront = false + } + } + else + { + animation = "at_mount_kneel_without_standing" + alignFront = false +// waittillAnimDone = false + } + + //sequence.blendTime = 0.5 + + printt("This is mount animation name: " + animation ) + if ( e.embarkAction.alignFrontEnabled && alignFront ) + { + local titanOrg = titan.GetOrigin() + local vec = player.GetOrigin() - titanOrg + local angles = VectorToAngles( vec ) + angles.x = 0 + angles.z = 0 + thread PlayAnimGravityClientSyncing( titan, animation, titanOrg, angles ) + } + else + { + thread PlayAnimGravityClientSyncing( titan, animation ) + } + + if ( ShouldSkipAheadIntoEmbark( standing, player, titan, e ) ) + { + local duration = titan.GetSequenceDuration( animation ) + + if ( duration >= SKIP_AHEAD_TIME ) // failsafe + titan.Anim_SetInitialTime( duration - SKIP_AHEAD_TIME ) + } + + // titan will become player now + WaitForever() +} + +bool function ClientCommand_TitanDisembark( entity player, array<string> args ) +{ + if ( !PlayerCanDisembarkTitan( player ) ) + return true + + ScreenFade( player, 0, 1, 0, 255, 0.2, 0.2, FFADE_IN | FFADE_PURGE ) + player.CockpitStartDisembark() + Remote_CallFunction_Replay( player, "ServerCallback_TitanDisembark" ) + + thread PlayerDisembarksTitan( player ) + + return true +} + +void function ForcedTitanDisembark( entity player ) +{ + Assert( PlayerCanDisembarkTitan( player ) ) + + player.CockpitStartDisembark() + Remote_CallFunction_Replay( player, "ServerCallback_TitanDisembark" ) + + waitthread PlayerDisembarksTitan( player ) +} + +function ForcedTitanDisembarkCustomAnims( entity player, FirstPersonSequenceStruct functionref( entity, entity ) playerSequenceFunc, FirstPersonSequenceStruct functionref( entity, entity ) titanSequenceFunc ) +{ + Assert( PlayerCanDisembarkTitan( player ) ) + + player.CockpitStartDisembark() + Remote_CallFunction_Replay( player, "ServerCallback_TitanDisembark" ) + + player.p.isCustomDisembark = true + waitthread PlayerDisembarksTitanWithSequenceFuncs( player, playerSequenceFunc, titanSequenceFunc ) + player.p.isCustomDisembark = false +} + +#endif // SERVER + +function PlayerCanDisembarkTitan( entity player ) +{ + if ( !player.IsTitan() ) + return false + + if ( !IsAlive( player ) ) + return false + + if ( IsValid( player.GetParent() ) ) + return false + + if ( Riff_TitanExitEnabled() == eTitanExitEnabled.Never ) + return false + + if ( !CanDisembark( player ) ) + return false + + #if SERVER + if ( player.IsNoclipping() ) + return false + // client doesn't know these things + if ( IsPlayerDisembarking( player ) ) + return false + if ( IsPlayerEmbarking( player ) ) + return false + #endif + + if ( !HasSoul( player ) ) + return false + + local soul = player.GetTitanSoul() + if ( soul.IsEjecting() ) + return false + + Assert( soul == player.GetTitanSoul() ) + + return true +} + +#if SERVER + +function PlayerDisembarksTitan( player ) +{ + expect entity( player ) + PlayerDisembarksTitanWithSequenceFuncs( player, GetDisembarkSequenceForPlayer, GetDisembarkSequenceForTitan ) +} + +void function PlayerDisembarksTitanWithSequenceFuncs( entity player, FirstPersonSequenceStruct functionref( entity, entity ) playerSequenceFunc, FirstPersonSequenceStruct functionref( entity, entity ) titanSequenceFunc ) +{ + //printt( "Player disembarking with origin " + player.GetOrigin() + " and yaw " + player.GetAngles().y ) + + //player.SetOrigin( Vector(420.847626, -5214.960938, 173.789520) ) + //player.SetAngles( Vector(0.000000, 179.572052, 0.000000 ) ) + + printt( "TitanDisembarkDebug: Player ", player.GetOrigin(), player.GetAngles(), GetMapName() ) + + player.EndSignal( "OnDeath" ) + player.Signal( "DisembarkingTitan" ) + + //Assert( !InSolid( player ), player + " is in solid" ) + + local e = {} + e.titan <- null + e.PilotCleanUpDone <- false + + e.startOrigin <- player.GetOrigin() + //e.startAngles <- player.GetAngles() + + player.p.isDisembarking = true + player.SetCinematicEventFlags( player.GetCinematicEventFlags() | CE_FLAG_DISEMBARK ) + + bool wasCustomDisembark = player.p.isCustomDisembark + + player.ContextAction_SetBusy() + + OnThreadEnd( + function() : ( player, e ) + { + if ( IsValid( player ) ) + { + PlayerEndsDisembark( player, e ) + + local titan = e.titan + if ( !IsValid( titan ) ) + titan = null + } + + if ( IsAlive( expect entity( e.titan ) ) ) + { + if ( IsAlive( player ) ) + { + thread PlayerOwnsTitanUntilSeparation( player, e.titan, 80 ) + } + + delete e.titan.s.disembarkingPlayer + ClearInvincible( expect entity( e.titan ) ) + + + //Make Titan get up immediately if he's kneeling and can stand + //If he can't get up, well, then since the titan doesn't move when crouched he's going to be stuck... + if ( !( "embarkingPlayer" in e.titan.s ) ) + { + thread TitanNPC_Think( expect entity( e.titan ) ) //titan.s.disableAutoTitanConversation is deleted inside here + } + } + } + ) + + bool standing = player.IsStanding() + + player.SetInvulnerable() + player.SnapFeetToEyes() + + entity titan = CreateAutoTitanForPlayer_ForTitanBecomesPilot( player ) + DispatchSpawn( titan ) + e.titan = titan + + if ( !PlayerIsFarOffTheGround( player, [ player,titan ] ) ) //PlayerIsFarOffTheGround() necessary now for R2 because we have Titans that can jump/geo where Titans can fall down from large heights. Without check, mid-air disembarking will cause player to be teleported to the ground + { + vector ornull clampedPos = NavMesh_ClampPointForAIWithExtents( titan.GetOrigin(), titan, file.smallDisembarkFailSafeTeleportVector ) + if ( clampedPos == null ) + clampedPos = NavMesh_ClampPointForAIWithExtents( titan.GetOrigin(), titan, file.largeDisembarkFailSafeTeleportVector ) + + if ( clampedPos != null ) + { + expect vector( clampedPos ) + vector titanOrigin = titan.GetOrigin() + + array<entity> ignoreEnts = [] + ignoreEnts.append( titan ) + + TraceResults result = TraceHull( titanOrigin, titanOrigin, titan.GetBoundingMins(), titan.GetBoundingMaxs(), ignoreEnts, TRACE_MASK_TITANSOLID, TRACE_COLLISION_GROUP_NONE ) + + // expensive checks to make sure titan doesn't teleport to navmesh on other side of wall usually in invalid places + if ( result.startSolid || + TraceLineSimple( titanOrigin + Vector( 0, 0, 128 ), clampedPos + Vector( 0, 0, 0 ), titan ) == 1.0 || + TraceLineSimple( titanOrigin + Vector( 0, 0, 200 ), clampedPos + Vector( 0, 0, 0 ), titan ) == 1.0 || + TraceLineSimple( titanOrigin + Vector( 0, 0, 200 ), clampedPos + Vector( 0, 0, 128 ), titan ) == 1.0 ) + { + #if DEV + if ( file.embarkDebugPrint ) + { + printt( "PlayerDisembarksTitanWithSequenceFuncs, player origin: " + player.GetOrigin()+ ", titan origin: " + titan.GetOrigin() + ", clampedPos: " + clampedPos ) + } + #endif + titan.SetOrigin( clampedPos ) + titan.ForceCheckGroundEntity() + } + } + } + else + { + #if DEV + if ( file.embarkDebugPrint ) + { + printt( "PlayerIsFarOffGround() returned true, skip doing NavMesh_ClampPointForAIWithExtents checks" ) + } + #endif + } + + titan.s.disembarkingPlayer <- player + titan.EndSignal( "OnDeath" ) + + player.SetSyncedEntity( titan ) + titan.s.disableAutoTitanConversation <- true + + Assert( titan.IsTitan() ) + Assert( IsAlive( player ) ) + Assert( player.IsTitan() ) + //Set player to be temporarily invulnerable. Will be removed at end of animation + //printt("Set player invulnerable") + HolsterViewModelAndDisableWeapons( player ) //Holstering weapon before becoming pilot so we don't play the holster animation as a pilot. Player as Titan won't play the holster animation either since it'll be interrupted by the disembark animation + + TitanBecomesPilot( player, titan ) + + string titanSubClass = GetSoulTitanSubClass( titan.GetTitanSoul() ) + switch ( titanSubClass ) + { + case "ogre": + ShowMainTitanWeapons( titan ) //JFS: Because we hide the Titan's weapons upon kneeling for ogre + break + } + + titan.s.disembarkTime <- Time() // disembark debounce + + // compound strings into these animations: + // pt_dismount_atlas_stand + // pt_dismount_ogre_stand + // pt_dismount_stryder_stand + // pt_dismount_atlas_crouch + // pt_dismount_ogre_crouch + // pt_dismount_stryder_crouch + // ptpov_dismount_atlas_stand + // ptpov_dismount_ogre_stand + // ptpov_dismount_stryder_stand + // ptpov_dismount_atlas_crouch + // ptpov_dismount_ogre_crouch + // ptpov_dismount_stryder_crouch + + FirstPersonSequenceStruct playerSequence = playerSequenceFunc( player, titan ) + FirstPersonSequenceStruct titanSequence = titanSequenceFunc( player, titan ) + + #if SERVER + StatusEffect_StopAll( player, eStatusEffect.lockon_detected_titan ) + #endif + + player.ForceStand() + + thread FirstPersonSequence( titanSequence, titan ) + thread FirstPersonSequence( playerSequence, player, titan ) + + if ( !wasCustomDisembark ) + thread ClearParentBeforeIntersect( player, titan, playerSequence.thirdPersonAnim, e ) + + if ( !standing ) + { + SetStanceKneel( titan.GetTitanSoul() ) + } + + //player.Anim_EnablePlanting() + + #if SERVER && MP + PIN_AddToPlayerCountStat( player, "disembarks" ) + PIN_PlayerAbility( player, "", "disembark", {}, 0 ) + #endif + + WaittillAnimDone( player ) +} + +bool function PlayerIsFarOffTheGround( entity player, array<entity> ignoreEnts ) +{ + vector boundingMaxs = player.GetBoundingMaxs() + float halfHeight = boundingMaxs.z / 2.0 + + vector startpos = player.GetOrigin() + vector endpos = startpos + endpos.z -= halfHeight + + TraceResults result = TraceHull( startpos, endpos, player.GetBoundingMins(), player.GetBoundingMaxs(), ignoreEnts, TRACE_MASK_PLAYERSOLID, TRACE_COLLISION_GROUP_PLAYER ) + //PrintTraceResults( result ) + + if ( result.startSolid ) + return false + + if ( result.allSolid ) + return false + + return ( result.fraction >= 1.0 ) +} + +FirstPersonSequenceStruct function GetDisembarkSequenceForPlayer( entity player, entity titan ) +{ + string titanSubClass = GetSoulTitanSubClass( titan.GetTitanSoul() ) + + string player3pAnim, player1pAnim + if ( player.IsStanding() ) + { + player3pAnim = "pt_dismount_" + titanSubClass + "_stand" + player1pAnim = "ptpov_dismount_" + titanSubClass + "_stand" + } + else + { + player3pAnim = "pt_dismount_" + titanSubClass + "_crouch" + player1pAnim = "ptpov_dismount_" + titanSubClass + "_crouch" + } + + if ( player.HasPassive( ePassives.PAS_FAST_EMBARK ) ) + { + player1pAnim += "_fast" + player3pAnim += "_fast" + } + + FirstPersonSequenceStruct playerSequence + playerSequence.blendTime = 0 + playerSequence.teleport = true + playerSequence.attachment = "hijack" + playerSequence.thirdPersonAnim = player3pAnim + playerSequence.firstPersonAnim = player1pAnim + playerSequence.useAnimatedRefAttachment = true + + return playerSequence +} + +FirstPersonSequenceStruct function GetDisembarkSequenceForTitan( entity player, entity titan ) +{ + bool standing = player.IsStanding() + + string titanDisembarkAnim + if ( standing ) + titanDisembarkAnim = "at_dismount_stand" + else + titanDisembarkAnim = "at_dismount_crouch" + + if ( player.HasPassive( ePassives.PAS_FAST_EMBARK ) ) + titanDisembarkAnim += "_fast" + + vector origin = titan.GetOrigin() + vector angles = titan.EyeAngles() + angles.z = 0 + angles.x = 0 + + FirstPersonSequenceStruct titanSequence + titanSequence.blendTime = 0.3 + titanSequence.thirdPersonAnim = titanDisembarkAnim + if ( !standing ) + titanSequence.thirdPersonAnimIdle = "at_MP_embark_idle_blended" + titanSequence.gravity = true + titanSequence.origin = origin + titanSequence.angles = angles + + return titanSequence +} + +function DelayedSafePlayerLocationForDisembark( entity player, entity titan ) +{ + float currentTime = Time() + float allowedTime = player.p.isCustomDisembark ? 10.0 : 2.0 + + player.EndSignal( "OnDestroy" ) + + while( IsPlayerDisembarking( player ) ) + { + Assert( Time() - currentTime < allowedTime ) // Failsafe of waiting 2 seconds in case SOMETHING REALLY GOES WRONG. + if ( !IsAlive( player ) ) + return + + WaitFrame() + } + + if ( player.ContextAction_IsActive() ) //Immediately after disembarking player might have gotten pulled into another context action e.g. embarking into evac dropship + return + + player.ClearParent() + player.PlayerCone_Disable() + player.ViewOffsetEntity_Clear() + player.GetFirstPersonProxy().HideFirstPersonProxy() + + vector safeStartPoint + + if ( IsValid( titan ) ) + { + vector titanBoundingMaxs = titan.GetBoundingMaxs() + float halfTitanHeight = titanBoundingMaxs.z * 0.5 + safeStartPoint = titan.GetOrigin() + < 0, 0, halfTitanHeight > //Let the start point of PutEntityInSafeSpot be closer to where the player is when disebmarking instead of the ground origin + } + else + { + titan = null + safeStartPoint = player.GetOrigin() + } + + #if DEV + if ( file.embarkDebugPrint ) + printt( "DelayedSafePlayerLocationForDisembark, before PutEntityInSafeSpot: player origin: " + player.GetOrigin() + ", titan origin: " + titan.GetOrigin() + " safeStartPoint: " + safeStartPoint ) + #endif + + + + if ( !PutEntityInSafeSpot( player, titan, null, safeStartPoint, player.GetOrigin() ) ) + player.SetOrigin( safeStartPoint ) + + #if DEV + if ( file.embarkDebugPrint ) + printt( "DelayedSafePlayerLocationForDisembark, after PutEntityInSafeSpot: player origin: " + player.GetOrigin() + ", titan origin: " + titan.GetOrigin() + " safeStartPoint: " + safeStartPoint ) + #endif + +} + +function ClearParentBeforeIntersect( entity player, entity titan, anim, e ) +{ + player.EndSignal( "OnDeath" ) + player.EndSignal( "OnAnimationDone" ) + player.EndSignal( "OnAnimationInterrupted" ) + //local mins = player.GetBoundingMins() + //local maxs = player.GetBoundingMaxs() + + OnThreadEnd( + function() : ( player, e ) + { + if ( IsValid( player ) ) + thread DelayedSafePlayerLocationForDisembark( player, expect entity( e.titan ) ) + } + ) + + wait 0.25 + + vector lastOrigin = player.GetOrigin() + for ( ;; ) + { + if ( EntityInSolid( player, titan, 24 ) ) + break + + lastOrigin = player.GetOrigin() + WaitFrame() + } + + player.SetOrigin( lastOrigin ) +} + +function LockedViewCone( human ) +{ + human.PlayerCone_FromAnim() + human.PlayerCone_SetMinYaw( 0 ) + human.PlayerCone_SetMaxYaw( 0 ) + human.PlayerCone_SetMinPitch( 0 ) + human.PlayerCone_SetMaxPitch( 0 ) +} + + +function PlayerOwnsTitanUntilSeparation( player, titan, dist ) +{ + titan.SetOwner( player ) + + player.EndSignal( "OnDeath" ) + titan.EndSignal( "OnDeath" ) + + OnThreadEnd( + function () : ( player, titan ) + { + if ( !IsValid( titan ) ) + return + + titan.SetOwner( null ) + } + ) + + // wait until player moves away + local distSqr = dist * dist + for ( ;; ) + { + if ( DistanceSqr( titan.GetOrigin(), player.GetOrigin() ) > distSqr ) + break + + wait 0.5 + } +} + +function PlayerEndsDisembark( player, e ) +{ + thread PlayerEndsDisembarkThread( player, e ) +} + +function PlayerEndsDisembarkThread( player, e ) +{ + expect entity( player ) + player.EndSignal( "OnDestroy" ) + + if ( e.PilotCleanUpDone ) + return + + e.PilotCleanUpDone = true + bool wasCustomDisembark = player.p.isCustomDisembark + + wait 0.1 + + ClearPlayerAnimViewEntity( player ) + if ( player.ContextAction_IsBusy() ) + player.ContextAction_ClearBusy() + + player.SetCinematicEventFlags( player.GetCinematicEventFlags() & (~CE_FLAG_DISEMBARK) ) + + player.Show() + TitanEmbark_PlayerCleanup( player ) + player.p.isDisembarking = false + + //// give a player a boost out the door + // + // + if ( IsAlive( player ) && !wasCustomDisembark ) + { + local angles = player.EyeAngles() + if ( IsValid( e.titan ) ) + angles = e.titan.GetAngles() + + angles.x = 0 + angles.z = 0 + local forward = AnglesToForward( angles ) + local up = AnglesToUp( angles ) + local vel = forward * 250 + up * 200 + player.SetVelocity( vel ) + //DebugDrawLine( player.GetOrigin(), player.GetOrigin() + forward * 500, 255, 0, 0, true, 5.0 ) + } +} + +function IsPlayerEmbarking( player ) +{ + expect entity ( player ) + return player.p.isEmbarking +} +#endif // SERVER + +function IsPlayerDisembarking( player ) +{ + expect entity ( player ) + return player.p.isDisembarking +} + +function PlayerCanEmbarkIntoTitan( entity player, entity titan ) //TODO: Collapse PlayerCanEmbarkTitan(), PlayerCanEmbarkIntoTitan(), PlayerCanImmediatelyEmbarkTitan() and TitanIsCurrentlyEmbarkableForPlayer() into one function +{ + if ( player.IsNoclipping() ) + return false + + if ( !TitanIsCurrentlyEmbarkableForPlayer( player, titan ) ) + return false + + return FindBestEmbark( player, titan ) != null +} + +bool function TitanIsCurrentlyEmbarkableForPlayer( entity player, entity titan ) //TODO: Collapse PlayerCanEmbarkTitan(), PlayerCanEmbarkIntoTitan(), PlayerCanImmediatelyEmbarkTitan() and TitanIsCurrentlyEmbarkableForPlayer() into one function +{ + if ( !CanEmbark( player ) ) + return false + + if ( player.Anim_IsActive() ) + return false + + if ( !player.IsHuman() ) + return false + + if ( player.ContextAction_IsActive() ) + return false + + if ( !titan.IsEntAlive() ) + return false + + if ( titan.ContextAction_IsActive() ) + return false + + if ( !titan.IsInterruptable() ) + return false + + if ( IsValid( titan.GetParent() ) ) + return false + + if ( !HasSoul( titan ) ) + return false + + local soul = titan.GetTitanSoul() + + if ( GetDoomedState( titan ) && !PROTO_AlternateDoomedState() ) + return false + + if ( soul.IsEjecting() ) + return false + + #if SERVER + // client doesn't know these things + if ( IsPlayerEmbarking( player ) ) + return false + + if ( IsPlayerDisembarking( player ) ) + return false + #endif + + if ( "disembarkTime" in titan.s ) + { + if ( Time() - titan.s.disembarkTime < 1.65 ) + return false + } + + return true +} + +function FindEmbarkActionForCriteria( criteria ) +{ + local embarkAction + foreach ( option in level.titanEmbarkActions ) + { + bool failed = false + foreach ( key, value in criteria ) + { + if ( value != option[key] ) + { + failed = true + break + } + } + + if ( !failed ) + { + embarkAction = option + break + } + } + + return embarkAction + +} + +function GetRandomEmbarkAction() +{ + return level.titanEmbarkActions[ RandomInt( level.titanEmbarkActions.len() ) ] +} + +function FindBestEmbark( entity player, entity titan, bool doDistCheck = true ) +{ +// if ( IsServer() ) +// printt( "finding best embark for " + player + " to " + titan ) + vector playerPos = player.GetOrigin() + vector titanPos = titan.GetOrigin() + + vector relTitanToPlayerDir = CalculateTitanToPlayerDir( titan, player ) + + local bestAction = null + float bestDot = -2 + float dist = 0 + + if ( doDistCheck ) + { + dist = Distance( playerPos, titanPos ) + if ( dist > level.titanEmbarkFarthestDistance ) + return null + } + //if ( IsServer() ) + // printt( "dist: " + dist ) + + for ( int i = 0; i < 3; i++ ) + { + bestAction = GetBestEmbarkAction( i, player, titan, dist, relTitanToPlayerDir ) + if ( bestAction != null ) + break + } + + if ( bestAction == null ) + return null + + return GenerateEmbarkActionTable( player, titan, bestAction, relTitanToPlayerDir ) +} + +vector function CalculateTitanToPlayerDir( entity titan, entity player ) +{ + vector playerPos = player.GetOrigin() + vector titanPos = titan.GetOrigin() + + vector absTitanToPlayerDir + if ( playerPos == titanPos ) + { + absTitanToPlayerDir = Vector( 1, 0, 0 ) + } + else + { + vector angles = player.EyeAngles() + vector forward = AnglesToForward( angles ) + + absTitanToPlayerDir = ( playerPos - titanPos ) + + + absTitanToPlayerDir.Norm() + +// not needed cause we can't get in without a legal use +// // is the target in my fov? +// if ( forward.Dot( absTitanToPlayerDir * -1 ) < 0.77 ) +// return null + } + + vector titanAngles = titan.GetAngles() + titanAngles.x = 0 + if ( titan.GetTitanSoul().GetStance() >= STANCE_STANDING ) + titanAngles = AnglesCompose( titanAngles, Vector( 0, -30, 0 ) ) + + vector relTitanToPlayerDir = CalcRelativeVector( titanAngles, absTitanToPlayerDir ) + return relTitanToPlayerDir +} + +function GenerateEmbarkActionTable( entity player, entity titan, bestAction, var relTitanToPlayerDir = null ) +{ + bool useFastAnims = player.IsPlayer() && player.HasPassive( ePassives.PAS_FAST_EMBARK ) + + if ( relTitanToPlayerDir == null ) + { + relTitanToPlayerDir = CalculateTitanToPlayerDir( titan, player ) + expect vector( relTitanToPlayerDir ) + } + else + { + expect vector( relTitanToPlayerDir ) + } + + local Table = {} + Table.action <- bestAction + + if ( "animSet" in bestAction ) + { + Table.animSet <- bestAction.animSet + Table.audioSet <- bestAction.audioSet + } + else + { + local bestAnimSet + local bestAudioSet + local bestDot = -2 + Assert( "animSets" in bestAction, "Table has no animSet and no animSets!" ) + foreach ( animSet in bestAction.animSets ) + { + local dot = relTitanToPlayerDir.Dot( animSet.direction ) + + if ( dot > bestDot ) + { + bestAnimSet = animSet + bestAudioSet = animSet.audioSet + bestDot = dot + } + } + + Table.animSet <- bestAnimSet + Table.audioSet <- bestAudioSet + } + + if ( useFastAnims ) + { + Table.animSet = clone Table.animSet + + foreach ( string idx, item in Table.animSet ) + { + if ( IsString( item ) ) + Table.animSet[ idx ] = item + "_fast" + } + } + + return Table +} + +function GetBestEmbarkAction( int priority, entity player, entity titan, float dist, vector relTitanToPlayerDir ) +{ + local bestAction = null + local bestDot = -2 + + foreach ( action in level.titanEmbarkActions ) + { + if ( action.priority != priority ) + continue + + if ( dist > action.distance ) + { + //if ( IsServer() ) + //printt( "Failed: Action " + action.embark + " had dist " + action.distance + " vs actual dist " + dist ) + continue + } + + if ( action.lungeCheck ) + { + if ( player.IsNPC() ) + continue + + if ( player.Lunge_IsActive() != action.lungeCheck ) + continue + } + + local dot = relTitanToPlayerDir.Dot( action.direction ) + + if ( dot < action.minDot ) + { + //if ( IsServer() ) + //printt( "Failed: Action " + action.embark + " had dot " + dot ) + continue + } + + if ( expect bool( action.titanCanStandRequired ) && !TitanCanStand( titan ) ) + { + //if ( IsServer() ) + //printt( "Failed: Action " + action.embark + " cant stand" ) + continue + } + + if ( dot > bestDot ) + { + //if ( IsServer() ) + //printt( "Action " + action.embark + " had dot " + dot ) + bestAction = action + bestDot = dot + } + } + + return bestAction +} + +function FindBestEmbarkForNpcAnim( entity npc, entity titan ) +{ + bool doDistCheck = false + return FindBestEmbark( npc, titan, doDistCheck ) +} + + + + +bool function TitanCanStand( entity titan ) +{ + #if SERVER + vector maxs = titan.GetBoundingMaxs() + vector mins = titan.GetBoundingMins() + + vector start = titan.GetOrigin() + vector end = titan.GetOrigin() + entity soul = titan.GetTitanSoul() + entity ignoreEnt = null + + if ( IsValid( soul.soul.bubbleShield ) ) + ignoreEnt = soul.soul.bubbleShield + int mask = titan.GetPhysicsSolidMask() + //printt( "mask has " + MaskTester( mask ) ) + TraceResults result = TraceHull( start, end, mins, maxs, ignoreEnt, mask, TRACE_COLLISION_GROUP_NONE ) + //printt( "start " + start + " end " + end ) + + //DebugDrawLine( start, result.endPos, 0, 255, 0, true, 5.0 ) + //DebugDrawLine( result.endPos, end, 255, 0, 0, true, 5.0 ) + + bool canStand = result.fraction >= 1.0 + titan.SetCanStand( canStand ) + return canStand + #else + return titan.GetCanStand() != 0 + #endif +} + +bool function PlayerCanEmbarkTitan( entity player, entity titan ) //TODO: Collapse PlayerCanEmbarkTitan(), PlayerCanEmbarkIntoTitan(), PlayerCanImmediatelyEmbarkTitan() and TitanIsCurrentlyEmbarkableForPlayer() into one function +{ + PerfStart( PerfIndexClient.PlayerCanEmbarkTitan1 ) + if ( !TitanIsCurrentlyEmbarkableForPlayer( player, titan ) ) + { + PerfEnd( PerfIndexClient.PlayerCanEmbarkTitan1 ) + return false + } + PerfEnd( PerfIndexClient.PlayerCanEmbarkTitan1 ) + + PerfStart( PerfIndexClient.FindBestEmbark ) + bool res = FindBestEmbark( player, titan ) != null + PerfEnd( PerfIndexClient.FindBestEmbark ) + + return res +} + +bool function PlayerCanImmediatelyEmbarkTitan( entity player, entity titan ) //TODO: Collapse PlayerCanEmbarkTitan(), PlayerCanEmbarkIntoTitan(), PlayerCanImmediatelyEmbarkTitan() and TitanIsCurrentlyEmbarkableForPlayer() into one function +{ + if ( "embarkingPlayer" in titan.s ) + return false + + if ( player.IsNoclipping() ) + return false + + if ( !IsAlive( player ) || !IsAlive( titan ) ) + return false + + return FindBestEmbark( player, titan ) != null +} + +#if SERVER +function PlayerLungesToEmbark( entity player, entity ent ) +{ + Assert( TitanIsCurrentlyEmbarkableForPlayer( player, ent ) ) + + if ( PlayerCanImmediatelyEmbarkTitan( player, ent ) ) + { + table embarkDirection = expect table( FindBestEmbark( player, ent ) ) + thread PlayerEmbarksTitan( player, ent, embarkDirection ) + return + } + + if ( player.IsNoclipping() ) + return + + // already lunging + if ( player.Lunge_IsActive() ) + return + + if ( ShouldStopLunging( player, ent ) ) + return + + player.Lunge_SetTargetEntity( ent, false ) + player.Lunge_SetSmoothTime( 3.0 ) +} + +void function TitanBecomesPilot_UpdateRodeoRiderHud( entity playerTitan, entity npc_titan ) +{ + entity rodeoPilot = GetRodeoPilot( npc_titan ) + if ( !IsValid( rodeoPilot ) ) + return + + Remote_CallFunction_Replay( rodeoPilot, "ServerCallback_UpdateRodeoRiderHud" ) +} + +void function PilotBecomesTitan_UpdateRodeoRiderHud( entity playerTitan, entity npc_titan ) +{ + entity rodeoPilot = GetRodeoPilot( playerTitan ) + if ( !IsValid( rodeoPilot ) ) + return + + Remote_CallFunction_Replay( rodeoPilot, "ServerCallback_UpdateRodeoRiderHud" ) + +} + +void function SetSmallDisembarkFailSafeTeleportVector( vector value ) //TODO: Re-examine this for next game, probably should have different values for SP versus MP +{ + file.smallDisembarkFailSafeTeleportVector = value +} + +void function SetLargeDisembarkFailSafeTeleportVector( vector value ) //TODO: Re-examine this for next game, probably should have different values for SP versus MP +{ + file.largeDisembarkFailSafeTeleportVector = value +} + +#endif // SERVER + +#if DEV +void function SetEmbarkDebugPrint( bool value ) +{ + file.embarkDebugPrint = value +} +#endif
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/weapons/mp_weapon_peacekraber.nut b/Northstar.Custom/mod/scripts/vscripts/weapons/mp_weapon_peacekraber.nut new file mode 100644 index 00000000..a9da541f --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/weapons/mp_weapon_peacekraber.nut @@ -0,0 +1,162 @@ +untyped + +// created by JustANormalUser#0001 on discord + +global function OnWeaponPrimaryAttack_peacekraber; +global function OnWeaponDeactivate_peacekraber +global function OnWeaponActivate_peacekraber + +#if SERVER +global function OnWeaponNpcPrimaryAttack_peacekraber +#endif // #if SERVER + + +const PEACEKRABER_MAX_BOLTS = 11 // this is the code limit for bolts per frame... do not increase. +bool isWeaponActive = false; +entity clientWeapon = null; + +struct +{ + float[2][PEACEKRABER_MAX_BOLTS] boltOffsets = [ + [0.0, 0.0], // center + [0.0, 1.0], // top + [-0.683, 0.327], + [0.683, 0.327], + [-0.636, -0.683], + [0.636, -0.683], + [0.0, 0.5], + [-0.342, 0.174], + [0.342, 0.174], + [-0.318, -0.342], + [0.318, -0.342], + ] + + int maxAmmo + float ammoRegenTime +} file +// "OnWeaponActivate" "OnWeaponActivate_peacekraber" +// "OnWeaponDeactivate" "OnWeaponDeactivate_peacekraber" +void function OnWeaponActivate_peacekraber (entity weapon) { + #if CLIENT + if (!weapon.GetWeaponOwner().IsPlayer() || weapon.GetWeaponOwner() != GetLocalViewPlayer()) return; + isWeaponActive = true; + clientWeapon = weapon; + thread CrosshairCycle(); + #endif +} + +void function OnWeaponDeactivate_peacekraber (entity weapon) { + #if CLIENT + if (!weapon.GetWeaponOwner().IsPlayer() || weapon.GetWeaponOwner() != GetLocalViewPlayer()) return; + isWeaponActive = false; + #endif +} +#if CLIENT +void function CrosshairCycle() { + var rui = RuiCreate( $"ui/crosshair_shotgun.rpak", clGlobal.topoCockpitHudPermanent, RUI_DRAW_COCKPIT, 0 ) + RuiSetFloat(rui, "adjustedSpread", 0.1) + array<int> spreadFrac = [1, 0.65, 0.45, 0.2] + array<vector> colors = [<1, 1, 1>, <0.666, 1, 1>, <0.333, 1, 1>, <0, 1, 1>] + int chargeLevel; + float chargeFrac; + while (isWeaponActive) { + WaitFrame() + chargeLevel = clientWeapon.GetWeaponChargeLevel(); + chargeFrac = clientWeapon.GetWeaponChargeFraction(); + RuiSetFloat3(rui, "teamColor", colors[chargeLevel]); + switch (chargeLevel) { + case 0: + if (chargeFrac > 0.266) { + RuiSetFloat(rui, "adjustedSpread", Graph(chargeFrac, 0.266, 0.333, 0.1, 0.065)) + } + else RuiSetFloat(rui, "adjustedSpread", 0.1) + break; + case 1: + if (chargeFrac > 0.6) { + RuiSetFloat(rui, "adjustedSpread", Graph(chargeFrac, 0.6, 0.666, 0.065, 0.045)) + } + else RuiSetFloat(rui, "adjustedSpread", 0.065) + break; + case 2: + if (chargeFrac > 0.933) { + RuiSetFloat(rui, "adjustedSpread", Graph(chargeFrac, 0.933, 1, 0.045, 0.02)) + } + else RuiSetFloat(rui, "adjustedSpread", 0.045) + break; + case 3: + RuiSetFloat(rui, "adjustedSpread", 0.025) + break; + default: + break; + } + } + + RuiDestroy(rui); + clientWeapon = null; +} +#endif + +var function OnWeaponPrimaryAttack_peacekraber( entity weapon, WeaponPrimaryAttackParams attackParams ) +{ + #if CLIENT + weapon.EmitWeaponSound( "Weapon_Titan_Sniper_LevelTick_2" ) + #endif + + return FireWeaponPlayerAndNPC( attackParams, true, weapon ) +} + +#if SERVER +var function OnWeaponNpcPrimaryAttack_peacekraber( entity weapon, WeaponPrimaryAttackParams attackParams ) +{ + return FireWeaponPlayerAndNPC( attackParams, false, weapon ) +} +#endif // #if SERVER + +function FireWeaponPlayerAndNPC( WeaponPrimaryAttackParams attackParams, bool playerFired, entity weapon ) +{ + entity owner = weapon.GetWeaponOwner() + bool shouldCreateProjectile = false + if ( IsServer() || weapon.ShouldPredictProjectiles() ) + shouldCreateProjectile = true + #if CLIENT + if ( !playerFired ) + shouldCreateProjectile = false + #endif + + vector attackAngles = VectorToAngles( attackParams.dir ) + vector baseUpVec = AnglesToUp( attackAngles ) + vector baseRightVec = AnglesToRight( attackAngles ) + + if ( shouldCreateProjectile ) + { + weapon.EmitWeaponNpcSound( LOUD_WEAPON_AI_SOUND_RADIUS_MP, 0.2 ) + array<int> spreadFrac = [1, 0.65, 0.45, 0.2] + + for ( int index = 0; index < PEACEKRABER_MAX_BOLTS; index++ ) + { + vector upVec = baseUpVec * file.boltOffsets[index][1] * 0.05 * spreadFrac[weapon.GetWeaponChargeLevel()] + vector rightVec = baseRightVec * file.boltOffsets[index][0] * 0.05 * spreadFrac[weapon.GetWeaponChargeLevel()] + + vector attackDir = attackParams.dir + upVec + rightVec + float projectileSpeed = 2800 + + if ( weapon.GetWeaponClassName() == "mp_weapon_peacekraber" ) + { + projectileSpeed = 6400 + } + + entity bolt = weapon.FireWeaponBolt( attackParams.pos, attackDir, projectileSpeed, damageTypes.largeCaliber | DF_SHOTGUN, damageTypes.largeCaliber | DF_SHOTGUN, playerFired, index ) + if ( bolt ) + { + bolt.kv.gravity = 0.4 // 0.09 + + if ( weapon.GetWeaponClassName() == "mp_weapon_peacekraber" ) + bolt.SetProjectileLifetime( RandomFloatRange( 1.0, 1.3 ) ) + else + bolt.SetProjectileLifetime( RandomFloatRange( 0.50, 0.65 ) ) + } + } + } + + return 1 +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/weapons/mp_weapon_toolgun.nut b/Northstar.Custom/mod/scripts/vscripts/weapons/mp_weapon_toolgun.nut new file mode 100644 index 00000000..94bd7429 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/weapons/mp_weapon_toolgun.nut @@ -0,0 +1,39 @@ +untyped +global function OnWeaponActivate_weapon_toolgun +global function OnWeaponDeactivate_weapon_toolgun +global function OnWeaponPrimaryAttack_weapon_toolgun +global function OnWeaponStartZoomIn_weapon_toolgun +global function OnWeaponStartZoomOut_weapon_toolgun +#if SERVER +global function OnWeaponNpcPrimaryAttack_weapon_toolgun +#endif + +void function OnWeaponActivate_weapon_toolgun( entity weapon ) +{ + CallToolOnEquipped( weapon.GetOwner(), weapon ) +} + +void function OnWeaponDeactivate_weapon_toolgun( entity weapon ) +{ + CallToolOnUnequipped( weapon.GetOwner(), weapon ) +} + +var function OnWeaponPrimaryAttack_weapon_toolgun( entity weapon, WeaponPrimaryAttackParams attackParams ) +{ + CallToolOnFired( weapon.GetOwner(), weapon, attackParams ) +} + +void function OnWeaponStartZoomIn_weapon_toolgun( entity weapon ) +{ + CallToolOnAds( weapon.GetOwner(), weapon ) +} + +void function OnWeaponStartZoomOut_weapon_toolgun( entity weapon ) +{ + CallToolOnUnAds( weapon.GetOwner(), weapon ) +} + +var function OnWeaponNpcPrimaryAttack_weapon_toolgun( entity weapon, WeaponPrimaryAttackParams attackParams ) +{ + // do nothing for now, maybe make it launch nukes or something later that could be funny +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_explode.nut b/Northstar.Custom/mod/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_explode.nut new file mode 100644 index 00000000..512c538c --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_explode.nut @@ -0,0 +1,30 @@ +global function ToolgunToolCreateExplosion_Init + +void function ToolgunToolCreateExplosion_Init() +{ + AddCallback_OnToolgunToolsInit( CreateToolgunToolCreateExplosion ) +} + +void function CreateToolgunToolCreateExplosion() +{ + ToolgunTool createExplosionTool + createExplosionTool.toolName = "Create explosion" + createExplosionTool.toolDescription = "Creates an explosion" + + createExplosionTool.onFired = ToolgunToolCreateExplosion_Fire + + RegisterTool( createExplosionTool ) +} + +void function ToolgunToolCreateExplosion_Fire( entity player, entity weapon, WeaponPrimaryAttackParams attackParams ) +{ + #if SERVER + int dist = 55555 // should hit edge of map at all times ideally + vector forward = AnglesToForward( player.EyeAngles() ) + + // raycast to explosion position + TraceResults trace = TraceLine( player.EyePosition(), player.EyePosition() + ( dist * forward ), null, TRACE_MASK_NPCSOLID ) + // make explosion + Explosion( trace.endPos, player, player, 90, 90, 100, 100, 0, trace.endPos, 5000, damageTypes.explosive, eDamageSourceId.burn, "exp_small" ) + #endif +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_throw.nut b/Northstar.Custom/mod/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_throw.nut new file mode 100644 index 00000000..d6975c6d --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/weapons/toolgun/sh_toolgun_tool_throw.nut @@ -0,0 +1,24 @@ +global function ToolgunToolThrowEntity_Init + +void function ToolgunToolThrowEntity_Init() +{ + AddCallback_OnToolgunToolsInit( CreateToolgunToolThrow ) +} + +void function CreateToolgunToolThrow() +{ + ToolgunTool throwTool + throwTool.toolName = "Throw entity" + throwTool.toolDescription = "Spawns and throws the currently selected entity type" + + throwTool.onFired = ToolgunToolThrow_OnFired + + RegisterTool( throwTool ) +} + +void function ToolgunToolThrow_OnFired( entity player, entity weapon, WeaponPrimaryAttackParams attackParams ) +{ + #if SERVER + ClientCommand( player, "ent_throw npc_frag_drone" ) + #endif +}
\ No newline at end of file diff --git a/Northstar.Custom/mod/scripts/vscripts/weapons/toolgun/sh_toolgun_tools.gnut b/Northstar.Custom/mod/scripts/vscripts/weapons/toolgun/sh_toolgun_tools.gnut new file mode 100644 index 00000000..4d7e9d89 --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/weapons/toolgun/sh_toolgun_tools.gnut @@ -0,0 +1,196 @@ +untyped +global function ToolgunTools_Init +global function AddCallback_OnToolgunToolsInit + +global function RegisterTool +global function CallToolOnEquipped +global function CallToolOnUnequipped +global function CallToolOnFired +global function CallToolOnAds +global function CallToolOnUnAds + +#if SERVER +global function SetToolgunAmmoCount +global function SetToolgunProscreen +#endif // #if SERVER + +global struct ToolgunTool +{ + string toolName = "" + string toolDescription = "" + + void functionref( entity, entity ) onEquipped + void functionref( entity, entity ) onUnequipped + void functionref( entity, entity, WeaponPrimaryAttackParams ) onFired + void functionref( entity, entity ) onAds + void functionref( entity, entity ) onUnAds +} + +struct ToolgunPlayerSettings +{ + int selectedToolIndex + int ammoCount + int proscreenNumber +} + +// doing preprocessor defs inside the struct seemed to cause compiler errors so we define them separately +#if CLIENT +struct { + array<void functionref()> onToolgunToolsInitCallbacks + + array<ToolgunTool> tools + ToolgunPlayerSettings clientPlayerSettings +} file +#endif // #if CLIENT + +#if SERVER +struct { + array<void functionref()> onToolgunToolsInitCallbacks + + array<ToolgunTool> tools + // serverside playersettings + table<int, ToolgunPlayerSettings> playerSettings +} file +#endif // #if SERVER + +void function AddCallback_OnToolgunToolsInit( void functionref() callback ) +{ + file.onToolgunToolsInitCallbacks.append( callback ) +} + +void function ToolgunTools_Init() +{ + //#if SERVER + //AddCallback_OnClientConnecting( InitialiseToolgunSettings ) + //AddCallback_OnClientDisconnected( DestroyToolgunSettings ) + //#endif // #if SERVER + // + //// need this threaded so we can wait a frame + //thread ToolgunTools_InitThreaded() +} + +void function ToolgunTools_InitThreaded() +{ + // wait a frame for tools to all init and add their callbacks + WaitFrame() + + // call callbacks + foreach ( void functionref() callback in file.onToolgunToolsInitCallbacks ) + callback() +} + +void function RegisterTool( ToolgunTool toolStruct ) +{ + file.tools.append( toolStruct ) +} + +void function CallToolOnEquipped( entity player, entity weapon ) +{ + #if CLIENT + if ( file.tools[ file.clientPlayerSettings.selectedToolIndex ].onEquipped != null ) + file.tools[ file.clientPlayerSettings.selectedToolIndex ].onEquipped( player, weapon ) + #endif // #if CLIENT + + #if SERVER + // set ammo and proscreen numbers when equipped + weapon.SetProScreenIntValForIndex( PRO_SCREEN_INT_LIFETIME_KILLS, file.playerSettings[ player.GetPlayerIndex() ].proscreenNumber ) + weapon.SetWeaponPrimaryClipCount( file.playerSettings[ player.GetPlayerIndex() ].ammoCount ) + + if ( file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onEquipped != null ) + file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onEquipped( player, weapon ) + #endif // #if SERVER +} + +void function CallToolOnUnequipped( entity player, entity weapon ) +{ + #if CLIENT + if ( file.tools[ file.clientPlayerSettings.selectedToolIndex ].onUnequipped != null ) + file.tools[ file.clientPlayerSettings.selectedToolIndex ].onUnequipped( player, weapon ) + #endif // #if CLIENT + + #if SERVER + if ( file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onUnequipped != null ) + file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onUnequipped( player, weapon ) + #endif // #if SERVER +} + +void function CallToolOnFired( entity player, entity weapon, WeaponPrimaryAttackParams attackParams ) +{ + #if CLIENT + if ( file.tools[ file.clientPlayerSettings.selectedToolIndex ].onFired != null ) + file.tools[ file.clientPlayerSettings.selectedToolIndex ].onFired( player, weapon, attackParams ) + #endif // #if CLIENT + + #if SERVER + // ammocount needs to be +1 because we lose 1 ammo immediately after this function is run + weapon.SetWeaponPrimaryClipCount( file.playerSettings[ player.GetPlayerIndex() ].ammoCount + 1 ) + + if ( file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onFired != null ) + file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onFired( player, weapon, attackParams ) + #endif // #if SERVER +} + +void function CallToolOnAds( entity player, entity weapon ) +{ + #if CLIENT + if ( file.tools[ file.clientPlayerSettings.selectedToolIndex ].onAds != null ) + file.tools[ file.clientPlayerSettings.selectedToolIndex ].onAds( player, weapon ) + #endif // #if CLIENT + + #if SERVER + if ( file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onUnequipped != null ) + file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onUnequipped( player, weapon ) + #endif // #if SERVER +} + +void function CallToolOnUnAds( entity player, entity weapon ) +{ + #if CLIENT + if ( file.tools[ file.clientPlayerSettings.selectedToolIndex ].onUnAds != null ) + file.tools[ file.clientPlayerSettings.selectedToolIndex ].onUnAds( player, weapon ) + #endif // #if CLIENT + + #if SERVER + if ( file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onUnequipped != null ) + file.tools[ file.playerSettings[ player.GetPlayerIndex() ].selectedToolIndex ].onUnequipped( player, weapon ) + #endif // #if SERVER +} + +#if SERVER +void function InitialiseToolgunSettings( entity player ) +{ + print( "initialising toolgun settings for player " + player ) + + ToolgunPlayerSettings playerSettings + playerSettings.selectedToolIndex = 0 + + file.playerSettings[ player.GetPlayerIndex() ] <- playerSettings +} + +void function DestroyToolgunSettings( entity player ) +{ + delete file.playerSettings[ player.GetPlayerIndex() ] +} + +void function SetToolgunAmmoCount( entity player, int ammoCount ) +{ + entity currentWeapon = player.GetActiveWeapon() + if ( ammoCount > currentWeapon.GetWeaponPrimaryClipCountMax() ) // setting clipcount to over max clipcount crashes so we need to prevent that + return + + ToolgunPlayerSettings playerSettings = file.playerSettings[ player.GetPlayerIndex() ] + playerSettings.ammoCount = ammoCount + + if ( currentWeapon.GetWeaponClassName() == "mp_weapon_toolgun" ) // set ammocount immediately if we've got toolgun equipped already + currentWeapon.SetWeaponPrimaryClipCount( ammoCount ) +} + +void function SetToolgunProscreen( entity player, int proscreenNumber ) +{ + ToolgunPlayerSettings playerSettings = file.playerSettings[ player.GetPlayerIndex() ] + playerSettings.proscreenNumber = proscreenNumber + + if ( player.GetActiveWeapon().GetWeaponClassName() == "mp_weapon_toolgun" ) // set proscreen number immediately if we've got toolgun equipped already + player.GetActiveWeapon().SetProScreenIntValForIndex( PRO_SCREEN_INT_LIFETIME_KILLS, proscreenNumber ) +} +#endif // #if SERVER
\ No newline at end of file |