Skip to content

Instantly share code, notes, and snippets.

@alexandre-bruffa
Created January 19, 2024 18:07
Show Gist options
  • Save alexandre-bruffa/d686083d09c29f5cadac171feb9e339d to your computer and use it in GitHub Desktop.
Save alexandre-bruffa/d686083d09c29f5cadac171feb9e339d to your computer and use it in GitHub Desktop.
// Example Realtime Server Script
'use strict';
// Importing the AWS SDK for Javascript V3
const { STSClient, AssumeRoleCommand } = require("@aws-sdk/client-sts");
const { LambdaClient, InvokeCommand } = require("@aws-sdk/client-lambda");
var lambdaClient;
// Example override configuration
const configuration = {
pingIntervalTime: 30000
};
// Timing mechanism used to trigger end of game session. Defines how long, in milliseconds, between each tick in the example tick loop
const tickTime = 1000;
// Defines how to long to wait in Seconds before beginning early termination check in the example tick loop
const minimumElapsedTime = 120;
var session; // The Realtime server session object
var logger; // Log at appropriate level via .info(), .warn(), .error(), .debug()
var startTime; // Records the time the process started
var activePlayers = 0; // Records the number of connected players
var onProcessStartedCalled = false; // Record if onProcessStarted has been called
var players = {};
var npc = {
id: 1,
charId: 0,
interactWith: -1
};
// Example custom op codes for user-defined messages
// Any positive op code number can be defined here. These should match your client code.
const CURRENT_PLAYER_ACCEPTED = 100;
const PLAYER_ACCEPTED = 101;
const PLAYER_DISCONNECTED = 102;
const CURRENT_PLAYER_UPDATE = 200;
const PLAYER_UPDATE = 201;
const PLAYER_MEETS_NPC = 301;
const PLAYER_TO_NPC = 302;
const NPC_TO_PLAYER = 303;
const PLAYER_LEAVES_NPC = 304;
// Called when game server is initialized, passed server's object of current session
async function init(rtSession) {
session = rtSession;
logger = session.getLogger();
// Assuming role
const client = new STSClient({
region: 'us-east-1'
});
const input = {
RoleArn: "arn:aws:iam::000000000000:role/realtimeServerRole",
RoleSessionName: "realtimeServerRole",
};
try {
const command = new AssumeRoleCommand(input);
const response = await client.send(command);
logger.info(response); // Successful response
// Building Lambda client
lambdaClient = new LambdaClient({
region: 'us-east-1',
credentials: {
accessKeyId: response.Credentials.AccessKeyId,
secretAccessKey: response.Credentials.SecretAccessKey,
sessionToken: response.Credentials.SessionToken
}
});
} catch (e) {
logger.error(e); // An error occurred
}
}
// On Process Started is called when the process has begun and we need to perform any
// bootstrapping. This is where the developer should insert any code to prepare
// the process to be able to host a game session, for example load some settings or set state
//
// Return true if the process has been appropriately prepared and it is okay to invoke the
// GameLift ProcessReady() call.
function onProcessStarted(args) {
onProcessStartedCalled = true;
logger.info("Starting process with args: " + args);
logger.info("Ready to host games...");
return true;
}
// Called when a new game session is started on the process
function onStartGameSession(gameSession) {
// Complete any game session set-up
// Set up an example tick loop to perform server initiated actions
startTime = getTimeInS();
tickLoop();
}
// Handle process termination if the process is being terminated by GameLift
// You do not need to call ProcessEnding here
function onProcessTerminate() {
// Perform any clean up
}
// Return true if the process is healthy
function onHealthCheck() {
return true;
}
// On Player Connect is called when a player has passed initial validation
// Return true if player should connect, false to reject
function onPlayerConnect(connectMsg) {
// Perform any validation needed for connectMsg.payload, connectMsg.peerId
return true;
}
// Called when a Player is accepted into the game
function onPlayerAccepted(currentPlayer) {
const playersValues = Object.values(players);
const playersStr = (playersValues.length == 0) ? ('{"players":[], "npc": ' + JSON.stringify(npc) + '}') : ('{"players":[' + playersValues.join(',') + '], "npc": ' + JSON.stringify(npc) + '}');
const outPlayerAcceptedMessage = session.newTextGameMessage(PLAYER_ACCEPTED, currentPlayer.peerId, "");
const outCurrentPlayerAcceptedMessage = session.newTextGameMessage(CURRENT_PLAYER_ACCEPTED, currentPlayer.peerId, playersStr);
for (var playerId in players) {
if (players.hasOwnProperty(playerId)) {
session.sendReliableMessage(outPlayerAcceptedMessage, parseInt(playerId));
}
}
session.sendReliableMessage(outCurrentPlayerAcceptedMessage, currentPlayer.peerId);
players[currentPlayer.peerId] = "";
activePlayers++;
}
// On Player Disconnect is called when a player has left or been forcibly terminated
// Is only called for players that actually connected to the server and not those rejected by validation
// This is called before the player is removed from the player list
function onPlayerDisconnect(peerId) {
const outMessage = session.newTextGameMessage(PLAYER_DISCONNECTED, peerId, "");
delete players[peerId];
for (var playerId in players) {
if (players.hasOwnProperty(playerId)) {
session.sendReliableMessage(outMessage, parseInt(playerId));
}
}
activePlayers--;
}
// Handle a message to the server
async function onMessage(gameMessage) {
switch (gameMessage.opCode) {
case PLAYER_UPDATE:
players[gameMessage.sender] = String.fromCharCode(...Object.values(gameMessage.payload));
const outPlayerChangedMessage = session.newTextGameMessage(PLAYER_UPDATE, gameMessage.sender, gameMessage.payload);
const outPlayerChangedCurrentMessage = session.newTextGameMessage(CURRENT_PLAYER_UPDATE, gameMessage.sender, gameMessage.payload);
for (var playerId in players) {
if (players.hasOwnProperty(playerId)) {
playerId = parseInt(playerId);
if (playerId != gameMessage.sender) {
session.sendReliableMessage(outPlayerChangedMessage, playerId);
}
}
}
session.sendReliableMessage(outPlayerChangedCurrentMessage, gameMessage.sender);
break;
case PLAYER_MEETS_NPC:
if (npc.interactWith == -1) {
npc.interactWith = gameMessage.sender;
const dataStr = '{"npc": ' + JSON.stringify(npc) + '}';
const outPlayerChangedMessage = session.newTextGameMessage(PLAYER_MEETS_NPC, gameMessage.sender, dataStr);
for (var playerId in players) {
if (players.hasOwnProperty(playerId)) {
playerId = parseInt(playerId);
session.sendReliableMessage(outPlayerChangedMessage, playerId);
}
}
}
break;
case PLAYER_TO_NPC:
if (lambdaClient) {
try {
var lambdaParams = {
FunctionName: 'npcFunction', // The lambda function we are going to invoke
InvocationType: 'RequestResponse',
LogType: 'Tail',
Payload: '{ "message" : "' + String.fromCharCode(...Object.values(gameMessage.payload)) + '" }'
};
const command = new InvokeCommand(lambdaParams);
const response = await lambdaClient.send(command);
logger.info('Response: ' + response.Payload); // Successful response
const outPlayerMessage = session.newTextGameMessage(NPC_TO_PLAYER, gameMessage.sender, response.Payload);
session.sendReliableMessage(outPlayerMessage, gameMessage.sender);
} catch (e) {
logger.error(e); // An error occurred
}
}
break;
case PLAYER_LEAVES_NPC:
if (npc.interactWith != -1) {
npc.interactWith = -1;
const dataStr = '{"npc": ' + JSON.stringify(npc) + '}';
const outPlayerChangedMessage = session.newTextGameMessage(PLAYER_LEAVES_NPC, gameMessage.sender, dataStr);
for (var playerId in players) {
if (players.hasOwnProperty(playerId)) {
playerId = parseInt(playerId);
session.sendReliableMessage(outPlayerChangedMessage, playerId);
}
}
}
break;
}
}
// Return true if the send should be allowed
function onSendToPlayer(gameMessage) {
// This example rejects any payloads containing "Reject"
return (!gameMessage.getPayloadAsText().includes("Reject"));
}
// Return true if the send to group should be allowed
// Use gameMessage.getPayloadAsText() to get the message contents
function onSendToGroup(gameMessage) {
return true;
}
// Return true if the player is allowed to join the group
function onPlayerJoinGroup(groupId, peerId) {
return true;
}
// Return true if the player is allowed to leave the group
function onPlayerLeaveGroup(groupId, peerId) {
return true;
}
// A simple tick loop example
// Checks to see if a minimum amount of time has passed before seeing if the game has ended
async function tickLoop() {
const elapsedTime = getTimeInS() - startTime;
logger.info("Tick... " + elapsedTime + " activePlayers: " + activePlayers);
// In Tick loop - see if all players have left early after a minimum period of time has passed
// Call processEnding() to terminate the process and quit
if ((activePlayers == 0) && (elapsedTime > minimumElapsedTime)) {
logger.info("All players disconnected. Ending game");
const outcome = await session.processEnding();
logger.info("Completed process ending with: " + outcome);
process.exit(0);
}
else {
setTimeout(tickLoop, tickTime);
}
}
// Calculates the current time in seconds
function getTimeInS() {
return Math.round(new Date().getTime() / 1000);
}
exports.ssExports = {
configuration: configuration,
init: init,
onProcessStarted: onProcessStarted,
onMessage: onMessage,
onPlayerConnect: onPlayerConnect,
onPlayerAccepted: onPlayerAccepted,
onPlayerDisconnect: onPlayerDisconnect,
onSendToPlayer: onSendToPlayer,
onSendToGroup: onSendToGroup,
onPlayerJoinGroup: onPlayerJoinGroup,
onPlayerLeaveGroup: onPlayerLeaveGroup,
onStartGameSession: onStartGameSession,
onProcessTerminate: onProcessTerminate,
onHealthCheck: onHealthCheck
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment