From b2a4ccc547e060c012e8925487530e080958c6bd Mon Sep 17 00:00:00 2001 From: SERENCH <3117094826@qq.com> Date: Mon, 31 Mar 2025 22:13:11 +0800 Subject: [PATCH 1/9] feat: :sparkles: python AI.py --- CAPI/python/AI.py | 58 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 CAPI/python/AI.py diff --git a/CAPI/python/AI.py b/CAPI/python/AI.py new file mode 100644 index 0000000..6c33751 --- /dev/null +++ b/CAPI/python/AI.py @@ -0,0 +1,58 @@ +import queue +import time +from typing import Final, List, Union, cast + +import PyAPI.structures as THUAI8 +from PyAPI.constants import Constants +from PyAPI.Interface import IAI, ICharacterAPI, ITeamAPI +from PyAPI.utils import AssistFunction + + +class Setting: + # 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新,大致一帧更新一次 + @staticmethod + def Asynchronous() -> bool: + return False + + @staticmethod + def CharacterTypes() -> List[THUAI8.CharacterType]: + return [ + THUAI8.CharacterType.Monk, + THUAI8.CharacterType.MonkyKing, + THUAI8.CharacterType.Pigsy, + THUAI8.CharacterType.ShaWujing, + THUAI8.CharacterType.Whitedragonhorse, + THUAI8.CharacterType.JiuTouYuanSheng, + THUAI8.CharacterType.Honghaier, + THUAI8.CharacterType.Gyuumao, + THUAI8.CharacterType.Princess_Iron_Fan, + THUAI8.CharacterType.Spider, + ] + + +numOfGridPerCell: Final[int] = 1000 + + +class AI(IAI): + def __init__(self, pID: int): + self.__playerID = pID + + def CharacterPlay(self, api: ICharacterAPI) -> None: + # 公共操作 + if self.__playerID == 1: + # player1的操作 + return + elif self.__playerID == 2: + # player2的操作 + return + elif self.__playerID == 3: + # player3的操作 + return + elif self.__playerID == 4: + # player4的操作 + return + return + + def TeamPlay(self, api: ITeamAPI) -> None: + # player0的操作 + return From f595daa491d6fd06d273deb0ecfa3d8016e6d4c5 Mon Sep 17 00:00:00 2001 From: Xyberoid <143863451+Xyberoid@users.noreply.github.com> Date: Mon, 31 Mar 2025 23:05:39 +0800 Subject: [PATCH 2/9] =?UTF-8?q?fix:=20=E5=B0=8Fbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- logic/Gaming/CharacterManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/logic/Gaming/CharacterManager.cs b/logic/Gaming/CharacterManager.cs index e42b3a2..457fe14 100644 --- a/logic/Gaming/CharacterManager.cs +++ b/logic/Gaming/CharacterManager.cs @@ -299,6 +299,7 @@ namespace Gaming character.Shoes.SubPositiveV(GameData.CrazySpeed); character.ATKFrequency = GameData.ATKFreq; character.BerserkTime = long.MaxValue; + character.IsBerserk = false; } } } From 8f0fea5e5799d85bf78b9341a339b6f42e72e9da Mon Sep 17 00:00:00 2001 From: SERENCH <3117094826@qq.com> Date: Mon, 31 Mar 2025 23:09:03 +0800 Subject: [PATCH 3/9] feat: :sparkles: py --- CAPI/cpp/API/include/constants.h | 56 ++++----- CAPI/cpp/API/src/AI.cpp | 11 +- CAPI/cpp/API/src/logic.cpp | 6 +- CAPI/python/AI.py | 9 +- CAPI/python/constants.py | 199 +++++++++++++++++++++++++++++++ 5 files changed, 244 insertions(+), 37 deletions(-) create mode 100644 CAPI/python/constants.py diff --git a/CAPI/cpp/API/include/constants.h b/CAPI/cpp/API/include/constants.h index f457e10..526de62 100644 --- a/CAPI/cpp/API/include/constants.h +++ b/CAPI/cpp/API/include/constants.h @@ -22,7 +22,7 @@ namespace Constants SCCI int32_t DestroyFarmBonus = 4000; // SCCI double recoverMultiplier = 1.2; // SCCI double recycleMultiplier = 0.5; - //角色 + // 角色 SCCI int32_t sizeofCharacter = 800; SCCI int32_t Speed = 2500; struct Monk @@ -92,39 +92,39 @@ namespace Constants SCCI int32_t attackRange = 5; SCCI int32_t Cost = 3000; }; - //模块 + // 模块 - struct resumption //生命之泉 + struct resumption // 生命之泉 { SCCI int32_t recovery1 = 50; SCCI int32_t recovery2 = 100; SCCI int32_t recovery3 = 150; - SCCI int32_t score1 = 2000; //一级生命之泉分数 - SCCI int32_t score2 = 3000; //二级生命之泉分数 - SCCI int32_t score3 = 4000; //三级生命之泉分数 - SCCI int32_t maxHp1 = 200; //一级生命之泉守关怪物血量 - SCCI int32_t maxHp2 = 300; //二级生命之泉守关怪物血量 - SCCI int32_t maxHp3 = 400; //三级生命之泉守关怪物血量 - SCCI int32_t attack = 10; //生命之泉守关怪物攻击力 + SCCI int32_t score1 = 2000; // 一级生命之泉分数 + SCCI int32_t score2 = 3000; // 二级生命之泉分数 + SCCI int32_t score3 = 4000; // 三级生命之泉分数 + SCCI int32_t maxHp1 = 200; // 一级生命之泉守关怪物血量 + SCCI int32_t maxHp2 = 300; // 二级生命之泉守关怪物血量 + SCCI int32_t maxHp3 = 400; // 三级生命之泉守关怪物血量 + SCCI int32_t attack = 10; // 生命之泉守关怪物攻击力 }; - struct Attack_Boost //狂战士之力 + struct Attack_Boost // 狂战士之力 { SCCI int32_t attack_boost1 = 10; SCCI int32_t attack_boost2 = 15; SCCI int32_t attack_boost3 = 20; - SCCI int32_t time1 = 30; //一级狂战士之力持续时间 - SCCI int32_t time2 = 45; //二级狂战士之力持续时间 - SCCI int32_t time3 = 60; //三级狂战士之力持续时间 - SCCI int32_t score1 = 4000; //一级狂战士之力分数 - SCCI int32_t score2 = 5000; //二级狂战士之力分数 - SCCI int32_t score3 = 6000; //三级狂战士之力分数 - SCCI int32_t maxHp1 = 400; //一级狂战士之力守关怪物血量 - SCCI int32_t maxHp2 = 500; //二级狂战士之力守关怪物血量 - SCCI int32_t maxHp3 = 600; //三级狂战士之力守关怪物血量 - SCCI int32_t attack1 = 10; //一级狂战士之力守关怪物攻击力 - SCCI int32_t attack2 = 15; //二级狂战士之力守关怪物攻击力 - SCCI int32_t attack3 = 20; //三级狂战士之力守关怪物攻击力 + SCCI int32_t time1 = 30; // 一级狂战士之力持续时间 + SCCI int32_t time2 = 45; // 二级狂战士之力持续时间 + SCCI int32_t time3 = 60; // 三级狂战士之力持续时间 + SCCI int32_t score1 = 4000; // 一级狂战士之力分数 + SCCI int32_t score2 = 5000; // 二级狂战士之力分数 + SCCI int32_t score3 = 6000; // 三级狂战士之力分数 + SCCI int32_t maxHp1 = 400; // 一级狂战士之力守关怪物血量 + SCCI int32_t maxHp2 = 500; // 二级狂战士之力守关怪物血量 + SCCI int32_t maxHp3 = 600; // 三级狂战士之力守关怪物血量 + SCCI int32_t attack1 = 10; // 一级狂战士之力守关怪物攻击力 + SCCI int32_t attack2 = 15; // 二级狂战士之力守关怪物攻击力 + SCCI int32_t attack3 = 20; // 三级狂战士之力守关怪物攻击力 }; struct Speed_Boost @@ -168,7 +168,7 @@ namespace Constants SCCI int32_t time_cost = 10; }; - struct Trap + struct Hole { SCCI int32_t cost = 1000; SCCI int32_t time = 5; @@ -182,7 +182,7 @@ namespace Constants SCCI int32_t time = 5; SCCI int32_t continous_time = 30; }; - //商店商品 + // 商店商品 struct blood_vial { SCCI int32_t cost1 = 1500; @@ -226,9 +226,9 @@ namespace Constants { SCCI int32_t cost = 10000; SCCI int32_t time = 30; - SCCI int32_t attack_boost = 1.2; //注意这是提升的倍数 - SCCI int32_t speed_boost = 300; //注意这是直接叠加 - SCCI int32_t attack_freq_boost = 1.25; //注意这是提升的倍数 + SCCI int32_t attack_boost = 1.2; // 注意这是提升的倍数 + SCCI int32_t speed_boost = 300; // 注意这是直接叠加 + SCCI int32_t attack_freq_boost = 1.25; // 注意这是提升的倍数 }; } // namespace Constants #endif \ No newline at end of file diff --git a/CAPI/cpp/API/src/AI.cpp b/CAPI/cpp/API/src/AI.cpp index 005eb2f..1518c32 100644 --- a/CAPI/cpp/API/src/AI.cpp +++ b/CAPI/cpp/API/src/AI.cpp @@ -8,23 +8,26 @@ // 为假则play()期间确保游戏状态不更新,为真则只保证游戏状态在调用相关方法时不更新,大致一帧更新一次 extern const bool asynchronous = false; -// 选手需要依次将player1到player4的角色类型在这里定义 -extern const std::array CharacterTypeDict = { +// 选手需要依次将player1到player5的角色类型在这里定义 +extern const std::array BuddhistsCharacterTypeDict = { THUAI8::CharacterType::Monk, THUAI8::CharacterType::MonkeyKing, THUAI8::CharacterType::Pigsy, THUAI8::CharacterType::ShaWujing, THUAI8::CharacterType::Whitedragonhorse, +}; + +extern const std::array MonstersCharacterTypeDict = { THUAI8::CharacterType::JiuTouYuanSheng, THUAI8::CharacterType::Honghaier, THUAI8::CharacterType::Gyuumao, THUAI8::CharacterType::Princess_Iron_Fan, THUAI8::CharacterType::Spider, -}; +} // 可以在AI.cpp内部声明变量与函数 -void AI::play(ICharacterAPI& api) +void AI::play(ICharacterAPI & api) { if (this->playerID == 1) { diff --git a/CAPI/cpp/API/src/logic.cpp b/CAPI/cpp/API/src/logic.cpp index 4240e45..645040c 100644 --- a/CAPI/cpp/API/src/logic.cpp +++ b/CAPI/cpp/API/src/logic.cpp @@ -27,9 +27,9 @@ Logic::Logic(int32_t pID, int32_t tID, THUAI8::PlayerType pType, THUAI8::Charact bufferState->gameInfo = std::make_shared(); bufferState->mapInfo = std::make_shared(); if (teamID == 0) - playerTeam = THUAI8::PlayerTeam::Red; + playerTeam = THUAI8::PlayerTeam::BuddhistsTeam; else if (teamID == 1) - playerTeam = THUAI8::PlayerTeam::Blue; + playerTeam = THUAI8::PlayerTeam::MonstersTeam; else playerTeam = THUAI8::PlayerTeam::NullTeam; } @@ -42,7 +42,7 @@ std::vector> Logic::GetCharacters() con return temp; } -std::vector> Logic::GetEnemySCharacters() const +std::vector> Logic::GetEnemyCharacters() const { std::unique_lock lock(mtxState); std::vector> temp(currentState->enemyCharacters.begin(), currentState->enemyCharacters.end()); diff --git a/CAPI/python/AI.py b/CAPI/python/AI.py index 6c33751..17053fd 100644 --- a/CAPI/python/AI.py +++ b/CAPI/python/AI.py @@ -15,15 +15,20 @@ class Setting: return False @staticmethod - def CharacterTypes() -> List[THUAI8.CharacterType]: + def BuddhistsCharacterTypes() -> List[THUAI8.CharacterType]: return [ THUAI8.CharacterType.Monk, THUAI8.CharacterType.MonkyKing, THUAI8.CharacterType.Pigsy, THUAI8.CharacterType.ShaWujing, THUAI8.CharacterType.Whitedragonhorse, + ] + + @staticmethod + def MonsterCharacterTypes() -> List[THUAI8.CharacterType]: + return [ THUAI8.CharacterType.JiuTouYuanSheng, - THUAI8.CharacterType.Honghaier, + THUAI8.CharacterType.HongHaier, THUAI8.CharacterType.Gyuumao, THUAI8.CharacterType.Princess_Iron_Fan, THUAI8.CharacterType.Spider, diff --git a/CAPI/python/constants.py b/CAPI/python/constants.py new file mode 100644 index 0000000..ffcbcef --- /dev/null +++ b/CAPI/python/constants.py @@ -0,0 +1,199 @@ +class Constants: + frameDuration = 50 + + numofGridPerCell = 1000 + rows = 50 + cols = 50 + + destroyBarracksBonus = 6000 + destroySpringBonus = 3000 + destroyFarmBonus = 4000 + + sizeofCharacter = 800 + speed = 2500 + + +class Monk: + maxHp = 1000 + + +class MonkyKing: + maxHp = 200 + commonAttackPower = 30 + attackRange = 1 + cost = 5000 + + +class Pigsy: + maxHp = 300 + commonAttackPower = 20 + attackRange = 2 + cost = 4000 + + +class ShaWujing: + maxHp = 150 + commonAttackPower = 10 + attackRange = 5 + cost = 3000 + + +class Whitedragonhorse: + maxHp = 150 + commonAttackPower = 10 + attackRange = 5 + cost = 4000 + + +class JiuTouYuanSheng: + maxHp = 1000 + + +class HongHaier: + maxHp = 200 + commonAttackPower = 25 + attackRange = 1 + cost = 5000 + + +class Gyuumao: + maxHp = 300 + commonAttackPower = 20 + attackRange = 2 + cost = 4000 + + +class Princess_Iron_Fan: + maxHp = 150 + commonAttackPower = 10 + attackRange = 5 + cost = 3000 + + +class Spider: + maxHp = 150 + commonAttackPower = 10 + attackRange = 5 + cost = 3000 + + +class resumption: + recovery1 = 50 + recovery2 = 100 + recovery3 = 150 + score1 = 2000 + score2 = 3000 + score3 = 4000 + maxHp1 = 200 + maxHp2 = 300 + maxHp3 = 400 + attack = 10 + + +class Attack_Boost: + attackBoost1 = 10 + attackBoost2 = 15 + attackBoost3 = 20 + time1 = 30 + time2 = 45 + time3 = 60 + score1 = 4000 + score2 = 5000 + score3 = 6000 + maxHp1 = 400 + maxHp2 = 500 + maxHp3 = 600 + attack1 = 10 + attack2 = 15 + attack3 = 20 + + +class Speed_Boost: + speedBoost1 = 500 + time = 60 + score = 3000 + maxHp = 300 + attack = 10 + + +class View_Boost: + time = 60 + score = 3000 + maxHp = 300 + attack = 10 + + +class Barracks: + maxHp = 600 + cost = 10000 + sabotage_score = 6000 + time_cost = 15 + + +class Spring: + maxHp = 300 + cost = 8000 + sabotage_score = 3000 + time_cost = 10 + + +class Farm: + maxHp = 400 + cost = 8000 + sabotage_score = 3000 + time_cost = 10 + + +class Hole: + cost = 1000 + time = 5 + attack = 20 + continous_time = 5 + + +class Cage: + cost = 1000 + time = 5 + continueous_time = 30 + + +class blood_vial: + cost1 = 1500 + cost2 = 3000 + cost3 = 4500 + recovery1 = 50 + recovery2 = 100 + recovery3 = 150 + + +class Shield: + cost1 = 2000 + cost2 = 3500 + cost3 = 5000 + defence1 = 50 + defence2 = 100 + defence3 = 150 + + +class Speed_shoes: + speedBoost = 500 + time = 60 + cost = 1500 + + +class purification_medicine: + cost = 2000 + time = 30 + + +class invisibility: + cost = 4000 + time = 10 + + +class berserk: + cost = 10000 + time = 30 + attack_boost = 1.2 # 注意这是提升的倍数 + speed_boost = 300 # 注意这是直接叠加 + attack_freq_boost = 1.25 # 注意这是提升的倍数 From 05b56345dae79d253ef096e67e154e46c40ebaef Mon Sep 17 00:00:00 2001 From: Xyberoid <143863451+Xyberoid@users.noreply.github.com> Date: Mon, 31 Mar 2025 23:28:14 +0800 Subject: [PATCH 4/9] =?UTF-8?q?feat:=207=E5=88=86=E9=92=9F=E5=90=8E?= =?UTF-8?q?=E6=97=A0=E9=9A=90=E8=BA=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- logic/Gaming/CharacterManager.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/logic/Gaming/CharacterManager.cs b/logic/Gaming/CharacterManager.cs index 457fe14..5bad1dd 100644 --- a/logic/Gaming/CharacterManager.cs +++ b/logic/Gaming/CharacterManager.cs @@ -315,7 +315,14 @@ namespace Gaming character.IsShoes = false; } } - + } + public void CheckInvisibility(Character character) + { + int nowtime = gameMap.Timer.NowTime(); + if (nowtime >= 420000) + { + character.visible = true; + } } } } From 55b666c7dbac9456de50200642cb22da0b90613d Mon Sep 17 00:00:00 2001 From: Xyberoid <143863451+Xyberoid@users.noreply.github.com> Date: Mon, 31 Mar 2025 23:34:13 +0800 Subject: [PATCH 5/9] =?UTF-8?q?feat:=207=E5=88=86=E9=92=9F=E8=A7=84?= =?UTF-8?q?=E5=88=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- logic/GameClass/GameObj/Character.cs | 8 +++++++- logic/Gaming/CharacterManager.cs | 2 +- logic/Preparation/Utility/GameData.cs | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/logic/GameClass/GameObj/Character.cs b/logic/GameClass/GameObj/Character.cs index 7b1bb65..415dc50 100644 --- a/logic/GameClass/GameObj/Character.cs +++ b/logic/GameClass/GameObj/Character.cs @@ -333,6 +333,11 @@ public class Character : Movable, ICharacter } case EquipmentType.INVISIBILITY_POTION: { + int nowtime = gameMap.Timer.NowTime(); + if (nowtime >= GameData.SevenMinutes) + { + return false; + } SetCharacterState(CharacterState1, CharacterState.INVISIBLE);//此处缺少时间限制 visible = false; SubMoney(EquipmentFactory.FindCost(equiptype)); @@ -340,7 +345,8 @@ public class Character : Movable, ICharacter } case EquipmentType.BERSERK_POTION: { - if (IsBerserk) + nowtime = gameMap.Timer.NowTime(); + if (IsBerserk || nowtime < GameData.SevenMinutes) { return false; } diff --git a/logic/Gaming/CharacterManager.cs b/logic/Gaming/CharacterManager.cs index 5bad1dd..a4f5b20 100644 --- a/logic/Gaming/CharacterManager.cs +++ b/logic/Gaming/CharacterManager.cs @@ -319,7 +319,7 @@ namespace Gaming public void CheckInvisibility(Character character) { int nowtime = gameMap.Timer.NowTime(); - if (nowtime >= 420000) + if (nowtime >= GameData.SevenMinutes) { character.visible = true; } diff --git a/logic/Preparation/Utility/GameData.cs b/logic/Preparation/Utility/GameData.cs index 3ef8aae..9b0c142 100644 --- a/logic/Preparation/Utility/GameData.cs +++ b/logic/Preparation/Utility/GameData.cs @@ -214,5 +214,6 @@ namespace Preparation.Utility public const int InitialMoney = 5000; public const int CharacterTotalNumMax = 6; public const double RecycleRate = 0.5; + public const int SevenMinutes = 420000; } } From cb8c1643960a56e1ccf736fbb00f4fb292ce0afe Mon Sep 17 00:00:00 2001 From: Xyberoid <143863451+Xyberoid@users.noreply.github.com> Date: Mon, 31 Mar 2025 23:45:28 +0800 Subject: [PATCH 6/9] =?UTF-8?q?fix:=20=E5=B0=867=E5=88=86=E9=92=9F?= =?UTF-8?q?=E6=A3=80=E6=B5=8B=E7=94=B1Character=E6=94=B9=E5=88=B0Game?= =?UTF-8?q?=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- logic/GameClass/GameObj/Character.cs | 10 ---------- logic/Gaming/CharacterManager.cs | 3 ++- logic/Gaming/Game.cs | 12 ++++++++++++ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/logic/GameClass/GameObj/Character.cs b/logic/GameClass/GameObj/Character.cs index 415dc50..eb85937 100644 --- a/logic/GameClass/GameObj/Character.cs +++ b/logic/GameClass/GameObj/Character.cs @@ -333,11 +333,6 @@ public class Character : Movable, ICharacter } case EquipmentType.INVISIBILITY_POTION: { - int nowtime = gameMap.Timer.NowTime(); - if (nowtime >= GameData.SevenMinutes) - { - return false; - } SetCharacterState(CharacterState1, CharacterState.INVISIBLE);//此处缺少时间限制 visible = false; SubMoney(EquipmentFactory.FindCost(equiptype)); @@ -345,11 +340,6 @@ public class Character : Movable, ICharacter } case EquipmentType.BERSERK_POTION: { - nowtime = gameMap.Timer.NowTime(); - if (IsBerserk || nowtime < GameData.SevenMinutes) - { - return false; - } IsBerserk = true; BerserkTime = Environment.TickCount64; SetCharacterState(CharacterState1, CharacterState.BERSERK);//此处缺少时间限制 diff --git a/logic/Gaming/CharacterManager.cs b/logic/Gaming/CharacterManager.cs index a4f5b20..6e6fc88 100644 --- a/logic/Gaming/CharacterManager.cs +++ b/logic/Gaming/CharacterManager.cs @@ -319,7 +319,8 @@ namespace Gaming public void CheckInvisibility(Character character) { int nowtime = gameMap.Timer.NowTime(); - if (nowtime >= GameData.SevenMinutes) + if (nowtime >= GameData.SevenMinutes + ) { character.visible = true; } diff --git a/logic/Gaming/Game.cs b/logic/Gaming/Game.cs index 8b83c86..ea5719e 100644 --- a/logic/Gaming/Game.cs +++ b/logic/Gaming/Game.cs @@ -423,6 +423,18 @@ namespace Gaming { if (!gameMap.Timer.IsGaming) return false; + int nowtime = gameMap.Timer.NowTime(); + if (nowtime >= GameData.SevenMinutes) + { + if (equiptype == EquipmentType.INVISIBILITY_POTION) + { + return false; + } + } + else if(equiptype == EquipmentType.BERSERK_POTION) + { + return false; + } Character? character = gameMap.FindCharacterInPlayerID(teamID, characterID); if (character != null && character.IsRemoved == false) return equipManager.GetEquipment(character, equiptype); From 99340fe4953e14712d3b376ea71a60b175b2fe28 Mon Sep 17 00:00:00 2001 From: Xyberoid <143863451+Xyberoid@users.noreply.github.com> Date: Mon, 31 Mar 2025 23:48:52 +0800 Subject: [PATCH 7/9] fix: :fire: FORMAT --- logic/Gaming/Game.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logic/Gaming/Game.cs b/logic/Gaming/Game.cs index ea5719e..f99895d 100644 --- a/logic/Gaming/Game.cs +++ b/logic/Gaming/Game.cs @@ -431,7 +431,7 @@ namespace Gaming return false; } } - else if(equiptype == EquipmentType.BERSERK_POTION) + else if (equiptype == EquipmentType.BERSERK_POTION) { return false; } From 927852eabd0c03bf62858b5acd3b1c3ec9b900d7 Mon Sep 17 00:00:00 2001 From: Henry <2280825018@qq.com> Date: Tue, 1 Apr 2025 20:36:39 +0800 Subject: [PATCH 8/9] fix: CharacterHPInit --- logic/ClientTest/Program.cs | 1 + logic/GameClass/GameObj/Character.cs | 1 + logic/Gaming/Game.cs | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/logic/ClientTest/Program.cs b/logic/ClientTest/Program.cs index c137a10..5e8094f 100644 --- a/logic/ClientTest/Program.cs +++ b/logic/ClientTest/Program.cs @@ -14,6 +14,7 @@ namespace ClientTest { CharacterId = 0, TeamId = 0, + SideFlag = 0, CharacterType = CharacterType.TangSeng }; var call = client.AddCharacter(playerInfo); diff --git a/logic/GameClass/GameObj/Character.cs b/logic/GameClass/GameObj/Character.cs index 7b1bb65..4135948 100644 --- a/logic/GameClass/GameObj/Character.cs +++ b/logic/GameClass/GameObj/Character.cs @@ -253,6 +253,7 @@ public class Character : Movable, ICharacter Shield = new(0); NiuShield = new(0); AttackSize = new(Occupation.BaseAttackSize); + HP = new(Occupation.MaxHp); AttackPower = new(Occupation.AttackPower); MoneyPool = pool; Init(); diff --git a/logic/Gaming/Game.cs b/logic/Gaming/Game.cs index 8b83c86..74e540f 100644 --- a/logic/Gaming/Game.cs +++ b/logic/Gaming/Game.cs @@ -35,7 +35,7 @@ namespace Gaming return GameObj.invalidID; } teamList[(int)playerInitInfo.teamID].CharacterNum.Add(1); - if (gameMap.TeamExists(playerInitInfo.teamID)) + if (!gameMap.TeamExists(playerInitInfo.teamID)) { return GameObj.invalidID; } From 733b8dd488a9cf2a074836207a13b27f3c91ed35 Mon Sep 17 00:00:00 2001 From: JackPWY <2351627401@qq.com> Date: Wed, 2 Apr 2025 21:32:35 +0800 Subject: [PATCH 9/9] Debug_interface - update Server Communicate --- installer/Data/ConfigFileData.cs | 4 +- installer/Model/Logger.cs | 3 +- .../debug_interface/App.axaml.cs | 61 +- .../debug_interface/Models/MapCell.cs | 83 +- .../Services/ServerCommunicationService.cs | 776 ++++++++++++++++++ .../ViewModels/CharacterViewModel.cs | 97 +-- .../ViewModels/MainWindowViewModel.cs | 143 ++-- .../ViewModels/MapViewModel.cs | 282 ++++--- .../ViewModels/ViewModelBase.cs | 463 ++--------- .../debug_interface/Views/MapView.axaml.cs | 274 +++++-- .../debug_interface/debug_interface.csproj | 74 +- logic/proto/Message2Clients.proto | 1 + 12 files changed, 1427 insertions(+), 834 deletions(-) create mode 100644 interface/AvaloniaUI_debug_interface/debug_interface/Services/ServerCommunicationService.cs create mode 100644 logic/proto/Message2Clients.proto diff --git a/installer/Data/ConfigFileData.cs b/installer/Data/ConfigFileData.cs index 2d8d466..c7379cc 100644 --- a/installer/Data/ConfigFileData.cs +++ b/installer/Data/ConfigFileData.cs @@ -1,4 +1,6 @@ -//using installer.ViewModel; +//ConfigDataFile.cs + +//using installer.ViewModel; using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; diff --git a/installer/Model/Logger.cs b/installer/Model/Logger.cs index bca38a2..803f435 100644 --- a/installer/Model/Logger.cs +++ b/installer/Model/Logger.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +//Logger.cs +using System.Collections.Concurrent; using System.Collections.ObjectModel; using System.Collections.Generic; using System.IO; diff --git a/interface/AvaloniaUI_debug_interface/debug_interface/App.axaml.cs b/interface/AvaloniaUI_debug_interface/debug_interface/App.axaml.cs index 998c8c8..0eb0be0 100644 --- a/interface/AvaloniaUI_debug_interface/debug_interface/App.axaml.cs +++ b/interface/AvaloniaUI_debug_interface/debug_interface/App.axaml.cs @@ -1,20 +1,18 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Data.Core; -using Avalonia.Data.Core.Plugins; using Avalonia.Markup.Xaml; +using debug_interface.Services; using debug_interface.ViewModels; using debug_interface.Views; -using System.Linq; +using System; +using System.IO; +using installer.Model; +using installer.Data; namespace debug_interface { - /// - /// Ӧóĺô룬ʼڡ - /// public partial class App : Application { - public override void Initialize() { AvaloniaXamlLoader.Load(this); @@ -24,34 +22,43 @@ namespace debug_interface { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - // Avoid duplicate validations from both Avalonia and the CommunityToolkit. - // More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins - DisableAvaloniaDataAnnotationValidation(); + // + var config = new ConfigData(); - var mainWindowViewModel = new MainWindowViewModel(); + // ־Ŀ¼ + string logDir = Path.Combine(config.InstallPath, "Logs"); + Directory.CreateDirectory(logDir); + // ־¼ + var logFilePath = Path.Combine(logDir, $"debug_interface_{DateTime.Now:yyyyMMdd_HHmmss}.log"); + var logger = new FileLogger(logFilePath); + + // ͼģ + var mainWindowViewModel = new MainWindowViewModel(logger, config); + + // ͨŷ + var serverCommunicationService = new ServerCommunicationService(mainWindowViewModel, logger, config); + + // desktop.MainWindow = new MainWindow { - DataContext = new MainWindowViewModel(), + DataContext = mainWindowViewModel }; + + // ӷط + if (string.IsNullOrEmpty(config.Commands.PlaybackFile)) + { + // ӵ + _ = serverCommunicationService.ConnectToServer(); + } + else + { + // طģʽ + serverCommunicationService.StartPlaybackMode(); + } } base.OnFrameworkInitializationCompleted(); } - - private void DisableAvaloniaDataAnnotationValidation() - { - // Get an array of plugins to remove - var dataValidationPluginsToRemove = - BindingPlugins.DataValidators.OfType().ToArray(); - - // remove each entry found - foreach (var plugin in dataValidationPluginsToRemove) - { - BindingPlugins.DataValidators.Remove(plugin); - } - } - - } } \ No newline at end of file diff --git a/interface/AvaloniaUI_debug_interface/debug_interface/Models/MapCell.cs b/interface/AvaloniaUI_debug_interface/debug_interface/Models/MapCell.cs index 2ff0f9b..7d21bbe 100644 --- a/interface/AvaloniaUI_debug_interface/debug_interface/Models/MapCell.cs +++ b/interface/AvaloniaUI_debug_interface/debug_interface/Models/MapCell.cs @@ -1,6 +1,7 @@ //MapCell.cs using Avalonia.Media; using CommunityToolkit.Mvvm.ComponentModel; +using debug_interface.ViewModels; using System; using System.Collections.Generic; using System.Linq; @@ -8,43 +9,53 @@ using System.Text; using System.Threading.Tasks; namespace debug_interface.Models +{ + //internal class MapCell + //{ + //} + // 定义地图单元格的类型,根据游戏规则可包含障碍物、空地、草丛、资源、建筑等 + public enum MapCellType { - //internal class MapCell - //{ - //} - // 定义地图单元格的类型,根据游戏规则可包含障碍物、空地、草丛、资源、建筑等 - public enum MapCellType - { - Obstacle, // 障碍物(例如地图边界或特定阻挡物) - OpenLand, // 空地,角色可自由移动 - Grass, // 草丛,可提供隐蔽效果(决战期后会消失) - Resource, // 资源点(用于开采经济资源) - Building // 建筑(预留,例如兵营、农场等) - } - - // 地图单元类,继承自 ObservableObject 便于数据绑定更新 - public partial class MapCell : ObservableObject - { - // 地图中的行号(0~49) - [ObservableProperty] - private int cellX; - - // 地图中的列号(0~49) - [ObservableProperty] - private int cellY; - - // 当前单元格的类型 - [ObservableProperty] - private MapCellType cellType; - - // 该单元格显示时使用的颜色 - [ObservableProperty] - private SolidColorBrush displayColor; - - // 可选:显示的文字,例如建筑血量、资源进度等 - [ObservableProperty] - private string displayText; - } + Empty, + Obstacle, + Building, + Resource, + Character } + public partial class MapCell : ViewModelBase + { + [ObservableProperty] + private int row; + + [ObservableProperty] + private int col; + + public int CellX + { + get => Col; + set => Col = value; + } + + public int CellY + { + get => Row; + set => Row = value; + } + + + [ObservableProperty] + private MapCellType cellType; + + [ObservableProperty] + private string displayText = ""; + + [ObservableProperty] + private IBrush displayColor = new SolidColorBrush(Colors.Black); + + [ObservableProperty] + private IBrush backgroundColor = new SolidColorBrush(Colors.White); + } +} + diff --git a/interface/AvaloniaUI_debug_interface/debug_interface/Services/ServerCommunicationService.cs b/interface/AvaloniaUI_debug_interface/debug_interface/Services/ServerCommunicationService.cs new file mode 100644 index 0000000..111579c --- /dev/null +++ b/interface/AvaloniaUI_debug_interface/debug_interface/Services/ServerCommunicationService.cs @@ -0,0 +1,776 @@ +using System; +using System.Threading.Tasks; +using Avalonia.Threading; +using debug_interface.ViewModels; +using Grpc.Core; +using installer.Model; +using installer.Data; + +namespace debug_interface.Services +{ + public class ServerCommunicationService + { + private readonly MainWindowViewModel _viewModel; + private readonly Logger _logger; + private readonly ConfigData _config; + private bool _isConnected = false; + private bool _isPlaybackMode = false; + + public ServerCommunicationService(MainWindowViewModel viewModel, Logger logger, ConfigData config) + { + _viewModel = viewModel; + _logger = logger; + _config = config; + } + + public async Task ConnectToServer() + { + try + { + string ip = _config.Commands.IP; + string port = _config.Commands.Port; + + _logger.LogInfo($"尝试连接服务器 {ip}:{port}"); + + // 由于没有正确的Proto引用,这里暂时只模拟连接行为 + await Task.Delay(1000); // 模拟连接延迟 + + await Dispatcher.UIThread.InvokeAsync(() => { + _viewModel.GameLog = $"已连接到服务器 {ip}:{port}"; + }); + + _isConnected = true; + _logger.LogInfo("已模拟连接到服务器"); + } + catch (Exception ex) + { + _logger.LogError($"连接失败: {ex.Message}"); + + await Dispatcher.UIThread.InvokeAsync(() => { + _viewModel.GameLog = $"连接失败: {ex.Message}"; + }); + } + } + + public void StartPlaybackMode() + { + try + { + _isPlaybackMode = true; + string playbackFile = _config.Commands.PlaybackFile ?? ""; + + _logger.LogInfo($"模拟回放: {playbackFile}"); + + Dispatcher.UIThread.InvokeAsync(() => { + _viewModel.GameLog = $"模拟回放: {playbackFile}"; + }); + } + catch (Exception ex) + { + _logger.LogError($"回放错误: {ex.Message}"); + + Dispatcher.UIThread.InvokeAsync(() => { + _viewModel.GameLog = $"回放错误: {ex.Message}"; + }); + } + } + } +} + + +//using System; +//using System.Linq; +//using System.Threading.Tasks; +//using Avalonia.Threading; +//using debug_interface.ViewModels; +//using Grpc.Core; +//using Google.Protobuf; // 假设这是proto文件生成的命名空间 +//using installer.Model; // Logger的命名空间 +//using installer.Data; // ConfigData的命名空间 +//using System.Collections.Generic; +//using Avalonia.Data; +//using System.Threading.Channels; + +//namespace debug_interface.Services +//{ +// public class ServerCommunicationService +// { +// private readonly MainWindowViewModel _viewModel; +// private readonly Logger _logger; +// private readonly ConfigData _config; +// private AvailableService.AvailableServiceClient? _client; +// private AsyncServerStreamingCall? _responseStream; +// private bool _isConnected = false; +// private bool _isPlaybackMode = false; + +// public ServerCommunicationService(MainWindowViewModel viewModel, Logger logger, ConfigData config) +// { +// _viewModel = viewModel; +// _logger = logger; +// _config = config; +// } + +// public async Task ConnectToServer() +// { +// try +// { +// if (_isPlaybackMode) +// return; + +// string ip = _config.Commands.IP; +// string port = _config.Commands.Port; +// long playerId = _config.Commands.PlayerID; +// long teamId = _config.Commands.TeamID; +// int characterType = _config.Commands.CharacterType; + +// string connectionString = $"{ip}:{port}"; +// _logger.LogInfo($"正在连接到服务器 {connectionString}"); + +// // 创建gRPC通道和客户端 +// var channel = new Channel(connectionString, ChannelCredentials.Insecure); +// _client = new AvailableService.AvailableServiceClient(channel); + +// // 创建连接消息 +// var characterMsg = new CharacterMsg +// { +// CharacterId = playerId, +// TeamId = teamId, +// CharacterType = (CharacterType)characterType +// }; + +// // 连接到服务器 +// _responseStream = _client.AddCharacter(characterMsg); +// _isConnected = true; + +// // 开始接收消息 +// _ = StartReceivingMessagesAsync(); + +// _logger.LogInfo("成功连接到服务器"); +// } +// catch (Exception ex) +// { +// _isConnected = false; +// _logger.LogError($"连接服务器失败: {ex.Message}"); + +// // 在UI中显示错误 +// await Dispatcher.UIThread.InvokeAsync(() => { +// _viewModel.GameLog = $"连接服务器错误: {ex.Message}"; +// }); + +// throw; +// } +// } + +// public void StartPlaybackMode() +// { +// _isPlaybackMode = true; +// string playbackFile = _config.Commands.PlaybackFile ?? ""; +// double playbackSpeed = _config.Commands.PlaybackSpeed; + +// _logger.LogInfo($"正在从{playbackFile}以{playbackSpeed}倍速开始回放模式"); + +// // 这里可以实现回放逻辑,读取文件并模拟服务器消息 +// // 例如启动一个单独的任务来读取和处理文件 +// _ = Task.Run(async () => { +// try +// { +// // 简单的回放逻辑示例 +// if (System.IO.File.Exists(playbackFile)) +// { +// // 读取文件内容... +// _logger.LogInfo("回放文件存在,开始读取数据"); + +// // 这里是简化实现,实际应根据文件格式处理 +// var fileBytes = System.IO.File.ReadAllBytes(playbackFile); + +// // 通知UI +// await Dispatcher.UIThread.InvokeAsync(() => { +// _viewModel.GameLog = "正在回放..."; +// }); +// } +// else +// { +// _logger.LogError($"回放文件不存在: {playbackFile}"); +// await Dispatcher.UIThread.InvokeAsync(() => { +// _viewModel.GameLog = "回放文件不存在!"; +// }); +// } +// } +// catch (Exception ex) +// { +// _logger.LogError($"回放过程中出错: {ex.Message}"); +// await Dispatcher.UIThread.InvokeAsync(() => { +// _viewModel.GameLog = $"回放错误: {ex.Message}"; +// }); +// } +// }); +// } + +// private async Task StartReceivingMessagesAsync() +// { +// if (_responseStream == null) +// return; + +// try +// { +// _logger.LogInfo("开始接收服务器消息"); + +// while (await _responseStream.ResponseStream.MoveNext(default)) +// { +// var message = _responseStream.ResponseStream.Current; + +// // 在UI线程上处理消息,以安全地更新UI +// await Dispatcher.UIThread.InvokeAsync(() => { +// ProcessServerMessage(message); +// }); +// } +// } +// catch (RpcException ex) when (ex.StatusCode == StatusCode.Cancelled) +// { +// _logger.LogInfo("服务器流已取消"); +// } +// catch (Exception ex) +// { +// _logger.LogError($"接收服务器消息错误: {ex.Message}"); + +// await Dispatcher.UIThread.InvokeAsync(() => { +// _viewModel.GameLog = $"连接错误: {ex.Message}"; +// }); + +// // 尝试重新连接 +// await TryReconnectAsync(); +// } +// finally +// { +// _isConnected = false; +// } +// } + +// private async Task TryReconnectAsync() +// { +// _logger.LogInfo("尝试重新连接服务器..."); + +// // 等待一段时间后重试 +// await Task.Delay(5000); + +// if (!_isConnected && !_isPlaybackMode) +// { +// try +// { +// await ConnectToServer(); +// } +// catch +// { +// // 重连失败,等待更长时间后再次尝试 +// await Task.Delay(10000); +// _ = TryReconnectAsync(); +// } +// } +// } + +// private void ProcessServerMessage(MessageToClient message) +// { +// try +// { +// // 更新游戏状态 +// switch (message.GameState) +// { +// case GameState.GameStart: +// _logger.LogInfo("游戏开始"); +// _viewModel.GameLog = "游戏开始..."; +// break; + +// case GameState.GameRunning: +// // 处理运行中的游戏数据 +// _viewModel.GameLog = "游戏运行中..."; +// break; + +// case GameState.GameEnd: +// _logger.LogInfo("游戏结束"); +// _viewModel.GameLog = "游戏结束"; +// break; +// } + +// // 更新全局游戏信息 +// if (message.AllMessage != null) +// { +// UpdateGlobalGameInfo(message.AllMessage); +// } + +// // 处理单个对象消息 +// foreach (var obj in message.ObjMessage) +// { +// ProcessObjectMessage(obj); +// } +// } +// catch (Exception ex) +// { +// _logger.LogError($"处理服务器消息错误: {ex.Message}"); +// } +// } + +// private void UpdateGlobalGameInfo(MessageOfAll allMessage) +// { +// try +// { +// // 更新游戏时间 +// _viewModel.CurrentTime = FormatGameTime(allMessage.GameTime); + +// // 更新队伍得分 +// _viewModel.RedScore = allMessage.BuddhistsTeamScore; +// _viewModel.BlueScore = allMessage.MonstersTeamScore; + +// // 更新队伍经济/资源 +// _viewModel.BuddhistTeamEconomy = allMessage.BuddhistsTeamEconomy; +// _viewModel.MonstersTeamEconomy = allMessage.MonstersTeamEconomy; + +// // 更新英雄HP (如果ViewModel中有对应属性) +// if (typeof(MainWindowViewModel).GetProperty("BuddhistHeroHp") != null) +// { +// _viewModel.BuddhistHeroHp = allMessage.BuddhistsHeroHp; +// _viewModel.MonstersHeroHp = allMessage.MonstersHeroHp; +// } +// } +// catch (Exception ex) +// { +// _logger.LogError($"更新全局游戏信息失败: {ex.Message}"); +// } +// } + +// private void ProcessObjectMessage(MessageOfObj objMessage) +// { +// try +// { +// switch (objMessage.MessageOfObjCase) +// { +// case MessageOfObj.MessageOfObjOneofCase.CharacterMessage: +// UpdateCharacter(objMessage.CharacterMessage); +// break; + +// case MessageOfObj.MessageOfObjOneofCase.BarracksMessage: +// UpdateBuilding(objMessage.BarracksMessage, "兵营"); +// break; + +// case MessageOfObj.MessageOfObjOneofCase.SpringMessage: +// UpdateBuilding(objMessage.SpringMessage, "泉水"); +// break; + +// case MessageOfObj.MessageOfObjOneofCase.FarmMessage: +// UpdateBuilding(objMessage.FarmMessage, "农场"); +// break; + +// case MessageOfObj.MessageOfObjOneofCase.TrapMessage: +// UpdateTrap(objMessage.TrapMessage); +// break; + +// case MessageOfObj.MessageOfObjOneofCase.EconomyResourceMessage: +// UpdateResource(objMessage.EconomyResourceMessage); +// break; + +// case MessageOfObj.MessageOfObjOneofCase.AdditionResourceMessage: +// UpdateAdditionResource(objMessage.AdditionResourceMessage); +// break; + +// case MessageOfObj.MessageOfObjOneofCase.MapMessage: +// UpdateMap(objMessage.MapMessage); +// break; +// } +// } +// catch (Exception ex) +// { +// _logger.LogError($"处理对象消息失败: {ex.Message} (类型: {objMessage.MessageOfObjCase})"); +// } +// } + +// private void UpdateCharacter(MessageOfCharacter character) +// { +// try +// { +// // 在适当的团队集合中查找角色 +// var isRedTeam = character.TeamId == 1; // 1表示取经队 +// var collection = isRedTeam +// ? _viewModel.RedTeamCharacters +// : _viewModel.BlueTeamCharacters; + +// // 计算网格坐标 +// int gridX = character.X / 1000; +// int gridY = character.Y / 1000; + +// // 尝试查找现有角色 +// var existingCharacter = collection.FirstOrDefault(c => c.CharacterId == character.PlayerId); + +// if (existingCharacter != null) +// { +// // 更新现有角色 +// existingCharacter.Name = GetCharacterTypeName(character.CharacterType); +// existingCharacter.Hp = character.Hp; +// existingCharacter.PosX = gridX; +// existingCharacter.PosY = gridY; + +// // 更新主动状态 +// existingCharacter.ActiveState = GetCharacterStateText(character.CharacterActiveState); + +// // 更新被动状态 +// existingCharacter.PassiveStates.Clear(); + +// // 根据需要添加被动状态 +// if (character.BlindState != CharacterState.NullCharacterState) +// existingCharacter.PassiveStates.Add("致盲"); +// if (character.StunnedState != CharacterState.NullCharacterState) +// existingCharacter.PassiveStates.Add("定身"); +// if (character.InvisibleState != CharacterState.NullCharacterState) +// existingCharacter.PassiveStates.Add("隐身"); +// if (character.BurnedState != CharacterState.NullCharacterState) +// existingCharacter.PassiveStates.Add("灼烧"); + +// // 更新装备 +// existingCharacter.EquipmentInventory.Clear(); +// if (character.ShieldEquipment > 0) +// existingCharacter.EquipmentInventory.Add(new EquipmentItem("护盾", character.ShieldEquipment)); +// if (character.ShoesEquipment > 0) +// existingCharacter.EquipmentInventory.Add(new EquipmentItem("鞋子", 1)); +// if (character.PurificationEquipmentTime > 0) +// existingCharacter.EquipmentInventory.Add(new EquipmentItem("净化药水", 1)); + +// // 通知UI更新显示的状态和装备文本 +// existingCharacter.OnPropertyChanged(nameof(CharacterViewModel.DisplayStates)); +// existingCharacter.OnPropertyChanged(nameof(CharacterViewModel.DisplayEquipments)); +// } +// else +// { +// // 创建新角色 +// var newCharacter = new CharacterViewModel +// { +// CharacterId = character.PlayerId, +// Name = GetCharacterTypeName(character.CharacterType), +// Hp = character.Hp, +// PosX = gridX, +// PosY = gridY, +// ActiveState = GetCharacterStateText(character.CharacterActiveState) +// }; + +// // 添加被动状态 +// if (character.BlindState != CharacterState.NullCharacterState) +// newCharacter.PassiveStates.Add("致盲"); +// if (character.StunnedState != CharacterState.NullCharacterState) +// newCharacter.PassiveStates.Add("定身"); +// if (character.InvisibleState != CharacterState.NullCharacterState) +// newCharacter.PassiveStates.Add("隐身"); +// if (character.BurnedState != CharacterState.NullCharacterState) +// newCharacter.PassiveStates.Add("灼烧"); + +// // 添加装备 +// if (character.ShieldEquipment > 0) +// newCharacter.EquipmentInventory.Add(new EquipmentItem("护盾", character.ShieldEquipment)); +// if (character.ShoesEquipment > 0) +// newCharacter.EquipmentInventory.Add(new EquipmentItem("鞋子", 1)); +// if (character.PurificationEquipmentTime > 0) +// newCharacter.EquipmentInventory.Add(new EquipmentItem("净化药水", 1)); + +// // 添加到集合 +// collection.Add(newCharacter); +// } + +// // 更新地图上的角色位置 +// _viewModel.MapVM.UpdateCharacterPosition( +// character.PlayerId, +// character.TeamId, +// gridX, +// gridY, +// GetCharacterTypeName(character.CharacterType) +// ); +// } +// catch (Exception ex) +// { +// _logger.LogError($"更新角色失败: {ex.Message}"); +// } +// } + +// private string GetCharacterTypeName(CharacterType type) +// { +// return type switch +// { +// CharacterType.TangSeng => "唐僧", +// CharacterType.SunWukong => "孙悟空", +// CharacterType.ZhuBajie => "猪八戒", +// CharacterType.ShaWujing => "沙悟净", +// CharacterType.BaiLongma => "白龙马", +// CharacterType.Monkid => "小和尚", +// CharacterType.JiuLing => "九灵元圣", +// CharacterType.HongHaier => "红孩儿", +// CharacterType.NiuMowang => "牛魔王", +// CharacterType.TieShan => "铁扇公主", +// CharacterType.ZhiZhujing => "蜘蛛精", +// CharacterType.Pawn => "小妖", +// _ => "未知角色" +// }; +// } + +// private string GetCharacterStateText(CharacterState state) +// { +// return state switch +// { +// CharacterState.Idle => "空置", +// CharacterState.Harvesting => "开采", +// CharacterState.Attacking => "攻击", +// CharacterState.SkillCasting => "释放技能", +// CharacterState.Constructing => "建造", +// CharacterState.Moving => "移动", +// _ => "未知状态" +// }; +// } + +// private void UpdateBuilding(MessageOfBarracks building, string buildingType) +// { +// try +// { +// // 转换为网格坐标 +// int gridX = building.X / 1000; +// int gridY = building.Y / 1000; + +// // 确保坐标在有效范围内 +// if (gridX >= 0 && gridX < 50 && gridY >= 0 && gridY < 50) +// { +// // 在地图上更新建筑 +// _viewModel.MapVM.UpdateBuildingCell( +// gridX, +// gridY, +// building.TeamId == 1 ? "取经队" : "妖怪队", +// buildingType, +// building.Hp +// ); + +// // 更新建筑信息文本 (假设ViewModel有这些属性) +// string buildingInfo = $"{buildingType} 位置:({gridX},{gridY}) 血量:{building.Hp}"; +// if (building.TeamId == 1) // 取经队 +// { +// // 尝试更新建筑信息,如果属性存在的话 +// if (typeof(MainWindowViewModel).GetProperty("SomeBuildingInfo") != null) +// { +// _viewModel.SomeBuildingInfo = buildingInfo; +// } +// } +// else // 妖怪队 +// { +// if (typeof(MainWindowViewModel).GetProperty("AnotherBuildingInfo") != null) +// { +// _viewModel.AnotherBuildingInfo = buildingInfo; +// } +// } +// } +// } +// catch (Exception ex) +// { +// _logger.LogError($"更新建筑(兵营)失败: {ex.Message}"); +// } +// } + +// private void UpdateBuilding(MessageOfSpring building, string buildingType) +// { +// try +// { +// // 类似于UpdateBuilding(MessageOfBarracks...)的实现 +// int gridX = building.X / 1000; +// int gridY = building.Y / 1000; + +// if (gridX >= 0 && gridX < 50 && gridY >= 0 && gridY < 50) +// { +// _viewModel.MapVM.UpdateBuildingCell( +// gridX, +// gridY, +// building.TeamId == 1 ? "取经队" : "妖怪队", +// buildingType, +// building.Hp +// ); + +// string buildingInfo = $"{buildingType} 位置:({gridX},{gridY}) 血量:{building.Hp}"; +// if (building.TeamId == 1 && +// typeof(MainWindowViewModel).GetProperty("SomeBuildingInfo") != null) +// { +// _viewModel.SomeBuildingInfo += "\n" + buildingInfo; +// } +// else if (building.TeamId != 1 && +// typeof(MainWindowViewModel).GetProperty("AnotherBuildingInfo") != null) +// { +// _viewModel.AnotherBuildingInfo += "\n" + buildingInfo; +// } +// } +// } +// catch (Exception ex) +// { +// _logger.LogError($"更新建筑(泉水)失败: {ex.Message}"); +// } +// } + +// private void UpdateBuilding(MessageOfFarm building, string buildingType) +// { +// try +// { +// // 类似于其他建筑更新方法 +// int gridX = building.X / 1000; +// int gridY = building.Y / 1000; + +// if (gridX >= 0 && gridX < 50 && gridY >= 0 && gridY < 50) +// { +// _viewModel.MapVM.UpdateBuildingCell( +// gridX, +// gridY, +// building.TeamId == 1 ? "取经队" : "妖怪队", +// buildingType, +// building.Hp +// ); + +// string buildingInfo = $"{buildingType} 位置:({gridX},{gridY}) 血量:{building.Hp}"; +// if (building.TeamId == 1 && +// typeof(MainWindowViewModel).GetProperty("SomeBuildingInfo") != null) +// { +// _viewModel.SomeBuildingInfo += "\n" + buildingInfo; +// } +// else if (building.TeamId != 1 && +// typeof(MainWindowViewModel).GetProperty("AnotherBuildingInfo") != null) +// { +// _viewModel.AnotherBuildingInfo += "\n" + buildingInfo; +// } +// } +// } +// catch (Exception ex) +// { +// _logger.LogError($"更新建筑(农场)失败: {ex.Message}"); +// } +// } + +// private void UpdateTrap(MessageOfTrap trap) +// { +// try +// { +// int gridX = trap.X / 1000; +// int gridY = trap.Y / 1000; + +// if (gridX >= 0 && gridX < 50 && gridY >= 0 && gridY < 50) +// { +// string trapTypeName = trap.TrapType switch +// { +// TrapType.Hole => "陷阱坑", +// TrapType.Cage => "牢笼", +// _ => "未知陷阱" +// }; + +// _viewModel.MapVM.UpdateTrapCell( +// gridX, +// gridY, +// trap.TeamId == 1 ? "取经队" : "妖怪队", +// trapTypeName +// ); +// } +// } +// catch (Exception ex) +// { +// _logger.LogError($"更新陷阱失败: {ex.Message}"); +// } +// } + +// private void UpdateResource(MessageOfEconomyResource resource) +// { +// try +// { +// int gridX = resource.X / 1000; +// int gridY = resource.Y / 1000; + +// if (gridX >= 0 && gridX < 50 && gridY >= 0 && gridY < 50) +// { +// string resourceTypeName = resource.EconomyResourceType switch +// { +// EconomyResourceType.SmallEconomyResource => "小经济资源", +// EconomyResourceType.MediumEconomyResource => "中经济资源", +// EconomyResourceType.LargeEconomyResource => "大经济资源", +// _ => "未知资源" +// }; + +// _viewModel.MapVM.UpdateResourceCell( +// gridX, +// gridY, +// resourceTypeName, +// resource.Process +// ); +// } +// } +// catch (Exception ex) +// { +// _logger.LogError($"更新经济资源失败: {ex.Message}"); +// } +// } + +// private void UpdateAdditionResource(MessageOfAdditionResource resource) +// { +// try +// { +// int gridX = resource.X / 1000; +// int gridY = resource.Y / 1000; + +// if (gridX >= 0 && gridX < 50 && gridY >= 0 && gridY < 50) +// { +// string resourceName = GetAdditionResourceName(resource.AdditionResourceType); + +// _viewModel.MapVM.UpdateAdditionResourceCell( +// gridX, +// gridY, +// resourceName, +// resource.Hp +// ); +// } +// } +// catch (Exception ex) +// { +// _logger.LogError($"更新附加资源失败: {ex.Message}"); +// } +// } + +// private string GetAdditionResourceName(AdditionResourceType type) +// { +// return type switch +// { +// AdditionResourceType.LifePool1 => "生命池(小)", +// AdditionResourceType.LifePool2 => "生命池(中)", +// AdditionResourceType.LifePool3 => "生命池(大)", +// AdditionResourceType.CrazyMan1 => "疯人(小)", +// AdditionResourceType.CrazyMan2 => "疯人(中)", +// AdditionResourceType.CrazyMan3 => "疯人(大)", +// AdditionResourceType.QuickStep => "神行步", +// AdditionResourceType.WideView => "千里眼", +// _ => "未知加成资源" +// }; +// } + +// private void UpdateMap(MessageOfMap map) +// { +// try +// { +// // 将地图数据转换为二维数组 +// int[,] mapData = new int[50, 50]; + +// for (int i = 0; i < map.Rows.Count && i < 50; i++) +// { +// for (int j = 0; j < map.Rows[i].Cols.Count && j < 50; j++) +// { +// mapData[i, j] = (int)map.Rows[i].Cols[j]; +// } +// } + +// // 更新地图视图模型 +// _viewModel.MapVM.UpdateMap(mapData); + +// _logger.LogInfo("地图数据已更新"); +// } +// catch (Exception ex) +// { +// _logger.LogError($"更新地图数据时出错: {ex.Message}"); +// } +// } + +// private string FormatGameTime(int milliseconds) +// { +// TimeSpan time = TimeSpan.FromMilliseconds(milliseconds); +// return $"{time.Minutes:00}:{time.Seconds:00}"; +// } +// } +//} \ No newline at end of file diff --git a/interface/AvaloniaUI_debug_interface/debug_interface/ViewModels/CharacterViewModel.cs b/interface/AvaloniaUI_debug_interface/debug_interface/ViewModels/CharacterViewModel.cs index ec1c2ba..560c59a 100644 --- a/interface/AvaloniaUI_debug_interface/debug_interface/ViewModels/CharacterViewModel.cs +++ b/interface/AvaloniaUI_debug_interface/debug_interface/ViewModels/CharacterViewModel.cs @@ -1,102 +1,55 @@ -//CharacterViewModel.cs +using System.Collections.ObjectModel; using CommunityToolkit.Mvvm.ComponentModel; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; namespace debug_interface.ViewModels { public partial class CharacterViewModel : ViewModelBase { [ObservableProperty] - private string name = "角色"; // 默认角色名,可由服务器更新 + private long characterId; [ObservableProperty] - private int hp = 1000; // 血量,可由服务器动态更新 + private string name = ""; [ObservableProperty] - private int posX = 0; + private int hp; [ObservableProperty] - private int posY = 0; + private int posX; - // 主动状态(单一) - // 可能值: "空置","开采","攻击","释放技能","建造","移动" [ObservableProperty] - private string activeState = "空置"; + private int posY; - // 被动状态(可叠加) - // 可能值: "致盲","击退","定身","隐身" 等,可由服务器控制增减 - public ObservableCollection PassiveStates { get; } = new ObservableCollection(); + [ObservableProperty] + private string activeState = ""; - // 装备清单:用名称+数量表示。如 {"小血瓶":2, "鞋子":1, "大护盾":1, "净化药水":3} - // 为了方便绑定,用 ObservableCollection 来存储装备条目,每个条目包含Name和Count - public ObservableCollection EquipmentInventory { get; } = new ObservableCollection(); + public ObservableCollection PassiveStates { get; } = new(); - // 状态选项列表和装备选项列表已不再使用ComboBox选择,而是纯展示。 - // 如果需要仍保留可由服务器更新,但这里不会再用于交互。 + public ObservableCollection EquipmentInventory { get; } = new(); - // 构造函数 - public CharacterViewModel() - { - PassiveStates = new ObservableCollection(); - EquipmentInventory = new ObservableCollection(); - } + // 格式化状态文本的属性 + public string DisplayStates => + $"{ActiveState} {(PassiveStates.Count > 0 ? $"[{string.Join(", ", PassiveStates)}]" : "")}"; - // 用于UI展示状态字符串 - // 格式: (主动:xx 被动:xx yy zz) - public string DisplayStates - { - get - { - var sb = new StringBuilder(); - sb.Append("(主动:").Append(ActiveState).Append(" 被动:"); - if (PassiveStates.Count > 0) - sb.Append(string.Join(" ", PassiveStates)); - else - sb.Append("无"); - sb.Append(")"); - return sb.ToString(); - } - } - - // 用于UI展示装备字符串 - // 格式: 装备: 小血瓶x2 鞋子x1 大护盾x1 净化药水x3 - public string DisplayEquipments - { - get - { - if (EquipmentInventory.Count == 0) - { - return "装备:无"; - } - return "装备: " + string.Join(", ", - EquipmentInventory.Select(e => $"{e.Name}×{e.Count}")); - } - } - - // 当ActiveState或PassiveStates变化后,通知UI更新 DisplayStates - partial void OnActiveStateChanged(string oldValue, string newValue) - { - OnPropertyChanged(nameof(DisplayStates)); - } - - // 如果被动状态列表更新,需要调用OnPropertyChanged(nameof(DisplayStates)) - // 可以在服务器更新逻辑中调用。 - - // 同理,当EquipmentInventory变化时,需要更新DisplayEquipments - // 可以在添加/移除装备后调用OnPropertyChanged(nameof(DisplayEquipments)) + // 格式化装备文本的属性 + public string DisplayEquipments => + EquipmentInventory.Count > 0 ? string.Join(", ", EquipmentInventory) : "无"; } - // 定义装备类 public class EquipmentItem { - public string Name { get; set; } - public int Count { get; set; } + public string Name { get; } + public int Count { get; } + public EquipmentItem(string name, int count) { Name = name; Count = count; } + + public override string ToString() + { + return Count > 1 ? $"{Name}x{Count}" : Name; + } } -} +} \ No newline at end of file diff --git a/interface/AvaloniaUI_debug_interface/debug_interface/ViewModels/MainWindowViewModel.cs b/interface/AvaloniaUI_debug_interface/debug_interface/ViewModels/MainWindowViewModel.cs index 7803f2c..c1b33ef 100644 --- a/interface/AvaloniaUI_debug_interface/debug_interface/ViewModels/MainWindowViewModel.cs +++ b/interface/AvaloniaUI_debug_interface/debug_interface/ViewModels/MainWindowViewModel.cs @@ -1,29 +1,19 @@ -// ViewModels/MainWindowViewModel.cs -using CommunityToolkit.Mvvm.ComponentModel; +using System; using System.Collections.ObjectModel; -using System; -using System.Timers; -using debug_interface.ViewModels; +using CommunityToolkit.Mvvm.ComponentModel; +using installer.Model; +using installer.Data; namespace debug_interface.ViewModels { public partial class MainWindowViewModel : ViewModelBase { - // Keep only one definition of MapVM + // 属性定义 [ObservableProperty] - private MapViewModel mapVM; - - public ObservableCollection RedTeamCharacters { get; } - public ObservableCollection BlueTeamCharacters { get; } + private string gameLog = "等待连接..."; [ObservableProperty] - private string someBuildingInfo = "红方建筑信息..."; - - [ObservableProperty] - private string anotherBuildingInfo = "蓝方建筑信息..."; - - [ObservableProperty] - private string currentTime = DateTime.Now.ToString("HH:mm:ss"); + private string currentTime = "00:00"; [ObservableProperty] private int redScore = 0; @@ -32,85 +22,74 @@ namespace debug_interface.ViewModels private int blueScore = 0; [ObservableProperty] - private bool isBlueView = true; - - public bool IsRedView - { - get => !IsBlueView; - set => IsBlueView = !value; - } + private string someBuildingInfo = ""; [ObservableProperty] - private string gameLog = "地图..."; + private string anotherBuildingInfo = ""; - private Timer _timer; + [ObservableProperty] + private int buddhistTeamEconomy = 0; - public MainWindowViewModel() + [ObservableProperty] + private int monstersTeamEconomy = 0; + + [ObservableProperty] + private MapViewModel mapVM; + + // 团队角色集合 + public ObservableCollection RedTeamCharacters { get; } = new(); + public ObservableCollection BlueTeamCharacters { get; } = new(); + + // 构造函数 + public MainWindowViewModel(Logger logger, ConfigData config) : base(logger) { - // Initialize collections first - RedTeamCharacters = new ObservableCollection(); - BlueTeamCharacters = new ObservableCollection(); - - // Initialize characters - for (int i = 0; i < 6; i++) - { - var redChar = new CharacterViewModel() - { - Name = "红方角色" + (i + 1), - Hp = 1000 * (i + 1), - ActiveState = i % 2 == 0 ? "攻击" : "移动", - }; - redChar.PassiveStates.Add("致盲"); - if (i % 3 == 0) redChar.PassiveStates.Add("定身"); - redChar.EquipmentInventory.Add(new EquipmentItem("小血瓶", 2)); - if (i % 2 == 0) - redChar.EquipmentInventory.Add(new EquipmentItem("大护盾", 1)); - - RedTeamCharacters.Add(redChar); - - var blueChar = new CharacterViewModel() - { - Name = "蓝方角色" + (i + 1), - Hp = 1500 + i * 500, - ActiveState = "空置", - }; - blueChar.PassiveStates.Add("隐身"); - blueChar.EquipmentInventory.Add(new EquipmentItem("净化药水", 3)); - if (i % 2 == 1) - blueChar.EquipmentInventory.Add(new EquipmentItem("鞋子", 1)); - - BlueTeamCharacters.Add(blueChar); - } - - // Initialize MapViewModel + // 初始化MapViewModel MapVM = new MapViewModel(); - // Assign random initial positions to characters - Random rnd = new Random(); - foreach (var character in RedTeamCharacters) + // 如果在设计模式下,可以添加一些测试数据 + if (Avalonia.Controls.Design.IsDesignMode) { - character.PosX = rnd.Next(10, 40); - character.PosY = rnd.Next(10, 40); + InitializeDesignTimeData(); } - foreach (var character in BlueTeamCharacters) - { - character.PosX = rnd.Next(10, 40); - character.PosY = rnd.Next(10, 40); - } - - _timer = new Timer(1000); - _timer.Elapsed += Timer_Elapsed; - _timer.Start(); + // 启动UI更新定时器 + StartUiUpdateTimer(); } - private void Timer_Elapsed(object? sender, ElapsedEventArgs e) + // 设计时数据 + private void InitializeDesignTimeData() { - CurrentTime = DateTime.Now.ToString("HH:mm:ss"); - Avalonia.Threading.Dispatcher.UIThread.Post(() => + GameLog = "设计模式 - 模拟数据"; + CurrentTime = "12:34"; + RedScore = 50; + BlueScore = 30; + + // 添加一些测试角色 + for (int i = 0; i < 3; i++) { - OnPropertyChanged(nameof(CurrentTime)); - }); + RedTeamCharacters.Add(new CharacterViewModel + { + CharacterId = i + 1, + Name = $"取经队角色{i + 1}", + Hp = 1000, + ActiveState = "空置" + }); + + BlueTeamCharacters.Add(new CharacterViewModel + { + CharacterId = i + 101, + Name = $"妖怪队角色{i + 1}", + Hp = 1200, + ActiveState = "移动" + }); + } + } + + // 定时器更新方法 + protected override void OnTimerTick(object? sender, EventArgs e) + { + // 更新当前时间显示 + CurrentTime = DateTime.Now.ToString("HH:mm:ss"); } } } \ No newline at end of file diff --git a/interface/AvaloniaUI_debug_interface/debug_interface/ViewModels/MapViewModel.cs b/interface/AvaloniaUI_debug_interface/debug_interface/ViewModels/MapViewModel.cs index b61b8b0..1911830 100644 --- a/interface/AvaloniaUI_debug_interface/debug_interface/ViewModels/MapViewModel.cs +++ b/interface/AvaloniaUI_debug_interface/debug_interface/ViewModels/MapViewModel.cs @@ -1,137 +1,201 @@ -//MapViewModel.cs -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.ObjectModel; using CommunityToolkit.Mvvm.ComponentModel; -using System.Collections.ObjectModel; -using debug_interface.Models; using Avalonia.Media; +using debug_interface.Models; namespace debug_interface.ViewModels { - // 地图视图模型,管理整个 50×50 的地图数据 - public partial class MapViewModel : ObservableObject + public partial class MapViewModel : ViewModelBase { - // 定义地图网格大小为 50(行)× 50(列) - public const int GridSize = 50; + private const int GridSize = 50; - // ObservableCollection 存放 2500 个 MapCell 对象,每个对象代表一个地图格子 - // 数据绑定到视图后,界面会显示出整个地图 - public ObservableCollection MapCells { get; } - public ObservableCollection RedTeamCharacters { get; } - public ObservableCollection BlueTeamCharacters { get; } + [ObservableProperty] + private ObservableCollection mapCells = new(); - public MapViewModel(ObservableCollection redTeam, ObservableCollection blueTeam) - { - MapCells = new ObservableCollection(); - RedTeamCharacters = redTeam; - BlueTeamCharacters = blueTeam; - InitializeDefaultMap(); - } - // 构造函数,用于测试 public MapViewModel() { - MapCells = new ObservableCollection(); - InitializeDefaultMap(); + // 初始化地图单元格 + InitializeMapCells(); } - // 初始化默认地图数据,用于测试效果 - private void InitializeDefaultMap() + private void InitializeMapCells() { - // 遍历所有行和列 - for (int i = 0; i < GridSize; i++) + MapCells.Clear(); + for (int i = 0; i < GridSize * GridSize; i++) { - for (int j = 0; j < GridSize; j++) + MapCells.Add(new MapCell { - // 创建一个 MapCell 对象,并设置行列信息 - var cell = new MapCell - { - CellX = i, - CellY = j, - // 根据坐标决定默认单元格类型 - CellType = GetDefaultCellType(i, j), - DisplayText = "" // 默认无文本显示,可后续根据需要设置 - }; - // 设置显示颜色,根据单元格类型获取颜色 - cell.DisplayColor = new SolidColorBrush(GetColorForCellType(cell.CellType)); - - MapCells.Add(cell); - } + Row = i / GridSize, + Col = i % GridSize, + CellType = MapCellType.Empty, + DisplayText = "", + DisplayColor = new SolidColorBrush(Colors.White), + BackgroundColor = new SolidColorBrush(Colors.LightGray) + }); } } - // 根据单元格在地图中的坐标决定默认类型 - private MapCellType GetDefaultCellType(int i, int j) + // 更新整个地图 + public void UpdateMap(int[,] mapData) { - // 1. 边界(第一行、最后一行、第一列、最后一列)均设为障碍物 - if (i == 0 || i == GridSize - 1 || j == 0 || j == GridSize - 1) - { - return MapCellType.Obstacle; - } - // 2. 在特定位置放置资源点(测试用) - if ((i == 10 && j == 10) || (i == 20 && j == 20) || (i == 30 && j == 30)) - { - return MapCellType.Resource; - } - // 3. 在特定位置放置草丛(测试用) - if ((i == 15 && j == 35) || (i == 35 && j == 15)) - { - return MapCellType.Grass; - } - // 4. 可预留一个建筑点,例如 (25,25) - if (i == 25 && j == 25) - { - return MapCellType.Building; - } - // 5. 其他区域默认为空地 - return MapCellType.OpenLand; - } - - // 根据单元格类型返回对应的颜色(可根据需要调整颜色) - private Color GetColorForCellType(MapCellType type) - { - switch (type) - { - case MapCellType.Obstacle: - return Colors.DarkGray; - case MapCellType.OpenLand: - return Colors.LightGreen; - case MapCellType.Grass: - return Colors.Green; - case MapCellType.Resource: - return Colors.Yellow; - case MapCellType.Building: - return Colors.Orange; - default: - return Colors.Black; - } - } - - // 用于更新地图数据的方法, - // newMapData 是一个 50x50 的二维整型数组,数值应能映射为 MapCellType 枚举值 - public void UpdateMap(int[,] newMapData) - { - if (newMapData.GetLength(0) != GridSize || newMapData.GetLength(1) != GridSize) - throw new ArgumentException("地图数据尺寸不正确"); - - // 遍历二维数组更新每个 MapCell 对象 for (int i = 0; i < GridSize; i++) { for (int j = 0; j < GridSize; j++) { int index = i * GridSize + j; - // 将整型转换为 MapCellType 枚举 - MapCellType newType = (MapCellType)newMapData[i, j]; - var cell = MapCells[index]; - cell.CellType = newType; - // 更新颜色 - cell.DisplayColor = new SolidColorBrush(GetColorForCellType(newType)); - // 如果需要,可以根据新数据更新文本(例如建筑血量、资源剩余) - cell.DisplayText = "123"; // 这里留空,可扩展 + if (index < MapCells.Count) + { + int cellType = mapData[i, j]; + UpdateCellType(MapCells[index], cellType); + } } } + + OnPropertyChanged(nameof(MapCells)); + } + + private void UpdateCellType(MapCell cell, int cellType) + { + // 根据cellType设置单元格属性 + // 这里简化处理 + switch (cellType) + { + case 0: // 空地 + cell.CellType = MapCellType.Empty; + cell.BackgroundColor = new SolidColorBrush(Colors.White); + break; + case 1: // 障碍物 + cell.CellType = MapCellType.Obstacle; + cell.BackgroundColor = new SolidColorBrush(Colors.DarkGray); + break; + case 2: // 资源 + cell.CellType = MapCellType.Resource; + cell.BackgroundColor = new SolidColorBrush(Colors.Green); + break; + default: + cell.CellType = MapCellType.Empty; + cell.BackgroundColor = new SolidColorBrush(Colors.LightGray); + break; + } + } + + // 更新角色位置 + public void UpdateCharacterPosition(long characterId, long teamId, int x, int y, string name) + { + // 简化实现 + int index = x * GridSize + y; + if (index >= 0 && index < MapCells.Count) + { + MapCells[index].DisplayText = name.Substring(0, 1); + MapCells[index].DisplayColor = teamId == 1 + ? new SolidColorBrush(Colors.Red) + : new SolidColorBrush(Colors.Blue); + + OnPropertyChanged(nameof(MapCells)); + } + } + + // 更新建筑 + public void UpdateBuildingCell(int x, int y, string team, string buildingType, int hp) + { + int index = x * GridSize + y; + if (index >= 0 && index < MapCells.Count) + { + MapCells[index].CellType = MapCellType.Building; + MapCells[index].DisplayText = buildingType.Substring(0, 1); + MapCells[index].DisplayColor = team == "取经队" + ? new SolidColorBrush(Colors.DarkRed) + : new SolidColorBrush(Colors.DarkBlue); + + OnPropertyChanged(nameof(MapCells)); + } + } + + // 更新陷阱 + public void UpdateTrapCell(int x, int y, string team, string trapType) + { + int index = x * GridSize + y; + if (index >= 0 && index < MapCells.Count) + { + MapCells[index].CellType = MapCellType.Obstacle; + MapCells[index].DisplayText = trapType.Substring(0, 1); + MapCells[index].DisplayColor = team == "取经队" + ? new SolidColorBrush(Colors.IndianRed) + : new SolidColorBrush(Colors.CornflowerBlue); + + OnPropertyChanged(nameof(MapCells)); + } + } + + // 更新资源 + public void UpdateResourceCell(int x, int y, string resourceType, int process) + { + int index = x * GridSize + y; + if (index >= 0 && index < MapCells.Count) + { + MapCells[index].CellType = MapCellType.Resource; + MapCells[index].DisplayText = process.ToString(); + MapCells[index].DisplayColor = new SolidColorBrush(Colors.DarkGreen); + + OnPropertyChanged(nameof(MapCells)); + } + } + + // 更新额外资源 + public void UpdateAdditionResourceCell(int x, int y, string resourceName, int value) + { + int index = x * GridSize + y; + if (index >= 0 && index < MapCells.Count) + { + MapCells[index].CellType = MapCellType.Resource; + MapCells[index].DisplayText = value.ToString(); + + // 根据资源类型选择颜色 + if (resourceName.Contains("生命池")) + { + MapCells[index].DisplayColor = new SolidColorBrush(Colors.LightGreen); + } + else if (resourceName.Contains("疯人")) + { + MapCells[index].DisplayColor = new SolidColorBrush(Colors.OrangeRed); + } + else + { + MapCells[index].DisplayColor = new SolidColorBrush(Colors.Purple); + } + + OnPropertyChanged(nameof(MapCells)); + } } } -} + + //public enum MapCellType + //{ + // Empty, + // Obstacle, + // Building, + // Resource, + // Character + //} + + //public partial class MapCell : ViewModelBase + //{ + // [ObservableProperty] + // private int row; + + // [ObservableProperty] + // private int col; + + // [ObservableProperty] + // private MapCellType cellType; + + // [ObservableProperty] + // private string displayText = ""; + + // [ObservableProperty] + // private IBrush displayColor = new SolidColorBrush(Colors.Black); + + // [ObservableProperty] + // private IBrush backgroundColor = new SolidColorBrush(Colors.White); + //} +} \ No newline at end of file diff --git a/interface/AvaloniaUI_debug_interface/debug_interface/ViewModels/ViewModelBase.cs b/interface/AvaloniaUI_debug_interface/debug_interface/ViewModels/ViewModelBase.cs index 55100f6..9a627f0 100644 --- a/interface/AvaloniaUI_debug_interface/debug_interface/ViewModels/ViewModelBase.cs +++ b/interface/AvaloniaUI_debug_interface/debug_interface/ViewModels/ViewModelBase.cs @@ -1,439 +1,70 @@ -//ViewModeBase.cs -using System; -using System.Collections.Generic; -using System.Threading.Tasks; +using System; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; -using debug_interface.Models; -using Google.Protobuf; -using Grpc.Core; -//using installer; -//using installer.Model; -using Avalonia.Controls.Shapes; -using Avalonia.Logging; -using System.Threading.Channels; +using installer.Model; // 使用现有的Logger namespace debug_interface.ViewModels { public class ViewModelBase : ObservableObject { - // 用于 UI 刷新的定时器(Avalonia 的 DispatcherTimer) - private DispatcherTimer timerViewModel; - private int counterViewModelTest = 0; + // 使用既有的Logger + protected readonly Logger? logger; - // 使用 CommunityToolkit 的 ObservableProperty 自动实现 INotifyPropertyChanged - //[ObservableProperty] - //private string title; - - //private MapPatch testPatch; - //public MapPatch TestPatch - //{ - // get => testPatch; - // set => SetProperty(ref testPatch, value); - //} - - //private List links; - //public List Links - //{ - // get => links ??= new List(); - // set => SetProperty(ref links, value); - //} - - // 与服务器通信相关的字段 - private long playerID; - private readonly string ip; - private readonly string port; - private readonly int shipTypeID; - private long teamID; - - //private ShipType shipType; - //private AvailableService.AvailableServiceClient? client; - //private AsyncServerStreamingCall? responseStream; - private bool isSpectatorMode = false; - private bool isPlaybackMode = false; - - // 用于存储上次移动角度(示例,仅作为参考) - private double lastMoveAngle; - - // 日志记录(保持你原来的 Logger 类) - //public Logger myLogger; - //public Logger lockGenerator; - - // 以下定义各个操作的命令(基于 CommunityToolkit.Mvvm.Input 的 RelayCommand) - public RelayCommand MoveUpCommand { get; } - public RelayCommand MoveDownCommand { get; } - public RelayCommand MoveLeftCommand { get; } - public RelayCommand MoveRightCommand { get; } - public RelayCommand MoveLeftUpCommand { get; } - public RelayCommand MoveRightUpCommand { get; } - public RelayCommand MoveLeftDownCommand { get; } - public RelayCommand MoveRightDownCommand { get; } - public RelayCommand AttackCommand { get; } - public RelayCommand RecoverCommand { get; } - public RelayCommand ProduceCommand { get; } - public RelayCommand ConstructCommand { get; } + // UI更新定时器 + protected DispatcherTimer? dispatcherTimer; + // 基本构造函数 public ViewModelBase() { - //Title = "THUAI8; - - // 读取配置(假设 ConfigData 类来自 installer.Data 命名空间) - //var d = new installer.Data.ConfigData(); - //ip = d.Commands.IP; - //port = d.Commands.Port; - //playerID = Convert.ToInt64(d.Commands.PlayerID); - //teamID = Convert.ToInt64(d.Commands.TeamID); - //shipTypeID = Convert.ToInt32(d.Commands.ShipType); - //string playbackFile = d.Commands.PlaybackFile; - //double playbackSpeed = d.Commands.PlaybackSpeed; - - // 初始化日志记录器 - //myLogger = LoggerProvider.FromFile(System.IO.Path.Combine(d.InstallPath, "Logs", $"Client.{teamID}.{playerID}.log")); - //lockGenerator = LoggerProvider.FromFile(System.IO.Path.Combine(d.InstallPath, "Logs", $"lock.{teamID}.{playerID}.log")); - - // 初始化命令:这里仅示例了部分命令,其他命令同理 - MoveUpCommand = new RelayCommand(() => - { - //if (client == null || isSpectatorMode || isPlaybackMode) - //{ - // myLogger.LogInfo("Client is null or in Spectator/Playback mode"); - // return; - //} - //// 构造移动消息,这里采用 gRPC 的消息格式(注意:根据你的 proto 定义,字段名称可能有所不同) - //MoveMsg movemsg = new MoveMsg - //{ - // character_id = playerID, - // team_id = teamID, - // angle = Math.PI, - // time_in_milliseconds = 50 - //}; - //lastMoveAngle = movemsg.angle; - //client.Move(movemsg); - }); - - MoveDownCommand = new RelayCommand(() => - { - //if (client == null || isSpectatorMode || isPlaybackMode) - //{ - // myLogger.LogInfo("Client is null or in Spectator/Playback mode"); - // return; - //} - //MoveMsg movemsg = new MoveMsg - //{ - // character_id = playerID, - // team_id = teamID, - // // 这里使用 0 表示“负零” - // angle = 0, - // time_in_milliseconds = 100 - //}; - //lastMoveAngle = movemsg.angle; - //client.Move(movemsg); - }); - - MoveLeftCommand = new RelayCommand(() => - { - //if (client == null || isSpectatorMode || isPlaybackMode) - //{ - // myLogger.LogInfo("Client is null or in Spectator/Playback mode"); - // return; - //} - //MoveMsg movemsg = new MoveMsg - //{ - // character_id = playerID, - // team_id = teamID, - // angle = Math.PI * 3 / 2, - // time_in_milliseconds = 100 - //}; - //lastMoveAngle = movemsg.angle; - //client.Move(movemsg); - }); - - MoveRightCommand = new RelayCommand(() => - { - //if (client == null || isSpectatorMode || isPlaybackMode) - //{ - // myLogger.LogInfo("Client is null or in Spectator/Playback mode"); - // return; - //} - //MoveMsg movemsg = new MoveMsg - //{ - // character_id = playerID, - // team_id = teamID, - // angle = Math.PI / 2, - // time_in_milliseconds = 100 - //}; - //lastMoveAngle = movemsg.angle; - //client.Move(movemsg); - }); - - MoveLeftUpCommand = new RelayCommand(() => - { - //if (client == null || isSpectatorMode || isPlaybackMode) - //{ - // myLogger.LogInfo("Client is null or in Spectator/Playback mode"); - // return; - //} - //MoveMsg movemsg = new MoveMsg - //{ - // character_id = playerID, - // team_id = teamID, - // angle = Math.PI * 5 / 4, - // time_in_milliseconds = 100 - //}; - //lastMoveAngle = movemsg.angle; - //client.Move(movemsg); - }); - - MoveRightUpCommand = new RelayCommand(() => - { - //if (client == null || isSpectatorMode || isPlaybackMode) - //{ - // myLogger.LogInfo("Client is null or in Spectator/Playback mode"); - // return; - //} - //MoveMsg movemsg = new MoveMsg - //{ - // character_id = playerID, - // team_id = teamID, - // angle = Math.PI * 3 / 4, - // time_in_milliseconds = 100 - //}; - //lastMoveAngle = movemsg.angle; - //client.Move(movemsg); - }); - - MoveLeftDownCommand = new RelayCommand(() => - { - //if (client == null || isSpectatorMode || isPlaybackMode) - //{ - // myLogger.LogInfo("Client is null or in Spectator/Playback mode"); - // return; - //} - //MoveMsg movemsg = new MoveMsg - //{ - // character_id = playerID, - // team_id = teamID, - // angle = Math.PI * 7 / 4, - // time_in_milliseconds = 100 - //}; - //lastMoveAngle = movemsg.angle; - //client.Move(movemsg); - }); - - MoveRightDownCommand = new RelayCommand(() => - { - //if (client == null || isSpectatorMode || isPlaybackMode) - //{ - // myLogger.LogInfo("Client is null or in Spectator/Playback mode"); - // return; - //} - //MoveMsg movemsg = new MoveMsg - //{ - // character_id = playerID, - // team_id = teamID, - // angle = Math.PI / 4, - // time_in_milliseconds = 100 - //}; - //lastMoveAngle = movemsg.angle; - //client.Move(movemsg); - }); - - AttackCommand = new RelayCommand(() => - { - //if (client == null || isSpectatorMode || isPlaybackMode) - //{ - // myLogger.LogInfo("Client is null or in Spectator/Playback mode"); - // return; - //} - //AttackMsg attackMsg = new AttackMsg - //{ - // character_id = playerID, - // team_id = teamID, - // attack_range = 50, // 示例值,根据实际情况修改 - // attacked_character_id = 0 // 示例目标ID - //}; - //client.Attack(attackMsg); - }); - - RecoverCommand = new RelayCommand(() => - { - //if (client == null || isSpectatorMode || isPlaybackMode) - //{ - // myLogger.LogInfo("Client is null or in Spectator/Playback mode"); - // return; - //} - //RecoverMsg recoverMsg = new RecoverMsg - //{ - // character_id = playerID, - // recovered_hp = 10, // 示例数值 - // team_id = teamID - //}; - //client.Recover(recoverMsg); - }); - - ProduceCommand = new RelayCommand(() => - { - //if (client == null || isSpectatorMode || isPlaybackMode) - //{ - // myLogger.LogInfo("Client is null or in Spectator/Playback mode"); - // return; - //} - //// 此处 Produce 可使用对应的 gRPC 方法(例如 Equip 或其他你定义的生产逻辑) - //IDMsg idMsg = new IDMsg - //{ - // character_id = playerID, - // team_id = teamID - //}; - //client.Equip(idMsg); - }); - - ConstructCommand = new RelayCommand(() => - { - //if (client == null || isSpectatorMode || isPlaybackMode) - //{ - // myLogger.LogInfo("Client is null or in Spectator/Playback mode"); - // return; - //} - //ConstructMsg constructMsg = new ConstructMsg - //{ - // character_id = playerID, - // team_id = teamID, - // construction_type = ConstructionType.BARRACKS // 示例建筑类型 - //}; - //client.Construct(constructMsg); - }); - - // 使用 Avalonia 的 DispatcherTimer 定时刷新 UI - timerViewModel = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(50) }; - timerViewModel.Tick += Refresh; - timerViewModel.Start(); - - // 判断是否走回放模式 - //if (string.IsNullOrEmpty(d.Commands.PlaybackFile)) - //{ - // string[] comInfo = new string[] - // { - // ip, - // port, - // Convert.ToString(playerID), - // Convert.ToString(teamID), - // Convert.ToString(shipTypeID), - // }; - // ConnectToServer(comInfo); - // OnReceive(); - //} - //else - //{ - // myLogger.LogInfo($"PlaybackFile: {d.Commands.PlaybackFile}"); - // Playback(d.Commands.PlaybackFile, playbackSpeed); - //} + // 子类可以初始化logger } - /// - /// 连接到服务器,初始化 gRPC 客户端 - /// - /// 包含 ip、port、playerID、teamID、shipTypeID 的数组 - public void ConnectToServer(string[] comInfo) + // 带logger的构造函数 + public ViewModelBase(Logger logger) { - //if (isPlaybackMode) return; - //if (Convert.ToInt64(comInfo[2]) > 2023) - //{ - // isSpectatorMode = true; - // myLogger.LogInfo("isSpectatorMode = true"); - //} - //if (comInfo.Length != 5) - //{ - // throw new Exception("Error Registration Information!"); - //} - - //string connect = $"{comInfo[0]}:{comInfo[1]}"; - //Channel channel = new Channel(connect, ChannelCredentials.Insecure); - //client = new AvailableService.AvailableServiceClient(channel); - //PlayerMsg playerMsg = new PlayerMsg(); - //playerID = Convert.ToInt64(comInfo[2]); - //playerMsg.PlayerId = playerID; - //if (!isSpectatorMode) - //{ - // teamID = Convert.ToInt64(comInfo[3]); - // playerMsg.TeamId = teamID; - // shipType = Convert.ToInt64(comInfo[4]) switch - // { - // 0 => ShipType.NullShipType, - // 1 => ShipType.CivilianShip, - // 2 => ShipType.MilitaryShip, - // 3 => ShipType.FlagShip, - // _ => ShipType.NullShipType - // }; - // playerMsg.ShipType = shipType; - //} - //responseStream = client.AddPlayer(playerMsg); + this.logger = logger; } - /// - /// 异步接收服务器流消息 - /// - private async void OnReceive() + // 启动UI更新定时器的方法 + protected void StartUiUpdateTimer(double intervalMs = 50) { - //try - //{ - // myLogger.LogInfo("============= OnReceiving Server Stream ================"); - // while (responseStream != null && await responseStream.ResponseStream.MoveNext()) - // { - // myLogger.LogInfo("============= Receiving Server Stream ================"); - // MessageToClient content = responseStream.ResponseStream.Current; - // // 根据 game_state 与消息内容,更新游戏状态,这里只给出一个示例 - // switch (content.game_state) - // { - // case GameState.GAME_START: - // myLogger.LogInfo("Game Start"); - // // TODO: 处理游戏开始消息 - // break; - // case GameState.GAME_RUNNING: - // myLogger.LogInfo("Game Running"); - // // TODO: 处理游戏运行消息 - // break; - // case GameState.GAME_END: - // myLogger.LogInfo("Game End"); - // // TODO: 处理游戏结束消息 - // break; - // default: - // break; - // } - // } - //} - //catch (Exception ex) - //{ - // myLogger.LogInfo($"OnReceive Exception: {ex.Message}"); - //} - } - - /// - /// 播放回放数据(示例方法) - /// - /// 回放文件 - /// 播放速度 - private void Playback(string fileName, double pbSpeed = 2.0) - { - //myLogger.LogInfo($"Starting playback with file: {fileName} at speed {pbSpeed}"); - // TODO: 实现回放逻辑 - isPlaybackMode = true; - } - - /// - /// 定时器回调方法,用于刷新 UI 与游戏状态 - /// - private void Refresh(object? sender, EventArgs e) - { - try + if (dispatcherTimer != null) { - counterViewModelTest++; - // TODO: 在此处添加更新绘制(例如调用绘图方法、更新数据绑定属性)的逻辑 + dispatcherTimer.Stop(); } - catch (Exception ex) + + dispatcherTimer = new DispatcherTimer { - //myLogger.LogInfo($"Refresh error: {ex.Message}"); + Interval = TimeSpan.FromMilliseconds(intervalMs) + }; + + dispatcherTimer.Tick += OnTimerTick; + dispatcherTimer.Start(); + + logger?.LogInfo($"UI更新定时器已启动,间隔:{intervalMs}毫秒"); + } + + // 停止定时器 + protected void StopUiUpdateTimer() + { + if (dispatcherTimer != null) + { + dispatcherTimer.Stop(); + dispatcherTimer.Tick -= OnTimerTick; + logger?.LogInfo("UI更新定时器已停止"); } } + + // 子类应覆盖此方法来处理定时器事件 + protected virtual void OnTimerTick(object? sender, EventArgs e) + { + // 默认实现为空 + } + + // 在UI线程上执行操作的便捷方法 + protected async void RunOnUiThread(Action action) + { + await Dispatcher.UIThread.InvokeAsync(action); + } } -} +} \ No newline at end of file diff --git a/interface/AvaloniaUI_debug_interface/debug_interface/Views/MapView.axaml.cs b/interface/AvaloniaUI_debug_interface/debug_interface/Views/MapView.axaml.cs index 2c33bc6..08dd7be 100644 --- a/interface/AvaloniaUI_debug_interface/debug_interface/Views/MapView.axaml.cs +++ b/interface/AvaloniaUI_debug_interface/debug_interface/Views/MapView.axaml.cs @@ -144,65 +144,159 @@ namespace debug_interface.Views { if (characterCanvas == null) return; + //bool isRedTeam = color.Equals(Colors.Red); + + //// ״ѡ - ʹԲΣʹ + //for (int i = 0; i < characters.Count; i++) + //{ + // var character = characters[i]; + + // // - ڶλ + // var container = new Canvas + // { + // Width = 16, + // Height = 16, + // Tag = character + // }; + + // Control characterShape; + + // if (isRedTeam) + // { + // // ʹԲ + // characterShape = new Ellipse + // { + // Width = 14, + // Height = 14, + // Fill = new SolidColorBrush(color) { Opacity = 0.7 }, + // Stroke = Brushes.White, + // StrokeThickness = 1 + // }; + // } + // else + // { + // // ʹ + // characterShape = new Rectangle + // { + // Width = 14, + // Height = 14, + // Fill = new SolidColorBrush(color) { Opacity = 0.7 }, + // Stroke = Brushes.White, + // StrokeThickness = 1 + // }; + // } + + // // ״ + // Canvas.SetLeft(characterShape, 1); + // Canvas.SetTop(characterShape, 1); + // container.Children.Add(characterShape); + + // // ӱʶ - ʹòͬķʽʶڲĽɫ + // var identifier = new TextBlock + // { + // Text = (i + 1).ToString(), + // FontSize = 8, + // Foreground = Brushes.White, + // HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center, + // VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center + // }; + + // // Ϊʶӱǿɶ + // var textContainer = new Border + // { + // Child = identifier, + // Width = 14, + // Height = 14, + // Background = Brushes.Transparent, + // HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center, + // VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center + // }; + + // Canvas.SetLeft(textContainer, 1); + // Canvas.SetTop(textContainer, 1); + // container.Children.Add(textContainer); + + // // ӹʾʾϸϢ + // var tooltip = new ToolTip + // { + // // ʹýɫķԣȷԷ + // Content = new TextBlock { Text = $"{(isRedTeam ? "" : "")} ɫ {i + 1}" } + // }; + // ToolTip.SetTip(container, tooltip); + + // // ӵ + // characterCanvas.Children.Add(container); + // characterElements[character.Name] = container; + + // // ʼλ - Ժ + + // Canvas.SetLeft(container, i*i * i*i); + // Canvas.SetTop(container, i*i * i * i ); + //} + ///////////////////////////////////// + ///////////////////////////////////// + //////////////////////////////////// for (int i = 0; i < characters.Count; i++) { - var character = characters[i]; - var id = color == Colors.Red ? $"red_{i}" : $"blue_{i}"; - - // һGridΪ߿ı/ͼ - var grid = new Grid { - Width = 15, - Height = 15, - }; + var character = characters[i]; + var id = color == Colors.Red ? $"red_{i}" : $"blue_{i}"; - // ɫ߿Բ - var borderellipse = new Ellipse - { - Width = 15, - Height = 15, - Fill = new SolidColorBrush(Colors.White), // ɫ - Stroke = new SolidColorBrush(color), // ɫ߿ - StrokeThickness = 2, - Tag = character.Name, - }; - - grid.Children.Add(borderellipse); - - // ===== ѡ1: ʾֱ ===== - // Ҫֱţע͵δ - //var textBlock = new TextBlock - //{ - // Text = (i + 1).ToString(), // ʹñ(1ʼ) - // HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center, - // VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center, - // FontSize = 8, - // Foreground = new SolidColorBrush(color), // ıɫɫһ - // FontWeight = FontWeight.Bold, - //}; - //grid.Children.Add(textBlock); - - // ʾϢ - ToolTip.SetTip(grid, character.Name); - - // óʼλ - Canvas.SetLeft(grid, character.PosY * 15); - Canvas.SetTop(grid, character.PosX * 15); - - characterCanvas.Children.Add(grid); - - // 洢Gridֵ - characterElements[id] = grid; - - // ԸĴ - character.PropertyChanged += (s, e) => - { - if (e.PropertyName == nameof(CharacterViewModel.PosX) || e.PropertyName == nameof(CharacterViewModel.PosY)) + // һGridΪ߿ı/ͼ + var grid = new Grid { - // Gridλ - UpdateCharacterPosition(grid, character.PosX, character.PosY); - } - }; + Width = 15, + Height = 15, + }; + + // ɫ߿Բ + var borderellipse = new Ellipse + { + Width = 15, + Height = 15, + Fill = new SolidColorBrush(Colors.White), // ɫ + Stroke = new SolidColorBrush(color), // ɫ߿ + StrokeThickness = 2, + Tag = character.Name, + }; + + grid.Children.Add(borderellipse); + + // ===== ѡ1: ʾֱ ===== + // Ҫֱţע͵δ + var textBlock = new TextBlock + { + Text = (i + 1).ToString(), // ʹñ(1ʼ) + HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Center, + VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center, + FontSize = 8, + Foreground = new SolidColorBrush(color), // ıɫɫһ + FontWeight = FontWeight.Bold, + }; + //grid.Children.Add(textBlock); + + // ʾϢ + ToolTip.SetTip(grid, character.Name); + + // óʼλ + Canvas.SetLeft(grid, character.PosY * 15); + Canvas.SetTop(grid, character.PosX * 15); + + characterCanvas.Children.Add(grid); + + // 洢Gridֵ + characterElements[id] = grid; + + // ԸĴ + character.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(CharacterViewModel.PosX) || e.PropertyName == nameof(CharacterViewModel.PosY)) + { + // Gridλ + UpdateCharacterPosition(grid, character.PosX, character.PosY); + } + }; + } } } @@ -226,5 +320,77 @@ namespace debug_interface.Views // When collection changes, refresh all characters for simplicity RefreshCharacters(); } + + + public void UpdateCharacterPosition(long characterId, int x, int y, bool isRedTeam, string name) + { + // нɫǻ򴴽± + var marker = FindCharacterMarker(characterId); + + if (marker == null) + { + // ½ɫ + marker = new Ellipse + { + Width = 10, + Height = 10, + Fill = new SolidColorBrush(isRedTeam ? Colors.Red : Colors.Blue), + Tag = characterId + }; + + // ıǩ + var label1 = new TextBlock + { + Text = name, + FontSize = 8, + Foreground = new SolidColorBrush(Colors.White) + }; + + // ӵ + CharacterCanvas.Children.Add(marker); + CharacterCanvas.Children.Add(label1); + } + + // λ + double cellWidth = CharacterCanvas.Bounds.Width / 50; + double cellHeight = CharacterCanvas.Bounds.Height / 50; + + Canvas.SetLeft(marker, y * cellWidth + cellWidth / 2 - marker.Width / 2); + Canvas.SetTop(marker, x * cellHeight + cellHeight / 2 - marker.Height / 2); + + // ±ǩλ + var label = FindCharacterLabel(characterId); + if (label != null) + { + Canvas.SetLeft(label, y * cellWidth + cellWidth / 2 - label.Bounds.Width / 2); + Canvas.SetTop(label, x * cellHeight + cellHeight / 2 + marker.Height); + } + } + + private Ellipse FindCharacterMarker(long characterId) + { + foreach (var child in CharacterCanvas.Children) + { + if (child is Ellipse ellipse && (long?)ellipse.Tag == characterId) + { + return ellipse; + } + } + return null; + } + + private TextBlock FindCharacterLabel(long characterId) + { + var marker = FindCharacterMarker(characterId); + if (marker == null) return null; + + int index = CharacterCanvas.Children.IndexOf(marker); + if (index >= 0 && index + 1 < CharacterCanvas.Children.Count && + CharacterCanvas.Children[index + 1] is TextBlock label) + { + return label; + } + return null; + } } } \ No newline at end of file diff --git a/interface/AvaloniaUI_debug_interface/debug_interface/debug_interface.csproj b/interface/AvaloniaUI_debug_interface/debug_interface/debug_interface.csproj index 557318c..f978b3c 100644 --- a/interface/AvaloniaUI_debug_interface/debug_interface/debug_interface.csproj +++ b/interface/AvaloniaUI_debug_interface/debug_interface/debug_interface.csproj @@ -1,22 +1,24 @@  - - WinExe - net8.0-windows - enable - true - app.manifest - true - debug_interface - - - - - + + WinExe + net8.0-windows + enable + true + app.manifest + true + debug_interface + - + + + + + Protos\%(RecursiveDir)%(Filename)%(Extension) + + @@ -28,28 +30,28 @@ - - - - - - - - - None - All - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + + + + + + None + All + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/logic/proto/Message2Clients.proto b/logic/proto/Message2Clients.proto new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/logic/proto/Message2Clients.proto @@ -0,0 +1 @@ + \ No newline at end of file