PC-HEWEI\hewei 4 hónapja
szülő
commit
1347d08eb4
57 módosított fájl, 1135 hozzáadás és 504 törlés
  1. 2 0
      sr/.idea/vcs.xml
  2. 4 2
      sr/etc/data/const.json
  3. 6 2
      sr/etc/data/game_debug.json
  4. 56 0
      sr/etc/data/mini_game.json
  5. 2 2
      sr/etc/data/proto_route.json
  6. 10 10
      sr/etc/data/season_quest.json
  7. 4 1
      sr/etc/data_embed/game_debug.json
  8. BIN
      sr/etc/data_map/s1/area_data.pb
  9. BIN
      sr/etc/data_map/s1/map_static_data.pb
  10. 11 2
      sr/internal/code/status_code.go
  11. 3 0
      sr/internal/constant/test.go
  12. 3 1
      sr/internal/data/const.go
  13. 1 1
      sr/internal/data/extend_chapter_level.go
  14. 37 0
      sr/internal/data/extend_const.go
  15. 17 1
      sr/internal/data/extend_drop_pool.go
  16. 90 0
      sr/internal/data/mini_game.go
  17. 1 1
      sr/internal/data/season_quest.go
  18. 5 0
      sr/internal/name/local/player.go
  19. 0 1
      sr/internal/name/remote/map.go
  20. 6 0
      sr/internal/name/route/player.go
  21. 29 9
      sr/internal/pb/map.pb.go
  22. 139 0
      sr/internal/pb/minigame.pb.go
  23. 2 0
      sr/nodes/game/internal/db/data/player/data.go
  24. 12 0
      sr/nodes/game/internal/db/data/player/mini_game.go
  25. 1 0
      sr/nodes/game/player/actor_player.go
  26. 1 0
      sr/nodes/game/player/asset/origin/origin.go
  27. 2 1
      sr/nodes/game/player/asset/type/equip.go
  28. 34 56
      sr/nodes/game/player/player_equip.go
  29. 106 0
      sr/nodes/game/player/player_minigame.go
  30. 1 1
      sr/nodes/game/player/player_recruit.go
  31. 3 3
      sr/nodes/map/internal/call/player.go
  32. 1 1
      sr/nodes/map/internal/db/data/logic/monster.go
  33. 16 14
      sr/nodes/map/internal/db/data/player/sand_map.go
  34. 2 3
      sr/nodes/map/internal/db/data/player/team.go
  35. 1 1
      sr/nodes/map/internal/db/data/season/season_quest.go
  36. 8 6
      sr/nodes/map/internal/db/logic_build_table.go
  37. 17 7
      sr/nodes/map/internal/db/logic_march_table.go
  38. 2 1
      sr/nodes/map/internal/db/logic_res_table.go
  39. 28 0
      sr/nodes/map/internal/db/logic_watcher_table.go
  40. 2 7
      sr/nodes/map/internal/db/map_player_table.go
  41. 26 13
      sr/nodes/map/logic/actor_build.go
  42. 19 16
      sr/nodes/map/logic/actor_castle.go
  43. 45 29
      sr/nodes/map/logic/actor_handle_remote.go
  44. 4 1
      sr/nodes/map/logic/actor_march.go
  45. 1 2
      sr/nodes/map/logic/actor_tile.go
  46. 3 2
      sr/nodes/map/logic/actor_tile_siege.go
  47. 0 2
      sr/nodes/map/logic/actor_tile_snatch.go
  48. 1 1
      sr/nodes/map/map.go
  49. 11 19
      sr/nodes/map/player/actor.go
  50. 3 10
      sr/nodes/map/player/actor_player.go
  51. 46 5
      sr/nodes/map/player/map/service.go
  52. 42 93
      sr/nodes/map/player/player_map.go
  53. 38 37
      sr/nodes/map/player/player_team.go
  54. 13 8
      sr/nodes/map/player/player_timer.go
  55. 5 0
      sr/nodes/map/player/storage/service.go
  56. 79 46
      sr/nodes/map/player/team/service.go
  57. 134 86
      sr/tool/test/map_static_data/map_static_data.go

+ 2 - 0
sr/.idea/vcs.xml

@@ -2,5 +2,7 @@
 <project version="4">
   <component name="VcsDirectoryMappings">
     <mapping directory="" vcs="Git" />
+    <mapping directory="$PROJECT_DIR$/_excel" vcs="Git" />
+    <mapping directory="$PROJECT_DIR$/_proto" vcs="Git" />
   </component>
 </project>

+ 4 - 2
sr/etc/data/const.json

@@ -123,7 +123,7 @@
 	"MapMeetBattleQueue": 5,
 	"MapDefendRecoverTime": 300,
 	"MapDrawBattleCount": 2,
-	"MapRedeployDefendRatio": 3334,
+	"MapAutoDefendTroopsRatio": 3334,
 	"MapMonsterFlushHour": 12,
 	"MapSnatchWaitTime": 180,
 	"MapAssembleMemberNum": 3,
@@ -142,6 +142,7 @@
 	"ChooseStateComebackCoolDown": 432000,
 	"MapResOccupyLimit": 4,
 	"BeginerInvincibleTime": 2880,
+	"ComebackInvincibleTime": 720,
 	"ChapterStaminaAddInterval": 5400,
 	"ChapterStaminaAddValue": [5,1],
 	"ChapterStaminaLimit": 16,
@@ -174,5 +175,6 @@
 	"mapSeasonOpenTime": "2026-03-02",
 	"MapScoutMarchTime": 5,
 	"MapScoutTime": 60,
-	"MapScoutCosts": [[2,1]]
+	"MapScoutCosts": [[2,1]],
+	"EquipSingleGetLimit": 100
 }

+ 6 - 2
sr/etc/data/game_debug.json

@@ -7,7 +7,8 @@
             "MapBuildTime":10000,
             "MapCityBaseCooldown":10000,
             "MapComebackCooldown":10000,
-            "SiegeValue":600,
+            "MapRechooseComebackCooldown":10000,
+            "SiegeValue":1,
             "MapSiegeWaitTime":10000,
             "MapSnatchWaitTime":10000,
             "MapDiscardWaitTime":10000,
@@ -15,7 +16,10 @@
             "MapMarchSpeedUp": 2,
             "MapReinforcePrepareTime": 30000,
             "MapReinforceEffectTime": 60000,
-            "MapReinforceCooldown": 10000
+            "MapReinforceCooldown": 10000,
+            "BeginerInvincibleTime": 60000,
+            "ComebackInvincibleTime": 60000,
+            "ResOccupyInvincibleTime": 10000
         }
     },
     {

+ 56 - 0
sr/etc/data/mini_game.json

@@ -0,0 +1,56 @@
+[
+    {"GameID":1,"LevelID":1,"NextLevelID":2,"Name":1,"Headlcon":"monster_bandits_normal5_Icon","ChapterUnlockLevelID":0,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":1,"LevelID":2,"NextLevelID":3,"Name":2,"Headlcon":"monster_bandits_elite2_Icon","ChapterUnlockLevelID":1013,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":1,"LevelID":3,"NextLevelID":4,"Name":3,"Headlcon":"monster_bandits_normal6_Icon","ChapterUnlockLevelID":1015,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":1,"LevelID":4,"NextLevelID":5,"Name":4,"Headlcon":"monster_bandits_elite3_Icon","ChapterUnlockLevelID":1017,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":1,"LevelID":5,"NextLevelID":6,"Name":0,"Headlcon":"","ChapterUnlockLevelID":0,"LevelType":2,"Reward":[[1,10000],[2,100]]},
+    {"GameID":1,"LevelID":6,"NextLevelID":7,"Name":5,"Headlcon":"monster_bandits_normal5_Icon","ChapterUnlockLevelID":1018,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":1,"LevelID":7,"NextLevelID":8,"Name":6,"Headlcon":"monster_bandits_elite2_Icon","ChapterUnlockLevelID":1021,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":1,"LevelID":8,"NextLevelID":9,"Name":7,"Headlcon":"monster_bandits_normal6_Icon","ChapterUnlockLevelID":1025,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":1,"LevelID":9,"NextLevelID":10,"Name":8,"Headlcon":"monster_bandits_elite3_Icon","ChapterUnlockLevelID":1027,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":1,"LevelID":10,"NextLevelID":11,"Name":0,"Headlcon":"","ChapterUnlockLevelID":0,"LevelType":2,"Reward":[[1,20000],[2,150]]},
+    {"GameID":1,"LevelID":11,"NextLevelID":12,"Name":9,"Headlcon":"monster_heretics_normal1_Icon","ChapterUnlockLevelID":1031,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":1,"LevelID":12,"NextLevelID":13,"Name":10,"Headlcon":"monster_heretics_normal2_Icon","ChapterUnlockLevelID":1035,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":1,"LevelID":13,"NextLevelID":14,"Name":11,"Headlcon":"monster_rebels_boss2_Icon","ChapterUnlockLevelID":1037,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":1,"LevelID":14,"NextLevelID":15,"Name":12,"Headlcon":"monster_rebels_elite2_Icon","ChapterUnlockLevelID":1038,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":1,"LevelID":15,"NextLevelID":16,"Name":0,"Headlcon":"","ChapterUnlockLevelID":0,"LevelType":2,"Reward":[[1,10000],[2,100]]},
+    {"GameID":1,"LevelID":16,"NextLevelID":17,"Name":13,"Headlcon":"monster_heretics_normal1_Icon","ChapterUnlockLevelID":1041,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":1,"LevelID":17,"NextLevelID":18,"Name":14,"Headlcon":"monster_heretics_normal2_Icon","ChapterUnlockLevelID":1043,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":1,"LevelID":18,"NextLevelID":19,"Name":15,"Headlcon":"monster_rebels_boss2_Icon","ChapterUnlockLevelID":1048,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":1,"LevelID":19,"NextLevelID":20,"Name":16,"Headlcon":"monster_rebels_elite2_Icon","ChapterUnlockLevelID":1058,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":1,"LevelID":20,"NextLevelID":21,"Name":0,"Headlcon":"","ChapterUnlockLevelID":0,"LevelType":2,"Reward":[[1,20000],[2,150]]},
+    {"GameID":1,"LevelID":21,"NextLevelID":22,"Name":17,"Headlcon":"monster_rebels_normal6_Icon","ChapterUnlockLevelID":1063,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":1,"LevelID":22,"NextLevelID":23,"Name":18,"Headlcon":"monster_rebels_boss1_Icon","ChapterUnlockLevelID":1065,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":1,"LevelID":23,"NextLevelID":24,"Name":19,"Headlcon":"monster_heretics_normal5_Icon","ChapterUnlockLevelID":1067,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":1,"LevelID":24,"NextLevelID":25,"Name":20,"Headlcon":"monster_heretics_normal4","ChapterUnlockLevelID":1068,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":1,"LevelID":25,"NextLevelID":26,"Name":21,"Headlcon":"monster_heretics_normal3_Icon","ChapterUnlockLevelID":1071,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":1,"LevelID":26,"NextLevelID":27,"Name":22,"Headlcon":"monster_convicts_normal4_Icon","ChapterUnlockLevelID":1073,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":1,"LevelID":27,"NextLevelID":0,"Name":0,"Headlcon":"","ChapterUnlockLevelID":0,"LevelType":2,"Reward":[[1,20000],[2,150]]},
+    {"GameID":2,"LevelID":1,"NextLevelID":2,"Name":1,"Headlcon":"monster_bandits_normal5_Icon","ChapterUnlockLevelID":0,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":2,"LevelID":2,"NextLevelID":3,"Name":2,"Headlcon":"monster_bandits_elite2_Icon","ChapterUnlockLevelID":1013,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":2,"LevelID":3,"NextLevelID":4,"Name":3,"Headlcon":"monster_bandits_normal6_Icon","ChapterUnlockLevelID":1015,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":2,"LevelID":4,"NextLevelID":5,"Name":4,"Headlcon":"monster_bandits_elite3_Icon","ChapterUnlockLevelID":1017,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":2,"LevelID":5,"NextLevelID":6,"Name":0,"Headlcon":"","ChapterUnlockLevelID":0,"LevelType":2,"Reward":[[1,10000],[2,100]]},
+    {"GameID":2,"LevelID":6,"NextLevelID":7,"Name":5,"Headlcon":"monster_bandits_normal5_Icon","ChapterUnlockLevelID":1018,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":2,"LevelID":7,"NextLevelID":8,"Name":6,"Headlcon":"monster_bandits_elite2_Icon","ChapterUnlockLevelID":1021,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":2,"LevelID":8,"NextLevelID":9,"Name":7,"Headlcon":"monster_bandits_normal6_Icon","ChapterUnlockLevelID":1025,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":2,"LevelID":9,"NextLevelID":10,"Name":8,"Headlcon":"monster_bandits_elite3_Icon","ChapterUnlockLevelID":1027,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":2,"LevelID":10,"NextLevelID":11,"Name":0,"Headlcon":"","ChapterUnlockLevelID":0,"LevelType":2,"Reward":[[1,20000],[2,150]]},
+    {"GameID":2,"LevelID":11,"NextLevelID":12,"Name":9,"Headlcon":"monster_heretics_normal1_Icon","ChapterUnlockLevelID":1031,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":2,"LevelID":12,"NextLevelID":13,"Name":10,"Headlcon":"monster_heretics_normal2_Icon","ChapterUnlockLevelID":1035,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":2,"LevelID":13,"NextLevelID":14,"Name":11,"Headlcon":"monster_rebels_boss2_Icon","ChapterUnlockLevelID":1037,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":2,"LevelID":14,"NextLevelID":15,"Name":12,"Headlcon":"monster_rebels_elite2_Icon","ChapterUnlockLevelID":1038,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":2,"LevelID":15,"NextLevelID":16,"Name":0,"Headlcon":"","ChapterUnlockLevelID":0,"LevelType":2,"Reward":[[1,10000],[2,100]]},
+    {"GameID":2,"LevelID":16,"NextLevelID":17,"Name":13,"Headlcon":"monster_heretics_normal1_Icon","ChapterUnlockLevelID":1041,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":2,"LevelID":17,"NextLevelID":18,"Name":14,"Headlcon":"monster_heretics_normal2_Icon","ChapterUnlockLevelID":1043,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":2,"LevelID":18,"NextLevelID":19,"Name":15,"Headlcon":"monster_rebels_boss2_Icon","ChapterUnlockLevelID":1048,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":2,"LevelID":19,"NextLevelID":20,"Name":16,"Headlcon":"monster_rebels_elite2_Icon","ChapterUnlockLevelID":1058,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":2,"LevelID":20,"NextLevelID":21,"Name":0,"Headlcon":"","ChapterUnlockLevelID":0,"LevelType":2,"Reward":[[1,20000],[2,150]]},
+    {"GameID":2,"LevelID":21,"NextLevelID":22,"Name":17,"Headlcon":"monster_rebels_normal6_Icon","ChapterUnlockLevelID":1063,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":2,"LevelID":22,"NextLevelID":23,"Name":18,"Headlcon":"monster_rebels_boss1_Icon","ChapterUnlockLevelID":1065,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":2,"LevelID":23,"NextLevelID":24,"Name":19,"Headlcon":"monster_heretics_normal5_Icon","ChapterUnlockLevelID":1067,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":2,"LevelID":24,"NextLevelID":25,"Name":20,"Headlcon":"monster_heretics_normal4","ChapterUnlockLevelID":1068,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":2,"LevelID":25,"NextLevelID":26,"Name":21,"Headlcon":"monster_heretics_normal3_Icon","ChapterUnlockLevelID":1071,"LevelType":1,"Reward":[[1,10000],[2,100]]},
+    {"GameID":2,"LevelID":26,"NextLevelID":27,"Name":22,"Headlcon":"monster_convicts_normal4_Icon","ChapterUnlockLevelID":1073,"LevelType":1,"Reward":[[1,20000],[2,150]]},
+    {"GameID":2,"LevelID":27,"NextLevelID":0,"Name":0,"Headlcon":"","ChapterUnlockLevelID":0,"LevelType":2,"Reward":[[1,20000],[2,150]]}
+]

+ 2 - 2
sr/etc/data/proto_route.json

@@ -583,7 +583,7 @@
         "enable": true,
         "name": "[请求]装备打造",
         "route": "game.player.equipCraft",
-        "request": "I32",
+        "request": "I32I32",
         "response": "AssetList",
         "push": "",
         "notify": "",
@@ -593,7 +593,7 @@
         "enable": true,
         "name": "[请求]装备随机打造",
         "route": "game.player.equipRandomCraft",
-        "request": "None",
+        "request": "I32",
         "response": "AssetList",
         "push": "",
         "notify": "",

+ 10 - 10
sr/etc/data/season_quest.json

@@ -1,12 +1,12 @@
 [
-    {"ID":101,"NextID":102,"QuestType":10001,"QuestParams":[5000,3],"Rewards":[[2,300]],"QuestDuration":72,"UnlockFunc":[1,2,3,4,5,6,7]},
-    {"ID":102,"NextID":103,"QuestType":10002,"QuestParams":[500],"Rewards":[[2,300]],"QuestDuration":48,"UnlockFunc":[8,9,10]},
-    {"ID":103,"NextID":104,"QuestType":10003,"QuestParams":[6,4],"Rewards":[[2,300]],"QuestDuration":72,"UnlockFunc":[11,12,13,14,15]},
-    {"ID":104,"NextID":105,"QuestType":10007,"QuestParams":[3],"Rewards":[[2,300]],"QuestDuration":48,"UnlockFunc":[16,17,18,19]},
-    {"ID":105,"NextID":106,"QuestType":10003,"QuestParams":[5,11],"Rewards":[[2,300]],"QuestDuration":72,"UnlockFunc":[20,21]},
-    {"ID":106,"NextID":107,"QuestType":10000,"QuestParams":[],"Rewards":[[2,300]],"QuestDuration":48,"UnlockFunc":[22,23,24,25]},
-    {"ID":107,"NextID":108,"QuestType":10008,"QuestParams":[100],"Rewards":[[2,300]],"QuestDuration":72,"UnlockFunc":[26]},
-    {"ID":108,"NextID":109,"QuestType":10004,"QuestParams":[1,3],"Rewards":[[2,300]],"QuestDuration":72,"UnlockFunc":[27]},
-    {"ID":109,"NextID":110,"QuestType":10005,"QuestParams":[1,10],"Rewards":[[2,300]],"QuestDuration":72,"UnlockFunc":[28]},
-    {"ID":110,"NextID":0,"QuestType":10006,"QuestParams":[110003],"Rewards":[[2,300]],"QuestDuration":72,"UnlockFunc":[29,30]}
+    {"ID":101,"NextID":102,"QuestType":10001,"QuestParams":[5000,3],"Rewards":[[2,300]],"QuestDuration":4320,"UnlockFunc":[1,2,3,4,5,6,7]},
+    {"ID":102,"NextID":103,"QuestType":10002,"QuestParams":[500],"Rewards":[[2,300]],"QuestDuration":2880,"UnlockFunc":[8,9,10]},
+    {"ID":103,"NextID":104,"QuestType":10003,"QuestParams":[6,4],"Rewards":[[2,300]],"QuestDuration":4320,"UnlockFunc":[11,12,13,14,15]},
+    {"ID":104,"NextID":105,"QuestType":10007,"QuestParams":[3],"Rewards":[[2,300]],"QuestDuration":2880,"UnlockFunc":[16,17,18,19]},
+    {"ID":105,"NextID":106,"QuestType":10003,"QuestParams":[5,11],"Rewards":[[2,300]],"QuestDuration":4320,"UnlockFunc":[20,21]},
+    {"ID":106,"NextID":107,"QuestType":10000,"QuestParams":[],"Rewards":[[2,300]],"QuestDuration":2880,"UnlockFunc":[22,23,24,25]},
+    {"ID":107,"NextID":108,"QuestType":10008,"QuestParams":[100],"Rewards":[[2,300]],"QuestDuration":4320,"UnlockFunc":[26]},
+    {"ID":108,"NextID":109,"QuestType":10004,"QuestParams":[1,3],"Rewards":[[2,300]],"QuestDuration":4320,"UnlockFunc":[27]},
+    {"ID":109,"NextID":110,"QuestType":10005,"QuestParams":[1,10],"Rewards":[[2,300]],"QuestDuration":4320,"UnlockFunc":[28]},
+    {"ID":110,"NextID":0,"QuestType":10006,"QuestParams":[110003],"Rewards":[[2,300]],"QuestDuration":4320,"UnlockFunc":[29,30]}
 ]

+ 4 - 1
sr/etc/data_embed/game_debug.json

@@ -16,7 +16,10 @@
             "MapMarchSpeedUp": 2,
             "MapReinforcePrepareTime": 30000,
             "MapReinforceEffectTime": 60000,
-            "MapReinforceCooldown": 10000
+            "MapReinforceCooldown": 10000,
+            "BeginerInvincibleTime": 60000,
+            "ComebackInvincibleTime": 60000,
+            "ResOccupyInvincibleTime": 10000
         }
     },
     {

BIN
sr/etc/data_map/s1/area_data.pb


BIN
sr/etc/data_map/s1/map_static_data.pb


+ 11 - 2
sr/internal/code/status_code.go

@@ -109,6 +109,7 @@ const (
 	ConfigNotFound_VipLevel         int32 = 11701 // VIP等级配置不存在
 	ConfigNotFound_ShopCard         int32 = 11702 // 周月卡商城配置不存在
 	ConfigNotFound_ShopGold         int32 = 11703 // 金币商城配置不存在
+	ConfigNotFound_MiniGameLevel    int32 = 11801 // 小游戏关卡配置不存在
 )
 
 // 账号错误码
@@ -613,8 +614,8 @@ const (
 	MapReinforce_Cooldown          int32 = 118085 // 加固冷却中
 	MapReinforce_Preparing         int32 = 118086 // 加固准备中
 	MapReinforce_Effecting         int32 = 118087 // 加固生效中
-	MapRes_Invincible              int32 = 118108 // 资源点保护中
-	MapReinforce_NotPreparing      int32 = 118109 // 非加固准备阶段不可取消
+	MapRes_Invincible              int32 = 118088 // 资源点保护中
+	MapReinforce_NotPreparing      int32 = 118089 // 非加固准备阶段不可取消
 	MapEnter_MapNotOpen            int32 = 118100 // 地图未开启
 	MapBornPoint_NotFound          int32 = 118101 // 找不到出生点
 	MapMark_AlreadyExist           int32 = 118102 // 地图标记已存在
@@ -762,3 +763,11 @@ const (
 	ScoutTypeError     int32 = 202005 // 侦察状态错误
 	ScoutFail          int32 = 202006 // 侦察失败
 )
+
+// 小游戏
+const (
+	MiniGameGameIDError   int32 = 203001 // gameId参数错误
+	MiniGameLevelIDError  int32 = 203002 // levelId参数错误
+	MiniGameLevelCleared  int32 = 203003 // 小游戏已通关
+	MiniGameChapterUnLock int32 = 203004 // 探险解锁关卡未解锁
+)

+ 3 - 0
sr/internal/constant/test.go

@@ -15,4 +15,7 @@ const (
 	GameDebug_MapReinforcePrepareTime     = "MapReinforcePrepareTime"
 	GameDebug_MapReinforceEffectTime      = "MapReinforceEffectTime"
 	GameDebug_MapReinforceCooldown        = "MapReinforceCooldown"
+	GameDebug_BeginerInvincibleTime       = "BeginerInvincibleTime"
+	GameDebug_ComebackInvincibleTime      = "ComebackInvincibleTime"
+	GameDebug_ResOccupyInvincibleTime     = "ResOccupyInvincibleTime"
 )

+ 3 - 1
sr/internal/data/const.go

@@ -137,7 +137,7 @@ type (
 		MapMeetBattleQueue            int                          `json:"MapMeetBattleQueue"`            // 地图遭遇战战斗队列大小
 		MapDefendRecoverTime          int64                        `json:"MapDefendRecoverTime"`          // 地图资源田守军恢复时间(秒)
 		MapDrawBattleCount            int32                        `json:"MapDrawBattleCount"`            // 地图平局战斗次数上限(连续平判输)
-		MapRedeployDefendRatio        float32                      `json:"MapRedeployDefendRatio"`        // 地图调动防守兵力比例(万分比)
+		MapAutoDefendTroopsRatio      int32                        `json:"MapAutoDefendTroopsRatio"`      // 地图自动防守兵力比例(万分比)
 		MapMonsterFlushHour           int                          `json:"MapMonsterFlushHour"`           // 地图怪物刷新时间(整点)
 		MapSnatchWaitTime             int64                        `json:"MapSnatchWaitTime"`             // 掠夺等待时间(秒)
 		MapAssembleMemberNum          int                          `json:"MapAssembleMemberNum"`          // 集结成员数量
@@ -156,6 +156,7 @@ type (
 		ChooseStateComebackCoolDown   int64                        `json:"ChooseStateComebackCoolDown"`   // 主城选州再起冷却时间(秒)
 		MapResOccupyLimit             int                          `json:"MapResOccupyLimit"`             // 地图资源占领上限
 		BeginerInvincibleTime         int64                        `json:"BeginerInvincibleTime"`         // 新手期免战分钟
+		ComebackInvincibleTime        int64                        `json:"ComebackInvincibleTime"`        // 再起免战分钟
 		ChapterStaminaAddInterval     int64                        `json:"ChapterStaminaAddInterval"`     // 探险精力恢复时间 单位秒
 		ChapterStaminaAddValue        types.Asset                  `json:"ChapterStaminaAddValue"`        // 探险精力恢复值
 		ChapterStaminaLimit           int64                        `json:"ChapterStaminaLimit"`           // 探险精力上限
@@ -189,6 +190,7 @@ type (
 		MapScoutMarchTime             int64                        `json:"MapScoutMarchTime"`             // 地图侦察行军时间 单位秒
 		MapScoutTime                  int64                        `json:"MapScoutTime"`                  // 地图侦察时间 单位秒
 		MapScoutCosts                 types.Assets                 `json:"MapScoutCosts"`                 // 地图侦察消耗
+		EquipSingleGetLimit           int64                        `json:"EquipSingleGetLimit"`           // 单次获得装备数量上限
 	}
 
 	constConfig struct {

+ 1 - 1
sr/internal/data/extend_chapter_level.go

@@ -42,7 +42,7 @@ func (p *ChapterLevelRow) RandomChapterLevelReward() types.Assets {
 			continue
 		}
 
-		row, ok := DropPool.Random(rewardPool.Key)
+		row, ok := DropPool.RandomOne(rewardPool.Key)
 		if code.IsFail(ok) {
 			clog.Warnf("chapter level reward not found. levelID:%d, pool:%d", p.ID, rewardPool.Key)
 			continue

+ 37 - 0
sr/internal/data/extend_const.go

@@ -263,3 +263,40 @@ func (p *constConfig) GetMapReinforceCooldown() int64 {
 
 	return p.ReinforceCoolDown * 1000
 }
+
+func (p *constConfig) GetEquipRandomCraftCost(craftNum int32) types.Assets {
+	var (
+		num   = int64(craftNum)
+		costs = types.Assets{}
+	)
+
+	for _, cost := range p.EquipRandomCraftCost {
+		costs.Add(cost.ID, num*cost.Num)
+	}
+
+	return costs
+}
+
+func (p *constConfig) GetBeginerInvincibleTime() int64 {
+	if value, ok := GameDebug.GetDebugValue(constant.GameDebug_BeginerInvincibleTime); ok {
+		return value
+	}
+
+	return p.BeginerInvincibleTime * ctime.MillisecondsPerMinute
+}
+
+func (p *constConfig) GetComebackInvincibleTime() int64 {
+	if value, ok := GameDebug.GetDebugValue(constant.GameDebug_ComebackInvincibleTime); ok {
+		return value
+	}
+
+	return p.ComebackInvincibleTime * ctime.MillisecondsPerMinute
+}
+
+func (p *constConfig) GetResOccupyInvincibleTime() int64 {
+	if value, ok := GameDebug.GetDebugValue(constant.GameDebug_ResOccupyInvincibleTime); ok {
+		return value
+	}
+
+	return p.MapResProtectTime * ctime.MillisecondsPerSecond
+}

+ 17 - 1
sr/internal/data/extend_drop_pool.go

@@ -23,7 +23,7 @@ func (p *dropPoolConfig) OnAfterLoad(_ bool) {
 	dropPoolWeightMap = loadMap
 }
 
-func (p *dropPoolConfig) Random(dropPoolID int32) (*DropPoolRow, int32) {
+func (p *dropPoolConfig) RandomOne(dropPoolID int32) (*DropPoolRow, int32) {
 	weighter, found := p.GetWeighter(dropPoolID)
 	if !found {
 		//道具池找不到
@@ -39,6 +39,22 @@ func (p *dropPoolConfig) Random(dropPoolID int32) (*DropPoolRow, int32) {
 	return row, code.OK
 }
 
+func (p *dropPoolConfig) Random(dropPoolID int32, num int32) ([]*DropPoolRow, int32) {
+	weighter, found := p.GetWeighter(dropPoolID)
+	if !found {
+		//道具池找不到
+		return nil, code.ConfigNotFound_DropPool
+	}
+
+	// 随机num个奖励
+	rows := weighter.Select(int(num))
+	if rows == nil {
+		return nil, code.HeroCannotRecruit
+	}
+
+	return rows, code.OK
+}
+
 func (p *dropPoolConfig) GetWeighter(itemPoolID int32) (*rand.Weight[*DropPoolRow], bool) {
 	value, found := dropPoolWeightMap[itemPoolID]
 	return value, found

+ 90 - 0
sr/internal/data/mini_game.go

@@ -0,0 +1,90 @@
+// Code generated by excel export. DO NOT EDIT.
+package data
+
+import (
+	"f1-game/internal/types"
+)
+
+var MiniGame = &miniGameConfig{}
+
+func init() {
+	_allList = append(_allList, MiniGame)
+}
+
+type (
+	MiniGameRow struct {
+		GameID               int32        `json:"GameID"`               // 游戏ID
+		LevelID              int32        `json:"LevelID"`              // 关卡ID
+		NextLevelID          int32        `json:"NextLevelID"`          // 下一关卡ID
+		Name                 int32        `json:"Name"`                 // 关卡名称
+		Headlcon             string       `json:"Headlcon"`             // 关卡怪物头像
+		ChapterUnlockLevelID int32        `json:"ChapterUnlockLevelID"` // 探险解锁关卡ID
+		LevelType            int32        `json:"LevelType"`            // 关卡类型
+		Reward               types.Assets `json:"Reward"`               // 奖励
+	}
+
+	miniGameConfig struct {
+		baseConfig
+		list                 []*MiniGameRow                   // all list
+		gameIDLevelIDUniMaps map[int32]map[int32]*MiniGameRow // key:gameID, levelID
+	}
+)
+
+func (p *miniGameConfig) Name() string {
+	return "mini_game"
+}
+
+func (p *miniGameConfig) Init() {
+	p.gameIDLevelIDUniMaps = map[int32]map[int32]*MiniGameRow{}
+}
+
+func (p *miniGameConfig) OnLoad(maps interface{}, reload bool) (int, error) {
+	if reload && p.denyLoad {
+		return 0, nil
+	}
+
+	var list []*MiniGameRow
+	if err := DecodeData(maps, &list); err != nil {
+		return 0, err
+	}
+	p.list = list
+
+	gameIDLevelIDUniMaps := map[int32]map[int32]*MiniGameRow{}
+	for _, row := range list {
+		itemMap := gameIDLevelIDUniMaps[row.GameID]
+		if itemMap == nil {
+			itemMap = map[int32]*MiniGameRow{}
+			gameIDLevelIDUniMaps[row.GameID] = itemMap
+		}
+		itemMap[row.LevelID] = row
+	}
+	p.gameIDLevelIDUniMaps = gameIDLevelIDUniMaps
+
+	return len(list), nil
+}
+
+// func (p *miniGameConfig) OnAfterLoad(_ bool) {
+// }
+
+func (p *miniGameConfig) List() []*MiniGameRow {
+	return p.list
+}
+func (p *miniGameConfig) GetByGameIDLevelID(gameID int32, levelID int32) (*MiniGameRow, bool) {
+	mapItem, found := p.gameIDLevelIDUniMaps[gameID]
+	if !found {
+		return nil, false
+	}
+
+	value, found := mapItem[levelID]
+	return value, found
+}
+
+func (p *miniGameConfig) ContainGameIDLevelID(gameID int32, levelID int32) bool {
+	mapItem, found := p.gameIDLevelIDUniMaps[gameID]
+	if !found {
+		return false
+	}
+
+	_, found = mapItem[levelID]
+	return found
+}

+ 1 - 1
sr/internal/data/season_quest.go

@@ -19,7 +19,7 @@ type (
 		QuestType     enum.SeasonQuestType `json:"QuestType"`     // 任务类型
 		QuestParams   types.List[int32]    `json:"QuestParams"`   // 任务参数
 		Rewards       types.Assets         `json:"Rewards"`       // 任务奖励
-		QuestDuration int32                `json:"QuestDuration"` // 任务时长(h)
+		QuestDuration int64                `json:"QuestDuration"` // 任务时长(分)
 		UnlockFunc    types.List[int32]    `json:"UnlockFunc"`    // 解锁功能
 	}
 

+ 5 - 0
sr/internal/name/local/player.go

@@ -97,6 +97,11 @@ const (
 	Player_ChapterTakeOutStamina  = "chapterTakeOutStamina"  // 取出精力池精力
 )
 
+const (
+	Player_MiniGameProgress = "miniGameProgress" // 小游戏获取进度
+	Player_MiniGameSettle   = "miniGameSettle"   // 小游戏结算
+)
+
 const (
 	Player_ShopBuy = "shopBuy" // 商店购买
 )

+ 0 - 1
sr/internal/name/remote/map.go

@@ -63,7 +63,6 @@ const (
 
 const (
 	MapPlayer_OnSessionClosed    = "onLogout"           // session断开
-	MapPlayer_OnCheckExit        = "onCheckExit"        // 检测是否退出actor
 	MapPlayer_SyncData           = "syncData"           // 数据同步
 	MapPlayer_SyncVip            = "syncVip"            // 同步VIP
 	MapPlayer_AutoReborn         = "autoReborn"         // 玩家重新出生

+ 6 - 0
sr/internal/name/route/player.go

@@ -73,6 +73,12 @@ const (
 	Player_ChapterTakeOutStamina  = "game.player.chapterTakeOutStamina"
 )
 
+// 小游戏
+const (
+	GamePlayer_MiniGameProgress = "game.player.miniGameProgress"
+	GamePlayer_MiniGameSettle   = "game.player.miniGameSettle"
+)
+
 // 商店
 const (
 	Player_ShopBuy = "game.player.shopBuy" // 大厅商店购买

+ 29 - 9
sr/internal/pb/map.pb.go

@@ -231,8 +231,10 @@ type OwnerMapObjectBuild struct {
 	DiscardTime int64 `protobuf:"varint,6,opt,name=discardTime,proto3" json:"discardTime,omitempty"`
 	// 免战结束时间(毫秒)
 	InvincibleEndTime int64 `protobuf:"varint,7,opt,name=invincibleEndTime,proto3" json:"invincibleEndTime,omitempty"`
-	unknownFields     protoimpl.UnknownFields
-	sizeCache         protoimpl.SizeCache
+	// 如果当前建筑是营帐 则为营帐下标1-5 其他默认为0
+	Index         int32 `protobuf:"varint,8,opt,name=index,proto3" json:"index,omitempty"`
+	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
 }
 
 func (x *OwnerMapObjectBuild) Reset() {
@@ -314,6 +316,13 @@ func (x *OwnerMapObjectBuild) GetInvincibleEndTime() int64 {
 	return 0
 }
 
+func (x *OwnerMapObjectBuild) GetIndex() int32 {
+	if x != nil {
+		return x.Index
+	}
+	return 0
+}
+
 // 地图对象列表
 type MapObjects struct {
 	state protoimpl.MessageState `protogen:"open.v1"`
@@ -3803,10 +3812,12 @@ type ComebackResponse struct {
 	state protoimpl.MessageState `protogen:"open.v1"`
 	// 新的坐标点
 	Point *Point `protobuf:"bytes,1,opt,name=point,proto3" json:"point,omitempty"`
-	// 冷却结束时间(毫秒)
+	// 再起冷却结束时间(毫秒)
 	ComebackEndTime int64 `protobuf:"varint,2,opt,name=comebackEndTime,proto3" json:"comebackEndTime,omitempty"`
-	unknownFields   protoimpl.UnknownFields
-	sizeCache       protoimpl.SizeCache
+	// 选州再起冷却结束时间(毫秒)
+	RechooseComebackEndTime int64 `protobuf:"varint,3,opt,name=rechooseComebackEndTime,proto3" json:"rechooseComebackEndTime,omitempty"`
+	unknownFields           protoimpl.UnknownFields
+	sizeCache               protoimpl.SizeCache
 }
 
 func (x *ComebackResponse) Reset() {
@@ -3853,6 +3864,13 @@ func (x *ComebackResponse) GetComebackEndTime() int64 {
 	return 0
 }
 
+func (x *ComebackResponse) GetRechooseComebackEndTime() int64 {
+	if x != nil {
+		return x.RechooseComebackEndTime
+	}
+	return 0
+}
+
 // 重新出生推送
 type RebornPush struct {
 	state protoimpl.MessageState `protogen:"open.v1"`
@@ -4401,7 +4419,7 @@ const file_map_proto_rawDesc = "" +
 	"\x03res\x18\x06 \x01(\v2\x15.pb.OwnerMapObjectResR\x03res\"c\n" +
 	"\x11OwnerMapObjectRes\x12 \n" +
 	"\vdiscardTime\x18\x01 \x01(\x03R\vdiscardTime\x12,\n" +
-	"\x11invincibleEndTime\x18\x02 \x01(\x03R\x11invincibleEndTime\"\x89\x02\n" +
+	"\x11invincibleEndTime\x18\x02 \x01(\x03R\x11invincibleEndTime\"\x9f\x02\n" +
 	"\x13OwnerMapObjectBuild\x12\x1e\n" +
 	"\n" +
 	"isBuilding\x18\x01 \x01(\bR\n" +
@@ -4411,7 +4429,8 @@ const file_map_proto_rawDesc = "" +
 	"\x0eupgradeEndTime\x18\x04 \x01(\x03R\x0eupgradeEndTime\x12\x14\n" +
 	"\x05level\x18\x05 \x01(\x05R\x05level\x12 \n" +
 	"\vdiscardTime\x18\x06 \x01(\x03R\vdiscardTime\x12,\n" +
-	"\x11invincibleEndTime\x18\a \x01(\x03R\x11invincibleEndTime\"j\n" +
+	"\x11invincibleEndTime\x18\a \x01(\x03R\x11invincibleEndTime\x12\x14\n" +
+	"\x05index\x18\b \x01(\x05R\x05index\"j\n" +
 	"\n" +
 	"MapObjects\x12-\n" +
 	"\n" +
@@ -4728,10 +4747,11 @@ const file_map_proto_rawDesc = "" +
 	"\x06armID3\x18\x04 \x01(\x05R\x06armID3\"M\n" +
 	"\x12MapFindPathRequest\x12\x16\n" +
 	"\x06teamID\x18\x01 \x01(\x05R\x06teamID\x12\x1f\n" +
-	"\x05point\x18\x02 \x01(\v2\t.pb.PointR\x05point\"]\n" +
+	"\x05point\x18\x02 \x01(\v2\t.pb.PointR\x05point\"\x97\x01\n" +
 	"\x10ComebackResponse\x12\x1f\n" +
 	"\x05point\x18\x01 \x01(\v2\t.pb.PointR\x05point\x12(\n" +
-	"\x0fcomebackEndTime\x18\x02 \x01(\x03R\x0fcomebackEndTime\"I\n" +
+	"\x0fcomebackEndTime\x18\x02 \x01(\x03R\x0fcomebackEndTime\x128\n" +
+	"\x17rechooseComebackEndTime\x18\x03 \x01(\x03R\x17rechooseComebackEndTime\"I\n" +
 	"\n" +
 	"RebornPush\x12\x1f\n" +
 	"\x05point\x18\x01 \x01(\v2\t.pb.PointR\x05point\x12\x1a\n" +

+ 139 - 0
sr/internal/pb/minigame.pb.go

@@ -0,0 +1,139 @@
+// message结构的属性名请使用 驼峰小写字母开始
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.36.8
+// 	protoc        v6.31.1
+// source: minigame.proto
+
+package pb
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+	unsafe "unsafe"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// 小游戏结算
+type MiniGameSettle struct {
+	state protoimpl.MessageState `protogen:"open.v1"`
+	// 当前关卡ID
+	LevelID int32 `protobuf:"varint,1,opt,name=levelID,proto3" json:"levelID,omitempty"`
+	// 奖励列表
+	Rewards       *AssetList `protobuf:"bytes,2,opt,name=rewards,proto3" json:"rewards,omitempty"`
+	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
+}
+
+func (x *MiniGameSettle) Reset() {
+	*x = MiniGameSettle{}
+	mi := &file_minigame_proto_msgTypes[0]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *MiniGameSettle) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MiniGameSettle) ProtoMessage() {}
+
+func (x *MiniGameSettle) ProtoReflect() protoreflect.Message {
+	mi := &file_minigame_proto_msgTypes[0]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use MiniGameSettle.ProtoReflect.Descriptor instead.
+func (*MiniGameSettle) Descriptor() ([]byte, []int) {
+	return file_minigame_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *MiniGameSettle) GetLevelID() int32 {
+	if x != nil {
+		return x.LevelID
+	}
+	return 0
+}
+
+func (x *MiniGameSettle) GetRewards() *AssetList {
+	if x != nil {
+		return x.Rewards
+	}
+	return nil
+}
+
+var File_minigame_proto protoreflect.FileDescriptor
+
+const file_minigame_proto_rawDesc = "" +
+	"\n" +
+	"\x0eminigame.proto\x12\x02pb\x1a\x0fbase_type.proto\"S\n" +
+	"\x0eMiniGameSettle\x12\x18\n" +
+	"\alevelID\x18\x01 \x01(\x05R\alevelID\x12'\n" +
+	"\arewards\x18\x02 \x01(\v2\r.pb.AssetListR\arewardsB\x05Z\x03/pbb\x06proto3"
+
+var (
+	file_minigame_proto_rawDescOnce sync.Once
+	file_minigame_proto_rawDescData []byte
+)
+
+func file_minigame_proto_rawDescGZIP() []byte {
+	file_minigame_proto_rawDescOnce.Do(func() {
+		file_minigame_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_minigame_proto_rawDesc), len(file_minigame_proto_rawDesc)))
+	})
+	return file_minigame_proto_rawDescData
+}
+
+var file_minigame_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_minigame_proto_goTypes = []any{
+	(*MiniGameSettle)(nil), // 0: pb.MiniGameSettle
+	(*AssetList)(nil),      // 1: pb.AssetList
+}
+var file_minigame_proto_depIdxs = []int32{
+	1, // 0: pb.MiniGameSettle.rewards:type_name -> pb.AssetList
+	1, // [1:1] is the sub-list for method output_type
+	1, // [1:1] is the sub-list for method input_type
+	1, // [1:1] is the sub-list for extension type_name
+	1, // [1:1] is the sub-list for extension extendee
+	0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_minigame_proto_init() }
+func file_minigame_proto_init() {
+	if File_minigame_proto != nil {
+		return
+	}
+	file_base_type_proto_init()
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: unsafe.Slice(unsafe.StringData(file_minigame_proto_rawDesc), len(file_minigame_proto_rawDesc)),
+			NumEnums:      0,
+			NumMessages:   1,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_minigame_proto_goTypes,
+		DependencyIndexes: file_minigame_proto_depIdxs,
+		MessageInfos:      file_minigame_proto_msgTypes,
+	}.Build()
+	File_minigame_proto = out.File
+	file_minigame_proto_goTypes = nil
+	file_minigame_proto_depIdxs = nil
+}

+ 2 - 0
sr/nodes/game/internal/db/data/player/data.go

@@ -11,6 +11,7 @@ type (
 		Shows     Shows     `bson:"Shows,omitempty"`     // 外显数据
 		MainQuest MainQuest `bson:"MainQuest,omitempty"` // 游戏主线数据
 		Vip       Vip       `bson:"Vip,omitempty"`       // VIP
+		MiniGame  MiniGame  `bson:"MiniGame,omitempty"`  // 小游戏
 	}
 )
 
@@ -24,6 +25,7 @@ func NewData() Data {
 		Shows:     newShows(),
 		MainQuest: newMainQuest(),
 		Vip:       newVip(),
+		MiniGame:  newMiniGame(),
 	}
 
 	return playerData

+ 12 - 0
sr/nodes/game/internal/db/data/player/mini_game.go

@@ -0,0 +1,12 @@
+package dbPlayer
+
+type (
+	MiniGame struct {
+		GameID  int32 `bson:"GameID,omitempty"`  // 游戏ID
+		LevelId int32 `bson:"LevelId,omitempty"` // 已通过关卡ID
+	}
+)
+
+func newMiniGame() MiniGame {
+	return MiniGame{}
+}

+ 1 - 0
sr/nodes/game/player/actor_player.go

@@ -74,6 +74,7 @@ func (p *actorPlayer) OnInit() {
 	p.initLord()
 	p.initMarquee()
 	p.initVip()
+	p.initMiniGame()
 }
 
 func (p *actorPlayer) initRemote() {

+ 1 - 0
sr/nodes/game/player/asset/origin/origin.go

@@ -86,6 +86,7 @@ var (
 	ReSignInReward           Origin = newOrigin(1902, "补签奖励")
 	CumSignInReward          Origin = newOrigin(1903, "累计签到奖励")
 	MapScoutCost             Origin = newOrigin(2001, "侦察消耗")
+	MiniGameReward           Origin = newOrigin(2002, "小游戏通关奖励")
 )
 
 func init() {

+ 2 - 1
sr/nodes/game/player/asset/type/equip.go

@@ -2,6 +2,7 @@ package assetType
 
 import (
 	"f1-game/internal/code"
+	"f1-game/internal/data"
 	"f1-game/internal/enum"
 	"f1-game/internal/types"
 	"f1-game/nodes/game/internal/db"
@@ -91,5 +92,5 @@ func (p *Equip) Push(playerID int64, assets types.Assets) {
 }
 
 func (p *Equip) MaxAddOnce() int64 {
-	return 100
+	return data.Const.EquipSingleGetLimit
 }

+ 34 - 56
sr/nodes/game/player/player_equip.go

@@ -28,51 +28,7 @@ func (p *actorPlayer) initEquip() {
 }
 
 // equipCraft 装备打造
-func (p *actorPlayer) equipCraft(session *cproto.Session, req *pb.I32) {
-	equipRow, ok := data.Equip.GetByID(req.Value)
-	if !ok {
-		p.ResponseCode(session, code.ConfigNotFound_Equip)
-		return
-	}
-
-	// 检查材料
-	errCode := asset.Service().IsAllEnough(p.playerID, equipRow.CraftCosts)
-	if code.IsFail(errCode) {
-		p.ResponseCode(session, errCode)
-		return
-	}
-
-	// 添加装备
-	addAssets, errCode := asset.Service().Add(p.playerID, equipRow.ID, 1, ao.None, true)
-	if code.IsFail(errCode) {
-		p.ResponseCode(session, errCode)
-		return
-	}
-
-	// 扣除材料
-	_, errCode = asset.Service().Subs(p.playerID, equipRow.CraftCosts, ao.None)
-	if code.IsFail(errCode) {
-		p.Warn("equipCraft error: equipID = %d, errCode = %d", equipRow.ID, errCode)
-		p.ResponseCode(session, errCode)
-		return
-	}
-
-	// 累计打造次数
-	equipTable := p.EquipTable()
-	equipTable.EquipExtend.AddCraftQualityCount(int32(equipRow.Quality), 1)
-	equipTable.Save2Queue()
-
-	// 发送打造事件
-	p.PostEvent(event.NewEquipCraft(p.playerID, addAssets))
-
-	// 返回新获得的装备列表
-	rsp := addAssets.ToAssetListProto()
-
-	p.Response(session, &rsp)
-}
-
-// equipCraftNew // TODO 后续和客户端联调时删除上面的装备打造方法,保留当前方法
-func (p *actorPlayer) equipCraftNew(session *cproto.Session, req *pb.I32I32) {
+func (p *actorPlayer) equipCraft(session *cproto.Session, req *pb.I32I32) {
 	var (
 		equipID = req.Key
 		num     = int64(req.Value)
@@ -128,36 +84,56 @@ func (p *actorPlayer) equipCraftNew(session *cproto.Session, req *pb.I32I32) {
 }
 
 // equipRandomCraft 装备随机打造
-func (p *actorPlayer) equipRandomCraft(session *cproto.Session, _ *pb.None) {
+func (p *actorPlayer) equipRandomCraft(session *cproto.Session, req *pb.I32) {
+	craftNum := req.Value
+	if craftNum <= 0 {
+		p.ResponseCode(session, code.IllegalArgument)
+		return
+	}
+
+	costs := data.Const.GetEquipRandomCraftCost(craftNum)
+
 	// 检查材料
-	errCode := asset.Service().IsAllEnough(p.playerID, data.Const.EquipRandomCraftCost)
+	errCode := asset.Service().IsAllEnough(p.playerID, costs)
 	if code.IsFail(errCode) {
 		p.ResponseCode(session, errCode)
 		return
 	}
 
-	// 随机一件装备
-	itemPoolRow, errCode := data.DropPool.Random(data.Const.EquipRandomCraftPoolID)
+	// 随机craftNum件装备
+	itemPoolRows, errCode := data.DropPool.Random(data.Const.EquipRandomCraftPoolID, craftNum)
 	if code.IsFail(errCode) {
 		p.ResponseCode(session, errCode)
 		return
 	}
 
-	equipRow, found := data.Equip.GetByID(itemPoolRow.ItemID)
-	if !found {
-		p.ResponseCode(session, code.ConfigNotFound_Equip)
-		return
+	var (
+		assets        = types.Assets{}
+		qualityCounts = map[int32]int32{}
+	)
+
+	for _, itemPoolRow := range itemPoolRows {
+		equipRow, found := data.Equip.GetByID(itemPoolRow.ItemID)
+		if !found {
+			p.ResponseCode(session, code.ConfigNotFound_Equip)
+			return
+		}
+
+		assets.Add(itemPoolRow.ItemID, itemPoolRow.Num)
+
+		quality := int32(equipRow.Quality)
+		qualityCounts[quality] = qualityCounts[quality] + int32(itemPoolRow.Num)
 	}
 
 	// 加资产(装备)
-	addAssets, errCode := asset.Service().Add(p.playerID, itemPoolRow.ItemID, itemPoolRow.Num, ao.None, true)
+	addAssets, errCode := asset.Service().Adds(p.playerID, assets, ao.None, true)
 	if code.IsFail(errCode) {
 		p.ResponseCode(session, errCode)
 		return
 	}
 
 	// 扣除材料
-	_, errCode = asset.Service().Subs(p.playerID, data.Const.EquipRandomCraftCost, ao.None)
+	_, errCode = asset.Service().Subs(p.playerID, costs, ao.None)
 	if code.IsFail(errCode) {
 		p.Warn("equipRandomCraft error: errCode = %d", errCode)
 		p.ResponseCode(session, errCode)
@@ -166,7 +142,9 @@ func (p *actorPlayer) equipRandomCraft(session *cproto.Session, _ *pb.None) {
 
 	// 累计打造次数
 	equipTable := p.EquipTable()
-	equipTable.EquipExtend.AddCraftQualityCount(int32(equipRow.Quality), int32(itemPoolRow.Num))
+	for quality, num := range qualityCounts {
+		equipTable.EquipExtend.AddCraftQualityCount(quality, num)
+	}
 	equipTable.Save2Queue()
 
 	// 发送打造事件

+ 106 - 0
sr/nodes/game/player/player_minigame.go

@@ -0,0 +1,106 @@
+package player
+
+import (
+	"f1-game/internal/code"
+	"f1-game/internal/data"
+	nameLocal "f1-game/internal/name/local"
+	"f1-game/internal/pb"
+	"f1-game/nodes/game/player/asset"
+	ao "f1-game/nodes/game/player/asset/origin"
+
+	cproto "github.com/cherry-game/cherry/net/proto"
+)
+
+func (p *actorPlayer) initMiniGame() {
+	p.Local().Register(nameLocal.Player_MiniGameProgress, p.miniGameProgress)
+	p.Local().Register(nameLocal.Player_MiniGameSettle, p.miniGameSettle)
+}
+
+// 获取小游戏进度
+func (p *actorPlayer) miniGameProgress(session *cproto.Session, req *pb.I32) {
+	playerTable := p.PlayerTable()
+	if _, found := data.MiniGame.GetByGameIDLevelID(req.Value, 1); !found {
+		p.ResponseCode(session, code.ConfigNotFound_MiniGameLevel)
+		return
+	}
+	if playerTable.MiniGame.GameID != 0 && playerTable.MiniGame.GameID != req.Value {
+		p.ResponseCode(session, code.MiniGameGameIDError)
+		return
+	}
+	rsp := &pb.I32{
+		Value: playerTable.MiniGame.LevelId,
+	}
+	p.Response(session, rsp)
+}
+
+// 小游戏结算
+func (p *actorPlayer) miniGameSettle(session *cproto.Session, req *pb.I64I64I32) {
+	var (
+		playerTable  = p.PlayerTable()
+		chapterTable = p.ChapterTable()
+	)
+
+	// 失败不用做任何处理
+	if req.Key3 == 0 {
+		rsp := &pb.MiniGameSettle{
+			LevelID: playerTable.MiniGame.LevelId,
+		}
+		p.Response(session, rsp)
+	}
+
+	if playerTable.MiniGame.GameID != 0 && playerTable.MiniGame.GameID != int32(req.Key1) {
+		p.ResponseCode(session, code.MiniGameGameIDError)
+		return
+	}
+	if playerTable.MiniGame.LevelId == 0 {
+		// 第一关
+		if int32(req.Key2) != 1 {
+			p.ResponseCode(session, code.MiniGameLevelIDError)
+			return
+		}
+	} else {
+		currLevelRow, found := data.MiniGame.GetByGameIDLevelID(int32(req.Key1), playerTable.MiniGame.LevelId)
+		if !found {
+			p.ResponseCode(session, code.ConfigNotFound_MiniGameLevel)
+			return
+		}
+		// 已通关所有关卡
+		if currLevelRow.NextLevelID == 0 {
+			p.ResponseCode(session, code.MiniGameLevelCleared)
+			return
+		}
+		// 关卡id错误
+		if currLevelRow.NextLevelID != int32(req.Key2) {
+			p.ResponseCode(session, code.MiniGameLevelIDError)
+			return
+		}
+	}
+	nextLevelRow, found := data.MiniGame.GetByGameIDLevelID(int32(req.Key1), int32(req.Key2))
+	if !found {
+		p.ResponseCode(session, code.ConfigNotFound_MiniGameLevel)
+		return
+	}
+	// 探险解锁关卡前置条件
+	if !chapterTable.Chapter.IsLevelUnlocked(nextLevelRow.ChapterUnlockLevelID) {
+		p.ResponseCode(session, code.MiniGameChapterUnLock)
+		return
+	}
+
+	playerTable.MiniGame.GameID = int32(req.Key1)
+	playerTable.MiniGame.LevelId = int32(req.Key2)
+	playerTable.Save2Queue()
+
+	rsp := &pb.MiniGameSettle{
+		LevelID: playerTable.MiniGame.LevelId,
+	}
+	if !nextLevelRow.Reward.IsEmpty() {
+		_, errCode := asset.Service().Adds(p.playerID, nextLevelRow.Reward, ao.MiniGameReward, false)
+		if code.IsFail(errCode) {
+			p.ResponseCode(session, errCode)
+			return
+		}
+		assertListProto := nextLevelRow.Reward.ToAssetListProto()
+		rsp.Rewards = &assertListProto
+	}
+	p.Response(session, rsp)
+}

+ 1 - 1
sr/nodes/game/player/player_recruit.go

@@ -68,7 +68,7 @@ func (p *actorPlayer) recruit(session *cproto.Session, req *pb.I32I32) {
 		recruitNum++
 		curRecruitPool := recruitRow.GetRecruitPool(recruitNum)
 
-		row, errCode := data.DropPool.Random(curRecruitPool)
+		row, errCode := data.DropPool.RandomOne(curRecruitPool)
 		if code.IsFail(errCode) {
 			p.ResponseCode(session, errCode)
 			return

+ 3 - 3
sr/nodes/map/internal/call/player.go

@@ -76,10 +76,10 @@ func (p *playerCall) MainCitySiege(playerID int64, mainCityObjectID int64) {
 	})
 }
 
-func (p *playerCall) AutoDefendQuit(playerID int64, teamIDList ...int32) {
+func (p *playerCall) AutoDefendQuit(playerID, buildObjectID int64) {
 	targetPath := cfacade.NewChildPath("", nameActor.Map_Player, playerID)
-	capp.Call(targetPath, nameRemote.MapPlayer_AutoDefendQuit, &pb.I32List{
-		List: teamIDList,
+	capp.Call(targetPath, nameRemote.MapPlayer_AutoDefendQuit, &pb.I64{
+		Value: buildObjectID,
 	})
 }
 

+ 1 - 1
sr/nodes/map/internal/db/data/logic/monster.go

@@ -90,7 +90,7 @@ func (p *Monster) GetRewards() types.Assets {
 			continue
 		}
 
-		row, ok := data.DropPool.Random(rewardPool.Key)
+		row, ok := data.DropPool.RandomOne(rewardPool.Key)
 		if code.IsFail(ok) {
 			continue
 		}

+ 16 - 14
sr/nodes/map/internal/db/data/player/sand_map.go

@@ -8,12 +8,12 @@ import (
 type (
 	// 沙盒相关对象
 	SandMap struct {
-		ResMap                  types.Map[int64, *Res] `bson:"ResMap,omitempty"`                  // 地图资源表
-		ComebackEndTime         int64                  `bson:"ComebackEndTime,omitempty"`         // 玩家再起截止时间(毫秒)
-		RechooseComebackEndTime int64                  `bson:"RechooseComebackEndTime,omitempty"` // 玩家选州再起截止时间(毫秒)
-		BornStateID             int32                  `bson:"BornStateID,omitempty"`             // 玩家出生州ID(0表示未选州)
-		IsRebornFailed          bool                   `bson:"IsFailed,omitempty"`                // 是否重生失败
-		RebornInfo              *RebornInfo            `bson:"RebornInfo,omitempty"`              // 重生信息
+		ResMap                  types.Map[int64, *Res]                    `bson:"ResMap,omitempty"`                  // 地图资源表
+		ComebackEndTime         int64                                     `bson:"ComebackEndTime,omitempty"`         // 玩家再起截止时间(毫秒)
+		RechooseComebackEndTime int64                                     `bson:"RechooseComebackEndTime,omitempty"` // 玩家选州再起截止时间(毫秒)
+		BornStateID             int32                                     `bson:"BornStateID,omitempty"`             // 玩家出生州ID(0表示未选州)
+		RebornInfo              RebornInfo                                `bson:"RebornInfo,omitempty"`              // 重生信息
+		AutoDefend              types.Map[int64, types.Map[int32, int64]] `bson:"AutoDefend,omitempty"`              // 自动防守表 <防守建筑ID,<防守队伍ID,上次防守时间(毫秒)>>
 	}
 
 	Res struct {
@@ -22,8 +22,11 @@ type (
 		ConfigID     int32 `bson:"ConfigID,omitempty"`     // 配置ID
 		InLeagueArea bool  `bson:"InLeagueArea,omitempty"` // 是否联盟领土内
 	}
+
 	RebornInfo struct {
-		Point types.Point `bson:"Point,omitempty"` // 重生点
+		IsFailed bool        `bson:"IsFailed,omitempty"` // 是否重生失败
+		NeedPush bool        `bson:"NeedPush,omitempty"` // 是否需要推送
+		Point    types.Point `bson:"Point,omitempty"`    // 重生点
 		// 其它信息,比如被谁击飞的
 	}
 
@@ -32,15 +35,10 @@ type (
 	}
 )
 
-func NewRebornInfo(x, y int32) *RebornInfo {
-	return &RebornInfo{
-		Point: types.NewPoint(x, y),
-	}
-}
-
 func newSandMap() SandMap {
 	return SandMap{
-		ResMap: types.NewMap[int64, *Res](),
+		ResMap:     types.NewMap[int64, *Res](),
+		AutoDefend: types.Map[int64, types.Map[int32, int64]]{},
 	}
 }
 
@@ -48,6 +46,10 @@ func (p *SandMap) CheckFiled() {
 	if p.ResMap == nil {
 		p.ResMap = types.NewMap[int64, *Res]()
 	}
+
+	if p.AutoDefend == nil {
+		p.AutoDefend = types.NewMap[int64, types.Map[int32, int64]]()
+	}
 }
 
 func (p *SandMap) GetAllYield() types.Map[int32, int64] {

+ 2 - 3
sr/nodes/map/internal/db/data/player/team.go

@@ -40,7 +40,6 @@ type (
 		FacilityAttrs    types.Attrs                        `bson:"FacilityAttrs,omitempty"`    // 建筑给战斗的属性
 		IsAutoTroops     bool                               `bson:"IsAutoTroops,omitempty"`     // 是否自动征兵(城外征兵)
 		CanOutsideTroops bool                               `bson:"CanOutsideTroops,omitempty"` // 是否可以城外征兵
-		IsAutoDefend     bool                               `bson:"IsAutoDefend,omitempty"`     // 是否自动防守中
 	}
 
 	OutsideConscript struct {
@@ -194,11 +193,11 @@ func (p Team) GetTeamIndexs() []int32 {
 }
 
 func (p Team) IsNotStandby() bool {
-	return p.State != enum.TeamState_Standby || p.IsAutoDefend
+	return p.State != enum.TeamState_Standby
 }
 
 func (p Team) IsStandby() bool {
-	return p.State == enum.TeamState_Standby && !p.IsAutoDefend
+	return p.State == enum.TeamState_Standby
 }
 
 func (p Team) IsNeedSync() bool {

+ 1 - 1
sr/nodes/map/internal/db/data/season/season_quest.go

@@ -46,7 +46,7 @@ func (p *Quest) CalcEndTime() int64 {
 
 	questRow, found := data.SeasonQuest.GetByID(p.QuestID)
 	if found {
-		return p.StartTime + int64(questRow.QuestDuration)*ctime.MillisecondsPerHour
+		return p.StartTime + questRow.QuestDuration*ctime.MillisecondsPerMinute
 	}
 
 	return 0

+ 8 - 6
sr/nodes/map/internal/db/logic_build_table.go

@@ -104,12 +104,14 @@ func (p *LogicBuildTable) ToOwnerMapObjectPb() *pb.OwnerMapObject {
 		ConfigID:   p.ConfigID,
 		Point:      p.Point.ToProto(),
 		Build: &pb.OwnerMapObjectBuild{
-			IsBuilding:     p.IsBuilding,
-			BuildEndTime:   p.BuildEndTime,
-			IsUpgrading:    p.IsUpgrading,
-			UpgradeEndTime: p.UpgradeEndTime,
-			Level:          p.Level,
-			DiscardTime:    p.DiscardEndTime,
+			IsBuilding:        p.IsBuilding,
+			BuildEndTime:      p.BuildEndTime,
+			IsUpgrading:       p.IsUpgrading,
+			UpgradeEndTime:    p.UpgradeEndTime,
+			Level:             p.Level,
+			DiscardTime:       p.DiscardEndTime,
+			InvincibleEndTime: p.InvincibleEndTime,
+			Index:             p.Index,
 		},
 	}
 }

+ 17 - 7
sr/nodes/map/internal/db/logic_march_table.go

@@ -67,16 +67,26 @@ func NewLogicMarchTable(teamID int32, objectID int64, nodeID string, x, y int32,
 	return table
 }
 
-func (p *LogicMarchTable) ToProto() *pb.MapObject {
-	return &pb.MapObject{
+func (p *LogicMarchTable) ToOwnerMapObjectPb() *pb.OwnerMapObject {
+	return &pb.OwnerMapObject{
 		ObjectID:   p.ObjectID,
-		PlayerID:   p.PlayerID,
-		LeagueID:   p.LeagueID,
-		Point:      p.Point.ToProto(),
 		ObjectType: int32(p.Type),
 		ConfigID:   p.ConfigID,
-		Path:       p.ToMapPathProto(),
-		March:      p.ToMarchProto(),
+		Point:      p.Point.ToProto(),
+	}
+}
+
+func (p *LogicMarchTable) ToProto() *pb.MapObject {
+	return &pb.MapObject{
+		ObjectID:          p.ObjectID,
+		PlayerID:          p.PlayerID,
+		LeagueID:          p.LeagueID,
+		Point:             p.Point.ToProto(),
+		ObjectType:        int32(p.Type),
+		ConfigID:          p.ConfigID,
+		InvincibleEndTime: p.InvincibleEndTime,
+		Path:              p.ToMapPathProto(),
+		March:             p.ToMarchProto(),
 	}
 
 }

+ 2 - 1
sr/nodes/map/internal/db/logic_res_table.go

@@ -67,7 +67,8 @@ func (p *LogicResTable) ToOwnerMapObjectPb() *pb.OwnerMapObject {
 		ConfigID:   p.ConfigID,
 		Point:      p.Point.ToProto(),
 		Res: &pb.OwnerMapObjectRes{
-			DiscardTime: p.DiscardEndTime,
+			DiscardTime:       p.DiscardEndTime,
+			InvincibleEndTime: p.InvincibleEndTime,
 		},
 	}
 }

+ 28 - 0
sr/nodes/map/internal/db/logic_watcher_table.go

@@ -179,6 +179,34 @@ func (p *LogicWatcherTable) GetWatcherObjectList(tables *LogicTables) []*pb.MapO
 	return rsp
 }
 
+func (p *LogicWatcherTable) GetOwnerObjects(tables *LogicTables) []*pb.OwnerMapObject {
+	var rsp []*pb.OwnerMapObject
+
+	// 打包玩家的地图对象
+	// 行军
+	for _, marchObjectID := range p.Marchs {
+		if marchTable, ok := tables.MarchTables.Get(marchObjectID); ok {
+			rsp = append(rsp, marchTable.ToOwnerMapObjectPb())
+		}
+	}
+
+	// 建筑
+	for buildObjectID := range p.Builds {
+		if buildTable, ok := tables.BuildTables.Get(buildObjectID); ok {
+			rsp = append(rsp, buildTable.ToOwnerMapObjectPb())
+		}
+	}
+
+	// 资源
+	for resObjectID := range p.Res {
+		if resTable, ok := tables.ResTables.Get(resObjectID); ok {
+			rsp = append(rsp, resTable.ToOwnerMapObjectPb())
+		}
+	}
+
+	return rsp
+}
+
 func (p *LogicWatcherTable) ToSiegePlayer() *pb.SiegePlayer {
 	return &pb.SiegePlayer{
 		PlayerID:   p.PlayerID,

+ 2 - 7
sr/nodes/map/internal/db/map_player_table.go

@@ -141,10 +141,10 @@ func LoadPlayerTables(nodeID string) map[int64]*MapPlayerTable {
 
 func (p *MapPlayerTable) ToRebornPush() *pb.RebornPush {
 	push := &pb.RebornPush{
-		IsFailed: p.IsRebornFailed,
+		IsFailed: p.RebornInfo.IsFailed,
 	}
 
-	if p.RebornInfo != nil {
+	if p.RebornInfo.NeedPush {
 		push.Point = p.RebornInfo.Point.ToProto()
 	}
 
@@ -264,11 +264,6 @@ func (p *MapPlayerTable) CheckTeamState(objectList []*pb.MapObject) {
 	}
 }
 
-func (p *MapPlayerTable) ClearRebornInfo() {
-	p.RebornInfo = nil
-	p.SetChangeData()
-}
-
 func (p *MapPlayerTable) ToMapLoginResponse() *pb.MapLoginResponse {
 	return &pb.MapLoginResponse{
 		BornStateID: p.BornStateID,

+ 26 - 13
sr/nodes/map/logic/actor_build.go

@@ -85,7 +85,7 @@ func (p *actor) buildReinforce(buildTable *db.LogicBuildTable) {
 			watcherTable.Save2Queue()
 
 			// AOI 通知
-			mapCall.AOI.UpdateAll(buildTable.ObjectID, buildTable.Type)
+			p.UpdateObjectFull(buildTable.ObjectID, buildTable.Type)
 
 		} else if buildTable.IsReinforceEffecting() {
 			buildTable.Reinforce.ChangeStage(enum.ReinforceStage_None, 0)
@@ -111,22 +111,35 @@ func (p *actor) buildSiegeCheck(buildTable *db.LogicBuildTable) {
 
 	// 如果没有再被攻城了,通知自动防守部队解散
 	if !buildTable.IsBeingSiege() {
-		playerTeams := map[int64][]int32{}
-		buildTable.AutoDefendList.Range(func(marchObjectID int64, _ *mapTypes.TileMarchInfo) bool {
-			marchTable := p.MarchTables.GetValue(marchObjectID)
-			playerTeams[marchTable.PlayerID] = append(playerTeams[marchTable.PlayerID], marchTable.TeamID)
-			return true
-		})
+		p.buildAutoDefendQuit(buildTable)
+	}
+}
 
-		buildTable.AutoDefendList.Clear()
-		buildTable.IsDataChanged = true
+func (p *actor) buildAutoDefendQuit(buildTable *db.LogicBuildTable) {
+	playerIDs := types.Set[int64]{}
+	for _, marchObjectID := range buildTable.RedeployObjects {
+		marchTable := p.MarchTables.GetValue(marchObjectID)
+		playerIDs.Add(marchTable.PlayerID)
+	}
 
-		if len(playerTeams) > 0 {
-			for playerID, teamIDs := range playerTeams {
-				mapCall.Player.AutoDefendQuit(playerID, teamIDs...)
-			}
+	buildTable.AutoDefendList.Clear()
+	buildTable.IsDataChanged = true
+
+	if !playerIDs.IsEmpty() {
+		for playerID := range playerIDs {
+			mapCall.Player.AutoDefendQuit(playerID, buildTable.ObjectID)
 		}
 	}
+
+	// 玩家城
+	if buildTable.ConfigID == constant.MapBuild_MainCity {
+		mapCall.Player.AutoDefendQuit(buildTable.PlayerID, buildTable.ObjectID)
+	}
+
+	// 联盟驻地
+	if buildTable.ConfigID == constant.MapBuild_LeaguePoint {
+		// TODO: 联盟驻地除了通知调用队列的玩家,还有发一个消息给联盟,并通知所有主城在驻地内的玩家
+	}
 }
 
 func (p *actor) doBuildDiscard(buildTable *db.LogicBuildTable) {

+ 19 - 16
sr/nodes/map/logic/actor_castle.go

@@ -5,10 +5,10 @@ import (
 	"f1-game/internal/enum"
 	"f1-game/internal/event"
 	"f1-game/internal/pb"
+	"f1-game/internal/types"
 	mapCall "f1-game/nodes/map/internal/call"
 	"f1-game/nodes/map/internal/db"
 	dbLogic "f1-game/nodes/map/internal/db/data/logic"
-	mapTypes "f1-game/nodes/map/internal/types"
 
 	ctime "github.com/cherry-game/cherry/extend/time"
 	clog "github.com/cherry-game/cherry/logger"
@@ -47,21 +47,7 @@ func (p *actor) castleSiegeCheck(castleTable *db.LogicCastleTable) {
 
 	// 如果没有再被攻城了,通知自动防守部队解散
 	if !castleTable.IsBeingSiege() {
-		playerTeams := map[int64][]int32{}
-		castleTable.AutoDefendList.Range(func(marchObjectID int64, _ *mapTypes.TileMarchInfo) bool {
-			marchTable := p.MarchTables.GetValue(marchObjectID)
-			playerTeams[marchTable.PlayerID] = append(playerTeams[marchTable.PlayerID], marchTable.TeamID)
-			return true
-		})
-
-		castleTable.AutoDefendList.Clear()
-		castleTable.IsDataChanged = true
-
-		if len(playerTeams) > 0 {
-			for playerID, teamIDs := range playerTeams {
-				mapCall.Player.AutoDefendQuit(playerID, teamIDs...)
-			}
-		}
+		p.castleAutoDefendQuit(castleTable)
 	}
 }
 
@@ -273,3 +259,20 @@ func (p *actor) castleSiegeTownAutoSiege(castleTable *db.LogicCastleTable) {
 
 	}
 }
+
+func (p *actor) castleAutoDefendQuit(castleTable *db.LogicCastleTable) {
+	playerIDs := types.Set[int64]{}
+	for _, marchObjectID := range castleTable.RedeployObjects {
+		marchTable := p.MarchTables.GetValue(marchObjectID)
+		playerIDs.Add(marchTable.PlayerID)
+	}
+
+	castleTable.AutoDefendList.Clear()
+	castleTable.IsDataChanged = true
+
+	if !playerIDs.IsEmpty() {
+		for playerID := range playerIDs {
+			mapCall.Player.AutoDefendQuit(playerID, castleTable.ObjectID)
+		}
+	}
+}

+ 45 - 29
sr/nodes/map/logic/actor_handle_remote.go

@@ -23,7 +23,6 @@ import (
 	"slices"
 
 	cstring "github.com/cherry-game/cherry/extend/string"
-	ctime "github.com/cherry-game/cherry/extend/time"
 	cfacade "github.com/cherry-game/cherry/facade"
 	clog "github.com/cherry-game/cherry/logger"
 )
@@ -118,33 +117,10 @@ func (p *actor) enter(req *pb.I64) (*pb.MapEnterResponse, int32) {
 }
 
 func (p *actor) pushOwnerMapObjects(watcherTable *db.LogicWatcherTable) {
-	rsp := pb.OwnerMapObjects{}
-	for _, object := range watcherTable.GetWatcherObjectList(p.LogicTables) {
-		// 这段代码等国英那边功能完善后需重构
-		ownerObjPb := &pb.OwnerMapObject{
-			ObjectID:   object.ObjectID,
-			ObjectType: object.ObjectType,
-			ConfigID:   object.ConfigID,
-			Point:      object.Point,
-		}
-		switch enum.ObjectType(ownerObjPb.ObjectType) {
-		case enum.ObjectType_Res:
-			ownerObjPb.Res = &pb.OwnerMapObjectRes{
-				DiscardTime: object.Res.DiscardTime,
-			}
-		case enum.ObjectType_Build:
-			ownerObjPb.Build = &pb.OwnerMapObjectBuild{
-				IsBuilding:     object.Build.IsBuilding,
-				BuildEndTime:   object.Build.BuildEndTime,
-				IsUpgrading:    object.Build.IsUpgrading,
-				UpgradeEndTime: object.Build.UpgradeEndTime,
-				Level:          object.Build.Level,
-				DiscardTime:    object.Build.DiscardTime,
-			}
-		}
-		rsp.List = append(rsp.List, ownerObjPb)
-
+	rsp := pb.OwnerMapObjects{
+		List: watcherTable.GetOwnerObjects(p.LogicTables),
 	}
+
 	sessions.PushPlayer(watcherTable.PlayerID, nameRoute.PushMapPlayer_OwnerMapObjects, &rsp)
 }
 
@@ -266,7 +242,7 @@ func (p *actor) selectState(req *pb.SelectStateRequest) (*pb.SelectStateResponse
 	// 新手期免战
 	// TODO 要通过赛季来确定
 	// 新手期免战时间
-	watcherTable.SetInvincibleEndTime(p.nowMillis + data.Const.BeginerInvincibleTime*ctime.MillisecondsPerMinute)
+	watcherTable.SetInvincibleEndTime(p.nowMillis + data.Const.GetBeginerInvincibleTime())
 	// 主城也设置免战时间
 	buildTable.SetInvincibleEndTime(watcherTable.InvincibleEndTime)
 	buildTable.IsDataChanged = true
@@ -327,6 +303,8 @@ func (p *actor) march(req *pb.MarchRequest) (*pb.I64I32, int32) {
 		if ok {
 			mainCity.SetInvincibleEndTime(0)
 			mainCity.IsDataChanged = true
+
+			p.UpdateObjectFull(mainCity.ObjectID, mainCity.Type)
 		}
 
 		// 行军对象破免
@@ -334,6 +312,8 @@ func (p *actor) march(req *pb.MarchRequest) (*pb.I64I32, int32) {
 			marchTable := p.MarchTables.GetValue(marchObjectID)
 			marchTable.SetInvincibleEndTime(0)
 			marchTable.IsDataChanged = true
+
+			p.UpdateObjectFull(marchTable.ObjectID, marchTable.Type)
 		}
 	}
 
@@ -835,6 +815,8 @@ func (p *actor) discardCancelPlayer(req *pb.I64I64I32) int32 {
 			resTable.StopDiscard()
 		}
 
+		p.UpdateObjectFull(resTable.ObjectID, enum.ObjectType_Res)
+
 	case enum.ObjectType_Build:
 		buildTable, ok := p.BuildTables.Get(objectID)
 		if !ok {
@@ -849,6 +831,7 @@ func (p *actor) discardCancelPlayer(req *pb.I64I64I32) int32 {
 			buildTable.StopDiscard()
 		}
 
+		p.UpdateObjectFull(buildTable.ObjectID, enum.ObjectType_Build)
 	default:
 		return code.MapObjectTypeUnkown
 	}
@@ -879,6 +862,7 @@ func (p *actor) discardCancelLeague(req *pb.I64I64I32) int32 {
 			buildTable.StopDiscard()
 		}
 
+		p.UpdateObjectFull(buildTable.ObjectID, enum.ObjectType_Build)
 	default:
 		return code.MapObjectTypeUnkown
 	}
@@ -1271,10 +1255,26 @@ func (p *actor) comeback(req *pb.PlayerComeback) (*pb.Point, int32) {
 		marchTable.IsDataChanged = true
 	}
 
+	// 再起添加免战时间
+	watcherTable.InvincibleEndTime = max(watcherTable.InvincibleEndTime, p.nowMillis+data.Const.GetComebackInvincibleTime())
+	watcherTable.Save2Queue()
+
+	mainCity.SetInvincibleEndTime(watcherTable.InvincibleEndTime)
+	mainCity.IsDataChanged = true
+	p.UpdateObjectFull(mainCity.ObjectID, mainCity.Type)
+
+	for _, marchObjectID := range watcherTable.Marchs {
+		marchTable := p.MarchTables.GetValue(marchObjectID)
+		marchTable.SetInvincibleEndTime(watcherTable.InvincibleEndTime)
+		marchTable.IsDataChanged = true
+
+		p.UpdateObjectFull(marchObjectID, marchTable.Type)
+	}
+
 	return mainCity.Point.ToProto(), code.OK
 }
 
-// comeback 玩家再起
+// reborn 重新出生
 func (p *actor) reborn(req *pb.I64I32) (*pb.Point, int32) {
 	var (
 		playerID = req.Key
@@ -1304,6 +1304,22 @@ func (p *actor) reborn(req *pb.I64I32) (*pb.Point, int32) {
 		}
 	}
 
+	// 再起添加免战时间
+	watcherTable.InvincibleEndTime = max(watcherTable.InvincibleEndTime, p.nowMillis+data.Const.GetComebackInvincibleTime())
+	watcherTable.Save2Queue()
+
+	mainCity.SetInvincibleEndTime(watcherTable.InvincibleEndTime)
+	mainCity.IsDataChanged = true
+	p.UpdateObjectFull(mainCity.ObjectID, mainCity.Type)
+
+	for _, marchObjectID := range watcherTable.Marchs {
+		marchTable := p.MarchTables.GetValue(marchObjectID)
+		marchTable.SetInvincibleEndTime(watcherTable.InvincibleEndTime)
+		marchTable.IsDataChanged = true
+
+		p.UpdateObjectFull(marchObjectID, marchTable.Type)
+	}
+
 	return mainCity.Point.ToProto(), code.OK
 }
 

+ 4 - 1
sr/nodes/map/logic/actor_march.go

@@ -568,8 +568,11 @@ func (p *actor) marchSiegeBuild(marchTable *db.LogicMarchTable, buildTable *db.L
 	// TODO: 先简单做,每次都通知一下,后面试试优化
 	// 通知玩家出来自动防守
 	// 如果是玩家主城,通知主城被攻击了
-	if buildTable.ConfigID == constant.MapBuild_MainCity {
+	switch buildTable.ConfigID {
+	case constant.MapBuild_MainCity:
 		mapCall.Player.MainCitySiege(buildTable.PlayerID, buildTable.ObjectID)
+	case constant.MapBuild_LeaguePoint:
+		// TODO: 联盟驻地除了通知调用队列的玩家,还有发一个消息给联盟,并通知所有主城在驻地内的玩家
 	}
 
 	// 如果调动列表不为空,通知出来自动防守

+ 1 - 2
sr/nodes/map/logic/actor_tile.go

@@ -14,7 +14,6 @@ import (
 	dbLogic "f1-game/nodes/map/internal/db/data/logic"
 	mapTypes "f1-game/nodes/map/internal/types"
 
-	ctime "github.com/cherry-game/cherry/extend/time"
 	clog "github.com/cherry-game/cherry/logger"
 )
 
@@ -371,7 +370,7 @@ func (p *actor) tileOccupy(tileTable *db.LogicTileTable) {
 	// 设置拥有者
 	resTable.SetOwner(marchTable.PlayerID, marchTable.LeagueID)
 	// 设置免战时间
-	resTable.SetInvincibleEndTime(p.nowMillis + data.Const.MapResProtectTime*ctime.MillisecondsPerSecond)
+	resTable.SetInvincibleEndTime(p.nowMillis + data.Const.GetResOccupyInvincibleTime())
 	// 重置战报组
 	resTable.ResetReport()
 	// 重置采集时间

+ 3 - 2
sr/nodes/map/logic/actor_tile_siege.go

@@ -306,8 +306,6 @@ func (p *actor) onTileSiegeDefendResult(tileTable *db.LogicTileTable, battleInfo
 		}
 
 		p.restoreMarchState(defender)
-		// 通知玩家退出部队退出自动防守
-		mapCall.Player.AutoDefendQuit(defender.PlayerID, defender.TeamID)
 	} else {
 		// 进攻方撤退
 		attacker := p.MarchTables.GetValue(battleInfo.AttackObjectID)
@@ -665,6 +663,9 @@ func (p *actor) onTileSiegeBuildSuccess(buildTable *db.LogicBuildTable, siegeTab
 	tileTable := p.TileTables.GetValue(buildTable.GetTileID())
 	p.tileGarrionRetreat(tileTable)
 
+	// 通知自动防守部队解散
+	p.buildAutoDefendQuit(buildTable)
+
 	// TODO: 下面的逻辑放到 buildRemove 方法中,buildRemove 写一个 OnBuildRemove 方法,执行所有建筑移除后相关逻辑
 	// 遣散城池的调动部队
 	for _, marchObjectID := range buildTable.RedeployObjects {

+ 0 - 2
sr/nodes/map/logic/actor_tile_snatch.go

@@ -185,8 +185,6 @@ func (p *actor) onTileSnatchDefendResult(tileTable *db.LogicTileTable, battleInf
 		}
 
 		p.restoreMarchState(defender)
-		// 通知玩家退出部队退出自动防守
-		mapCall.Player.AutoDefendQuit(defender.PlayerID, defender.TeamID)
 	} else {
 		// 进攻方撤退
 		attacker := p.MarchTables.GetValue(battleInfo.AttackObjectID)

+ 1 - 1
sr/nodes/map/map.go

@@ -146,7 +146,7 @@ func initMap(app cfacade.IApplication) {
 	mapLogic.Init(app, mapTables, playerTables, leagueTables)
 
 	// 初始化 mapPlayer
-	mapPlayer.Init(app)
+	mapPlayer.Init(app, playerTables)
 
 	// 初始化 联邦
 	mapUnion.Init(app)

+ 11 - 19
sr/nodes/map/player/actor.go

@@ -6,12 +6,11 @@ import (
 	nameEvent "f1-game/internal/name/event"
 	nameRemote "f1-game/internal/name/remote"
 	"f1-game/internal/sessions"
+	"f1-game/nodes/map/internal/db"
 	"f1-game/nodes/map/player/quest"
-	"time"
 
 	cslice "github.com/cherry-game/cherry/extend/slice"
 	cstring "github.com/cherry-game/cherry/extend/string"
-	ctime "github.com/cherry-game/cherry/extend/time"
 	cfacade "github.com/cherry-game/cherry/facade"
 	clog "github.com/cherry-game/cherry/logger"
 	cactor "github.com/cherry-game/cherry/net/actor"
@@ -20,18 +19,24 @@ import (
 // actor 玩家管理
 type actor struct {
 	cactor.Base
-	cleanChildTime time.Duration
 	mapEventRemote map[string]string
 }
 
-func Init(app cfacade.IApplication) {
+func Init(app cfacade.IApplication, playerTables map[int64]*db.MapPlayerTable) {
 	actor := NewActor()
 	app.ActorSystem().CreateActor(actor.AliasID(), actor)
+
+	// 初始化玩家子actor
+	for playerID := range playerTables {
+		child := newActorPlayer(playerID)
+		actor.Child().Create(cstring.ToString(playerID), &child)
+	}
+
+	clog.Infof("[Init] player actor init finished! player count = %d", len(playerTables))
 }
 
 func NewActor() *actor {
 	return &actor{
-		cleanChildTime: 10 * time.Minute,
 		mapEventRemote: map[string]string{
 			nameEvent.Map_PlayerResGather:     nameRemote.MapPlayer_ResGatherStats,
 			nameEvent.Map_PlayerCrusadeBattle: nameRemote.MapPlayer_CrusadeBattleStats,
@@ -48,10 +53,9 @@ func (p *actor) AliasID() string {
 }
 
 func (p *actor) OnInit() {
-	p.Timer().Add(p.cleanChildTime, p.cleanChildTimer)
 
 	// 注册任务事件
-	p.Event().Registers(cslice.Unique[string](quest.Service().EventNames()...), p.onQuestEvent)
+	p.Event().Registers(cslice.Unique(quest.Service().EventNames()...), p.onQuestEvent)
 
 	for eventName := range p.mapEventRemote {
 		p.Event().Register(eventName, p.onEvent)
@@ -99,18 +103,6 @@ func (p *actor) OnRemoteReceived(m *cfacade.Message) (bool, bool) {
 	return false, false
 }
 
-// cleanChildTimer 定期清理actorPlayer
-func (p *actor) cleanChildTimer() {
-	// 玩家已下线,并且超过childExitTime时间没有收发消息,则退出actor
-	deadline := ctime.Now().Add(-p.cleanChildTime).Unix()
-
-	p.Child().Each(func(iActor cfacade.IActor) {
-		if iActor.LastAt() < deadline {
-			p.Call(iActor.Path().String(), nameRemote.MapPlayer_OnCheckExit, nil)
-		}
-	})
-}
-
 func (p *actor) onQuestEvent(e cfacade.IEventData) {
 	playerID := e.UniqueID()
 	if playerID <= 0 {

+ 3 - 10
sr/nodes/map/player/actor_player.go

@@ -66,11 +66,12 @@ func (p *actorPlayer) OnInit() {
 	p.initSeason()
 
 	p.initEvent()
+
+	p.startTimer()
 }
 
 func (p *actorPlayer) initRemote() {
 	p.Remote().Register(nameRemote.MapPlayer_OnSessionClosed, p.onSessionClosed)
-	p.Remote().Register(nameRemote.MapPlayer_OnCheckExit, p.onCheckExit)
 	p.Remote().Register(nameRemote.MapPlayer_SyncData, p.syncData)
 	p.Remote().Register(nameRemote.MapPlayer_SyncVip, p.syncVipAttrs)
 }
@@ -176,7 +177,7 @@ func (p *actorPlayer) onSessionClosed() {
 	p.isOnline = false
 
 	//移除当前玩家所有定时器
-	p.Timer().RemoveAll()
+	// p.Timer().RemoveAll()
 
 	if playerTable := db.GetMapPlayerTable(p.playerID); playerTable != nil {
 		if playerTable.ObjectID > 0 {
@@ -190,11 +191,3 @@ func (p *actorPlayer) onSessionClosed() {
 
 	p.Info("[onLogout] finished! playerID = %d", p.playerID)
 }
-
-// 父actor检测到当前子actor很久收到消息,确认是否要退出
-func (p *actorPlayer) onCheckExit() {
-	// 不在线则销毁当前actor
-	if !p.isOnline {
-		p.Exit()
-	}
-}

+ 46 - 5
sr/nodes/map/player/map/service.go

@@ -1,11 +1,17 @@
 package sandMap
 
 import (
+	"f1-game/internal/code"
+	"f1-game/internal/data"
 	"f1-game/internal/facade"
 	nameRoute "f1-game/internal/name/route"
+	"f1-game/internal/pb"
 	"f1-game/internal/sessions"
+	mapCall "f1-game/nodes/map/internal/call"
 	"f1-game/nodes/map/internal/db"
+	"f1-game/nodes/map/player/team"
 
+	ctime "github.com/cherry-game/cherry/extend/time"
 	cproto "github.com/cherry-game/cherry/net/proto"
 )
 
@@ -18,12 +24,47 @@ type service struct {
 }
 
 func (p *service) Push(playerTable *db.MapPlayerTable, session *cproto.Session) {
-	if playerTable.RebornInfo == nil {
-		return
+	if playerTable.RebornInfo.NeedPush || playerTable.RebornInfo.IsFailed {
+		sessions.PushWithSession(session, nameRoute.PushMapPlayer_Reborn, playerTable.ToRebornPush())
+
+		playerTable.RebornInfo.NeedPush = false
+		playerTable.Save2Queue()
 	}
+}
+
+func (p *service) CheckAutoDefend(playerTable *db.MapPlayerTable, nowMillis int64) {
+	for buildObjectID, defendTeams := range playerTable.AutoDefend {
+		var members []*pb.AutoDefendMember
+		for teamID, lastDefendTime := range defendTeams {
+			// 上次防守时间小于5秒
+			if lastDefendTime+5*ctime.MillisecondsPerSecond > nowMillis {
+				continue
+			}
+
+			dbTeam, ok := playerTable.Teams.GetTeam(teamID)
+			if !ok {
+				continue
+			}
 
-	sessions.PushWithSession(session, nameRoute.PushMapPlayer_Reborn, playerTable.ToRebornPush())
+			if code.IsFail(dbTeam.IsBusy()) || team.Service().GetTeamTroopRatio(playerTable, dbTeam) < data.Const.MapAutoDefendTroopsRatio {
+				continue
+			}
 
-	playerTable.ClearRebornInfo()
-	playerTable.Save2Queue()
+			members = append(members, &pb.AutoDefendMember{
+				MarchObjectID: dbTeam.MarchObjectID,
+				MarchSync:     team.Service().GetTeamMarchSyncProto(playerTable, dbTeam),
+			})
+		}
+
+		if len(members) == 0 {
+			continue
+		}
+
+		req := &pb.AutoDefend{
+			BuildObjectID: buildObjectID,
+			Members:       members,
+		}
+
+		mapCall.Logic.AutoDefend(req)
+	}
 }

+ 42 - 93
sr/nodes/map/player/player_map.go

@@ -24,6 +24,7 @@ import (
 	mapTypes "f1-game/nodes/map/internal/types"
 	"f1-game/nodes/map/player/battle"
 	"f1-game/nodes/map/player/facility"
+	sandMap "f1-game/nodes/map/player/map"
 	"f1-game/nodes/map/player/player"
 	"f1-game/nodes/map/player/scout"
 	"f1-game/nodes/map/player/storage"
@@ -709,8 +710,9 @@ func (p *actorPlayer) comeback(session *cproto.Session, req *pb.I32Bool) {
 	}
 
 	rsp := pb.ComebackResponse{
-		Point:           point,
-		ComebackEndTime: playerTable.ComebackEndTime,
+		Point:                   point,
+		ComebackEndTime:         playerTable.ComebackEndTime,
+		RechooseComebackEndTime: playerTable.RechooseComebackEndTime,
 	}
 	p.Response(session, &rsp)
 }
@@ -730,7 +732,7 @@ func (p *actorPlayer) autoReborn() {
 		p.Debug("[autoReborn] errCode = %d", errCode)
 
 		// 重新出生失败,保存失败信息,以待重试
-		playerTable.IsRebornFailed = true
+		playerTable.RebornInfo.IsFailed = true
 		playerTable.Save2Queue()
 
 		// 推送通知
@@ -741,6 +743,7 @@ func (p *actorPlayer) autoReborn() {
 		return
 	}
 
+	playerTable.RebornInfo.IsFailed = false
 	// 更新坐标
 	player.Service().UpdateWatcherPoint(playerTable, point.X, point.Y)
 
@@ -751,7 +754,9 @@ func (p *actorPlayer) autoReborn() {
 		})
 	} else {
 		// 保存以登录时推送
-		playerTable.RebornInfo = dbMapPlayer.NewRebornInfo(point.X, point.Y)
+		playerTable.RebornInfo.Point.SetValue(point.X, point.Y)
+		playerTable.RebornInfo.NeedPush = true
+
 		playerTable.Save2Queue()
 	}
 }
@@ -759,7 +764,7 @@ func (p *actorPlayer) autoReborn() {
 // reborn 重新随机出生点
 func (p *actorPlayer) reborn(session *cproto.Session, _ *pb.None) {
 	playerTable := db.GetMapPlayerTable(p.playerID)
-	if !playerTable.IsRebornFailed {
+	if !playerTable.RebornInfo.IsFailed {
 		p.ResponseCode(session, code.OperationNotSupportYet)
 		return
 	}
@@ -777,7 +782,10 @@ func (p *actorPlayer) reborn(session *cproto.Session, _ *pb.None) {
 		return
 	}
 
-	playerTable.ClearRebornInfo()
+	playerTable.RebornInfo.IsFailed = false
+	playerTable.RebornInfo.NeedPush = false
+	playerTable.Save2Queue()
+
 	player.Service().UpdateWatcherPoint(playerTable, point.X, point.Y)
 
 	p.Response(session, point)
@@ -1108,19 +1116,12 @@ func (p *actorPlayer) mapObjectPosition(session *cproto.Session, req *pb.MapObje
 	})
 }
 
-func (p *actorPlayer) autoDefendQuit(req *pb.I32List) {
+// 自动防守退出
+func (p *actorPlayer) autoDefendQuit(req *pb.I64) {
 	p.Debug("[autoDefendQuit] req = %+v", req)
 	playerTable := db.GetMapPlayerTable(p.playerID)
-	// 自动防守退出
-	for _, teamID := range req.List {
-		dbTeam, ok := playerTable.Teams.GetTeam(teamID)
-		if !ok {
-			continue
-		}
-
-		dbTeam.IsAutoDefend = false
-	}
 
+	playerTable.AutoDefend.Remove(req.Value)
 	playerTable.Save2Queue()
 }
 
@@ -1129,49 +1130,23 @@ func (p *actorPlayer) mainCitySiege(req *pb.I64) {
 	p.Debug("[mainCitySiege] ObjectID = %v", req.Value)
 	playerTable := db.GetMapPlayerTable(p.playerID)
 
-	// 获取所有在城内并且非重伤的部队去自动防守
-	arg := &pb.AutoDefend{
-		BuildObjectID: req.Value,
-	}
-
+	// 让所有在城内的部队加入自动防守
 	for _, dbTeam := range playerTable.Teams {
-		if dbTeam.IsNotStandby() || code.IsFail(dbTeam.IsBusy()) || team.Service().IsTeamTroopsLack(playerTable, dbTeam) {
-			continue
-		}
-
-		member := &pb.AutoDefendMember{
-			MarchObjectID: dbTeam.MarchObjectID,
-		}
-
-		if dbTeam.IsNeedSync() {
-			member.MarchSync = team.Service().GetTeamMarchSyncProto(playerTable, dbTeam)
+		if dbTeam.IsStandby() {
+			defendTeams, ok := playerTable.AutoDefend[req.Value]
+			if !ok {
+				defendTeams = types.Map[int32, int64]{}
+				playerTable.AutoDefend[req.Value] = defendTeams
+			}
+			defendTeams.Put(dbTeam.TeamID, 0)
 		}
-
-		arg.Members = append(arg.Members, member)
 	}
 
-	p.Debug("[mainCitySiege] ObjectID = %v, March size = %v", req.Value, len(arg.Members))
-
-	// 通知加入自动防守
-	if len(arg.Members) > 0 {
-		rsp, errCode := mapCall.Logic.AutoDefend(arg)
-		if code.IsFail(errCode) {
-			p.Warn("[mainCitySiege] auto defend error: req = %+v, code = %v", arg, errCode)
-			return
-		}
-
-		for _, pair := range rsp.List {
-			if dbTeam, ok := playerTable.Teams.GetTeam(pair.Value); ok {
-				if dbTeam.MarchObjectID == 0 {
-					dbTeam.MarchObjectID = pair.Key
-				}
-
-				dbTeam.IsAutoDefend = true
-			}
-		}
+	playerTable.Save2Queue()
 
-		playerTable.Save2Queue()
-	}
+	p.updateNowTime()
+	// 立即触发一次自动防守
+	sandMap.Service().CheckAutoDefend(playerTable, p.now.ToMillisecond())
 }
 
 func (p *actorPlayer) updateWatcherPoint(req *pb.Point) {
@@ -1190,46 +1165,20 @@ func (p *actorPlayer) autoDefendRequest(req *pb.AutoDefendRequest) {
 	p.Debug("[autoDefendRequest] req = %+v", req)
 	playerTable := db.GetMapPlayerTable(p.playerID)
 
-	// 获取所有在城内并且非重伤的部队去自动防守
-	arg := &pb.AutoDefend{
-		BuildObjectID: req.BuildObjectID,
-	}
-
-	for _, teamID := range req.TeamIDs {
-		dbTeam, ok := playerTable.Teams.GetTeam(teamID)
-		if !ok {
-			continue
-		}
-
-		if dbTeam.IsAutoDefend || code.IsFail(dbTeam.IsBusy()) || team.Service().IsTeamTroopsLack(playerTable, dbTeam) {
-			continue
-		}
-
-		member := &pb.AutoDefendMember{
-			MarchObjectID: dbTeam.MarchObjectID,
-		}
-
-		arg.Members = append(arg.Members, member)
+	defendTeams, ok := playerTable.AutoDefend.Get(req.BuildObjectID)
+	if !ok {
+		defendTeams = types.Map[int32, int64]{}
+		playerTable.AutoDefend[req.BuildObjectID] = defendTeams
 	}
 
-	p.Debug("[autoDefendRequest] ObjectID = %v, Arg = %+v", req.BuildObjectID, arg)
+	// 所有部队加入自动防守
+	defendTeams.PutKeys(req.TeamIDs, 0)
 
-	// 通知加入自动防守
-	if len(arg.Members) > 0 {
-		rsp, errCode := mapCall.Logic.AutoDefend(arg)
-		if code.IsFail(errCode) {
-			p.Warn("[autoDefendRequest] auto defend error: req = %+v, code = %v", arg, errCode)
-			return
-		}
-
-		for _, pair := range rsp.List {
-			if dbTeam, ok := playerTable.Teams.GetTeam(pair.Value); ok {
-				dbTeam.IsAutoDefend = true
-			}
-		}
+	playerTable.Save2Queue()
 
-		playerTable.Save2Queue()
-	}
+	p.updateNowTime()
+	// 立即触发一次自动防守
+	sandMap.Service().CheckAutoDefend(playerTable, p.now.ToMillisecond())
 }
 
 func (p *actorPlayer) resOccupy(e cfacade.IEventData) {
@@ -1510,8 +1459,8 @@ func (p *actorPlayer) notifyRebornPush(req *pb.Point) {
 	}
 
 	// 玩家不在线,保存一下,玩家登录时推送
-	playerTable.RebornInfo = &dbMapPlayer.RebornInfo{
-		Point: types.NewPoint(req.X, req.Y),
-	}
+	playerTable.RebornInfo.NeedPush = true
+	playerTable.RebornInfo.Point.SetValue(req.X, req.Y)
+
 	playerTable.Save2Queue()
 }

+ 38 - 37
sr/nodes/map/player/player_team.go

@@ -136,6 +136,9 @@ func (p *actorPlayer) teamUp(session *cproto.Session, req *pb.TeamUpRequest) {
 	// 重新刷新上阵英雄阵容位置
 	team.Service().RefreshTeamLineup(playerTable, curTeam)
 
+	// 刷新队伍带兵上限
+	team.Service().RefreshTeamTroopsLimit(playerTable, curTeam)
+
 	// 更新队伍羁绊列表
 	p.updateTeamFetterList(curTeam)
 
@@ -311,6 +314,9 @@ func (p *actorPlayer) teamChange(session *cproto.Session, req *pb.TeamChangeRequ
 	// 重新刷新上阵英雄阵容位置
 	team.Service().RefreshTeamLineup(playerTable, dstTeam)
 
+	// 刷新英雄带兵上限
+	team.Service().RefreshTeamTroopsLimit(playerTable, dstTeam, srcTeam)
+
 	// 更新源队伍&目标队伍羁绊列表
 	p.updateTeamFetterList(srcTeam, dstTeam)
 
@@ -373,7 +379,10 @@ func (p *actorPlayer) teamInnerChange(session *cproto.Session, req *pb.TeamInner
 		return
 	}
 
-	changeHeroes := []*dbMapPlayer.Hero{srcHero}
+	var (
+		changeHeroes             = []*dbMapPlayer.Hero{srcHero}
+		dstTeamIndexReturnTroops = false
+	)
 	// 更换队伍位置
 	dstTeamMember, found := curTeam.GetTeamMember(dstTeamIndex)
 	if !found {
@@ -386,14 +395,21 @@ func (p *actorPlayer) teamInnerChange(session *cproto.Session, req *pb.TeamInner
 		}
 
 		srcTeamMember.TeamIndex, dstTeamMember.TeamIndex = dstTeamMember.TeamIndex, srcTeamMember.TeamIndex
-		// 如果被替换的位置原来是主将位置, 则需要检测是否需要返还溢出兵力
-		p.teamInnerChangeReturTroops(dstTeamMember, dstTeamIndex, srcTeamIndex)
+		dstTeamIndexReturnTroops = true
 
 		changeHeroes = append(changeHeroes, dstHero)
 	}
 
+	// 刷新英雄带兵上限
+	team.Service().RefreshTeamTroopsLimit(playerTable, curTeam)
+
+	// 如果被替换的位置原来是主将位置, 则需要检测是否需要返还溢出兵力 这之前需要先刷新带兵上限
+	if dstTeamIndexReturnTroops {
+		p.teamInnerChangeReturTroops(dstTeamMember, dstTeamIndex)
+	}
+
 	// 如果替换的位置原来是主将位置 则也需要返还溢出的兵力
-	p.teamInnerChangeReturTroops(srcTeamMember, srcTeamIndex, dstTeamIndex)
+	p.teamInnerChangeReturTroops(srcTeamMember, srcTeamIndex)
 
 	// 重新刷新上阵英雄阵容位置
 	team.Service().RefreshTeamLineup(playerTable, curTeam)
@@ -412,25 +428,17 @@ func (p *actorPlayer) teamInnerChange(session *cproto.Session, req *pb.TeamInner
 }
 
 // 队伍内位置切换 如果原来位置是主将位置 当前位置不是主将位置 则返还溢出的兵力
-func (p *actorPlayer) teamInnerChangeReturTroops(teamMember *dbMapPlayer.TeamMember, srcTeamIndex, dstTeamIndex int32) {
+func (p *actorPlayer) teamInnerChangeReturTroops(teamMember *dbMapPlayer.TeamMember, srcTeamIndex int32) {
 	if !constant.IsTeamMainIndex(srcTeamIndex) {
 		return
 	}
 
-	playerTable := db.GetMapPlayerTable(p.playerID)
-	heroEntity, found := playerTable.Heroes.Get(teamMember.HeroGUID)
-	if !found {
-		p.Warn("teamInnerChangeReturTroops: hero not found, heroGUID: %d", teamMember.HeroGUID)
-		return
-	}
-
-	curTroopsNumLimit := team.Service().GetHeroTroopsNum(p.playerID, heroEntity.HeroID, dstTeamIndex)
-	diffTroopsNum := teamMember.TroopsNum - curTroopsNumLimit
+	diffTroopsNum := teamMember.TroopsNum - teamMember.TroopsLimit
 	if diffTroopsNum <= 0 {
 		return
 	}
 
-	teamMember.TroopsNum = curTroopsNumLimit
+	teamMember.TroopsNum = teamMember.TroopsLimit
 
 	// 返还兵力
 	mapFacade.Storage.AssetAddOne(p.playerID, teamMember.TroopsID, int64(diffTroopsNum), ao.TeamInnerChangeReturn)
@@ -617,7 +625,6 @@ func (p *actorPlayer) groupTroops(memberTroops []*pb.TeamMemberTroops) (map[int3
 // 获取队伍最终分兵数
 func (p *actorPlayer) GetFinalTeamTroopNum(curTeam *dbMapPlayer.Team, troopsID int32, memberTroops map[int32]int32, finalMemberTroops map[int32]*pb.I32I32) int32 {
 	var (
-		playerTable       = db.GetMapPlayerTable(p.playerID)
 		costTroopsNum     int32
 		curTroopNum       int32
 		finalAddTroopsNum int32
@@ -642,17 +649,8 @@ func (p *actorPlayer) GetFinalTeamTroopNum(curTeam *dbMapPlayer.Team, troopsID i
 			curTroopNum = 0
 		}
 
-		heroEntity, found := playerTable.Heroes.Get(member.HeroGUID)
-		if !found {
-			p.Warn("GetFinalTeamTroopNum hero not found, heroGUID: %d", member.HeroGUID)
-			continue
-		}
-
-		// 获取英雄带兵上限
-		heroArmsLimit := team.Service().GetHeroTroopsNum(p.playerID, heroEntity.HeroID, member.TeamIndex)
-
-		if curTroopNum+finalAddTroopsNum > heroArmsLimit {
-			finalAddTroopsNum = heroArmsLimit - curTroopNum
+		if curTroopNum+finalAddTroopsNum > member.TroopsLimit {
+			finalAddTroopsNum = member.TroopsLimit - curTroopNum
 		}
 
 		costTroopsNum += finalAddTroopsNum
@@ -926,26 +924,20 @@ func (p *actorPlayer) useTroopsMedicine(playerTable *db.MapPlayerTable, curTeam
 	}
 
 	for _, member := range curTeam.Members {
-		heroEntity, found := playerTable.Heroes.Get(member.HeroGUID)
-		if !found {
-			return code.HeroNotExist
-		}
-
-		// 当前英雄带兵上限
-		heroTroopsLimit := team.Service().GetHeroTroopsNum(p.playerID, heroEntity.HeroID, member.TeamIndex)
 		// 超出上限跳过
-		if member.TroopsNum >= heroTroopsLimit {
+		if member.TroopsNum >= member.TroopsLimit {
 			continue
 		}
 
 		// 执行补兵 超出上限直接销毁
 		// 计算实际可以补兵的数量
-		add := min(addTroops, heroTroopsLimit-member.TroopsNum)
+		add := min(addTroops, member.TroopsLimit-member.TroopsNum)
 		member.TroopsNum += add
 
 		marchTroops = append(marchTroops, &pb.MarchTroops{
 			TeamIndex: member.TeamIndex,
-			TroopsNum: add,
+			TroopsNum: member.TroopsNum,
+			TroopsMax: member.TroopsLimit,
 		})
 
 		update = true
@@ -1243,6 +1235,15 @@ func (p *actorPlayer) onFacilityUpRefreshTeam(c cfacade.IEventData) {
 		}
 	}
 
+	// 刷新所有队伍成员带兵上限
+	refreshTeams := team.Service().RefreshTeamTroopsLimit(playerTable, playerTable.Teams...)
+	if len(refreshTeams) > 0 {
+		updateTeams = append(updateTeams, refreshTeams...)
+
+		// 同步队伍兵力
+		team.Service().UpdatePlayerTeams(p.playerID, refreshTeams...)
+	}
+
 	if len(updateTeams) > 0 {
 		team.Service().ChangePush(p.playerID, updateTeams...)
 	}

+ 13 - 8
sr/nodes/map/player/player_timer.go

@@ -8,6 +8,7 @@ import (
 	"f1-game/nodes/map/player/conscript"
 	"f1-game/nodes/map/player/facility"
 	"f1-game/nodes/map/player/hero"
+	sandMap "f1-game/nodes/map/player/map"
 	show "f1-game/nodes/map/player/show"
 	"f1-game/nodes/map/player/storage"
 	"f1-game/nodes/map/player/team"
@@ -15,10 +16,6 @@ import (
 )
 
 func (p *actorPlayer) startTimer() {
-	if !p.isOnline {
-		return
-	}
-
 	//移除所有定时器
 	p.Timer().RemoveAll()
 
@@ -28,6 +25,8 @@ func (p *actorPlayer) startTimer() {
 	p.Timer().Add(1*time.Second, p.every1STimer)
 	// 资源定时器
 	p.Timer().Add(3*time.Second, p.every3STimer)
+	// 每隔5秒检查自动防守
+	p.Timer().Add(5*time.Second, p.every5Stimer)
 }
 
 // onCrossDayTimer 跨天执行定时器
@@ -49,10 +48,6 @@ func (p *actorPlayer) onCrossDayTimer() {
 
 // oneSecondTimer 每1秒定时器
 func (p *actorPlayer) every1STimer() {
-	if !p.isOnline {
-		return
-	}
-
 	p.updateNowTime()
 
 	nowSecond := p.now.ToSecond()
@@ -95,6 +90,16 @@ func (p *actorPlayer) every3STimer() {
 	}
 }
 
+func (p *actorPlayer) every5Stimer() {
+	playerTable := db.GetMapPlayerTable(p.playerID)
+	if playerTable == nil {
+		return
+	}
+
+	// 检查自动防守
+	sandMap.Service().CheckAutoDefend(playerTable, p.now.ToMillisecond())
+}
+
 // onReset 重置处理(每次登录、跨天触发)
 func (p *actorPlayer) onReset(playerTable *db.MapPlayerTable, byLogin bool) {
 	playerTable.Stats.CheckReset(p.now)

+ 5 - 0
sr/nodes/map/player/storage/service.go

@@ -253,6 +253,11 @@ func (p *service) GetMapCurrencyYield(mapPlayerTable *db.MapPlayerTable, itemIDs
 		// 绑定资源
 		if attrID, ok := enum.MapItemAttrs[itemID]; ok {
 			totalYield += p.calcAssetOutputFinalAttr(mapPlayerTable, attrID)
+
+			// 资源田产出可能是绑定的
+			if yield, ok := mapResYields.Get(itemID); ok {
+				totalYield += yield
+			}
 		}
 
 		// 非绑定资源

+ 79 - 46
sr/nodes/map/player/team/service.go

@@ -95,6 +95,11 @@ func (p *service) Push(playerTable *db.MapPlayerTable, session *cproto.Session)
 
 // ChangePush 推送变化的队伍信息
 func (p *service) ChangePush(playerID int64, teams ...*dbMapPlayer.Team) {
+	session, found := sessions.GetSession(playerID)
+	if !found {
+		return
+	}
+
 	var (
 		playerTable = db.GetMapPlayerTable(playerID)
 		list        []*pb.Team
@@ -118,7 +123,7 @@ func (p *service) ChangePush(playerID int64, teams ...*dbMapPlayer.Team) {
 		List: list,
 	}
 
-	sessions.PushPlayer(playerID, nameRoute.PushMapPlayer_TeamChange, resp)
+	sessions.PushWithSession(session, nameRoute.PushMapPlayer_TeamChange, resp)
 }
 
 // 英雄状态更新推送
@@ -241,17 +246,9 @@ func (p *service) CheckAutoAddTeamTroops(playerID int64, teamID int32) bool {
 
 		needCostCount := int32(0)
 		for _, member := range members {
-			heroEntity, found := playerTable.Heroes.Get(member.HeroGUID)
-			if !found {
-				// 英雄不存在 整组失败
-				needCostCount = 0
-				break
-			}
-
-			heroArmsLimit := p.GetHeroTroopsNum(playerID, heroEntity.HeroID, member.TeamIndex)
 			// 当前需要补兵数量 = 带兵上限 - 当前剩余兵力 - 伤兵数
-			needCostCount += heroArmsLimit - member.TroopsNum - member.InjureTroops
-			heroArmsLimitMap[member.TeamIndex] = heroArmsLimit
+			needCostCount += member.TroopsLimit - member.TroopsNum - member.InjureTroops
+			heroArmsLimitMap[member.TeamIndex] = member.TroopsLimit
 		}
 
 		// 不需要补兵.0
@@ -711,14 +708,8 @@ func (p *service) DoTeamOutsideTroops(playerID int64, team *dbMapPlayer.Team, tr
 			return code.TeamOutsideConscripting
 		}
 
-		heroEntity, found := playerTable.Heroes.Get(teamMember.HeroGUID)
-		if !found {
-			return code.HeroNotExist
-		}
-
-		troopsLimit := p.GetHeroTroopsNum(playerID, heroEntity.HeroID, troopsNum.Key)
 		// 兵力数量超出上限
-		if troopsNum.Value+teamMember.TroopsNum > troopsLimit {
+		if troopsNum.Value+teamMember.TroopsNum > teamMember.TroopsLimit {
 			return code.TeamArmExceed
 		}
 
@@ -832,16 +823,8 @@ func (p *service) doTeamOutsideAutoTroops(playerTable *db.MapPlayerTable, second
 				getCount = remainCount
 			}
 
-			heroEntity, found := playerTable.Heroes.Get(member.HeroGUID)
-			if !found {
-				clog.Warnf("[doTeamOutsideAutoTroops] heroEntity not found, playerID: %d, heroGUID: %d", playerTable.PlayerID, member.HeroGUID)
-				continue
-			}
-
-			troopsLimit := p.GetHeroTroopsNum(playerTable.PlayerID, heroEntity.HeroID, member.TeamIndex)
-
-			if member.TroopsNum+getCount > troopsLimit {
-				getCount = troopsLimit - member.TroopsNum
+			if member.TroopsNum+getCount > member.TroopsLimit {
+				getCount = member.TroopsLimit - member.TroopsNum
 			}
 
 			if getCount <= 0 {
@@ -904,8 +887,13 @@ func (p *service) TeamOutsideTroopsChangePush(playerID int64, teams ...*dbMapPla
 		teamPush.List = append(teamPush.List, team.ToOutsideTroopsProto())
 	}
 
+	session, found := sessions.GetSession(playerID)
+	if !found {
+		return
+	}
+
 	if len(teamPush.List) > 0 {
-		sessions.PushPlayer(playerID, nameRoute.PushMapPlayer_TeamOutsideTroops, teamPush)
+		sessions.PushWithSession(session, nameRoute.PushMapPlayer_TeamOutsideTroops, teamPush)
 	}
 }
 
@@ -985,14 +973,7 @@ func (p *service) OutsideAutoTroops(playerTable *db.MapPlayerTable, team *dbMapP
 			continue
 		}
 
-		heroEntity, found := playerTable.Heroes.Get(member.HeroGUID)
-		if !found {
-			clog.Warnf("[outsideAutoTroops] hero not found. playerID:%d, teamID: %d, heroGUID: %d", playerTable.PlayerID, team.TeamID, member.HeroGUID)
-			continue
-		}
-
-		troopsLimit := p.GetHeroTroopsNum(playerTable.PlayerID, heroEntity.HeroID, member.Index)
-		needTroops := troopsLimit - member.TroopsNum
+		needTroops := member.TroopsLimit - member.TroopsNum
 		if needTroops <= 0 {
 			continue
 		}
@@ -1000,7 +981,7 @@ func (p *service) OutsideAutoTroops(playerTable *db.MapPlayerTable, team *dbMapP
 		teamMembers[member.TeamIndex] = &pb.TeamOutsideTroopsMember{
 			TroopsID:    member.TroopsID,
 			TroopsNum:   member.TroopsNum,
-			TroopsLimit: troopsLimit,
+			TroopsLimit: member.TroopsLimit,
 		}
 
 		// 获取征一个士兵的消耗
@@ -1379,18 +1360,70 @@ func (p *service) IsTeamTroopsLack(playerTable *db.MapPlayerTable, team *dbMapPl
 	)
 
 	for _, member := range team.Members {
-		heroEntity, found := playerTable.Heroes.Get(member.HeroGUID)
-		if !found {
-			clog.Warnf("[IsTeamTroopsLack] fail, hero not found. playerID:%d, heroGUID:%d", playerTable.PlayerID, member.HeroGUID)
-			return false
+		total += member.TroopsLimit
+	}
+
+	ratio := float32(current) / float32(total) * 10000.0
+
+	return ratio <= data.Const.TeamTroopsLackRatio
+}
+
+// 刷新队伍的带兵上限
+func (p *service) RefreshTeamTroopsLimit(playerTable *db.MapPlayerTable, teams ...*dbMapPlayer.Team) []*dbMapPlayer.Team {
+	if len(teams) <= 0 {
+		return nil
+	}
+
+	updateTeams := []*dbMapPlayer.Team{}
+	for _, curTeam := range teams {
+		if len(curTeam.Members) <= 0 {
+			continue
 		}
 
-		heroTroopsNum := p.GetHeroTroopsNum(playerTable.PlayerID, heroEntity.HeroID, member.TeamIndex)
+		var update bool
+		for _, member := range curTeam.Members {
+			if member.HeroGUID <= 0 {
+				continue
+			}
+
+			heroEntity, found := playerTable.Heroes.Get(member.HeroGUID)
+			if !found {
+				clog.Warnf("[RefreshTeamTroopsLimit] fail, hero not found. playerID:%d, heroGUID:%d", playerTable.PlayerID, member.HeroGUID)
+				continue
+			}
+
+			troopNums := p.GetHeroTroopsNum(playerTable.PlayerID, heroEntity.HeroID, member.TeamIndex)
+			if member.TroopsLimit != troopNums {
+				member.TroopsLimit = troopNums
+				update = true
+			}
+		}
 
-		total += heroTroopsNum
+		if update {
+			updateTeams = append(updateTeams, curTeam)
+		}
 	}
 
-	ratio := float32(current) / float32(total) * 10000.0
+	// 更新队伍列表
+	return updateTeams
+}
 
-	return ratio <= data.Const.TeamTroopsLackRatio
+// 获取队伍兵力万分比
+func (p *service) GetTeamTroopRatio(playerTable *db.MapPlayerTable, team *dbMapPlayer.Team) int32 {
+	var (
+		total int32
+		count = team.GetTeamTroopCount()
+	)
+
+	for _, member := range team.Members {
+		heroEntity, found := playerTable.Heroes.Get(member.HeroGUID)
+		if !found {
+			clog.Warnf("[GetTeamTroopRatio] fail, hero not found. playerID:%d, heroGUID:%d", playerTable.PlayerID, member.HeroGUID)
+			return 0
+		}
+
+		total += p.GetHeroTroopsNum(playerTable.PlayerID, heroEntity.HeroID, member.TeamIndex)
+	}
+
+	return math.Round[float32, int32](float32(count) / float32(total) * constant.FRatioBase)
 }

+ 134 - 86
sr/tool/test/map_static_data/map_static_data.go

@@ -12,17 +12,46 @@ import (
 	"google.golang.org/protobuf/proto"
 )
 
+var (
+	_mapData    *pb.MapData
+	_innerScene *pb.MapData
+	_mapDataExt *pb.MapDataEXT
+)
+
 func main() {
-	var (
-		mapData    = loadMapStaticData()
-		_          = loadInnerScene()
-		mapDataExt = loadAreaData()
-	)
+	_mapData = loadMapStaticData()
+	_innerScene = loadInnerScene()
+	_mapDataExt = loadAreaData()
 
 	// 检查地图数据是否有效
-	checkMapData(mapData, mapDataExt)
+	checkMapData()
+
+	// 打印信息
+	printMapData()
+}
+
+func printMapData() {
+	m := map[int32]struct{}{}
+	for _, tile := range _mapData.Tiles {
+		state, _, _ := _mapData.GetTileLocateInfo(tile.CoordIndex)
+		m[state] = struct{}{}
+	}
+
+	fmt.Printf("map state list: ")
+	for state := range m {
+		fmt.Printf("%d ", state)
+	}
+	fmt.Println()
 
-	// printCastles(mapData)
+	m = map[int32]struct{}{}
+	for _, bornData := range _mapDataExt.BornData {
+		m[bornData.Area1] = struct{}{}
+	}
+	fmt.Printf("born state list: ")
+	for state := range m {
+		fmt.Printf("%d ", state)
+	}
+	fmt.Println()
 }
 
 func printCastles(mapData *pb.MapData) {
@@ -60,18 +89,81 @@ func loadMapStaticData() *pb.MapData {
 	return &mapData
 }
 
-func checkMapData(mapData *pb.MapData, mapDataExt *pb.MapDataEXT) {
-	for _, castle := range mapData.Castles {
-		tile := mapData.Tiles[castle.Center]
+func loadInnerScene() *pb.MapData {
+
+	mapData := pb.MapData{}
+
+	dir := cfile.GetWorkDir()
+	filepath := fmt.Sprintf("%s/../../../etc/data_map/s1/inner_scene.pb", dir)
+	file, err := os.Open(filepath)
+	if err != nil {
+		log.Fatalf("open map data file failed: %v", err)
+	}
+	defer file.Close()
+
+	data, err := io.ReadAll(file)
+	if err != nil {
+		log.Fatalf("read map data file failed: %v", err)
+	}
+
+	start := ctime.Now()
+	if err := proto.Unmarshal(data, &mapData); err != nil {
+		log.Fatalf("unmarshal map data failed: %v", err)
+	}
+	end := ctime.Now()
+	elapsed := start.DiffInMillisecond(&end)
+	fmt.Printf("unmarshal map data elapsed: %vms\n", elapsed)
+
+	return &mapData
+}
+
+func loadAreaData() *pb.MapDataEXT {
+	areaData := pb.MapDataEXT{}
+
+	dir := cfile.GetWorkDir()
+	filepath := fmt.Sprintf("%s/../../../etc/data_map/s1/area_data.pb", dir)
+	file, err := os.Open(filepath)
+	if err != nil {
+		log.Fatalf("open area data file failed: %v", err)
+	}
+	defer file.Close()
+
+	data, err := io.ReadAll(file)
+	if err != nil {
+		log.Fatalf("read area data file failed: %v", err)
+	}
+
+	start := ctime.Now()
+	if err := proto.Unmarshal(data, &areaData); err != nil {
+		log.Fatalf("unmarshal area data failed: %v", err)
+	}
+	end := ctime.Now()
+	elapsed := start.DiffInMillisecond(&end)
+	fmt.Printf("unmarshal area data elapsed: %vms\n", elapsed)
+
+	fmt.Printf("area data: size = %v\n", len(areaData.AreaData))
+	fmt.Printf("born data: size = %v\n", len(areaData.BornData))
+
+	return &areaData
+}
+
+func checkMapData() {
+	checkBlock()
+	checkOverrap()
+}
+
+func checkBlock() {
+	for _, castle := range _mapData.Castles {
+		tile := _mapData.Tiles[castle.Center]
 		if tile.IsBlock() {
-			x, y := mapData.TileIDToPoint(castle.Center)
+			x, y := _mapData.TileIDToPoint(castle.Center)
 			log.Printf("castle center is block. tileID = %d, point = (%d, %d) ,configID = %d", castle.Center, x, y, castle.ConfigId)
 		}
 
 		for _, tileID := range castle.Childs {
-			tile := mapData.Tiles[tileID]
+			tile := _mapData.Tiles[tileID]
 			if tile.IsBlock() {
-				x, y := mapData.TileIDToPoint(tileID)
+				x, y := _mapData.TileIDToPoint(tileID)
 				log.Printf("castle child is block. tileID = %d, point = (%d, %d) ,configID = %d", tileID, x, y, castle.ConfigId)
 			}
 		}
@@ -82,124 +174,80 @@ func checkMapData(mapData *pb.MapData, mapDataExt *pb.MapDataEXT) {
 			}
 
 			for _, tileID := range value.Points {
-				tile := mapData.Tiles[tileID]
+				tile := _mapData.Tiles[tileID]
 				if tile.IsBlock() {
-					x, y := mapData.TileIDToPoint(tileID)
+					x, y := _mapData.TileIDToPoint(tileID)
 					log.Printf("castle tag tile is block. tileID = %d, point = (%d, %d) ,configID = %d, tag = %s", tileID, x, y, castle.ConfigId, pb.MapRuleTag_name[tag])
 				}
 			}
 		}
 	}
 
-	for _, resData := range mapDataExt.ResData {
-		if mapData.IsTileBlock(resData.Center) {
-			x, y := mapData.TileIDToPoint(resData.Center)
+	for _, resData := range _mapDataExt.ResData {
+		if _mapData.IsTileBlock(resData.Center) {
+			x, y := _mapData.TileIDToPoint(resData.Center)
 			log.Printf("resData center is block. tileID = %d, point = (%d, %d) ,configID = %d", resData.Center, x, y, resData.ConfigId)
 		}
 	}
 
-	for _, areaData := range mapDataExt.AreaData {
+	for _, areaData := range _mapDataExt.AreaData {
 		for areaID, monsterData := range areaData.MonsterData {
-			if mapData.IsTileBlock(monsterData.Center) {
-				x, y := mapData.TileIDToPoint(monsterData.Center)
+			if _mapData.IsTileBlock(monsterData.Center) {
+				x, y := _mapData.TileIDToPoint(monsterData.Center)
 				log.Printf("monsterData center is block. tileID = %d, point = (%d, %d) ,configID = %d, areaID = %d", monsterData.Center, x, y, monsterData.ConfigId, areaID)
 			}
 		}
 	}
 
-	for _, bornData := range mapDataExt.BornData {
-		if mapData.IsTileBlock(bornData.Center) {
-			x, y := mapData.TileIDToPoint(bornData.Center)
+	for _, bornData := range _mapDataExt.BornData {
+		if _mapData.IsTileBlock(bornData.Center) {
+			x, y := _mapData.TileIDToPoint(bornData.Center)
 			log.Printf("bornData center is block. tileID = %d, point = (%d, %d)", bornData.Center, x, y)
 		}
 
 		for _, tileID := range bornData.OccupiedCoords {
-			if mapData.IsTileBlock(tileID) {
-				x, y := mapData.TileIDToPoint(tileID)
+			if _mapData.IsTileBlock(tileID) {
+				x, y := _mapData.TileIDToPoint(tileID)
 				log.Printf("bornData occupied tile is block. tileID = %d, point = (%d, %d)", tileID, x, y)
 			}
 		}
 
 		if bornData.Area1 == 0 {
-			x, y := mapData.TileIDToPoint(bornData.Center)
+			x, y := _mapData.TileIDToPoint(bornData.Center)
 			log.Printf("bornData state is zero. tileID = %d, point = (%d, %d) ", bornData.Center, x, y)
 		}
 	}
 
-	for _, bornData := range mapDataExt.ReserveBornData {
-		if mapData.IsTileBlock(bornData.Center) {
-			x, y := mapData.TileIDToPoint(bornData.Center)
+	for _, bornData := range _mapDataExt.ReserveBornData {
+		if _mapData.IsTileBlock(bornData.Center) {
+			x, y := _mapData.TileIDToPoint(bornData.Center)
 			log.Printf("reserve bornData center is block. tileID = %d, point = (%d, %d)", bornData.Center, x, y)
 		}
 
 		for _, tileID := range bornData.OccupiedCoords {
-			if mapData.IsTileBlock(tileID) {
-				x, y := mapData.TileIDToPoint(tileID)
+			if _mapData.IsTileBlock(tileID) {
+				x, y := _mapData.TileIDToPoint(tileID)
 				log.Printf("reserve bornData occupied tile is block. tileID = %d, point = (%d, %d)", tileID, x, y)
 			}
 		}
 
 		if bornData.Area1 == 0 {
-			x, y := mapData.TileIDToPoint(bornData.Center)
+			x, y := _mapData.TileIDToPoint(bornData.Center)
 			log.Printf("reserve bornData state is zero. tileID = %d, point = (%d, %d) ", bornData.Center, x, y)
 		}
 	}
 }
 
-func loadInnerScene() *pb.MapData {
-
-	mapData := pb.MapData{}
-
-	dir := cfile.GetWorkDir()
-	filepath := fmt.Sprintf("%s/../../../etc/data_map/s1/inner_scene.pb", dir)
-	file, err := os.Open(filepath)
-	if err != nil {
-		log.Fatalf("open map data file failed: %v", err)
-	}
-	defer file.Close()
-
-	data, err := io.ReadAll(file)
-	if err != nil {
-		log.Fatalf("read map data file failed: %v", err)
-	}
-
-	start := ctime.Now()
-	if err := proto.Unmarshal(data, &mapData); err != nil {
-		log.Fatalf("unmarshal map data failed: %v", err)
-	}
-	end := ctime.Now()
-	elapsed := start.DiffInMillisecond(&end)
-	fmt.Printf("unmarshal map data elapsed: %vms\n", elapsed)
-
-	return &mapData
-}
-
-func loadAreaData() *pb.MapDataEXT {
-	areaData := pb.MapDataEXT{}
-
-	dir := cfile.GetWorkDir()
-	filepath := fmt.Sprintf("%s/../../../etc/data_map/s1/area_data.pb", dir)
-	file, err := os.Open(filepath)
-	if err != nil {
-		log.Fatalf("open area data file failed: %v", err)
+func checkOverrap() {
+	m := map[int32]int32{}
+	for _, resData := range _mapDataExt.ResData {
+		m[resData.Center]++
 	}
-	defer file.Close()
 
-	data, err := io.ReadAll(file)
-	if err != nil {
-		log.Fatalf("read area data file failed: %v", err)
-	}
-
-	start := ctime.Now()
-	if err := proto.Unmarshal(data, &areaData); err != nil {
-		log.Fatalf("unmarshal area data failed: %v", err)
+	for tileID, count := range m {
+		if count > 1 {
+			x, y := _mapData.TileIDToPoint(tileID)
+			log.Printf("Found overrap res data. tileID = %d, point = (%d, %d), count = %d", tileID, x, y, count)
+		}
 	}
-	end := ctime.Now()
-	elapsed := start.DiffInMillisecond(&end)
-	fmt.Printf("unmarshal area data elapsed: %vms\n", elapsed)
-
-	fmt.Printf("area data: size = %v\n", len(areaData.AreaData))
-	fmt.Printf("born data: size = %v\n", len(areaData.BornData))
-
-	return &areaData
 }