diff --git a/Northstar.Custom/mod/scripts/vscripts/sh_message_utils.gnut b/Northstar.Custom/mod/scripts/vscripts/sh_message_utils.gnut index 4cfdc6fba..048182959 100644 --- a/Northstar.Custom/mod/scripts/vscripts/sh_message_utils.gnut +++ b/Northstar.Custom/mod/scripts/vscripts/sh_message_utils.gnut @@ -3,6 +3,9 @@ global function MessageUtils_ServerInit global function NSCreatePollOnPlayer global function NSGetPlayerResponse +global function NSCreateChoiceOnAllPlayers +global function NSCreateChoiceOnPlayer +global function NSGetPlayerChoiceResponse global function NSSendLargeMessageToPlayer global function NSSendPopUpMessageToPlayer @@ -13,9 +16,22 @@ global function NSCreateStatusMessageOnPlayer global function NSEditStatusMessageOnPlayer global function NSDeleteStatusMessageOnPlayer +global enum ePlayerChoiceStatus +{ + UNKNOWN, + NOT_FOUND, // no choice matching the key was found + NOT_FOUND_FOR_PLAYER, // choice exists, but the player was not proposed with it + + ONGOING, // choice is currently proposed to the player, whom hasn't answered it yet + NOT_ANSWERED, // player did not answer the choice (RUI timed out) + CHOICE_1, // player selected first option + CHOICE_2 // player selected second option +} + struct { table playerPollResponses + table > playerChoiceResponses } server #endif // SERVER @@ -49,6 +65,16 @@ struct bool pollActive array ruis } poll + + struct + { + string option1 + string option2 + float duration + bool active + var rui + string key + } choice string id tempMessage temp @@ -76,7 +102,8 @@ enum eMessageType INFO, CREATE_STATUS, EDIT_STATUS, - DELETE_STATUS + DELETE_STATUS, + CHOICE } enum eDataType @@ -92,7 +119,10 @@ enum eDataType COLOR, PRIORITY, STYLE, - ID + ID, + CHOICE_OPTION, + CHOICE_DURATION, + CHOICE_KEY } #if SERVER @@ -100,6 +130,7 @@ void function MessageUtils_ServerInit() { AddClientCommandCallback( "vote", ClientCommand_Vote ) AddClientCommandCallback( "poll_respond", ClientCommand_PollRespond ) + AddClientCommandCallback( "choice_respond", ClientCommand_ChoiceRespond ) } bool function ClientCommand_Vote( entity player, array args ) @@ -120,6 +151,40 @@ bool function ClientCommand_PollRespond( entity player, array args ) return true } +bool function ClientCommand_ChoiceRespond( entity player, array args ) +{ + if( args.len() != 2 ) + return false + + // todo: what if args[0] is not an integer? + int responseCode = args[0].tointeger() + int responseValue = ePlayerChoiceStatus.NOT_FOUND + switch( responseCode ) + { + case 0: + responseValue = ePlayerChoiceStatus.NOT_ANSWERED + break + case 1: + responseValue = ePlayerChoiceStatus.CHOICE_1 + break + case 2: + responseValue = ePlayerChoiceStatus.CHOICE_2 + break + default: + // todo: test this usecase + print( format( "Player %s sent incorrect choice response value (was %s).", player.GetPlayerName(), responseCode ) ) + responseValue = ePlayerChoiceStatus.UNKNOWN + break + } + + string key = args[1] + if ( !( key in server.playerChoiceResponses ) || !( player in server.playerChoiceResponses[key] ) ) + return false + + server.playerChoiceResponses[key][player] <- responseValue + return true +} + void function NSCreateStatusMessageOnPlayer( entity player, string title, string description, string id ) { ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.TITLE + " " + title ) @@ -168,6 +233,55 @@ int function NSGetPlayerResponse( entity player ) return server.playerPollResponses[ player ] - 1 } +string function NSCreateChoiceOnAllPlayers( array options, float duration ) +{ + string key = UniqueString() + foreach( player in GetPlayerArray() ) + { + if ( !IsValid( player ) ) + continue + NSCreateChoiceOnPlayer( player, options, duration, key ) + } + return key +} + +string function NSCreateChoiceOnPlayer( entity player, array options, float duration, string key = "" ) +{ + if ( ![1, 2].contains( options.len() ) ) + { + throw "NSCreateChoiceOnPlayer expected one or two options (got " + options.len() + ")." + } + + string id = key != "" ? key : UniqueString() + + ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.CHOICE_OPTION + " " + options[0] ) + if ( options.len() > 1 ) + { + ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.CHOICE_OPTION + " " + options[1] ) + } + + ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.CHOICE_DURATION + " " + duration ) + ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.CHOICE_KEY + " " + id ) + + if( !( id in server.playerChoiceResponses ) ) + server.playerChoiceResponses[id] <- {} + server.playerChoiceResponses[id][player] <- ePlayerChoiceStatus.ONGOING // Reset choice response table entry + ServerToClientStringCommand( player, "ServerHUDMessageShow " + eMessageType.CHOICE ) + + return id +} + +int function NSGetPlayerChoiceResponse( entity player, string key ) +{ + if( !( key in server.playerChoiceResponses ) ) + return ePlayerChoiceStatus.NOT_FOUND + + if( !( player in server.playerChoiceResponses[key] ) ) + return ePlayerChoiceStatus.NOT_FOUND_FOR_PLAYER + + return server.playerChoiceResponses[ key ][ player ] +} + void function NSSendLargeMessageToPlayer( entity player, string title, string description, float duration, string image ) { ServerToClientStringCommand( player, "ServerHUDMessagePut " + eDataType.TITLE + " " + title ) @@ -208,6 +322,13 @@ void function NSSendInfoMessageToPlayer( entity player, string text ) #if CLIENT void function MessageUtils_ClientInit() { + // Choice RUI signals + RegisterSignal( "DialogueChoice1" ) + RegisterSignal( "DialogueChoice2" ) + RegisterSignal( "DialogueChoiceTimeout" ) + RegisterConCommandTriggeredCallback( "+scriptCommand1", Pressed_Choice1 ) + RegisterConCommandTriggeredCallback( "+scriptCommand2", Pressed_Choice2 ) + // ServerHUDMessageRequest AddServerToClientStringCommandCallback( "ServerHUDMessageShow", ServerCallback_CreateServerHUDMessage ) // ServerHUDMessageRequest @@ -270,6 +391,18 @@ void function ServerCallback_UpdateServerHUDMessage ( array args ) case eDataType.ID: client.id = args[1] break + case eDataType.CHOICE_OPTION: + if (client.choice.option1.len() == 0) + client.choice.option1 = CombineArgsIntoString( args ) + else + client.choice.option2 = CombineArgsIntoString( args ) + break + case eDataType.CHOICE_DURATION: + client.choice.duration = args[1].tofloat() + break + case eDataType.CHOICE_KEY: + client.choice.key = args[1] + break } } @@ -301,6 +434,9 @@ void function ServerCallback_CreateServerHUDMessage ( array args ) case eMessageType.DELETE_STATUS: thread DeleteStatusMessage( client.id ) break + case eMessageType.CHOICE: + thread ShowChoice_Threaded() + break } } @@ -419,6 +555,107 @@ void function ShowPollMessage_Threaded() client.poll.pollActive = false } +void function ShowChoice_Threaded() +{ + if( client.choice.active ) + return + + client.choice.active = true + entity player = GetLocalViewPlayer() + + // durations + float introDuration = 1.0 + float promptDuration = client.choice.duration + + // RUI creation + var rui = RuiCreate( $"ui/conversation.rpak", clGlobal.topoFullScreen, RUI_DRAW_HUD, 0 ) + RuiSetFloat( rui, "startTime", Time() ) + RuiSetFloat( rui, "introDuration", introDuration ) + RuiSetFloat( rui, "timer", promptDuration ) + RuiSetResolutionToScreenSize( rui ) + + // first answer + RuiSetString( rui, "text1", client.choice.option1 ) + RuiSetBool( rui, "choice1Available", true ) + RuiSetBool( rui, "choice1WasSelected", false ) + + // second answer + bool hasSecondOption = client.choice.option2 != "" + if ( hasSecondOption ) + { + RuiSetString( rui, "text2", client.choice.option2 ) + } + RuiSetBool( rui, "choice2Available", hasSecondOption ) + RuiSetBool( rui, "choice2WasSelected", false ) + RuiSetInt( rui, "numChoices", hasSecondOption ? 2 : 1 ) + + client.choice.rui = rui + EmitSoundOnEntity( player, "UI_PlayerDialogue_Selection" ) + + // Wait for player answer + table results + if ( hasSecondOption ) + { + thread DialogueChoiceTimeout( player, introDuration + promptDuration, "DialogueChoice1", "DialogueChoice2" ) + results = WaitSignal( player, "DialogueChoice1", "DialogueChoice2", "DialogueChoiceTimeout" ) + } + else + { + thread DialogueChoiceTimeout( player, introDuration + promptDuration, "DialogueChoice1" ) + results = WaitSignal( player, "DialogueChoice1", "DialogueChoiceTimeout" ) + } + + int choice = 0 + if ( results.signal == "DialogueChoice1" ) + choice = 1 + else if ( results.signal == "DialogueChoice2" ) + choice = 2 + player.ClientCommand( "choice_respond " + choice + " " + client.choice.key) + + float textFadeOutDuration = choice == 0 ? 1.0 : 0.75 + float responseDuration = choice == 0 ? 0.0 : 3.0 + RuiSetFloat( client.choice.rui, "choiceMadeTime", Time() ) + RuiSetFloat( client.choice.rui, "choiceDuration", responseDuration ) + RuiSetFloat( client.choice.rui, "textRemoveDuration", textFadeOutDuration ) + RuiSetInt( client.choice.rui, "choiceMade", choice ) + + EmitSoundOnEntity( GetLocalViewPlayer(), choice == 0 ? "UI_PlayerDialogue_Notification" : "ui_holotutorial_Analyzingfinish" ) + wait textFadeOutDuration + responseDuration + + RuiDestroyIfAlive( client.choice.rui ) + client.choice.rui = null + client.choice.active = false + client.choice.option1 = "" + client.choice.option2 = "" + client.choice.key = "" +} + +void function DialogueChoiceTimeout( entity player, float waitTime, string cancelTimeoutSignal_A, string cancelTimeoutSignal_B = "") +{ + EndSignal( player, "OnDeath" ) + EndSignal( player, "OnDestroy" ) + EndSignal( player, cancelTimeoutSignal_A ) + if ( cancelTimeoutSignal_B != "") + { + EndSignal( player, cancelTimeoutSignal_B ) + } + + wait waitTime + + if ( IsValid( player ) ) + Signal( player, "DialogueChoiceTimeout" ) +} + +void function Pressed_Choice1( entity player ) +{ + Signal( player, "DialogueChoice1" ) +} + +void function Pressed_Choice2( entity player ) +{ + Signal( player, "DialogueChoice2" ) +} + void function InfoMessageHandler_Threaded() { while( true )