diff --git a/authorization/auth.controller.js b/authorization/auth.controller.js index f1c87eb..fa8785b 100644 --- a/authorization/auth.controller.js +++ b/authorization/auth.controller.js @@ -126,7 +126,7 @@ exports.KeepOnline = async (req, res, next) => { }; exports.GetVersion = (req, res) => { - return res.status(200).send({ version: config.version }); + return res.status(200).send({description:config.description, version:config.version, md5:config.md5, timestamp: Date.now()}); }; // ----- verify user ----------- diff --git a/config.js b/config.js index df4309b..35e3111 100644 --- a/config.js +++ b/config.js @@ -1,5 +1,7 @@ module.exports = { - version: "1.13", + version: "0.0.5", + description: "0.0.1", + md5: "36b441e48050bb919b5f0afcae3f076d", port: 8080, port_https: 443, diff --git a/jobs/jobs.js b/jobs/jobs.js index 0df34c2..4c21806 100644 --- a/jobs/jobs.js +++ b/jobs/jobs.js @@ -1,5 +1,5 @@ const schedule = require('node-schedule'); -const ladderModel = require('./ladder/ladder.model'); +const ladderModel = require('../ladder/ladder.model'); const ExecuteJobs = async() => { diff --git a/ladder-config.json b/ladder-config.json index 3c7ada1..4980930 100644 --- a/ladder-config.json +++ b/ladder-config.json @@ -6,10 +6,10 @@ "Level": 1, "BeginStar": 0, "RankDownStar": 0, - "MaxStar": 5, + "MaxStar": 2, "WinGetStar": 1, - "ExtraGetStar": 0, - "LoseLostStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 0, "LoseRankDown": 0, "RankScore": 0, "AITimes": 30, @@ -23,12 +23,12 @@ "RankName": "Bronze", "Level": 2, "BeginStar": 0, - "RankDownStar": 3, - "MaxStar": 5, + "RankDownStar": 0, + "MaxStar": 2, "WinGetStar": 1, - "ExtraGetStar": 0, - "LoseLostStar": 1, - "LoseRankDown": 1, + "ExtraGetStar": 1, + "LoseLostStar": 0, + "LoseRankDown": 0, "RankScore": 0, "AITimes": 30, "AIDeck": "bronze_ai", @@ -37,34 +37,70 @@ }, { "Id": 3, - "Rank": 2, - "RankName": "Silver", + "Rank": 1, + "RankName": "Bronze", "Level": 3, "BeginStar": 0, - "RankDownStar": 3, - "MaxStar": 6, + "RankDownStar": 0, + "MaxStar": 2, "WinGetStar": 1, "ExtraGetStar": 1, - "LoseLostStar": 1, - "LoseRankDown": 1, + "LoseLostStar": 0, + "LoseRankDown": 0, "RankScore": 0, "AITimes": 30, - "AIDeck": "silver_ai", - "WaitTime": 15, - "MaxWaitTime": 25 + "AIDeck": "bronze_ai", + "WaitTime": 10, + "MaxWaitTime": 20 }, { "Id": 4, - "Rank": 2, - "RankName": "Silver", + "Rank": 1, + "RankName": "Bronze", "Level": 4, "BeginStar": 0, - "RankDownStar": 3, - "MaxStar": 6, + "RankDownStar": 0, + "MaxStar": 2, "WinGetStar": 1, "ExtraGetStar": 1, - "LoseLostStar": 1, - "LoseRankDown": 1, + "LoseLostStar": 0, + "LoseRankDown": 0, + "RankScore": 0, + "AITimes": 30, + "AIDeck": "bronze_ai", + "WaitTime": 10, + "MaxWaitTime": 20 + }, + { + "Id": 5, + "Rank": 1, + "RankName": "Bronze", + "Level": 5, + "BeginStar": 0, + "RankDownStar": 0, + "MaxStar": 2, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 0, + "LoseRankDown": 0, + "RankScore": 0, + "AITimes": 30, + "AIDeck": "bronze_ai", + "WaitTime": 10, + "MaxWaitTime": 20 + }, + { + "Id": 6, + "Rank": 2, + "RankName": "Silver", + "Level": 6, + "BeginStar": 0, + "RankDownStar": 2, + "MaxStar": 3, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 0, + "LoseRankDown": 0, "RankScore": 0, "AITimes": 30, "AIDeck": "silver_ai", @@ -72,21 +108,455 @@ "MaxWaitTime": 25 }, { - "Id": 5, + "Id": 7, + "Rank": 2, + "RankName": "Silver", + "Level": 7, + "BeginStar": 0, + "RankDownStar": 2, + "MaxStar": 3, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 0, + "LoseRankDown": 0, + "RankScore": 0, + "AITimes": 30, + "AIDeck": "silver_ai", + "WaitTime": 15, + "MaxWaitTime": 25 + }, + { + "Id": 8, + "Rank": 2, + "RankName": "Silver", + "Level": 8, + "BeginStar": 0, + "RankDownStar": 2, + "MaxStar": 3, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 0, + "LoseRankDown": 0, + "RankScore": 0, + "AITimes": 30, + "AIDeck": "silver_ai", + "WaitTime": 15, + "MaxWaitTime": 25 + }, + { + "Id": 9, + "Rank": 2, + "RankName": "Silver", + "Level": 9, + "BeginStar": 0, + "RankDownStar": 2, + "MaxStar": 3, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 0, + "LoseRankDown": 0, + "RankScore": 0, + "AITimes": 30, + "AIDeck": "silver_ai", + "WaitTime": 15, + "MaxWaitTime": 25 + }, + { + "Id": 10, + "Rank": 2, + "RankName": "Silver", + "Level": 10, + "BeginStar": 0, + "RankDownStar": 2, + "MaxStar": 3, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 0, + "LoseRankDown": 0, + "RankScore": 0, + "AITimes": 30, + "AIDeck": "silver_ai", + "WaitTime": 15, + "MaxWaitTime": 25 + }, + { + "Id": 11, "Rank": 3, "RankName": "Gold", - "Level": 5, + "Level": 11, + "BeginStar": 0, + "RankDownStar": 2, + "MaxStar": 4, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 1, + "LoseRankDown": 0, + "RankScore": 0, + "AITimes": 30, + "AIDeck": "gold_ai", + "WaitTime": 20, + "MaxWaitTime": 30 + }, + { + "Id": 12, + "Rank": 3, + "RankName": "Gold", + "Level": 12, + "BeginStar": 0, + "RankDownStar": 2, + "MaxStar": 4, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 1, + "LoseRankDown": 0, + "RankScore": 0, + "AITimes": 30, + "AIDeck": "gold_ai", + "WaitTime": 20, + "MaxWaitTime": 30 + }, + { + "Id": 13, + "Rank": 3, + "RankName": "Gold", + "Level": 13, + "BeginStar": 0, + "RankDownStar": 2, + "MaxStar": 4, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 1, + "LoseRankDown": 0, + "RankScore": 0, + "AITimes": 30, + "AIDeck": "gold_ai", + "WaitTime": 20, + "MaxWaitTime": 30 + }, + { + "Id": 14, + "Rank": 3, + "RankName": "Gold", + "Level": 14, + "BeginStar": 0, + "RankDownStar": 2, + "MaxStar": 4, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 1, + "LoseRankDown": 0, + "RankScore": 0, + "AITimes": 30, + "AIDeck": "gold_ai", + "WaitTime": 20, + "MaxWaitTime": 30 + }, + { + "Id": 15, + "Rank": 3, + "RankName": "Gold", + "Level": 15, + "BeginStar": 0, + "RankDownStar": 2, + "MaxStar": 4, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 1, + "LoseRankDown": 0, + "RankScore": 0, + "AITimes": 30, + "AIDeck": "gold_ai", + "WaitTime": 20, + "MaxWaitTime": 30 + }, + { + "Id": 16, + "Rank": 4, + "RankName": "Platinum", + "Level": 16, "BeginStar": 0, "RankDownStar": 3, - "MaxStar": 7, + "MaxStar": 5, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 1, + "LoseRankDown": 1, + "RankScore": 0, + "AITimes": 30, + "AIDeck": "platinum_ai", + "WaitTime": 25, + "MaxWaitTime": 35 + }, + { + "Id": 17, + "Rank": 4, + "RankName": "Platinum", + "Level": 17, + "BeginStar": 0, + "RankDownStar": 3, + "MaxStar": 5, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 1, + "LoseRankDown": 1, + "RankScore": 0, + "AITimes": 30, + "AIDeck": "platinum_ai", + "WaitTime": 25, + "MaxWaitTime": 35 + }, + { + "Id": 18, + "Rank": 4, + "RankName": "Platinum", + "Level": 18, + "BeginStar": 0, + "RankDownStar": 3, + "MaxStar": 5, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 1, + "LoseRankDown": 1, + "RankScore": 0, + "AITimes": 30, + "AIDeck": "platinum_ai", + "WaitTime": 25, + "MaxWaitTime": 35 + }, + { + "Id": 19, + "Rank": 4, + "RankName": "Platinum", + "Level": 19, + "BeginStar": 0, + "RankDownStar": 3, + "MaxStar": 5, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 1, + "LoseRankDown": 1, + "RankScore": 0, + "AITimes": 30, + "AIDeck": "platinum_ai", + "WaitTime": 25, + "MaxWaitTime": 35 + }, + { + "Id": 20, + "Rank": 4, + "RankName": "Platinum", + "Level": 20, + "BeginStar": 0, + "RankDownStar": 3, + "MaxStar": 5, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 1, + "LoseRankDown": 1, + "RankScore": 0, + "AITimes": 30, + "AIDeck": "platinum_ai", + "WaitTime": 25, + "MaxWaitTime": 35 + }, + { + "Id": 21, + "Rank": 5, + "RankName": "Diamond", + "Level": 21, + "BeginStar": 0, + "RankDownStar": 3, + "MaxStar": 5, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 1, + "LoseRankDown": 1, + "RankScore": 0, + "AITimes": 30, + "AIDeck": "diamond_ai", + "WaitTime": 30, + "MaxWaitTime": 40 + }, + { + "Id": 22, + "Rank": 5, + "RankName": "Diamond", + "Level": 22, + "BeginStar": 0, + "RankDownStar": 3, + "MaxStar": 5, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 1, + "LoseRankDown": 1, + "RankScore": 0, + "AITimes": 30, + "AIDeck": "diamond_ai", + "WaitTime": 30, + "MaxWaitTime": 40 + }, + { + "Id": 23, + "Rank": 5, + "RankName": "Diamond", + "Level": 23, + "BeginStar": 0, + "RankDownStar": 3, + "MaxStar": 5, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 1, + "LoseRankDown": 1, + "RankScore": 0, + "AITimes": 30, + "AIDeck": "diamond_ai", + "WaitTime": 30, + "MaxWaitTime": 40 + }, + { + "Id": 24, + "Rank": 5, + "RankName": "Diamond", + "Level": 24, + "BeginStar": 0, + "RankDownStar": 3, + "MaxStar": 5, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 1, + "LoseRankDown": 1, + "RankScore": 0, + "AITimes": 30, + "AIDeck": "diamond_ai", + "WaitTime": 30, + "MaxWaitTime": 40 + }, + { + "Id": 25, + "Rank": 5, + "RankName": "Diamond", + "Level": 25, + "BeginStar": 0, + "RankDownStar": 3, + "MaxStar": 5, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 1, + "LoseRankDown": 1, + "RankScore": 0, + "AITimes": 30, + "AIDeck": "diamond_ai", + "WaitTime": 30, + "MaxWaitTime": 40 + }, + { + "Id": 26, + "Rank": 6, + "RankName": "King", + "Level": 26, + "BeginStar": 0, + "RankDownStar": 3, + "MaxStar": 5, "WinGetStar": 1, "ExtraGetStar": 1, "LoseLostStar": 1, "LoseRankDown": 1, "RankScore": 1, + "InitialRankScore": 1100, + "WinScoreMin": 80, + "WinScoreMax": 150, + "LoseScore": 20, "AITimes": 30, - "AIDeck": "gold_ai", - "WaitTime": 20, - "MaxWaitTime": 30 + "AIDeck": "king_ai", + "WaitTime": 35, + "MaxWaitTime": 45 + }, + { + "Id": 27, + "Rank": 6, + "RankName": "King", + "Level": 27, + "BeginStar": 0, + "RankDownStar": 3, + "MaxStar": 5, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 1, + "LoseRankDown": 1, + "RankScore": 1, + "InitialRankScore": 1100, + "WinScoreMin": 80, + "WinScoreMax": 150, + "LoseScore": 20, + "AITimes": 30, + "AIDeck": "king_ai", + "WaitTime": 35, + "MaxWaitTime": 45 + }, + { + "Id": 28, + "Rank": 6, + "RankName": "King", + "Level": 28, + "BeginStar": 0, + "RankDownStar": 3, + "MaxStar": 5, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 1, + "LoseRankDown": 1, + "RankScore": 1, + "InitialRankScore": 1100, + "WinScoreMin": 80, + "WinScoreMax": 150, + "LoseScore": 20, + "AITimes": 30, + "AIDeck": "king_ai", + "WaitTime": 35, + "MaxWaitTime": 45 + }, + { + "Id": 29, + "Rank": 6, + "RankName": "King", + "Level": 29, + "BeginStar": 0, + "RankDownStar": 3, + "MaxStar": 5, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 1, + "LoseRankDown": 1, + "RankScore": 1, + "InitialRankScore": 1100, + "WinScoreMin": 80, + "WinScoreMax": 150, + "LoseScore": 20, + "AITimes": 30, + "AIDeck": "king_ai", + "WaitTime": 35, + "MaxWaitTime": 45 + }, + { + "Id": 30, + "Rank": 6, + "RankName": "King", + "Level": 30, + "BeginStar": 0, + "RankDownStar": 3, + "MaxStar": 5, + "WinGetStar": 1, + "ExtraGetStar": 1, + "LoseLostStar": 1, + "LoseRankDown": 1, + "RankScore": 1, + "InitialRankScore": 1100, + "WinScoreMin": 80, + "WinScoreMax": 150, + "LoseScore": 20, + "AITimes": 30, + "AIDeck": "king_ai", + "WaitTime": 35, + "MaxWaitTime": 45 } ] \ No newline at end of file diff --git a/ladder/ladder.model.js b/ladder/ladder.model.js index 05724d5..323edf8 100644 --- a/ladder/ladder.model.js +++ b/ladder/ladder.model.js @@ -11,6 +11,7 @@ const leaderboardSchema = new Schema({ stars: { type: Number, default: 0 }, totalWins: { type: Number, default: 0 }, position: { type: Number, required: true }, + lastWinDeck: { type: Object, default: null }, // 添加最后获胜牌组信息 lastUpdated: { type: Date, default: Date.now } }); @@ -28,7 +29,7 @@ exports.generateLeaderboard = async () => { // Sort users based on ranking criteria: // 1. Rank level (higher is better) - // 2. Rank score (for王者分数 mechanism) + // 2. Rank score (for King rank mechanism) // 3. Stars // 4. Total wins const sortedUsers = users.sort((a, b) => { @@ -40,7 +41,7 @@ exports.generateLeaderboard = async () => { return configB.Level - configA.Level; } - // For players with rank score mechanism + // For players with rank score mechanism (King rank) if (configA.RankScore === 1 && configB.RankScore === 1) { if (a.rankScore !== b.rankScore) { return b.rankScore - a.rankScore; @@ -71,6 +72,7 @@ exports.generateLeaderboard = async () => { rankScore: player.rankScore, stars: player.stars, totalWins: player.totalWins, + lastWinDeck: player.lastWinDeck, // 包含最后获胜牌组 position: i + 1 }); leaderboardEntries.push(entry); @@ -115,5 +117,6 @@ exports.initializePlayerLadder = (player) => { player.rankScore = 0; player.winStreak = 0; player.totalWins = 0; + player.lastWinDeck = null; // 初始化最后获胜牌组 return player; }; \ No newline at end of file diff --git a/ladder/ladder.service.js b/ladder/ladder.service.js index 40bc573..7e547c0 100644 --- a/ladder/ladder.service.js +++ b/ladder/ladder.service.js @@ -31,7 +31,7 @@ class LadderService { const config = this.getRankConfig(player.rankId); if (config.RankScore === 1) { - // 王者分数 mechanism + // King rank score mechanism this.updateRankScore(player, opponent, true); } else { // Star-based mechanism @@ -49,7 +49,7 @@ class LadderService { const config = this.getRankConfig(player.rankId); if (config.RankScore === 1) { - // 王者分数 mechanism + // King rank score mechanism this.updateRankScore(player, opponent, false); } else { // Star-based mechanism @@ -64,8 +64,8 @@ class LadderService { updateStars(player, config, isWin) { if (isWin) { let starsToAdd = config.WinGetStar; - // Add extra stars for win streak - if (player.winStreak >= 3) { + // Add extra stars for win streak (3+ wins) + if (player.winStreak >= 2) { // Streak starts at 2 (after 2 wins) starsToAdd += config.ExtraGetStar; } player.stars += starsToAdd; @@ -79,7 +79,7 @@ class LadderService { player.stars = Math.max(0, player.stars - 1); // Level down check - if (player.stars === 0) { + if (player.stars === 0 && config.LoseRankDown === 1) { this.levelDown(player, config); } } @@ -87,18 +87,35 @@ class LadderService { } updateRankScore(player, opponent, isWin) { + const config = this.getRankConfig(player.rankId); + if (isWin) { - let scoreToAdd = 20; + // Initialize rank score if not already set + if (player.rankScore === 0) { + player.rankScore = config.InitialRankScore || 1100; + } + + // Calculate score to add based on opponent's rank + let scoreToAdd = config.WinScoreMin || 80; + // If opponent has a higher rank score, calculate bonus if (opponent.rankScore > player.rankScore) { - const ratio = Math.min(opponent.rankScore / player.rankScore, 3); - scoreToAdd = Math.round(ratio * 20); + // Calculate bonus based on rank difference (simplified) + const ratio = Math.min((opponent.rankScore - player.rankScore) / 100, 1); + scoreToAdd = Math.round(config.WinScoreMin + ratio * (config.WinScoreMax - config.WinScoreMin)); } + player.rankScore += scoreToAdd; } else { // Deduct points on loss + const scoreToDeduct = config.LoseScore || 20; if (player.rankScore > 0) { - player.rankScore = Math.max(0, player.rankScore - 20); + player.rankScore = Math.max(0, player.rankScore - scoreToDeduct); + } + + // Check for level down if score is too low + if (player.rankScore < 1000 && config.LoseRankDown === 1) { + this.levelDown(player, config); } } } @@ -108,6 +125,11 @@ class LadderService { if (nextConfig) { player.rankId++; player.stars = nextConfig.BeginStar; + + // Initialize rank score for King rank + if (nextConfig.RankScore === 1) { + player.rankScore = nextConfig.InitialRankScore || 1100; + } } } @@ -115,7 +137,14 @@ class LadderService { if (currentConfig.LoseRankDown === 1 && player.rankId > 1) { player.rankId--; const prevConfig = this.getRankConfig(player.rankId); - player.stars = prevConfig.RankDownStar; + if (prevConfig.RankScore === 1) { + // Stay in rank score system + player.rankScore = Math.max(prevConfig.InitialRankScore || 1100, player.rankScore - (prevConfig.LoseScore || 20)); + } else { + // Back to star system + player.stars = prevConfig.RankDownStar; + player.rankScore = 0; + } } } diff --git a/server.js b/server.js index 9ae271f..84f96e0 100644 --- a/server.js +++ b/server.js @@ -41,7 +41,7 @@ app.use(function (req, res, next) { }); //Parse JSON body -app.use(express.json({ limit: "100kb" })); +app.use(express.json({ limit: "10kb" })); //Log request app.use((req, res, next) => { @@ -96,6 +96,14 @@ ActivityRouter.route(app); const LadderRouter = require('./ladder/ladder.routes'); LadderRouter.route(app); +// Task system routes +const TasksRouter = require('./tasks/tasks.routes'); +TasksRouter.route(app); + +// Initialize task system +const TasksModel = require('./tasks/tasks.model.js'); +TasksModel.initializeTaskConfig('./tasks-config.json'); + //Read SSL cert var ReadSSL = function() { diff --git a/tasks-api.md b/tasks-api.md new file mode 100644 index 0000000..e88417d --- /dev/null +++ b/tasks-api.md @@ -0,0 +1,119 @@ +# 任务系统接口文档 + +## 概述 + +任务系统允许玩家完成各种游戏内任务以获得奖励。系统支持多种任务类型,包括日常任务、成就任务等。 + +## API端点 + +### 获取所有任务配置 +``` +GET /api/tasks +``` + +**响应** +```json +[ + { + "id": "login_task_1", + "name": "每日登录", + "desc": "每日登录游戏", + "condition": 1, + "value1": 1, + "value2": "", + "value3": "", + "rewardTypes": [0], + "rewardNums": [100], + "isDailyTask": true, + "durationHours": 24 + }, + { + "id": "win_task_1", + "name": "胜利之路", + "desc": "获得3场对战胜利", + "condition": 3, + "value1": 3, + "value2": "", + "value3": "", + "rewardTypes": [0], + "rewardNums": [200], + "isDailyTask": true, + "durationHours": 24 + } +] +``` + +### 获取玩家任务数据 +``` +GET /api/tasks/{userId} +``` + +**响应** +```json +{ + "tasks": [ + { + "taskId": "login_task_1", + "assignedTime": 16409952000000000, + "expireTime": 16410816000000000, + "status": 1, + "progress": 1 + } + ], + "lastDailyTaskAssigned": 16409952000000000 +} +``` + +### 保存玩家任务数据 +``` +POST /api/tasks/{userId} +``` + +**请求体** +```json +{ + "tasks": [ + { + "taskId": "login_task_1", + "assignedTime": 16409952000000000, + "expireTime": 16410816000000000, + "status": 1, + "progress": 1 + } + ], + "lastDailyTaskAssigned": 16409952000000000 +} +``` + +**响应** +```json +{ + "success": true, + "error": "" +} +``` + +## 枚举值定义 + +### 任务条件类型 (TaskConditionType) +| 值 | 名称 | 描述 | +|---|------|------| +| 1 | LoginGame | 登入游戏 | +| 2 | PlayGames | 进行X场对战 | +| 3 | WinGames | 胜利X场 | +| 4 | DefeatHeroWithAttributes | 击败Y属性和Z属性的英雄X次 | +| 5 | SummonHeroWithAttributes | 召唤Y属性和Z属性的英雄X次 | +| 6 | UseHeroSkillWithAttributes | 使用Y属性和Z属性英雄的技能X次 | + +### 任务奖励类型 (TaskRewardType) +| 值 | 名称 | 描述 | +|---|------|------| +| 0 | Coins | 金币 | + +### 任务状态 (TaskStatus) +| 值 | 名称 | 描述 | +|---|------|------| +| 0 | Active | 激活 | +| 1 | Completed | 完成 | +| 2 | Expired | 过期 | +| 3 | Claimed | 已领取 | \ No newline at end of file diff --git a/tasks-config.json b/tasks-config.json new file mode 100644 index 0000000..ada96e3 --- /dev/null +++ b/tasks-config.json @@ -0,0 +1,80 @@ +[ + { + "id": "login_task_1", + "name": "每日登录", + "desc": "每日登录游戏", + "condition": 1, + "value1": 1, + "value2": "", + "value3": "", + "rewardTypes": [0], + "rewardNums": [100], + "isDailyTask": true, + "durationHours": 24 + }, + { + "id": "play_task_1", + "name": "参与对战", + "desc": "参与5场对战", + "condition": 2, + "value1": 5, + "value2": "", + "value3": "", + "rewardTypes": [0], + "rewardNums": [150], + "isDailyTask": true, + "durationHours": 24 + }, + { + "id": "win_task_1", + "name": "胜利之路", + "desc": "获得3场对战胜利", + "condition": 3, + "value1": 3, + "value2": "", + "value3": "", + "rewardTypes": [0], + "rewardNums": [200], + "isDailyTask": true, + "durationHours": 24 + }, + { + "id": "defeat_hero_task_1", + "name": "阵营克星", + "desc": "击败5个义勇军或帝国军英雄", + "condition": 4, + "value1": 5, + "value2": "YiYongJun", + "value3": "DiGuoJun", + "rewardTypes": [0], + "rewardNums": [300], + "isDailyTask": true, + "durationHours": 24 + }, + { + "id": "summon_hero_task_1", + "name": "召唤大师", + "desc": "召唤5个王国军或自由人英雄", + "condition": 5, + "value1": 5, + "value2": "WangGuoJun", + "value3": "ZiYouRen", + "rewardTypes": [0], + "rewardNums": [250], + "isDailyTask": true, + "durationHours": 24 + }, + { + "id": "skill_use_task_1", + "name": "技能专家", + "desc": "使用守郡或邪魔英雄的技能10次", + "condition": 6, + "value1": 10, + "value2": "ShouQun", + "value3": "XieMo", + "rewardTypes": [0], + "rewardNums": [350], + "isDailyTask": true, + "durationHours": 24 + } +] \ No newline at end of file diff --git a/tasks/tasks.controller.js b/tasks/tasks.controller.js new file mode 100644 index 0000000..09228a9 --- /dev/null +++ b/tasks/tasks.controller.js @@ -0,0 +1,52 @@ +const TasksModel = require("./tasks.model.js"); + +// Get all task configurations +exports.getAllTasks = async (req, res) => { + try { + const tasks = await TasksModel.getAllTaskConfigs(); + res.status(200).send(tasks); + } catch (error) { + res.status(500).send({ error: "Internal server error" }); + } +}; + +// Get player tasks +exports.getPlayerTasks = async (req, res) => { + try { + const userId = req.params.userId; + if (!userId) { + return res.status(400).send({ error: "User ID is required" }); + } + + const playerTasks = await TasksModel.getPlayerTasks(userId); + if (!playerTasks) { + return res.status(404).send({ error: "Player tasks not found" }); + } + + res.status(200).send(playerTasks); + } catch (error) { + res.status(500).send({ error: "Internal server error" }); + } +}; + +// Save player tasks +exports.savePlayerTasks = async (req, res) => { + try { + const userId = req.params.userId; + if (!userId) { + return res.status(400).send({ error: "User ID is required" }); + } + + const tasksData = req.body; + tasksData.userId = userId; + + const updatedTasks = await TasksModel.savePlayerTasks(tasksData); + if (!updatedTasks) { + return res.status(500).send({ error: "Failed to save player tasks" }); + } + + res.status(200).send({ success: true }); + } catch (error) { + res.status(500).send({ error: "Internal server error" }); + } +}; \ No newline at end of file diff --git a/tasks/tasks.model.js b/tasks/tasks.model.js new file mode 100644 index 0000000..1e04d94 --- /dev/null +++ b/tasks/tasks.model.js @@ -0,0 +1,94 @@ +const mongoose = require("mongoose"); +const fs = require("fs"); +const path = require("path"); + +const Schema = mongoose.Schema; + +// Task data structure based on the documentation +const playerTaskSchema = new Schema({ + taskId: { type: String, required: true }, + assignedTime: { type: Date, required: true }, + expireTime: { type: Date, required: true }, + status: { type: Number, required: true, default: 0 }, // 0: Active, 1: Completed, 2: Expired, 3: Claimed + progress: { type: Number, required: true, default: 0 } +}); + +const playerTaskDataSchema = new Schema({ + userId: { type: String, required: true, index: true }, + tasks: [playerTaskSchema], + lastDailyTaskAssigned: { type: Date, default: null } +}); + +playerTaskDataSchema.methods.toObj = function () { + var elem = this.toObject(); + delete elem.__v; + delete elem._id; + return elem; +}; + +const PlayerTaskData = mongoose.model("PlayerTaskData", playerTaskDataSchema); +exports.PlayerTaskData = PlayerTaskData; + +// Task configuration storage +let taskConfigurations = []; + +// Get all task configurations +exports.getAllTaskConfigs = async () => { + return taskConfigurations; +}; + +// Get task configuration by ID +exports.getTaskConfigById = async (taskId) => { + return taskConfigurations.find(task => task.id === taskId); +}; + +// Get player tasks +exports.getPlayerTasks = async (userId) => { + try { + let playerTaskData = await PlayerTaskData.findOne({ userId: userId }); + if (!playerTaskData) { + playerTaskData = new PlayerTaskData({ + userId: userId, + tasks: [], + lastDailyTaskAssigned: null + }); + await playerTaskData.save(); + } + return playerTaskData; + } catch (e) { + console.error("Error getting player tasks:", e); + return null; + } +}; + +// Save player tasks +exports.savePlayerTasks = async (playerTaskData) => { + try { + const updated = await PlayerTaskData.findOneAndUpdate( + { userId: playerTaskData.userId }, + playerTaskData, + { new: true, upsert: true } + ); + return updated; + } catch (e) { + console.error("Error saving player tasks:", e); + return null; + } +}; + +// Initialize task configurations from file +exports.initializeTaskConfig = (configPath) => { + try { + if (fs.existsSync(configPath)) { + const configFile = fs.readFileSync(configPath, 'utf8'); + taskConfigurations = JSON.parse(configFile); + console.log(`Loaded ${taskConfigurations.length} task configurations`); + } else { + console.log("Task configuration file not found, using empty configuration"); + taskConfigurations = []; + } + } catch (e) { + console.error("Error initializing task configuration:", e); + taskConfigurations = []; + } +}; \ No newline at end of file diff --git a/tasks/tasks.routes.js b/tasks/tasks.routes.js new file mode 100644 index 0000000..2cdb6a3 --- /dev/null +++ b/tasks/tasks.routes.js @@ -0,0 +1,30 @@ +const TasksController = require('./tasks.controller.js'); +const AuthTool = require('../authorization/auth.tool.js'); +const config = require('../config.js'); + +const ADMIN = config.permissions.ADMIN; // Highest permission, can read and write all users +const SERVER = config.permissions.SERVER; // Middle permission, can read all users +const USER = config.permissions.USER; // Lowest permission, can only do things on same user + +exports.route = (app) => { + // Get all task configurations + app.get('/api/tasks', + AuthTool.isValidJWT, + AuthTool.isPermissionLevel(SERVER), + TasksController.getAllTasks + ); + + // Get player tasks + app.get('/api/tasks/:userId', + AuthTool.isValidJWT, + AuthTool.isSameUserOr(SERVER), + TasksController.getPlayerTasks + ); + + // Save player tasks + app.post('/api/tasks/:userId', + AuthTool.isValidJWT, + AuthTool.isSameUserOr(SERVER), + TasksController.savePlayerTasks + ); +}; \ No newline at end of file diff --git a/tasks/tasks.tool.js b/tasks/tasks.tool.js new file mode 100644 index 0000000..a443467 --- /dev/null +++ b/tasks/tasks.tool.js @@ -0,0 +1,111 @@ +const TasksModel = require("./tasks.model.js"); + +// Task status constants +const TASK_STATUS = { + ACTIVE: 0, + COMPLETED: 1, + EXPIRED: 2, + CLAIMED: 3 +}; + +// Task condition types +const TASK_CONDITION_TYPES = { + LOGIN_GAME: 1, + PLAY_GAMES: 2, + WIN_GAMES: 3, + DEFEAT_HERO_WITH_ATTRIBUTES: 4, + SUMMON_HERO_WITH_ATTRIBUTES: 5, + USE_HERO_SKILL_WITH_ATTRIBUTES: 6 +}; + +// Task reward types +const TASK_REWARD_TYPES = { + COINS: 0 +}; + +// Check if a task is expired +exports.isTaskExpired = (task) => { + return new Date() > new Date(task.expireTime); +}; + +// Check if a task is completed +exports.isTaskCompleted = (task) => { + return task.status === TASK_STATUS.COMPLETED; +}; + +// Check if a task is claimed +exports.isTaskClaimed = (task) => { + return task.status === TASK_STATUS.CLAIMED; +}; + +// Get a random task from the configuration +exports.getRandomTask = async () => { + const tasks = await TasksModel.getAllTaskConfigs(); + if (tasks.length === 0) { + return null; + } + + const randomIndex = Math.floor(Math.random() * tasks.length); + return tasks[randomIndex]; +}; + +// Create a new player task based on task configuration +exports.createPlayerTask = async (taskConfig) => { + const now = new Date(); + const expireTime = new Date(now.getTime() + (taskConfig.durationHours * 60 * 60 * 1000)); + + return { + taskId: taskConfig.id, + assignedTime: now, + expireTime: expireTime, + status: TASK_STATUS.ACTIVE, + progress: 0 + }; +}; + +// Update task progress +exports.updateTaskProgress = async (playerTask, taskConfig, increment = 1) => { + // Check if task is already completed or claimed + if (playerTask.status === TASK_STATUS.COMPLETED || playerTask.status === TASK_STATUS.CLAIMED) { + return playerTask; + } + + // Update progress + playerTask.progress += increment; + + // Check if task is completed + if (playerTask.progress >= taskConfig.value1) { + playerTask.progress = taskConfig.value1; // Cap at the required value + playerTask.status = TASK_STATUS.COMPLETED; + } + + return playerTask; +}; + +// Give reward for completing a task +exports.giveTaskReward = async (userId, taskConfig) => { + // In a real implementation, this would integrate with the rewards system + // For now, we'll just log the reward information + console.log(`Giving reward to user ${userId} for task ${taskConfig.id}`); + + for (let i = 0; i < taskConfig.rewardTypes.length; i++) { + const rewardType = taskConfig.rewardTypes[i]; + const rewardNum = taskConfig.rewardNums[i]; + + switch (rewardType) { + case TASK_REWARD_TYPES.COINS: + console.log(`Rewarding ${rewardNum} coins to user ${userId}`); + // Here we would call the rewards system to give coins to the user + break; + default: + console.log(`Unknown reward type: ${rewardType}`); + } + } + + return true; +}; + +// Export constants +exports.TASK_STATUS = TASK_STATUS; +exports.TASK_CONDITION_TYPES = TASK_CONDITION_TYPES; +exports.TASK_REWARD_TYPES = TASK_REWARD_TYPES; \ No newline at end of file