diff --git a/data/modules/index.js b/data/modules/index.js deleted file mode 100644 index 369e0251db..0000000000 --- a/data/modules/index.js +++ /dev/null @@ -1,542 +0,0 @@ -"use strict"; -// Copyright 2020 The Nakama Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -var rpcIdRewards = 'rewards_js'; -var rpcIdFindMatch = 'find_match_js'; -function InitModule(ctx, logger, nk, initializer) { - initializer.registerRpc(rpcIdRewards, rpcReward); - initializer.registerRpc(rpcIdFindMatch, rpcFindMatch); - initializer.registerMatch(moduleName, { - matchInit: matchInit, - matchJoinAttempt: matchJoinAttempt, - matchJoin: matchJoin, - matchLeave: matchLeave, - matchLoop: matchLoop, - matchTerminate: matchTerminate, - matchSignal: matchSignal, - }); - var notifs = [ - { userId: '767da49c-9792-4fed-b6ca-589b0bc2aaca', code: 10, content: { foo: 'bar' }, senderId: '00000000-0000-0000-0000-000000000000', subject: 'test', persistent: true }, - ]; - // nk.notificationsSend(notifs); - logger.info('JavaScript logic loaded.'); -} -// Copyright 2020 The Nakama Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -function rpcReward(context, logger, nk, payload) { - if (!context.userId) { - throw Error('No user ID in context'); - } - if (payload) { - throw Error('no input allowed'); - } - var objectId = { - collection: 'reward', - key: 'daily', - userId: context.userId, - }; - var objects; - try { - objects = nk.storageRead([objectId]); - } - catch (error) { - logger.error('storageRead error: %s', error); - throw error; - } - var dailyReward = { - lastClaimUnix: 0, - }; - objects.forEach(function (object) { - if (object.key == 'daily') { - dailyReward = object.value; - } - }); - var resp = { - coinsReceived: 0, - }; - var d = new Date(); - d.setHours(0, 0, 0, 0); - // If last claimed is before the new day grant a new reward! - if (dailyReward.lastClaimUnix < msecToSec(d.getTime())) { - resp.coinsReceived = 500; - // Update player wallet. - var changeset = { - coins: resp.coinsReceived, - }; - try { - nk.walletUpdate(context.userId, changeset, {}, false); - } - catch (error) { - logger.error('walletUpdate error: %q', error); - throw error; - } - var notification = { - code: 1001, - content: changeset, - persistent: true, - subject: "You've received your daily reward!", - userId: context.userId, - }; - try { - nk.notificationsSend([notification]); - } - catch (error) { - logger.error('notificationsSend error: %q', error); - throw error; - } - dailyReward.lastClaimUnix = msecToSec(Date.now()); - var write = { - collection: 'reward', - key: 'daily', - permissionRead: 1, - permissionWrite: 0, - value: dailyReward, - userId: context.userId, - }; - if (objects.length > 0) { - write.version = objects[0].version; - } - try { - nk.storageWrite([write]); - } - catch (error) { - logger.error('storageWrite error: %q', error); - throw error; - } - } - var result = JSON.stringify(resp); - logger.debug('rpcReward resp: %q', result); - return result; -} -function msecToSec(n) { - return Math.floor(n / 1000); -} -var Mark; -(function (Mark) { - Mark[Mark["X"] = 0] = "X"; - Mark[Mark["O"] = 1] = "O"; - Mark[Mark["UNDEFINED"] = 2] = "UNDEFINED"; -})(Mark || (Mark = {})); -// The complete set of opcodes used for communication between clients and server. -var OpCode; -(function (OpCode) { - // New game round starting. - OpCode[OpCode["START"] = 1] = "START"; - // Update to the state of an ongoing round. - OpCode[OpCode["UPDATE"] = 2] = "UPDATE"; - // A game round has just completed. - OpCode[OpCode["DONE"] = 3] = "DONE"; - // A move the player wishes to make and sends to the server. - OpCode[OpCode["MOVE"] = 4] = "MOVE"; - // Move was rejected. - OpCode[OpCode["REJECTED"] = 5] = "REJECTED"; -})(OpCode || (OpCode = {})); -// Copyright 2020 The Nakama Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -var moduleName = "tic-tac-toe_js"; -var tickRate = 5; -var maxEmptySec = 30; -var delaybetweenGamesSec = 5; -var turnTimeFastSec = 10; -var turnTimeNormalSec = 20; -var winningPositions = [ - [0, 1, 2], - [3, 4, 5], - [6, 7, 8], - [0, 3, 6], - [1, 4, 7], - [2, 5, 8], - [0, 4, 8], - [2, 4, 6], -]; -var matchInit = function (ctx, logger, nk, params) { - var fast = !!params['fast']; - var label = { - open: 1, - fast: 0, - }; - if (fast) { - label.fast = 1; - } - var state = { - label: label, - emptyTicks: 0, - presences: {}, - joinsInProgress: 0, - playing: false, - board: [], - marks: {}, - mark: Mark.UNDEFINED, - deadlineRemainingTicks: 0, - winner: null, - winnerPositions: null, - nextGameRemainingTicks: 0, - }; - return { - state: state, - tickRate: tickRate, - label: JSON.stringify(label), - }; -}; -var matchJoinAttempt = function (ctx, logger, nk, dispatcher, tick, state, presence, metadata) { - // Check if it's a user attempting to rejoin after a disconnect. - if (presence.userId in state.presences) { - if (state.presences[presence.userId] === null) { - // User rejoining after a disconnect. - state.joinsInProgress++; - return { - state: state, - accept: false, - }; - } - else { - // User attempting to join from 2 different devices at the same time. - return { - state: state, - accept: false, - rejectMessage: 'already joined', - }; - } - } - // Check if match is full. - if (connectedPlayers(state) + state.joinsInProgress >= 2) { - return { - state: state, - accept: false, - rejectMessage: 'match full', - }; - } - // New player attempting to connect. - state.joinsInProgress++; - return { - state: state, - accept: true, - }; -}; -var matchJoin = function (ctx, logger, nk, dispatcher, tick, state, presences) { - var t = msecToSec(Date.now()); - for (var _i = 0, presences_1 = presences; _i < presences_1.length; _i++) { - var presence = presences_1[_i]; - state.emptyTicks = 0; - state.presences[presence.userId] = presence; - state.joinsInProgress--; - // Check if we must send a message to this user to update them on the current game state. - if (state.playing) { - // There's a game still currently in progress, the player is re-joining after a disconnect. Give them a state update. - var update = { - board: state.board, - mark: state.mark, - deadline: t + Math.floor(state.deadlineRemainingTicks / tickRate), - }; - // Send a message to the user that just joined. - dispatcher.broadcastMessage(OpCode.UPDATE, JSON.stringify(update)); - } - else if (state.board.length !== 0 && Object.keys(state.marks).length !== 0 && state.marks[presence.userId]) { - logger.debug('player %s rejoined game', presence.userId); - // There's no game in progress but we still have a completed game that the user was part of. - // They likely disconnected before the game ended, and have since forfeited because they took too long to return. - var done = { - board: state.board, - winner: state.winner, - winnerPositions: state.winnerPositions, - nextGameStart: t + Math.floor(state.nextGameRemainingTicks / tickRate) - }; - // Send a message to the user that just joined. - dispatcher.broadcastMessage(OpCode.DONE, JSON.stringify(done)); - } - } - // Check if match was open to new players, but should now be closed. - if (Object.keys(state.presences).length >= 2 && state.label.open != 0) { - state.label.open = 0; - var labelJSON = JSON.stringify(state.label); - dispatcher.matchLabelUpdate(labelJSON); - } - return { state: state }; -}; -var matchLeave = function (ctx, logger, nk, dispatcher, tick, state, presences) { - for (var _i = 0, presences_2 = presences; _i < presences_2.length; _i++) { - var presence = presences_2[_i]; - logger.info("Player: %s left match: %s.", presence.userId, ctx.matchId); - state.presences[presence.userId] = null; - } - return { state: state }; -}; -var matchLoop = function (ctx, logger, nk, dispatcher, tick, state, messages) { - var _a; - logger.debug('Running match loop. Tick: %d', tick); - if (connectedPlayers(state) + state.joinsInProgress === 0) { - state.emptyTicks++; - if (state.emptyTicks >= maxEmptySec * tickRate) { - // Match has been empty for too long, close it. - logger.info('closing idle match'); - return null; - } - } - var t = msecToSec(Date.now()); - // If there's no game in progress check if we can (and should) start one! - if (!state.playing) { - // Between games any disconnected users are purged, there's no in-progress game for them to return to anyway. - for (var userID in state.presences) { - if (state.presences[userID] === null) { - delete state.presences[userID]; - } - } - // Check if we need to update the label so the match now advertises itself as open to join. - if (Object.keys(state.presences).length < 2 && state.label.open != 1) { - state.label.open = 1; - var labelJSON = JSON.stringify(state.label); - dispatcher.matchLabelUpdate(labelJSON); - } - // Check if we have enough players to start a game. - if (Object.keys(state.presences).length < 2) { - return { state: state }; - } - // Check if enough time has passed since the last game. - if (state.nextGameRemainingTicks > 0) { - state.nextGameRemainingTicks--; - return { state: state }; - } - // We can start a game! Set up the game state and assign the marks to each player. - state.playing = true; - state.board = new Array(9); - state.marks = {}; - var marks_1 = [Mark.X, Mark.O]; - Object.keys(state.presences).forEach(function (userId) { - var _a; - state.marks[userId] = (_a = marks_1.shift(), (_a !== null && _a !== void 0 ? _a : null)); - }); - state.mark = Mark.X; - state.winner = null; - state.winnerPositions = null; - state.deadlineRemainingTicks = calculateDeadlineTicks(state.label); - state.nextGameRemainingTicks = 0; - // Notify the players a new game has started. - var msg = { - board: state.board, - marks: state.marks, - mark: state.mark, - deadline: t + Math.floor(state.deadlineRemainingTicks / tickRate), - }; - dispatcher.broadcastMessage(OpCode.START, JSON.stringify(msg)); - return { state: state }; - } - // There's a game in progresstate. Check for input, update match state, and send messages to clientstate. - for (var _i = 0, messages_1 = messages; _i < messages_1.length; _i++) { - var message = messages_1[_i]; - switch (message.opCode) { - case OpCode.MOVE: - logger.debug('Received move message from user: %v', state.marks); - var mark = (_a = state.marks[message.sender.userId], (_a !== null && _a !== void 0 ? _a : null)); - if (mark === null || state.mark != mark) { - // It is not this player's turn. - dispatcher.broadcastMessage(OpCode.REJECTED, null, [message.sender]); - continue; - } - var msg = {}; - try { - msg = JSON.parse(nk.binaryToString(message.data)); - } - catch (error) { - // Client sent bad data. - dispatcher.broadcastMessage(OpCode.REJECTED, null, [message.sender]); - logger.debug('Bad data received: %v', error); - continue; - } - if (state.board[msg.position]) { - // Client sent a position outside the board, or one that has already been played. - dispatcher.broadcastMessage(OpCode.REJECTED, null, [message.sender]); - continue; - } - // Update the game state. - state.board[msg.position] = mark; - state.mark = mark === Mark.O ? Mark.X : Mark.O; - state.deadlineRemainingTicks = calculateDeadlineTicks(state.label); - // Check if game is over through a winning move. - var _b = winCheck(state.board, mark), winner = _b[0], winningPos = _b[1]; - if (winner) { - state.winner = mark; - state.winnerPositions = winningPos; - state.playing = false; - state.deadlineRemainingTicks = 0; - state.nextGameRemainingTicks = delaybetweenGamesSec * tickRate; - } - // Check if game is over because no more moves are possible. - var tie = state.board.every(function (v) { return v !== null; }); - if (tie) { - // Update state to reflect the tie, and schedule the next game. - state.playing = false; - state.deadlineRemainingTicks = 0; - state.nextGameRemainingTicks = delaybetweenGamesSec * tickRate; - } - var opCode = void 0; - var outgoingMsg = void 0; - if (state.playing) { - opCode = OpCode.UPDATE; - var msg_1 = { - board: state.board, - mark: state.mark, - deadline: t + Math.floor(state.deadlineRemainingTicks / tickRate), - }; - outgoingMsg = msg_1; - } - else { - opCode = OpCode.DONE; - var msg_2 = { - board: state.board, - winner: state.winner, - winnerPositions: state.winnerPositions, - nextGameStart: t + Math.floor(state.nextGameRemainingTicks / tickRate), - }; - outgoingMsg = msg_2; - } - dispatcher.broadcastMessage(opCode, JSON.stringify(outgoingMsg)); - break; - default: - // No other opcodes are expected from the client, so automatically treat it as an error. - dispatcher.broadcastMessage(OpCode.REJECTED, null, [message.sender]); - logger.error('Unexpected opcode received: %d', message.opCode); - } - } - // Keep track of the time remaining for the player to submit their move. Idle players forfeit. - if (state.playing) { - state.deadlineRemainingTicks--; - if (state.deadlineRemainingTicks <= 0) { - // The player has run out of time to submit their move. - state.playing = false; - state.winner = state.mark === Mark.O ? Mark.X : Mark.O; - state.deadlineRemainingTicks = 0; - state.nextGameRemainingTicks = delaybetweenGamesSec * tickRate; - var msg = { - board: state.board, - winner: state.winner, - nextGameStart: t + Math.floor(state.nextGameRemainingTicks / tickRate), - winnerPositions: null, - }; - dispatcher.broadcastMessage(OpCode.DONE, JSON.stringify(msg)); - } - } - return { state: state }; -}; -var matchTerminate = function (ctx, logger, nk, dispatcher, tick, state, graceSeconds) { - return { state: state }; -}; -var matchSignal = function (ctx, logger, nk, dispatcher, tick, state) { - return { state: state }; -}; -function calculateDeadlineTicks(l) { - if (l.fast === 1) { - return turnTimeFastSec * tickRate; - } - else { - return turnTimeNormalSec * tickRate; - } -} -function winCheck(board, mark) { - for (var _i = 0, winningPositions_1 = winningPositions; _i < winningPositions_1.length; _i++) { - var wp = winningPositions_1[_i]; - if (board[wp[0]] === mark && - board[wp[1]] === mark && - board[wp[2]] === mark) { - return [true, wp]; - } - } - return [false, null]; -} -function connectedPlayers(s) { - var count = 0; - for (var _i = 0, _a = Object.keys(s.presences); _i < _a.length; _i++) { - var p = _a[_i]; - if (s.presences[p] !== null) { - count++; - } - } - return count; -} -// Copyright 2020 The Nakama Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -var rpcFindMatch = function (ctx, logger, nk, payload) { - if (!ctx.userId) { - throw Error('No user ID in context'); - } - if (!payload) { - throw Error('Expects payload.'); - } - var request = {}; - try { - request = JSON.parse(payload); - } - catch (error) { - logger.error('Error parsing json message: %q', error); - throw error; - } - var matches; - try { - var query = "+label.open:1 +label.fast:" + (request.fast ? 1 : 0); - matches = nk.matchList(10, true, null, null, 1, query); - } - catch (error) { - logger.error('Error listing matches: %v', error); - throw error; - } - var matchIds = []; - if (matches.length > 0) { - // There are one or more ongoing matches the user could join. - matchIds = matches.map(function (m) { return m.matchId; }); - } - else { - // No available matches found, create a new one. - try { - matchIds.push(nk.matchCreate(moduleName, { fast: request.fast })); - } - catch (error) { - logger.error('Error creating match: %v', error); - throw error; - } - } - var res = { matchIds: matchIds }; - return JSON.stringify(res); -};