{Details}
- Versions: Nakama {3.5}, {Windows, Mac, Linux binary or Docker}, {client library (SDK) and version}
- Server Framework Runtime language (If relevant) {Go, TS/JS, Lua}
//getting below error
nakama | {"level":"error","ts":"2024-06-10T18:24:53.871Z","caller":"server/runtime_javascript.go:175","msg":"JavaScript runtime function invalid.","key":"dune_CreateMatch"}
//=== main.ts =====
let InitModule: nkruntime.InitModule = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, initializer: nkruntime.Initializer)
{
logger.warn('Tag::serverMain start Init');
//<editor-fold desc="Dunes game">
let dunes=new BetBlack.Dunes.DunesGameManager();
let dune_matchInit = dunes.MatchInit;
let dune_matchJoinAttempt = dunes.MatchJoinAttempted;
let dune_matchJoin = dunes.MatchJoin
let dune_matchLeave = dunes.MatchLeave;
let dune_matchLoop = dunes.MatchLoop
let dune_matchSignal = dunes.MatchSignal;
let dune_matchTerminate = dunes.MatchTerminate;
initializer.registerMatch('Dunes_Lobby', {
matchInit: dune_matchInit,
matchJoinAttempt: dune_matchJoinAttempt,
matchJoin: dune_matchJoin,
matchLeave: dune_matchLeave,
matchLoop: dune_matchLoop,
matchSignal: dune_matchSignal,
matchTerminate: dune_matchTerminate
});
let dune_CreateMatch=dunes.CreateMatch;
initializer.registerRpc('Dunes_CreateMatch',dune_CreateMatch);
//</editor-fold>
//<editor-fold desc="Pool game">
//let pool=new BetBlack.Pool.PoolGameManager();
//let pool_matchInit = pool.MatchInit;
//let pool_matchJoinAttempt = pool.MatchJoinAttempted;
//let pool_matchJoin = pool.MatchJoin
//let pool_matchLeave = pool.MatchLeave;
//let pool_matchLoop = pool.MatchLoop
//let pool_matchSignal = pool.MatchSignal;
//let pool_matchTerminate = pool.MatchTerminate;
//initializer.registerMatch('Pool_Lobby', {
// matchInit: pool_matchInit,
// matchJoinAttempt: pool_matchJoinAttempt,
// matchJoin: pool_matchJoin,
// matchLeave: pool_matchLeave,
// matchLoop: pool_matchLoop,
// matchSignal: pool_matchSignal,
// matchTerminate: pool_matchTerminate
//});
//let pool_CreateMatch=pool.CreateMatch;
//initializer.registerRpc('Dunes_CreateMatch',pool_CreateMatch);
//</editor-fold>
logger.warn('Tag::serverMain start Done');
}
//==== BaseGame.ts ======
///<reference path="MiscData.ts"/>
namespace BetBlack
{
export class PlayerDetails
{
playerName: string="";
playerAvatarId: string="";
}
export class MatchMakingDetails
{
roomId: string = "not found";
minPlayerCount: number = 1;
maxPlayerCount: number = 1;
autoDestroyRoom: number = 3600;
constructor(roomId: string, minPlayerCount: number, maxPlayerCount: number, autoDestroyRoom: number)
{
this.roomId = roomId;
this.minPlayerCount = minPlayerCount;
this.maxPlayerCount = maxPlayerCount;
this.autoDestroyRoom = autoDestroyRoom;
}
// Method to display student information
toString(): string
{
return `roomId: ${this.roomId}, maxPlayerCount: ${this.maxPlayerCount}, minPlayerCount: ${this.minPlayerCount}, autoDestroyRoom: ${this.autoDestroyRoom}`;
}
}
export class MatchMakingResponseData
{
roomId: string;
matchId: string;
minPlayerCount: number = 1;
maxPlayerCount: number = 1;
currentPlayerCount: number = 0;
playerDetails: PlayerDetails[] | null;
constructor(roomId: string, matchId: string, minPlayerCount: number,maxPlayerCount: number, playerDetails: PlayerDetails[] | null)
{
this.roomId = roomId;
this.matchId = matchId;
this.maxPlayerCount = maxPlayerCount;
this.minPlayerCount = minPlayerCount;
this.playerDetails = playerDetails;
}
}
export class MatchMakingResponse extends Response<MatchMakingResponseData>
{
}
export interface IGameState extends nkruntime.MatchState
{
}
class WaitingMatches
{
public waitingMatchesByRoomId : Map<string,MatchMakingResponseData>;
public waitingMatchesByMatchId :Map<string,MatchMakingResponseData>;
public constructor()
{
this.waitingMatchesByRoomId = new Map<string,MatchMakingResponseData>();
this.waitingMatchesByMatchId = new Map<string,MatchMakingResponseData>();
}
public Set(roomId :string ,matchId:string,mmr: MatchMakingResponseData) : void
{
this.waitingMatchesByRoomId.set(roomId,mmr);
this.waitingMatchesByMatchId.set(matchId,mmr);
}
public GetMMRByRoomId(roomId:string) : MatchMakingResponseData |null
{
if(this.waitingMatchesByRoomId.has(roomId))
{
return this.waitingMatchesByRoomId.get(roomId) || null;
}
else
{
return null;
}
}
public GetMMRByMatchId(matchId:string) : MatchMakingResponseData |null
{
if(this.waitingMatchesByMatchId.has(matchId))
{
return this.waitingMatchesByMatchId.get(matchId) || null;
}
else
{
return null;
}
}
public DeletedByRoomId (matchId:string) : boolean
{
this.waitingMatchesByMatchId.delete(matchId)
return this.waitingMatchesByRoomId.delete(matchId);
}
public DeletedByMatchId (matchId:string) : boolean
{
this.waitingMatchesByRoomId.delete(matchId)
return this.waitingMatchesByMatchId.delete(matchId);
}
}
export abstract class BaseGameManager
{
protected waitingMap = new WaitingMatches();
public GetAll(): Map<string, nkruntime.RpcFunction>
{
let rpcFunctions: Map<string, nkruntime.RpcFunction> = new Map<string, nkruntime.RpcFunction>();
let gameName = this.GetGameName();
rpcFunctions.set(`${gameName}_CreateMatch`, this.CreateMatch);
return rpcFunctions;
}
abstract GetGameName(): string;
CreateMatch(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string): string
{
let responseJson = emptyResponse;
let matchDetail: MatchMakingDetails | null = null;
try
{
matchDetail = JSON.parse(payload) as MatchMakingDetails;
} catch (error)
{
logger.error(`Failed to deserialize JSON: ${payload}`);
}
if (matchDetail == null)
{
return responseJson;
} else
{
let mmResponseData = this.waitingMap.GetMMRByRoomId(matchDetail?.roomId);
if (mmResponseData == null)
{
var matchId = nk.matchCreate(this.GetGameName());
mmResponseData = new MatchMakingResponseData(matchDetail?.roomId, matchId, matchDetail.minPlayerCount, matchDetail.maxPlayerCount, null);
this.waitingMap.Set(matchDetail?.roomId, matchId, mmResponseData);
}
return JSON.stringify(mmResponseData);
}
}
abstract MatchInit(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, params: {
[p: string]: any
}): { state: nkruntime.MatchState, tickRate: number, label: string };
abstract MatchJoinAttempted(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presence: nkruntime.Presence, metadata: {
[key: string]: any
}): { state: nkruntime.MatchState, accept: boolean, rejectMessage?: string } | null;
MatchJoin(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presences: nkruntime.Presence[]): {
state: nkruntime.MatchState } | null
{
let mmr=this.waitingMap.GetMMRByMatchId(ctx.matchId);
if(mmr != null)
{
mmr.currentPlayerCount+=1;
}
else
{
logger.error('Tag::serverBase MatchJoin mmr is null');
}
return {state};
}
MatchLeave(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presences: nkruntime.Presence[]): { state: nkruntime.MatchState } | null
{
let mmr=this.waitingMap.GetMMRByMatchId(ctx.matchId);
if(mmr != null)
{
mmr.currentPlayerCount-=1;
}
else
{
logger.error('Tag::serverBase MatchJoin mmr is null');
}
return { state};
}
abstract MatchLoop(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, messages: nkruntime.MatchMessage[]): {
state: nkruntime.MatchState
} | null;
MatchTerminate(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, graceSeconds: number): { state: nkruntime.MatchState } | null
{
this.waitingMap.DeletedByMatchId(ctx.matchId);
logger.warn(`Tag::serverBase MatchTerminate mmr is ${ctx.matchId}`);
return {state};
}
abstract MatchSignal(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, data: string): {
state: nkruntime.MatchState,
data?: string
} | null;
// matchInit: nkruntime.MatchInitFunction<T> = (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, params: {
// [p: string]: any
// }): { state: T, tickRate: number, label: string } =>
// {
// return this.MatchInit(ctx, logger, nk, params);
// }
// matchJoinAttempt: nkruntime.MatchJoinAttemptFunction<T> = (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: T, presence: nkruntime.Presence, metadata: {
// [key: string]: any
// }): { state: T, accept: boolean, rejectMessage?: string } | null =>
// {
// return this.MatchJoinAttempted(ctx, logger, nk, dispatcher, tick, state, presence, metadata);
// }
// matchJoin: nkruntime.MatchJoinFunction<T> = (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: T, presences: nkruntime.Presence[]) =>
// {
// return this.MatchJoin(ctx, logger, nk, dispatcher, tick, state, presences);
// }
// matchLeave: nkruntime.MatchLeaveFunction<T> = (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: T, presences: nkruntime.Presence[]) =>
// {
// return this.MatchLeave(ctx, logger, nk, dispatcher, tick, state, presences);
// }
// matchLoop: nkruntime.MatchLoopFunction<T> = (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: T, messages: nkruntime.MatchMessage[]) =>
// {
// return this.MatchLoop(ctx, logger, nk, dispatcher, tick, state, messages);
// }
// matchTerminate: nkruntime.MatchTerminateFunction<T> = (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: T, graceSeconds: number) =>
// {
// return this.MatchTerminate(ctx, logger, nk, dispatcher, tick, state, graceSeconds);
// }
// matchSignal: nkruntime.MatchSignalFunction<T> = (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: T, data: string) =>
// {
// return this.MatchSignal(ctx, logger, nk, dispatcher, tick, state, data);
// }
}
}
//===Dunes.ts====
namespace BetBlack.Dunes
{
class ScoreData
{
public playerId:string;
public score : number;
constructor(playerId: string, score: number)
{
this.playerId = playerId;
this.score = score;
}
}
export class DunesGameManager extends BetBlack.BaseGameManager
{
GetGameName(): string
{
return 'Dunes';
}
MatchInit(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, params: {
[p: string]: any;
}): { state: nkruntime.MatchState; tickRate: number; label: string; }
{
var mmr = this.waitingMap.GetMMRByMatchId(ctx.matchId);
if (mmr == null)
{
let mmd= new MatchMakingDetails('Not_Found',1,1,3600);
logger.error('Tag::Dunes error MMR is null')
return {
state: {
matchData: mmd,
scores: Array.from({ length: 1 }, () => new ScoreData("", 0))
},
tickRate: 60, // 1 tick per second = 1 MatchLoop func invocations per second
label: this.GetGameName()
};
}
let mmd= new MatchMakingDetails(mmr.roomId, mmr.minPlayerCount, mmr.maxPlayerCount,3600);
return {
state: {
matchData: mmd,
scores: Array.from({ length: mmr.maxPlayerCount }, () => new ScoreData("", 0))
},
tickRate: 1, // 1 tick per second = 1 MatchLoop func invocations per second
label: this.GetGameName()
};
}
MatchJoinAttempted(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, presence: nkruntime.Presence, metadata: {
[key: string]: any;
}): { state: nkruntime.MatchState; accept: boolean; rejectMessage?: string | undefined; } | null
{
return {
state,
accept: true
};
}
MatchLoop(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, messages: nkruntime.MatchMessage[]): {
state: nkruntime.MatchState;
} | null
{
return {
state
}
}
MatchSignal(ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, dispatcher: nkruntime.MatchDispatcher, tick: number, state: nkruntime.MatchState, data: string): {
state: nkruntime.MatchState;
data?: string | undefined;
} | null
{
return {
state
}
}
}
}
Media: