Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Server-side choice RUI #831

Draft
wants to merge 22 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ce1c984
feat: add NSCreateChoiceOnPlayer server-side function
Alystrasz Aug 5, 2024
c16037f
feat: send choice parameters to client
Alystrasz Aug 5, 2024
749d200
feat: use RUI component to display choice on client
Alystrasz Aug 5, 2024
51fbb68
feat: emit sound on choice UI spawn
Alystrasz Aug 5, 2024
fd06f34
feat: RUI disappears with a smooth transition
Alystrasz Aug 5, 2024
e5454d4
fix: multiple choices can be triggered one after another
Alystrasz Aug 5, 2024
07f519e
feat: player can answer choice prompts
Alystrasz Aug 5, 2024
f50ed89
feat: add NSGetPlayerChoiceResponse method
Alystrasz Aug 5, 2024
ee9d35b
refactor: NSGetPlayerChoiceResponse returns -2 if input player is not…
Alystrasz Aug 5, 2024
6787f28
refactor: integrate key to choice API
Alystrasz Aug 5, 2024
e19cb6c
feat: add NSCreateChoiceOnAllPlayers util function
Alystrasz Aug 5, 2024
06de4be
Merge branch 'main' into feat/serverside-choice
Zanieon Aug 11, 2024
6325524
Merge branch 'main' into feat/serverside-choice
Alystrasz Aug 18, 2024
7a8b711
refactor: API takes an array of strings as input parameter
Alystrasz Aug 18, 2024
18ec783
feat: NSCreateChoiceOnPlayer throws when not getting 1 or 2 options
Alystrasz Aug 18, 2024
d4525b8
Merge branch 'main' into feat/serverside-choice
Alystrasz Aug 18, 2024
39cacda
refactor: add ePlayerChoiceStatus enum
Alystrasz Aug 19, 2024
7030714
Merge branch 'main' into feat/serverside-choice
Alystrasz Aug 26, 2024
71a79f2
style: use spaces to align enum (instead of tabs)
Alystrasz Aug 26, 2024
8141ccd
feat: support one choice only
Alystrasz Aug 26, 2024
f7909ec
Merge branch 'main' into feat/serverside-choice
Alystrasz Aug 27, 2024
a3c55f7
Merge branch 'main' into feat/serverside-choice
Alystrasz Sep 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
241 changes: 239 additions & 2 deletions Northstar.Custom/mod/scripts/vscripts/sh_message_utils.gnut
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<entity,int> playerPollResponses
table<string, table<entity,int> > playerChoiceResponses
} server
#endif // SERVER

Expand Down Expand Up @@ -49,6 +65,16 @@ struct
bool pollActive
array<var> ruis
} poll

struct
{
string option1
string option2
float duration
bool active
var rui
string key
} choice

string id
tempMessage temp
Expand Down Expand Up @@ -76,7 +102,8 @@ enum eMessageType
INFO,
CREATE_STATUS,
EDIT_STATUS,
DELETE_STATUS
DELETE_STATUS,
CHOICE
}

enum eDataType
Expand All @@ -92,14 +119,18 @@ enum eDataType
COLOR,
PRIORITY,
STYLE,
ID
ID,
CHOICE_OPTION,
CHOICE_DURATION,
CHOICE_KEY
}

#if SERVER
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<string> args )
Expand All @@ -120,6 +151,40 @@ bool function ClientCommand_PollRespond( entity player, array<string> args )
return true
}

bool function ClientCommand_ChoiceRespond( entity player, array<string> 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 )
Expand Down Expand Up @@ -168,6 +233,55 @@ int function NSGetPlayerResponse( entity player )
return server.playerPollResponses[ player ] - 1
}

string function NSCreateChoiceOnAllPlayers( array<string> 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<string> 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 )
Expand Down Expand Up @@ -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 )
Comment on lines +329 to +330
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scriptCommand1/scriptCommand2 feel like placeholder names :P


// ServerHUDMessageRequest <eMessageType>
AddServerToClientStringCommandCallback( "ServerHUDMessageShow", ServerCallback_CreateServerHUDMessage )
// ServerHUDMessageRequest <eDataType> <Data>
Expand Down Expand Up @@ -270,6 +391,18 @@ void function ServerCallback_UpdateServerHUDMessage ( array<string> 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
}
}

Expand Down Expand Up @@ -301,6 +434,9 @@ void function ServerCallback_CreateServerHUDMessage ( array<string> args )
case eMessageType.DELETE_STATUS:
thread DeleteStatusMessage( client.id )
break
case eMessageType.CHOICE:
thread ShowChoice_Threaded()
break
}
}

Expand Down Expand Up @@ -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 )
Expand Down
Loading