forked from gcdsfh/PMDT
3564 lines
118 KiB
C++
Executable File
3564 lines
118 KiB
C++
Executable File
|
|
struct sConfig {
|
|
|
|
struct sPlayerESP {
|
|
bool Alert;
|
|
bool Line;
|
|
bool Box;
|
|
bool Skeleton;
|
|
bool NoBot;
|
|
bool LootBox;
|
|
bool 血量;
|
|
bool 剩余人数;
|
|
bool 名字;
|
|
bool 距离;
|
|
|
|
float RadarX;
|
|
float RadarY;
|
|
bool Radar;
|
|
bool RadarDraw2;
|
|
bool Radar1;
|
|
bool Radar2;
|
|
bool Radar3;
|
|
bool Radar4;
|
|
|
|
};
|
|
sPlayerESP PlayerESP{0};
|
|
|
|
struct sOTHER {
|
|
bool HIDEESP;
|
|
};
|
|
sOTHER OTHER{0};
|
|
|
|
};
|
|
sConfig Config{0};
|
|
|
|
|
|
|
|
|
|
bool 提示=true;
|
|
bool 物资功能=false;
|
|
bool 单机设置=true;
|
|
bool 联机调试=false;
|
|
bool 功能设置=false;
|
|
bool 物资调试=false;
|
|
bool 坐标调试=false;
|
|
|
|
bool HIDEESP = true;
|
|
bool 人数 = false;
|
|
bool 世界 = false;
|
|
bool 更多功能 = false;
|
|
|
|
bool 高跳 = false;
|
|
float 高度 = 500.0f;
|
|
|
|
bool chongchongche = false;
|
|
float chongchongche1;
|
|
float chongchongche2;
|
|
float tocdoquay = 0.0f;
|
|
|
|
bool 人物变大 = false;
|
|
float 巨人 = 1.0f;
|
|
|
|
bool 飞天跳开 = false;
|
|
bool 飞天跳关 = false;
|
|
bool 飞天2 = false;
|
|
bool 飞天1 = false;
|
|
bool 飞天关 = false;
|
|
|
|
bool 能量加速开 = false;
|
|
bool 能量加速关 = false;
|
|
|
|
bool 伤害测试2 = false;
|
|
bool 枪械一套 = false;
|
|
bool 枪械变大 = false;
|
|
float Gun_Size = 1.0f;
|
|
bool 开启伤害 = false;
|
|
float 伤害值 = 0.0f;
|
|
float 伤害倍率 = 1.0f;
|
|
|
|
bool 广角 = false;
|
|
float 视角 = 90.0f;
|
|
|
|
bool 自控初始化 = false;
|
|
bool 喇叭飞天 = false;
|
|
bool 载具加速 = false;
|
|
bool 转圈 = false;
|
|
bool 转圈2 = false;
|
|
bool 无视碰撞开 = false;
|
|
bool 无视重力开 = false;
|
|
bool 老爷模式 = false;
|
|
bool 飞船模式 = false;
|
|
float 载具速度 = 1.0f;
|
|
float 喇叭高度 = 1.0f;
|
|
float XXXC = 3.0f;
|
|
bool 开发者菜单 = false;
|
|
bool 修复着陆 = false;
|
|
bool 修复部分 = false;
|
|
bool 修复车辆 = false;
|
|
bool DamageFix = false;
|
|
float 驾驶员X偏移 = 100.0f;
|
|
float 驾驶员Y偏移 = 0.0f;
|
|
float 驾驶员Z偏移 = 80.0f;
|
|
float 乘客X偏移 = 50.0f;
|
|
float 乘客Y偏移 = 50.0f;
|
|
float 乘客Z偏移 = 80.0f;
|
|
extern void 执行单次生成();
|
|
extern void 更新建筑跟随状态();
|
|
extern void 探测并添加附近建筑(float Radius);
|
|
extern void 清空建筑列表();
|
|
extern void 加载选中的建筑();
|
|
extern void 获取对象();
|
|
inline bool 建筑跟随模式 = false;
|
|
inline float 建筑探测半径 = 1000.0f;
|
|
inline float 建筑生成偏移 = 500.0f;
|
|
inline char 建筑路径输入[256] = {0};
|
|
|
|
bool(*ogm)();
|
|
bool gm(){
|
|
return true;
|
|
}
|
|
void CreateItem(int ItemType, int TypeSpecificID, int Count) {
|
|
获取对象();
|
|
FTransform Transform;
|
|
if(PlayerCharacter){
|
|
Transform=PlayerCharacter->GetTransform();
|
|
}
|
|
AActor*Actor=UGameplayStatics::BeginSpawningActorFromClass(GetWorld(),APickUpWrapperActor::StaticClass(),Transform,true,UGameplayStatics::GetPlayerController(GetWorld(),0));
|
|
APickUpWrapperActor*PickUpActor=reinterpret_cast<APickUpWrapperActor*>(Actor);
|
|
if(PickUpActor){
|
|
FItemDefineID Item;
|
|
Item.Type=ItemType;
|
|
Item.TypeSpecificID=TypeSpecificID;
|
|
PickUpActor->SetDefineID(Item);
|
|
PickUpActor->SetCountOnServerAfterSpawn(Count);
|
|
PickUpActor->ShowMesh(true);
|
|
PickUpActor->ShowActor();
|
|
|
|
UGameplayStatics::FinishSpawningActor(PickUpActor,Transform);
|
|
PickUpActor->RegisterToPlayerPickUpList();
|
|
}
|
|
}
|
|
|
|
void src(int ItemType, int TypeSpecificID, int Count) {
|
|
获取对象();
|
|
FTransform Transform;
|
|
if(PlayerCharacter){
|
|
Transform=PlayerCharacter->GetTransform();
|
|
}
|
|
AActor*Actor=UGameplayStatics::BeginSpawningActorFromClass(GetWorld(),APickUpWrapperActor::StaticClass(),Transform,true,UGameplayStatics::GetPlayerController(GetWorld(),0));
|
|
APickUpWrapperActor*PickUpActor=reinterpret_cast<APickUpWrapperActor*>(Actor);
|
|
if(PickUpActor){
|
|
FItemDefineID Item;
|
|
Item.Type=ItemType;
|
|
Item.TypeSpecificID=TypeSpecificID;
|
|
PickUpActor->SetDefineID(Item);
|
|
PickUpActor->SetCountOnServerAfterSpawn(Count);
|
|
PickUpActor->ShowMesh(true);
|
|
PickUpActor->ShowActor();
|
|
|
|
UGameplayStatics::FinishSpawningActor(PickUpActor,Transform);
|
|
PickUpActor->RegisterToPlayerPickUpList();
|
|
}
|
|
}
|
|
|
|
// 武器皮肤 - 类型1 (正确)
|
|
void AKM_Chicheng() { src(1, 101001001, 10); }
|
|
void AKM_Rongyao() { src(1, 101001002, 10); }
|
|
void AKM_Wumujinwen() { src(1, 101001003, 10); }
|
|
void AKM_Xueshi() { src(1, 101001004, 10); }
|
|
void AKM_Kuangnu() { src(1, 101001005, 10); }
|
|
|
|
void M16A4_Shachi() { src(1, 101002001, 10); }
|
|
void M16A4_Qingyao() { src(1, 101002002, 10); }
|
|
void M16A4_Wumujinwen() { src(1, 101002003, 10); }
|
|
void M16A4_Huangshi() { src(1, 101002004, 10); }
|
|
void M16A4_Yezhanyinghao() { src(1, 101002005, 10); }
|
|
void M16A4_Kuangnu() { src(1, 101002006, 10); }
|
|
|
|
void SCARL_Kuangnu() { src(1, 101003001, 10); }
|
|
void SCARL_Wumujinwen() { src(1, 101003002, 10); }
|
|
void SCARL_Chicheng() { src(1, 101003003, 10); }
|
|
void SCARL_Xueshi() { src(1, 101003004, 10); }
|
|
|
|
void M416_RocketGirl() { src(1, 101004001, 10); }
|
|
void M416_Wumujinwen() { src(1, 101004002, 10); }
|
|
void M416_Kuishe() { src(1, 101004003, 10); }
|
|
void M416_Tuya() { src(1, 101004004, 10); }
|
|
void M416_Shamofengbao() { src(1, 101004005, 10); }
|
|
void M416_Xueyumicai() { src(1, 101004006, 10); }
|
|
void M416_Xueshi() { src(1, 101004007, 10); }
|
|
void M416_Chicheng() { src(1, 101004008, 10); }
|
|
void M416_Spring() { src(1, 101004009, 10); }
|
|
|
|
void GROZA_Kuangnu() { src(1, 101005001, 10); }
|
|
void AUG_Xueshi() { src(1, 101006001, 10); }
|
|
|
|
void UZI_Kuangnu() { src(1, 102001001, 10); }
|
|
|
|
void UMP9_Chicheng() { src(1, 102002001, 10); }
|
|
void UMP9_Rongyao() { src(1, 102002002, 10); }
|
|
void UMP9_RocketGirl() { src(1, 102002003, 10); }
|
|
void UMP9_Spring() { src(1, 102002004, 10); }
|
|
|
|
void Kar98K_Qingyao() { src(1, 103001001, 10); }
|
|
void Kar98K_Wumujinwen() { src(1, 103001002, 10); }
|
|
void Kar98K_FireLava() { src(1, 103001003, 10); }
|
|
void Kar98K_Xueshi() { src(1, 103001004, 10); }
|
|
void Kar98K_Spring() { src(1, 103001005, 10); }
|
|
|
|
void AWM_Rainbow() { src(1, 103003001, 10); }
|
|
void SKS_Chicheng() { src(1, 103004001, 10); }
|
|
|
|
void S686_Chicheng() { src(1, 104001001, 10); }
|
|
|
|
void P92_Kuangnu() { src(1, 106001001, 10); }
|
|
void P1911_Qingyao() { src(1, 106002001, 10); }
|
|
|
|
void Crowbar_RedNight() { src(1, 108002001, 10); }
|
|
|
|
void Pan_DoubleYolk() { src(1, 108004001, 10); }
|
|
void Pan_Survivor() { src(1, 108004002, 10); }
|
|
void Pan_NoHunt() { src(1, 108004003, 10); }
|
|
void Pan_RottenTomato() { src(1, 108004004, 10); }
|
|
void Pan_Forward() { src(1, 108004005, 10); }
|
|
void Pan_RedNight() { src(1, 108004006, 10); }
|
|
|
|
// 表情动作 - 类型22 (正确)
|
|
void 你好() { src(22, 2200101, 20); }
|
|
void 感谢() { src(22, 2200201, 20); }
|
|
void 鼓掌() { src(22, 2200301, 20); }
|
|
void 大笑() { src(22, 2200401, 20); }
|
|
void 来这里() { src(22, 2200501, 20); }
|
|
void 走() { src(22, 2200601, 20); }
|
|
void 否定() { src(22, 2200701, 20); }
|
|
void 肯定() { src(22, 2200801, 20); }
|
|
void 投降() { src(22, 2200901, 20); }
|
|
void 愤怒() { src(22, 2201001, 20); }
|
|
void 摇摆舞() { src(22, 2201101, 20); }
|
|
void 电摇() { src(22, 2201201, 20); }
|
|
void 拍灰舞() { src(22, 2201301, 20); }
|
|
void 俄舞() { src(22, 2201401, 20); }
|
|
void 安静() { src(22, 2201501, 20); }
|
|
void 雀跃() { src(22, 2201601, 20); }
|
|
void 崩溃() { src(22, 2201701, 20); }
|
|
void 海带舞() { src(22, 2201801, 20); }
|
|
void 街舞() { src(22, 2201901, 20); }
|
|
void 壁虎步() { src(22, 2202001, 20); }
|
|
void 机械舞() { src(22, 2202101, 20); }
|
|
void 蹦迪舞() { src(22, 2202201, 20); }
|
|
void 斗舞() { src(22, 2202301, 20); }
|
|
void 秀舞() { src(22, 2202401, 20); }
|
|
void 拉票舞() { src(22, 2202501, 20); }
|
|
void 创造舞() { src(22, 2202601, 20); }
|
|
void 三连赞() { src(22, 2202701, 20); }
|
|
void 桑巴舞() { src(22, 2202801, 20); }
|
|
void 空翻() { src(22, 2202901, 20); }
|
|
void 摇篮舞() { src(22, 2203001, 20); }
|
|
void 摆裙舞() { src(22, 2203101, 20); }
|
|
void 吃鸡舞() { src(22, 2203201, 20); }
|
|
void 抖胸舞() { src(22, 2203301, 20); }
|
|
void 青蛙舞() { src(22, 2203401, 20); }
|
|
void 甩手舞() { src(22, 2203501, 20); }
|
|
void 点赞() { src(22, 2203601, 20); }
|
|
void 欢庆舞() { src(22, 2203701, 20); }
|
|
void 螃蟹舞() { src(22, 2203801, 20); }
|
|
void 快看我() { src(22, 2203901, 20); }
|
|
void 难过() { src(22, 2204001, 20); }
|
|
void 扭秧歌() { src(22, 2204101, 20); }
|
|
void 拜年男() { src(22, 2204201, 20); }
|
|
void 红包来了() { src(22, 2204301, 20); }
|
|
void 拜年女() { src(22, 2204401, 20); }
|
|
|
|
// 服装 - 类型4 (正确)
|
|
void 雪地吉利服() { src(4, 403038, 10); }
|
|
void 晶翼女神套装() { src(4, 403409, 10); }
|
|
void 黑曜金尊套装() { src(4, 403410, 10); }
|
|
void 火箭少女101() { src(4, 453021, 10); }
|
|
void 至尊金龙外套() { src(4, 403188, 10); }
|
|
void S1战斗裤() { src(4, 404049, 10); }
|
|
void S1战斗外套() { src(4, 403124, 10); }
|
|
void 小黄衣() { src(4, 403017, 10); }
|
|
void 小黄裤() { src(4, 404001, 10); }
|
|
void 黑色连帽大衣() { src(4, 403184, 10); }
|
|
void 西部牛仔面罩() { src(4, 402037, 10); }
|
|
void 黑色rock() { src(4, 403178, 10); }
|
|
void 青色面罩() { src(4, 402032, 10); }
|
|
void 师傅专用外套() { src(4, 403075, 10); }
|
|
void 时尚圣诞套装() { src(4, 403393, 10); }
|
|
void 卡路里套装() { src(4, 403408, 10); }
|
|
|
|
// 货币 - 类型100 (正确)
|
|
void 现金() { src(100, 1011, 520); }
|
|
void Q币() { src(100, 1012, 520); }
|
|
void 点券() { src(100, 1006, 520); }
|
|
|
|
// 特效道具 - 类型30 (正确)
|
|
void 新年烟花() { src(30, 3000301, 50); }
|
|
void 新年爆竹() { src(30, 3000302, 50); }
|
|
void 红包() { src(30, 3001027, 50); }
|
|
void 坚甲() { src(30, 3001028, 50); }
|
|
void 利爪() { src(30, 3001029, 50); }
|
|
void 小鸡() { src(30, 3002001, 50); }
|
|
void 鸡蛋() { src(30, 3002002, 50); }
|
|
void 南瓜() { src(30, 3001013, 50); }
|
|
void 圣诞糖果() { src(30, 3001014, 50); }
|
|
void 西瓜() { src(30, 3001001, 50); }
|
|
void 小黄鸭() { src(30, 3001002, 90); }
|
|
void 鸡() { src(30, 3001003, 90); }
|
|
|
|
// 道具/材料 - 类型16 (正确)
|
|
void 机密情报() { src(16, 1602056, 50); }
|
|
void 鸡神奖杯() { src(16, 1602046, 50); }
|
|
void 胜利手雷() { src(16, 1602039, 50); }
|
|
void 足球() { src(16, 1602030, 50); }
|
|
|
|
// 消耗品 - 类型6 (正确,但需要修正参数个数)
|
|
void 震爆弹() { src(6, 602001, 50); }
|
|
void 烟雾弹() { src(6, 602002, 50); }
|
|
void 燃烧瓶() { src(6, 602003, 50); }
|
|
void 破片手榴弹() { src(6, 602004, 50); }
|
|
void 跳舞弹() { src(6, 602006, 50); }
|
|
// 修正参数个数错误:
|
|
void 大春雷() { src(6, 602010, 50); } // 原来有4个参数
|
|
void 鞭炮() { src(6, 602011, 50); } // 原来有4个参数
|
|
void 小春雷() { src(6, 602012, 50); } // 原来有4个参数
|
|
void 南瓜炸弹() { src(6, 602013, 50); } // 原来有4个参数
|
|
void 苹果() { src(6, 602005, 50); }
|
|
void 圣诞玩具苹果() { src(6, 602007, 50); }
|
|
void 圣诞小鸡玩偶() { src(6, 602008, 50); }
|
|
void 冬季雪球() { src(6, 602009, 50); }
|
|
void 肾上腺素() { src(6, 601002, 50); }
|
|
|
|
// 武器 - 类型1 (正确)
|
|
void AKM() { src(1, 101001, 10); }
|
|
void M416() { src(1, 101004, 10); }
|
|
void AWM() { src(1, 103003, 10); }
|
|
void M16A4() { src(1, 101002, 10); }
|
|
void GROZA() { src(1, 101005, 10); }
|
|
void SCARL() { src(1, 101003, 10); }
|
|
void AUG() { src(1, 101006, 10); }
|
|
void QBZ() { src(1, 101007, 10); }
|
|
void M762() { src(1, 101008, 10); }
|
|
void MK47() { src(1, 101009, 10); }
|
|
void G36C() { src(1, 101010, 10); }
|
|
void UZI() { src(1, 102001, 10); }
|
|
void UMP9() { src(1, 102002, 10); }
|
|
void Vector() { src(1, 102003, 10); }
|
|
void Thompson() { src(1, 102004, 10); }
|
|
void PP19() { src(1, 102005, 10); }
|
|
void Kar98K() { src(1, 103001, 10); }
|
|
void M24() { src(1, 103002, 10); }
|
|
void SKS() { src(1, 103004, 10); }
|
|
void VSS() { src(1, 103005, 10); }
|
|
void Mini14() { src(1, 103006, 10); }
|
|
void Mk14() { src(1, 103007, 10); }
|
|
void Win94() { src(1, 103008, 10); }
|
|
void SLR() { src(1, 103009, 10); }
|
|
void QBU() { src(1, 103010, 10); }
|
|
void S686() { src(1, 104001, 10); }
|
|
void S1897() { src(1, 104002, 10); }
|
|
void S12K() { src(1, 104003, 10); }
|
|
void M249() { src(1, 105001, 10); }
|
|
void DP28() { src(1, 105002, 10); }
|
|
void P92() { src(1, 106001, 10); }
|
|
void P1911() { src(1, 106002, 10); }
|
|
void R1895() { src(1, 106003, 10); }
|
|
void P18C() { src(1, 106004, 10); }
|
|
void R45() { src(1, 106005, 10); }
|
|
void SawedOff() { src(1, 106006, 10); }
|
|
void SignalGun() { src(1, 106007, 10); }
|
|
void Scorpion() { src(1, 106008, 10); }
|
|
void ChickenFirework() { src(1, 106095, 10); }
|
|
void LanternFirework() { src(1, 106096, 10); }
|
|
void BeastSignal() { src(1, 106097, 10); }
|
|
void ChristmasSignal() { src(1, 106098, 10); }
|
|
void NewYearFirework() { src(1, 106099, 10); }
|
|
void Crossbow() { src(1, 107001, 10); }
|
|
void RiotShield() { src(1, 107003, 10); }
|
|
void Machete() { src(1, 108001, 10); }
|
|
void Crowbar() { src(1, 108002, 10); }
|
|
void Sickle() { src(1, 108003, 10); }
|
|
void Pan() { src(1, 108004, 10); }
|
|
|
|
// 弹药 - 类型3 (正确)
|
|
void Bullet762() { src(3, 302001, 900); }
|
|
void Bullet556() { src(3, 303001, 900); }
|
|
void ShotgunShell() { src(3, 304001, 900); }
|
|
void Bullet45() { src(3, 305001, 900); }
|
|
void Magnum() { src(3, 306001, 900); }
|
|
void SignalFlare() { src(3, 308001, 900); }
|
|
void Arrow() { src(3, 307001, 900); }
|
|
void FireworkShell() { src(3, 308002, 900); }
|
|
void BeastBait() { src(3, 308003, 900); }
|
|
|
|
// 配件 - 类型2 (修正:之前是类型1,应该改为类型2)
|
|
void Choke() { src(2, 201001, 10); }
|
|
void SMGCompensator() { src(2, 201002, 10); }
|
|
void SniperCompensator() { src(2, 201003, 10); }
|
|
void SMGFlashHider() { src(2, 201004, 10); }
|
|
void SniperFlashHider() { src(2, 201005, 10); }
|
|
void SMGSilencer() { src(2, 201006, 10); }
|
|
void SniperSilencer() { src(2, 201007, 10); }
|
|
void PistolSilencer() { src(2, 201008, 10); }
|
|
void RifleCompensator() { src(2, 201009, 10); }
|
|
void RifleFlashHider() { src(2, 201010, 10); }
|
|
void RifleSilencer() { src(2, 201011, 10); }
|
|
void Duckbill() { src(2, 201012, 10); }
|
|
void AngledGrip() { src(2, 202001, 10); }
|
|
void VerticalGrip() { src(2, 202002, 10); }
|
|
void LightGrip() { src(2, 202004, 10); }
|
|
void HalfGrip() { src(2, 202005, 10); }
|
|
void ThumbGrip() { src(2, 202006, 10); }
|
|
void LaserSight() { src(2, 202007, 10); }
|
|
void RedDot() { src(2, 203001, 10); }
|
|
void Holographic() { src(2, 203002, 10); }
|
|
void Scope2x() { src(2, 203003, 10); }
|
|
void Scope4x() { src(2, 203004, 10); }
|
|
void Scope8x() { src(2, 203005, 10); }
|
|
void CantedSight() { src(2, 203018, 10); }
|
|
void PistolExtMag() { src(2, 204001, 10); }
|
|
void PistolQuickMag() { src(2, 204002, 10); }
|
|
void PistolQuickExtMag() { src(2, 204003, 10); }
|
|
void SMGExtMag() { src(2, 204004, 10); }
|
|
void SMGQuickMag() { src(2, 204005, 10); }
|
|
void SMGQuickExtMag() { src(2, 204006, 10); }
|
|
void SniperExtMag() { src(2, 204007, 10); }
|
|
void SniperQuickMag() { src(2, 204008, 10); }
|
|
void SniperQuickExtMag() { src(2, 204009, 10); }
|
|
void ShotgunBulletLoops() { src(2, 204010, 10); }
|
|
void RifleExtMag() { src(2, 204011, 10); }
|
|
void RifleQuickMag() { src(2, 204012, 10); }
|
|
void RifleQuickExtMag() { src(2, 204013, 10); }
|
|
void Kar98kLoops() { src(2, 204014, 10); }
|
|
void UziStock() { src(2, 205001, 10); }
|
|
void TacticalStock() { src(2, 205002, 10); }
|
|
void CheekPad() { src(2, 205003, 10); }
|
|
void Quiver() { src(2, 205004, 10); }
|
|
|
|
// 装备 - 类型5 (正确)
|
|
void 夜视仪() { src(5, 504001, 10); }
|
|
void HelmetLv3() { src(5, 502003, 10); }
|
|
void VestLv3() { src(5, 503003, 10); }
|
|
void BackpackLv3() { src(5, 501003, 10); }
|
|
void HelmetLv1() { src(5, 502001, 10); }
|
|
void VestLv1() { src(5, 503001, 10); }
|
|
void BackpackLv1() { src(5, 501004, 10); }
|
|
void HelmetLv2() { src(5, 502002, 10); }
|
|
void VestLv2() { src(5, 503002, 10); }
|
|
void BackpackLv2() { src(5, 501005, 10); }
|
|
void ChristmasHelmet() { src(5, 502010, 10); }
|
|
void BeastHelmet() { src(5, 502011, 10); }
|
|
// 一键刷新所有皮肤武器
|
|
void SpawnAllSkinWeapons() {
|
|
AKM_Chicheng(); AKM_Rongyao(); AKM_Wumujinwen(); AKM_Xueshi(); AKM_Kuangnu();
|
|
M16A4_Shachi(); M16A4_Qingyao(); M16A4_Wumujinwen(); M16A4_Huangshi(); M16A4_Yezhanyinghao(); M16A4_Kuangnu();
|
|
SCARL_Kuangnu(); SCARL_Wumujinwen(); SCARL_Chicheng(); SCARL_Xueshi();
|
|
M416_RocketGirl(); M416_Wumujinwen(); M416_Kuishe(); M416_Tuya(); M416_Shamofengbao();
|
|
M416_Xueyumicai(); M416_Xueshi(); M416_Chicheng(); M416_Spring();
|
|
GROZA_Kuangnu(); AUG_Xueshi();
|
|
UZI_Kuangnu();
|
|
Kar98K_Qingyao(); Kar98K_Wumujinwen(); Kar98K_FireLava(); Kar98K_Xueshi(); Kar98K_Spring();
|
|
AWM_Rainbow();
|
|
|
|
Crowbar_RedNight();
|
|
Pan_DoubleYolk(); Pan_Survivor(); Pan_NoHunt(); Pan_RottenTomato(); Pan_Forward(); Pan_RedNight();
|
|
}
|
|
void 一键生成其他物资() {
|
|
红包(); 坚甲(); 利爪(); 小鸡(); 鸡蛋(); 南瓜(); 圣诞糖果();
|
|
西瓜(); 小黄鸭(); 鸡(); 机密情报(); 鸡神奖杯(); 胜利手雷(); 足球();
|
|
震爆弹(); 烟雾弹(); 燃烧瓶(); 破片手榴弹(); 跳舞弹(); 大春雷();
|
|
鞭炮(); 小春雷(); 南瓜炸弹(); 苹果(); 圣诞玩具苹果(); 圣诞小鸡玩偶();
|
|
冬季雪球(); 肾上腺素(); Q币(); 现金(); 点券(); 新年爆竹(); 新年烟花();
|
|
}
|
|
void 一键生成衣服() {
|
|
雪地吉利服();晶翼女神套装();黑曜金尊套装();火箭少女101();至尊金龙外套();S1战斗裤();S1战斗外套();小黄衣();小黄裤();黑色连帽大衣();西部牛仔面罩();黑色rock();青色面罩();师傅专用外套();时尚圣诞套装();卡路里套装();
|
|
}
|
|
|
|
void 一键生成动作() {
|
|
你好(); 感谢(); 鼓掌(); 大笑(); 来这里(); 走(); 否定(); 肯定(); 投降(); 愤怒();
|
|
摇摆舞(); 电摇(); 拍灰舞(); 俄舞(); 安静(); 雀跃(); 崩溃(); 海带舞(); 街舞();
|
|
壁虎步(); 机械舞(); 蹦迪舞(); 斗舞(); 秀舞(); 拉票舞(); 创造舞(); 三连赞();
|
|
桑巴舞(); 空翻(); 摇篮舞(); 摆裙舞(); 吃鸡舞(); 抖胸舞(); 青蛙舞(); 甩手舞();
|
|
点赞(); 欢庆舞(); 螃蟹舞(); 快看我(); 难过(); 扭秧歌(); 拜年男(); 红包来了();
|
|
拜年女();
|
|
}
|
|
void SpawnAllWeapons() {
|
|
AKM(); M416(); AWM(); M16A4(); GROZA(); SCARL(); AUG(); QBZ(); M762(); MK47(); G36C();
|
|
UZI(); UMP9(); Vector(); Thompson(); PP19(); Kar98K(); M24(); SKS(); VSS(); Mini14();
|
|
Mk14(); Win94(); SLR(); QBU(); S686(); S1897(); S12K(); M249(); DP28(); P92(); P1911();
|
|
R1895(); P18C(); R45(); SawedOff(); SignalGun(); Scorpion(); ChickenFirework(); LanternFirework();
|
|
BeastSignal(); ChristmasSignal(); NewYearFirework(); Crossbow(); RiotShield(); Machete();
|
|
Crowbar(); Sickle(); Pan();
|
|
}
|
|
|
|
void SpawnAllAmmo() {
|
|
Bullet762(); Bullet556(); ShotgunShell(); Bullet45(); Magnum(); SignalFlare();
|
|
Arrow(); FireworkShell(); BeastBait();
|
|
}
|
|
|
|
void SpawnAllAttachments() {
|
|
Choke(); SMGCompensator(); SniperCompensator(); SMGFlashHider(); SniperFlashHider();
|
|
SMGSilencer(); SniperSilencer(); PistolSilencer(); RifleCompensator(); RifleFlashHider();
|
|
RifleSilencer(); Duckbill(); AngledGrip(); VerticalGrip(); LightGrip(); HalfGrip();
|
|
ThumbGrip(); LaserSight(); RedDot(); Holographic(); Scope2x(); Scope4x(); Scope8x();
|
|
CantedSight(); PistolExtMag(); PistolQuickMag(); PistolQuickExtMag(); SMGExtMag();
|
|
SMGQuickMag(); SMGQuickExtMag(); SniperExtMag(); SniperQuickMag(); SniperQuickExtMag();
|
|
ShotgunBulletLoops(); RifleExtMag(); RifleQuickMag(); RifleQuickExtMag(); Kar98kLoops();
|
|
UziStock(); TacticalStock(); CheekPad(); Quiver();
|
|
}
|
|
|
|
void SpawnAllGear() {
|
|
HelmetLv3(); VestLv3(); BackpackLv3(); HelmetLv1(); VestLv1(); BackpackLv1(); 夜视仪();
|
|
HelmetLv2(); VestLv2(); BackpackLv2(); ChristmasHelmet(); BeastHelmet();
|
|
}
|
|
|
|
|
|
|
|
|
|
void SpawnItemM1(){
|
|
获取对象();
|
|
FTransform Transform;
|
|
if(PlayerCharacter){
|
|
Transform=PlayerCharacter->GetTransform();
|
|
}
|
|
AActor*Actor=UGameplayStatics::BeginSpawningActorFromClass(GetWorld(),APickUpWrapperActor::StaticClass(),Transform,true,UGameplayStatics::GetPlayerController(GetWorld(),0));
|
|
APickUpWrapperActor*PickUpActor=reinterpret_cast<APickUpWrapperActor*>(Actor);
|
|
if(PickUpActor){
|
|
FItemDefineID Item;
|
|
Item.Type=5;
|
|
Item.TypeSpecificID=501003;
|
|
PickUpActor->SetDefineID(Item);
|
|
PickUpActor->SetCountOnServerAfterSpawn(1);
|
|
PickUpActor->ShowMesh(true);
|
|
PickUpActor->ShowActor();
|
|
UGameplayStatics::FinishSpawningActor(PickUpActor,Transform);
|
|
PickUpActor->RegisterToPlayerPickUpList();
|
|
}
|
|
}
|
|
void SpawnItemM2(){
|
|
获取对象();
|
|
FTransform Transform;
|
|
if(PlayerCharacter){
|
|
Transform=PlayerCharacter->GetTransform();
|
|
}
|
|
AActor*Actor=UGameplayStatics::BeginSpawningActorFromClass(GetWorld(),APickUpWrapperActor::StaticClass(),Transform,true,UGameplayStatics::GetPlayerController(GetWorld(),0));
|
|
APickUpWrapperActor*PickUpActor=reinterpret_cast<APickUpWrapperActor*>(Actor);
|
|
if(PickUpActor){
|
|
FItemDefineID Item;
|
|
Item.Type=5;
|
|
Item.TypeSpecificID=502003;
|
|
PickUpActor->SetDefineID(Item);
|
|
PickUpActor->SetCountOnServerAfterSpawn(1);
|
|
PickUpActor->ShowMesh(true);
|
|
PickUpActor->ShowActor();
|
|
UGameplayStatics::FinishSpawningActor(PickUpActor,Transform);
|
|
PickUpActor->RegisterToPlayerPickUpList();
|
|
}
|
|
}
|
|
void SpawnItemM3(){
|
|
获取对象();
|
|
FTransform Transform;
|
|
if(PlayerCharacter){
|
|
Transform=PlayerCharacter->GetTransform();
|
|
}
|
|
AActor*Actor=UGameplayStatics::BeginSpawningActorFromClass(GetWorld(),APickUpWrapperActor::StaticClass(),Transform,true,UGameplayStatics::GetPlayerController(GetWorld(),0));
|
|
APickUpWrapperActor*PickUpActor=reinterpret_cast<APickUpWrapperActor*>(Actor);
|
|
if(PickUpActor){
|
|
FItemDefineID Item;
|
|
Item.Type=5;
|
|
Item.TypeSpecificID=503003;
|
|
PickUpActor->SetDefineID(Item);
|
|
PickUpActor->SetCountOnServerAfterSpawn(1);
|
|
PickUpActor->ShowMesh(true);
|
|
PickUpActor->ShowActor();
|
|
UGameplayStatics::FinishSpawningActor(PickUpActor,Transform);
|
|
PickUpActor->RegisterToPlayerPickUpList();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void SpawnItemM4(){
|
|
获取对象();
|
|
FTransform Transform;
|
|
if(PlayerCharacter){
|
|
Transform=PlayerCharacter->GetTransform();
|
|
}
|
|
AActor*Actor=UGameplayStatics::BeginSpawningActorFromClass(GetWorld(),APickUpWrapperActor::StaticClass(),Transform,true,UGameplayStatics::GetPlayerController(GetWorld(),0));
|
|
APickUpWrapperActor*PickUpActor=reinterpret_cast<APickUpWrapperActor*>(Actor);
|
|
if(PickUpActor){
|
|
FItemDefineID Item;
|
|
Item.Type=5;
|
|
Item.TypeSpecificID=404049;
|
|
PickUpActor->SetDefineID(Item);
|
|
PickUpActor->SetCountOnServerAfterSpawn(1);
|
|
PickUpActor->ShowMesh(true);
|
|
PickUpActor->ShowActor();
|
|
UGameplayStatics::FinishSpawningActor(PickUpActor,Transform);
|
|
PickUpActor->RegisterToPlayerPickUpList();
|
|
}
|
|
}
|
|
void SpawnItemM5(){
|
|
获取对象();
|
|
FTransform Transform;
|
|
if(PlayerCharacter){
|
|
Transform=PlayerCharacter->GetTransform();
|
|
}
|
|
AActor*Actor=UGameplayStatics::BeginSpawningActorFromClass(GetWorld(),APickUpWrapperActor::StaticClass(),Transform,true,UGameplayStatics::GetPlayerController(GetWorld(),0));
|
|
APickUpWrapperActor*PickUpActor=reinterpret_cast<APickUpWrapperActor*>(Actor);
|
|
if(PickUpActor){
|
|
FItemDefineID Item;
|
|
Item.Type=5;
|
|
Item.TypeSpecificID=403124;
|
|
PickUpActor->SetDefineID(Item);
|
|
PickUpActor->SetCountOnServerAfterSpawn(1);
|
|
PickUpActor->ShowMesh(true);
|
|
PickUpActor->ShowActor();
|
|
UGameplayStatics::FinishSpawningActor(PickUpActor,Transform);
|
|
PickUpActor->RegisterToPlayerPickUpList();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void SpawnItemM6(){
|
|
获取对象();
|
|
FTransform Transform;
|
|
if(PlayerCharacter){
|
|
Transform=PlayerCharacter->GetTransform();
|
|
}
|
|
AActor*Actor=UGameplayStatics::BeginSpawningActorFromClass(GetWorld(),APickUpWrapperActor::StaticClass(),Transform,true,UGameplayStatics::GetPlayerController(GetWorld(),0));
|
|
APickUpWrapperActor*PickUpActor=reinterpret_cast<APickUpWrapperActor*>(Actor);
|
|
if(PickUpActor){
|
|
FItemDefineID Item;
|
|
Item.Type=5;
|
|
Item.TypeSpecificID=404001;
|
|
PickUpActor->SetDefineID(Item);
|
|
PickUpActor->SetCountOnServerAfterSpawn(1);
|
|
PickUpActor->ShowMesh(true);
|
|
PickUpActor->ShowActor();
|
|
UGameplayStatics::FinishSpawningActor(PickUpActor,Transform);
|
|
PickUpActor->RegisterToPlayerPickUpList();
|
|
}
|
|
}
|
|
void SpawnItemM7(){
|
|
获取对象();
|
|
FTransform Transform;
|
|
if(PlayerCharacter){
|
|
Transform=PlayerCharacter->GetTransform();
|
|
}
|
|
AActor*Actor=UGameplayStatics::BeginSpawningActorFromClass(GetWorld(),APickUpWrapperActor::StaticClass(),Transform,true,UGameplayStatics::GetPlayerController(GetWorld(),0));
|
|
APickUpWrapperActor*PickUpActor=reinterpret_cast<APickUpWrapperActor*>(Actor);
|
|
if(PickUpActor){
|
|
FItemDefineID Item;
|
|
Item.Type=5;
|
|
Item.TypeSpecificID=403017;
|
|
PickUpActor->SetDefineID(Item);
|
|
PickUpActor->SetCountOnServerAfterSpawn(1);
|
|
PickUpActor->ShowMesh(true);
|
|
PickUpActor->ShowActor();
|
|
UGameplayStatics::FinishSpawningActor(PickUpActor,Transform);
|
|
PickUpActor->RegisterToPlayerPickUpList();
|
|
}
|
|
}
|
|
void SpawnItem(int ItemID,int ItemType,int Count){
|
|
获取对象();
|
|
FTransform Transform;
|
|
if(PlayerCharacter){
|
|
Transform=PlayerCharacter->GetTransform();
|
|
}
|
|
AActor*Actor=UGameplayStatics::BeginSpawningActorFromClass(GetWorld(),APickUpWrapperActor::StaticClass(),Transform,true,UGameplayStatics::GetPlayerController(GetWorld(),0));
|
|
APickUpWrapperActor*PickUpActor=reinterpret_cast<APickUpWrapperActor*>(Actor);
|
|
if(PickUpActor){
|
|
FItemDefineID Item;
|
|
Item.Type=ItemType;
|
|
Item.TypeSpecificID=ItemID;
|
|
PickUpActor->SetDefineID(Item);
|
|
PickUpActor->SetCountOnServerAfterSpawn(Count);
|
|
PickUpActor->ShowMesh(true);
|
|
PickUpActor->ShowActor();
|
|
UGameplayStatics::FinishSpawningActor(PickUpActor,Transform);
|
|
PickUpActor->RegisterToPlayerPickUpList();
|
|
}
|
|
}
|
|
|
|
static bool bDisableTimeouts = false; // 默认启用超时检查
|
|
// 判断物品类型
|
|
int getItemType(int itemID) {
|
|
// 1. 货币类 (1000-1015)
|
|
if (itemID >= 1000 && itemID <= 1015) {
|
|
return 100;
|
|
}
|
|
|
|
// 2. 武器类 (101001-108004)
|
|
if ((itemID >= 101001 && itemID <= 101010) || // 突击步枪
|
|
(itemID >= 102001 && itemID <= 102006) || // 冲锋枪
|
|
(itemID >= 103001 && itemID <= 103010) || // 狙击枪
|
|
(itemID >= 104001 && itemID <= 104003) || // 霰弹枪
|
|
(itemID >= 105001 && itemID <= 105002) || // 轻机枪
|
|
(itemID >= 106001 && itemID <= 106099) || // 手枪/特殊武器
|
|
(itemID == 107001 || itemID == 107003) || // 十字弩/防爆盾
|
|
(itemID >= 108001 && itemID <= 108004)) { // 近战武器
|
|
return 1;
|
|
}
|
|
|
|
// 3. 配件类 (201001-296008)
|
|
if ((itemID >= 201001 && itemID <= 201012) || // 枪口类
|
|
(itemID >= 202001 && itemID <= 202007) || // 握把类
|
|
(itemID >= 203001 && itemID <= 203018) || // 瞄准镜类
|
|
(itemID >= 203104 && itemID <= 203104) || // 瞄准镜测试皮肤
|
|
(itemID >= 204001 && itemID <= 204014) || // 弹匣类
|
|
(itemID >= 205001 && itemID <= 205006) || // 枪托类
|
|
(itemID >= 281002 && itemID <= 281004) || // 默认枪口
|
|
(itemID >= 283009 && itemID <= 283010) || // 默认枪口
|
|
(itemID >= 291001 && itemID <= 291010) || // 突击步枪默认弹匣
|
|
(itemID >= 292001 && itemID <= 292006) || // 冲锋枪默认弹匣
|
|
(itemID >= 293002 && itemID <= 293010) || // 狙击枪默认弹匣
|
|
(itemID >= 294003 && itemID <= 294003) || // 霰弹枪默认弹匣
|
|
(itemID >= 295001 && itemID <= 295002) || // 轻机枪默认弹匣
|
|
(itemID >= 296001 && itemID <= 296008)) { // 手枪默认弹匣
|
|
return 2;
|
|
}
|
|
|
|
// 4. 弹药类 (301001-308003)
|
|
if ((itemID >= 301001 && itemID <= 301001) || // 9毫米子弹
|
|
(itemID >= 302001 && itemID <= 302001) || // 7.62毫米子弹
|
|
(itemID >= 303001 && itemID <= 303001) || // 5.56毫米子弹
|
|
(itemID >= 304001 && itemID <= 304001) || // 12口径霰弹
|
|
(itemID >= 305001 && itemID <= 305001) || // .45口径子弹
|
|
(itemID >= 306001 && itemID <= 306001) || // .300马格南子弹
|
|
(itemID >= 307001 && itemID <= 307001) || // 弩箭
|
|
(itemID >= 308001 && itemID <= 308003)) { // 信号弹/烟花弹/年兽诱饵弹
|
|
return 3;
|
|
}
|
|
|
|
// 5. 服装类 (400997-407008)
|
|
if ((itemID >= 400997 && itemID <= 400999) || // 创建头
|
|
(itemID >= 401003 && itemID <= 401992) || // 帽子类
|
|
(itemID >= 401992001 && itemID <= 401992002) || // 经典脸型体验
|
|
(itemID >= 401993 && itemID <= 401999) || // 经典脸型
|
|
(itemID >= 402001 && itemID <= 402079) || // 面罩/眼镜类
|
|
(itemID >= 403001 && itemID <= 403999) || // 上衣类
|
|
(itemID >= 404001 && itemID <= 404231) || // 下装类
|
|
(itemID >= 405001 && itemID <= 405999) || // 鞋子类
|
|
(itemID >= 406001 && itemID <= 406003) || // 头发
|
|
(itemID >= 406001001 && itemID <= 406001002) || // 经典发型体验
|
|
(itemID >= 40601001 && itemID <= 40606009) || // 经典发型
|
|
(itemID >= 407001001 && itemID <= 407001004) || // 脸涂
|
|
(itemID >= 407008 && itemID <= 407008) || // 海岛探路者墨镜
|
|
(itemID >= 410001 && itemID <= 410039) || // 套装类
|
|
(itemID >= 453001 && itemID <= 455003) || // 合作定制服装
|
|
(itemID >= 473001 && itemID <= 474009)) { // 腾讯系定制服装
|
|
return 4;
|
|
}
|
|
|
|
// 6. 防具/背包类 (501001-504001)
|
|
if ((itemID >= 501001 && itemID <= 501006) || // 背包
|
|
(itemID >= 502001 && itemID <= 502011) || // 头盔
|
|
(itemID >= 503001 && itemID <= 503003) || // 防弹衣
|
|
(itemID >= 504001 && itemID <= 504001)) { // 夜视仪
|
|
return 5;
|
|
}
|
|
|
|
// 7. 消耗品类 (601001-603001)
|
|
if ((itemID >= 601001 && itemID <= 601061) || // 医疗品
|
|
(itemID >= 602001 && itemID <= 602014) || // 投掷物
|
|
(itemID >= 603001 && itemID <= 603001)) { // 燃料
|
|
return 6;
|
|
}
|
|
|
|
// 8. 降落伞类 (701001-703086)
|
|
if ((itemID >= 701001 && itemID <= 703086)) {
|
|
return 4; // 注意:根据文件,降落伞也是类型4
|
|
}
|
|
|
|
// 9. 宝箱/礼盒类 (1500001-1601002)
|
|
if ((itemID >= 1500001 && itemID <= 1500008) || // 军备宝箱
|
|
(itemID >= 1501001 && itemID <= 1514005) || // 各种宝箱礼盒
|
|
(itemID == 1601002)) { // 改名卡
|
|
return 15;
|
|
}
|
|
|
|
// 10. 道具/材料类 (1601001-1602118)
|
|
if ((itemID == 1601001) || // 重置角色卡
|
|
(itemID >= 1602001 && itemID <= 1602118)) { // 各种道具材料
|
|
return 16;
|
|
}
|
|
|
|
// 11. 军团相关 (1701001)
|
|
if (itemID == 1701001) {
|
|
return 17;
|
|
}
|
|
|
|
// 12. 头像框类 (2001001-2004308)
|
|
if ((itemID >= 2001001 && itemID <= 2004308)) {
|
|
return 20;
|
|
}
|
|
|
|
// 13. 经验/金币卡类 (2101001-2105001)
|
|
if ((itemID >= 2101001 && itemID <= 2105001)) {
|
|
return 21;
|
|
}
|
|
|
|
// 14. 表情动作类 (2200101-2204401)
|
|
if ((itemID >= 2200101 && itemID <= 2204401)) {
|
|
return 22;
|
|
}
|
|
|
|
// 15. 时效房卡类 (2401001-2408055)
|
|
if ((itemID >= 2401001 && itemID <= 2408055)) {
|
|
return 24;
|
|
}
|
|
|
|
// 16. 模式体验房卡类 (2501001-2508001)
|
|
if ((itemID >= 2501001 && itemID <= 2508001)) {
|
|
return 25;
|
|
}
|
|
|
|
// 17. 特效道具类 (3000301-3002002)
|
|
if ((itemID >= 3000301 && itemID <= 3002002)) {
|
|
return 30;
|
|
}
|
|
|
|
// 18. 浇水完成证明类 (4001001-4001006)
|
|
if ((itemID >= 4001001 && itemID <= 4001006)) {
|
|
return 40;
|
|
}
|
|
|
|
// 19. 武器皮肤配件 (100100101-200400103)
|
|
if ((itemID >= 100100101 && itemID <= 100400903) || // M416等武器皮肤配件
|
|
(itemID >= 200200101 && itemID <= 200400103)) { // UMP9等武器皮肤配件
|
|
return 2; // 注意:根据文件,这些是类型2
|
|
}
|
|
|
|
// 20. 武器皮肤 (101001001-108004006)
|
|
if ((itemID >= 101001001 && itemID <= 108004006)) {
|
|
return 1; // 注意:根据文件,武器皮肤是类型1
|
|
}
|
|
|
|
// 默认返回1(武器类)
|
|
return 1;
|
|
}
|
|
|
|
|
|
#undef bind // fix NDK macro collision with std::bind
|
|
using namespace std;
|
|
#define STATUS_ICON_RUNNING "◆" // 实心圆
|
|
#define STATUS_ICON_STOPPED "◆" // 空心圆
|
|
static std::mutex g_log_mutex;
|
|
static std::vector<std::string> g_log_lines;
|
|
static const size_t MAX_LOG_LINES = 2000;
|
|
static bool g_AutoWeaponFixEnabled = false;
|
|
static bool enabled = false;
|
|
class UDPProxy {
|
|
public:
|
|
UDPProxy(int local_port=7788, const std::string &target_ip="192.168.100.100", int target_port=7777)
|
|
: local_port(local_port), target_ip(target_ip), target_port(target_port),
|
|
sockfd(-1), running(false) {}
|
|
|
|
~UDPProxy() { stop(); }
|
|
|
|
bool start() {
|
|
if (running.load()) return true;
|
|
|
|
// 创建新的日志文件名
|
|
{
|
|
auto now = std::chrono::system_clock::now();
|
|
std::time_t t = std::chrono::system_clock::to_time_t(now);
|
|
std::tm tm = *std::localtime(&t);
|
|
char buf[128];
|
|
snprintf(buf, sizeof(buf), "/sdcard/udp_proxy_%04d%02d%02d_%02d%02d%02d.log",
|
|
tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
|
|
logfile = buf;
|
|
}
|
|
|
|
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
|
|
if (sockfd < 0) {
|
|
push_log(std::string("[ERR] socket create failed: ") + strerror(errno));
|
|
return false;
|
|
}
|
|
int opt = 1;
|
|
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
|
|
|
sockaddr_in addr;
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = INADDR_ANY;
|
|
addr.sin_port = htons(local_port);
|
|
|
|
if (::bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
|
|
push_log(std::string("[ERR] bind failed: ") + strerror(errno));
|
|
close(sockfd);
|
|
sockfd = -1;
|
|
return false;
|
|
}
|
|
|
|
// 日志头
|
|
{
|
|
std::ofstream f(logfile, std::ios::app);
|
|
if (f.is_open()) {
|
|
f << std::string(80, '=') << "\nUDP代理日志 - 启动时间: " << timestamp_date()
|
|
<< "\n监听端口: " << local_port << " -> 目标地址: " << target_ip << ":" << target_port
|
|
<< "\n" << std::string(80, '=') << "\n\n";
|
|
}
|
|
}
|
|
|
|
running.store(true);
|
|
worker = std::thread(&UDPProxy::recv_loop, this);
|
|
push_log(std::string("[启动] 监听 ") + std::to_string(local_port) + " -> 转发到 " +
|
|
target_ip + ":" + std::to_string(target_port));
|
|
return true;
|
|
}
|
|
|
|
void stop() {
|
|
if (!running.load()) return;
|
|
running.store(false);
|
|
if (sockfd != -1) { close(sockfd); sockfd = -1; }
|
|
if (worker.joinable()) worker.join();
|
|
push_log(std::string("[STOP] 服务端已关闭"));
|
|
}
|
|
|
|
bool is_running() const { return running.load(); }
|
|
|
|
void get_logs(std::vector<std::string>& out) {
|
|
std::lock_guard<std::mutex> lk(log_mutex);
|
|
out = logs;
|
|
}
|
|
|
|
private:
|
|
int local_port;
|
|
std::string target_ip;
|
|
int target_port;
|
|
int sockfd;
|
|
std::atomic<bool> running;
|
|
std::thread worker;
|
|
|
|
std::mutex log_mutex;
|
|
std::vector<std::string> logs;
|
|
|
|
std::mutex clients_mutex;
|
|
std::map<std::pair<std::string,int>, long long> client_timestamps;
|
|
|
|
std::string logfile;
|
|
|
|
// 时间函数
|
|
static std::string timestamp_ms() {
|
|
using namespace std::chrono;
|
|
auto now = system_clock::now();
|
|
auto ms = duration_cast<milliseconds>(now.time_since_epoch()) % 1000;
|
|
auto t = system_clock::to_time_t(now);
|
|
std::tm tm = *std::localtime(&t);
|
|
char buf[64];
|
|
snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%03lld", tm.tm_hour, tm.tm_min, tm.tm_sec, (long long)ms.count());
|
|
return buf;
|
|
}
|
|
|
|
static std::string timestamp_date() {
|
|
auto t = std::time(nullptr);
|
|
std::tm tm = *std::localtime(&t);
|
|
char buf[64];
|
|
snprintf(buf, sizeof(buf), "%04d-%02d-%02d %02d:%02d:%02d", tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
|
|
tm.tm_hour, tm.tm_min, tm.tm_sec);
|
|
return buf;
|
|
}
|
|
|
|
static std::string to_hex(const uint8_t *buf, size_t len) {
|
|
std::ostringstream oss;
|
|
oss << std::hex << std::setfill('0');
|
|
for (size_t i = 0; i < len; ++i) oss << std::setw(2) << (int)(buf[i] & 0xFF);
|
|
return oss.str();
|
|
}
|
|
|
|
void push_log(const std::string &s) {
|
|
std::lock_guard<std::mutex> lk(log_mutex);
|
|
logs.push_back(s);
|
|
if (logs.size() > 2000) logs.erase(logs.begin(), logs.begin() + (logs.size() - 2000));
|
|
std::ofstream f(logfile, std::ios::app);
|
|
if (f.is_open()) f << s << "\n";
|
|
}
|
|
|
|
void push_packet_log(const std::string &direction, double delay, ssize_t len, const std::string &ip, int port, const std::string &hexdata) {
|
|
std::ostringstream ss;
|
|
ss << timestamp_ms() << " | " << direction << " | 延迟: " << std::fixed << std::setprecision(2)
|
|
<< delay << "ms | 长度: " << len << "字节 | 地址: " << ip << ":" << port;
|
|
push_log(ss.str());
|
|
push_log("原始数据: " + hexdata);
|
|
}
|
|
|
|
void recv_loop() {
|
|
while (running.load()) {
|
|
sockaddr_in addr;
|
|
socklen_t addrlen = sizeof(addr);
|
|
uint8_t buf[65536];
|
|
ssize_t len = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr*)&addr, &addrlen);
|
|
if (len < 0) {
|
|
if (!running.load()) break;
|
|
push_log(std::string("[ERR] recvfrom failed: ") + strerror(errno));
|
|
continue;
|
|
}
|
|
|
|
auto t0 = std::chrono::high_resolution_clock::now();
|
|
char ipbuf[64];
|
|
inet_ntop(AF_INET, &addr.sin_addr, ipbuf, sizeof(ipbuf));
|
|
int port = ntohs(addr.sin_port);
|
|
bool from_client = (port != target_port);
|
|
|
|
if (from_client) {
|
|
{
|
|
std::lock_guard<std::mutex> lk(clients_mutex);
|
|
client_timestamps[{ipbuf, port}] =
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
std::chrono::system_clock::now().time_since_epoch()).count();
|
|
}
|
|
|
|
sockaddr_in target{};
|
|
target.sin_family = AF_INET;
|
|
inet_pton(AF_INET, target_ip.c_str(), &target.sin_addr);
|
|
target.sin_port = htons(target_port);
|
|
sendto(sockfd, buf, len, 0, (struct sockaddr*)&target, sizeof(target));
|
|
|
|
auto t1 = std::chrono::high_resolution_clock::now();
|
|
double delay = std::chrono::duration<double, std::milli>(t1 - t0).count();
|
|
push_packet_log("客户端->服务端", delay, len, ipbuf, port, to_hex(buf, len));
|
|
} else {
|
|
std::pair<std::string,int> latest;
|
|
bool found = false;
|
|
{
|
|
std::lock_guard<std::mutex> lk(clients_mutex);
|
|
long long best = 0;
|
|
for (auto &kv : client_timestamps) {
|
|
if (kv.second > best) { best = kv.second; latest = kv.first; found = true; }
|
|
}
|
|
}
|
|
if (found) {
|
|
sockaddr_in dst{};
|
|
dst.sin_family = AF_INET;
|
|
inet_pton(AF_INET, latest.first.c_str(), &dst.sin_addr);
|
|
dst.sin_port = htons(latest.second);
|
|
sendto(sockfd, buf, len, 0, (struct sockaddr*)&dst, sizeof(dst));
|
|
|
|
auto t1 = std::chrono::high_resolution_clock::now();
|
|
double delay = std::chrono::duration<double, std::milli>(t1 - t0).count();
|
|
push_packet_log("服务端->客户端", delay, len, latest.first, latest.second, to_hex(buf, len));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
static UDPProxy* g_proxy = nullptr;
|
|
|
|
extern "C" bool StartPacketProxy() {
|
|
if (g_proxy && g_proxy->is_running()) return true;
|
|
g_proxy = new UDPProxy();
|
|
return g_proxy->start();
|
|
}
|
|
|
|
extern "C" void StopPacketProxy() {
|
|
if (g_proxy) {
|
|
g_proxy->stop();
|
|
delete g_proxy;
|
|
g_proxy = nullptr;
|
|
}
|
|
}
|
|
|
|
extern "C" void GetPacketProxyLogs(std::vector<std::string>& out) {
|
|
if (g_proxy) g_proxy->get_logs(out);
|
|
}
|
|
|
|
|
|
|
|
///===========抓包结束=========///
|
|
inline void push_log(const string &s) {
|
|
std::lock_guard<std::mutex> lk(g_log_mutex);
|
|
if (g_log_lines.size() >= MAX_LOG_LINES) {
|
|
g_log_lines.erase(g_log_lines.begin(), g_log_lines.begin() + (g_log_lines.size() - MAX_LOG_LINES + 1));
|
|
}
|
|
g_log_lines.push_back(s);
|
|
std::cout << s << std::endl;
|
|
}
|
|
|
|
static string bytes_to_hex_lower(const uint8_t *buf, size_t len) {
|
|
std::ostringstream oss;
|
|
oss << std::hex << std::setfill('0');
|
|
for (size_t i = 0; i < len; ++i) {
|
|
oss << std::setw(2) << (int)(buf[i] & 0xFF);
|
|
}
|
|
string s = oss.str();
|
|
std::transform(s.begin(), s.end(), s.begin(), ::tolower);
|
|
return s;
|
|
}
|
|
|
|
static vector<uint8_t> hex_to_bytes(const string &hex_in) {
|
|
string filtered;
|
|
filtered.reserve(hex_in.size());
|
|
for (char c : hex_in) {
|
|
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) filtered.push_back(c);
|
|
}
|
|
if (filtered.size() % 2 == 1) filtered.insert(filtered.begin(), '0');
|
|
vector<uint8_t> out;
|
|
out.reserve(filtered.size()/2);
|
|
for (size_t i = 0; i + 1 < filtered.size(); i += 2) {
|
|
unsigned int byte = 0;
|
|
std::stringstream ss;
|
|
ss << std::hex << filtered.substr(i,2);
|
|
ss >> byte;
|
|
out.push_back(static_cast<uint8_t>(byte & 0xFF));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static string current_time_str() {
|
|
using namespace std::chrono;
|
|
auto now = system_clock::now();
|
|
auto t = system_clock::to_time_t(now);
|
|
std::tm tm = *std::localtime(&t);
|
|
char buf[64];
|
|
auto ms = duration_cast<milliseconds>(now.time_since_epoch()).count() % 1000;
|
|
std::snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%03lld", tm.tm_hour, tm.tm_min, tm.tm_sec, (long long)ms);
|
|
return string(buf);
|
|
}
|
|
|
|
|
|
class GameUDPProxy {
|
|
private:
|
|
// 成员变量
|
|
int listen_port;
|
|
std::string target_host;
|
|
int target_port;
|
|
std::string log_file;
|
|
std::atomic<bool> running; // 改为 atomic
|
|
int sockfd;
|
|
sockaddr_in client_address;
|
|
std::thread worker; // 添加线程成员
|
|
|
|
// 响应数据存储
|
|
std::vector<std::vector<uint8_t>> response_data;
|
|
size_t response_index;
|
|
std::string current_map;
|
|
|
|
public:
|
|
GameUDPProxy(int listen_port=7788, const std::string &target_host="127.0.0.1",
|
|
int target_port=7777, const std::string &log_file="")
|
|
: listen_port(listen_port), target_host(target_host), target_port(target_port),
|
|
log_file(log_file), running(false), sockfd(-1), response_index(0), current_map("无")
|
|
{
|
|
// 构造函数中不自动加载数据
|
|
}
|
|
|
|
~GameUDPProxy() {
|
|
stop();
|
|
}
|
|
|
|
bool start() {
|
|
if (running.load()) return true;
|
|
|
|
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
|
|
if (sockfd < 0) {
|
|
push_log(std::string("[错误] 套接字创建失败: ") + strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
int opt = 1;
|
|
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
|
|
|
sockaddr_in addr;
|
|
memset(&addr, 0, sizeof(addr));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_addr.s_addr = INADDR_ANY;
|
|
addr.sin_port = htons(listen_port);
|
|
|
|
if (::bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
|
|
push_log(std::string("[错误] 绑定失败: ") + strerror(errno));
|
|
close(sockfd);
|
|
sockfd = -1;
|
|
return false;
|
|
}
|
|
|
|
running.store(true);
|
|
|
|
// 启动日志头
|
|
push_log(repeat_string("=", 80));
|
|
push_log(std::string("UDP代理启动 - 时间: ") + get_current_time());
|
|
push_log(std::string("监听端口: ") + std::to_string(listen_port) + " -> 目标地址: " + target_host + ":" + std::to_string(target_port));
|
|
push_log(std::string("当前地图数据: ") + current_map);
|
|
push_log(repeat_string("=", 80));
|
|
push_log("代理服务已启动,等待客户端连接...");
|
|
|
|
// 在新线程中运行
|
|
worker = std::thread(&GameUDPProxy::run, this);
|
|
|
|
return true;
|
|
}
|
|
|
|
void stop() {
|
|
running.store(false);
|
|
if (sockfd != -1) {
|
|
close(sockfd);
|
|
sockfd = -1;
|
|
}
|
|
if (worker.joinable()) {
|
|
worker.join();
|
|
}
|
|
push_log("代理服务已停止");
|
|
}
|
|
|
|
bool is_running() const {
|
|
return running.load();
|
|
}
|
|
|
|
// 设置地图数据
|
|
bool set_map_data(const std::string& map_choice) {
|
|
std::map<std::string, std::string> map_files = {
|
|
{"1", "训练场"},
|
|
{"2", "海岛"},
|
|
{"3", "雪地"},
|
|
{"4", "雨林"},
|
|
{"5", "沙漠"}
|
|
};
|
|
|
|
if (map_files.find(map_choice) != map_files.end()) {
|
|
current_map = map_files[map_choice];
|
|
|
|
// 根据选择设置对应的数据
|
|
const char* log_data = nullptr;
|
|
if (map_choice == "1") {
|
|
log_data = TRAINING_LOG_DATA;
|
|
} else if (map_choice == "2") {
|
|
log_data = ISLAND_LOG_DATA;
|
|
} else if (map_choice == "3") {
|
|
log_data = SNOW_LOG_DATA;
|
|
}else if (map_choice == "4") {
|
|
log_data = YULINH_LOG_DATA;
|
|
}else if (map_choice == "5") {
|
|
log_data = SANOP_LOG_DATA;
|
|
}
|
|
|
|
response_data.clear(); // 清空原有数据
|
|
response_index = 0;
|
|
|
|
if (log_data) {
|
|
load_response_data_from_string(log_data);
|
|
|
|
// 立即在日志中显示地图切换信息
|
|
std::ostringstream ss;
|
|
ss << "已切换到地图: " << current_map << " (数据条数: " << response_data.size() << ")";
|
|
push_log(ss.str());
|
|
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::string get_current_map() const {
|
|
return current_map;
|
|
}
|
|
|
|
private:
|
|
// 从字符串加载响应数据
|
|
void load_response_data_from_string(const char* log_content) {
|
|
std::istringstream iss(log_content);
|
|
std::string line;
|
|
|
|
// 解析服务端->客户端的数据
|
|
std::vector<std::string> lines;
|
|
while (std::getline(iss, line)) {
|
|
lines.push_back(line);
|
|
}
|
|
|
|
for (size_t i = 0; i < lines.size(); i++) {
|
|
if (lines[i].find("服务端->客户端") != std::string::npos && i + 1 < lines.size()) {
|
|
if (lines[i + 1].find("原始数据:") != std::string::npos) {
|
|
size_t pos = lines[i + 1].find("原始数据: ");
|
|
if (pos != std::string::npos) {
|
|
std::string hex_data = lines[i + 1].substr(pos + 10);
|
|
// 移除可能的空格
|
|
hex_data.erase(std::remove(hex_data.begin(), hex_data.end(), ' '), hex_data.end());
|
|
|
|
try {
|
|
auto bytes = hex_to_bytes(hex_data);
|
|
if (!bytes.empty()) {
|
|
response_data.push_back(bytes);
|
|
}
|
|
} catch (...) {
|
|
push_log("[错误] 解析十六进制数据失败: " + hex_data);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 根据客户端数据获取对应的服务端响应
|
|
std::vector<uint8_t> get_response_for_client(const std::vector<uint8_t>& client_data) {
|
|
if (response_data.empty()) {
|
|
// 如果没有预加载的数据,返回默认响应
|
|
return hex_to_bytes("19137909a95516a2a40ba6feb8f8c91c94d7388ee999a66004");
|
|
}
|
|
|
|
// 简单策略:轮询返回预加载的响应数据
|
|
response_index = (response_index + 1) % response_data.size();
|
|
return response_data[response_index];
|
|
}
|
|
|
|
void run() {
|
|
while (running.load()) {
|
|
sockaddr_in client_addr;
|
|
socklen_t client_len = sizeof(client_addr);
|
|
uint8_t buffer[4096];
|
|
|
|
// 设置超时,避免阻塞无法退出
|
|
struct timeval tv;
|
|
tv.tv_sec = 1;
|
|
tv.tv_usec = 0;
|
|
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
|
|
|
ssize_t len = recvfrom(sockfd, buffer, sizeof(buffer), 0,
|
|
(struct sockaddr*)&client_addr, &client_len);
|
|
|
|
if (len < 0) {
|
|
if (!running.load()) break;
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
continue;
|
|
}
|
|
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
|
push_log(std::string("[错误] 接收数据失败: ") + strerror(errno));
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (len == 0) {
|
|
continue;
|
|
}
|
|
|
|
client_address = client_addr;
|
|
|
|
// 记录客户端消息
|
|
std::vector<uint8_t> data(buffer, buffer + len);
|
|
log_packet("客户端->服务端", 0.24, len, client_addr, data);
|
|
|
|
// 处理客户端请求并发送响应
|
|
handle_client_request(data, client_addr);
|
|
}
|
|
}
|
|
|
|
void handle_client_request(const std::vector<uint8_t>& client_data, const sockaddr_in& client_addr) {
|
|
try {
|
|
// 获取对应的服务端响应
|
|
auto response_data_vec = get_response_for_client(client_data);
|
|
|
|
// 模拟网络延迟
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(45));
|
|
|
|
// 发送响应
|
|
ssize_t sent = sendto(sockfd, response_data_vec.data(), response_data_vec.size(), 0,
|
|
(struct sockaddr*)&client_addr, sizeof(client_addr));
|
|
|
|
if (sent > 0) {
|
|
// 记录服务端响应
|
|
log_packet("服务端->客户端", 0.33, response_data_vec.size(), client_addr, response_data_vec);
|
|
} else {
|
|
push_log(std::string("[错误] 发送响应失败: ") + strerror(errno));
|
|
}
|
|
|
|
} catch (const std::exception& e) {
|
|
push_log(std::string("[错误] 处理客户端请求失败: ") + e.what());
|
|
}
|
|
}
|
|
|
|
// 记录数据包日志
|
|
void log_packet(const std::string& direction, double delay, size_t len,
|
|
const sockaddr_in& addr, const std::vector<uint8_t>& data) {
|
|
char ipbuf[64];
|
|
inet_ntop(AF_INET, &addr.sin_addr, ipbuf, sizeof(ipbuf));
|
|
int port = ntohs(addr.sin_port);
|
|
|
|
std::ostringstream ss;
|
|
ss << current_time_str() << " | " << direction << " | 延迟: "
|
|
<< std::fixed << std::setprecision(2) << delay << "ms | 长度: "
|
|
<< len << "字节 | 地址: " << ipbuf << ":" << port;
|
|
push_log(ss.str());
|
|
push_log("原始数据: " + bytes_to_hex_lower(data.data(), data.size()));
|
|
push_log(repeat_string("-", 60));
|
|
}
|
|
|
|
// 获取当前时间字符串(带毫秒)
|
|
std::string current_time_str() {
|
|
auto now = std::chrono::system_clock::now();
|
|
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
now.time_since_epoch()) % 1000;
|
|
auto t = std::chrono::system_clock::to_time_t(now);
|
|
std::tm tm = *std::localtime(&t);
|
|
char buf[64];
|
|
std::snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%03lld",
|
|
tm.tm_hour, tm.tm_min, tm.tm_sec, (long long)ms.count());
|
|
return std::string(buf);
|
|
}
|
|
|
|
// 获取当前日期时间字符串
|
|
std::string get_current_time() {
|
|
auto now = std::chrono::system_clock::now();
|
|
std::time_t t = std::chrono::system_clock::to_time_t(now);
|
|
std::tm tm = *std::localtime(&t);
|
|
char buf[64];
|
|
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm);
|
|
return std::string(buf);
|
|
}
|
|
|
|
// 字符串重复辅助函数
|
|
std::string repeat_string(const std::string& str, int n) {
|
|
std::string result;
|
|
for (int i = 0; i < n; ++i) {
|
|
result += str;
|
|
}
|
|
return result;
|
|
}
|
|
};
|
|
|
|
// 全局代理实例
|
|
static GameUDPProxy* g_proxy_server = nullptr;
|
|
|
|
// 启动代理服务
|
|
extern "C" bool StartEmbeddedUDPServer() {
|
|
if (g_proxy_server && g_proxy_server->is_running()) return true;
|
|
|
|
try {
|
|
// 如果还没有创建实例,先创建
|
|
if (!g_proxy_server) {
|
|
g_proxy_server = new GameUDPProxy(7788, "127.0.0.1", 7777, "");
|
|
}
|
|
return g_proxy_server->start();
|
|
} catch (...) {
|
|
push_log("[错误] 启动嵌入式服务器异常");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 停止代理服务
|
|
extern "C" void StopEmbeddedUDPServer() {
|
|
try {
|
|
if (g_proxy_server) {
|
|
g_proxy_server->stop();
|
|
delete g_proxy_server;
|
|
g_proxy_server = nullptr;
|
|
}
|
|
} catch (...) {
|
|
push_log("[错误] 停止嵌入式服务器异常");
|
|
}
|
|
}
|
|
|
|
// 设置地图数据
|
|
extern "C" bool SetProxyMapData(const std::string& map_choice) {
|
|
// 确保代理实例存在
|
|
if (!g_proxy_server) {
|
|
g_proxy_server = new GameUDPProxy(7788, "127.0.0.1", 7777, "");
|
|
}
|
|
return g_proxy_server->set_map_data(map_choice);
|
|
}
|
|
|
|
// 获取当前地图
|
|
extern "C" std::string GetProxyCurrentMap() {
|
|
if (g_proxy_server) {
|
|
return g_proxy_server->get_current_map();
|
|
}
|
|
return "无";
|
|
}
|
|
|
|
// 获取代理日志
|
|
extern "C" void GetEmbeddedLogs(std::vector<std::string>& out_lines) {
|
|
std::lock_guard<std::mutex> lk(g_log_mutex);
|
|
out_lines = g_log_lines;
|
|
}
|
|
|
|
|
|
// ==== Original main.cpp content follows ====
|
|
|
|
// ====================================================
|
|
// 车辆修复专用日志系统 - 独立于现有日志系统
|
|
// ====================================================
|
|
|
|
namespace VehicleFixLog {
|
|
static std::vector<std::string> LogEntries;
|
|
static std::mutex LogMutex;
|
|
static const int MAX_LOG_ENTRIES = 500;
|
|
|
|
// 添加日志
|
|
void AddLog(const std::string& message) {
|
|
std::lock_guard<std::mutex> lock(LogMutex);
|
|
|
|
// 获取当前时间 - 修复编译错误
|
|
time_t now = time(0);
|
|
struct tm* tstruct = localtime(&now);
|
|
if (tstruct) {
|
|
char buf[80];
|
|
strftime(buf, sizeof(buf), "[%H:%M:%S] ", tstruct);
|
|
std::string fullMessage = std::string(buf) + message;
|
|
LogEntries.push_back(fullMessage);
|
|
} else {
|
|
LogEntries.push_back(message);
|
|
}
|
|
|
|
// 限制日志数量
|
|
if (LogEntries.size() > MAX_LOG_ENTRIES) {
|
|
LogEntries.erase(LogEntries.begin());
|
|
}
|
|
}
|
|
|
|
// 清空日志
|
|
void ClearLogs() {
|
|
std::lock_guard<std::mutex> lock(LogMutex);
|
|
LogEntries.clear();
|
|
}
|
|
|
|
// 获取日志副本
|
|
std::vector<std::string> GetLogs() {
|
|
std::lock_guard<std::mutex> lock(LogMutex);
|
|
return LogEntries;
|
|
}
|
|
|
|
// 获取最近N条日志
|
|
std::vector<std::string> GetRecentLogs(int count) {
|
|
std::lock_guard<std::mutex> lock(LogMutex);
|
|
if (LogEntries.size() <= count) {
|
|
return LogEntries;
|
|
}
|
|
return std::vector<std::string>(LogEntries.end() - count, LogEntries.end());
|
|
}
|
|
}
|
|
|
|
// 车辆修复日志宏
|
|
#define VEHICLE_LOG(...) \
|
|
do { \
|
|
char buffer[512]; \
|
|
snprintf(buffer, sizeof(buffer), __VA_ARGS__); \
|
|
VehicleFixLog::AddLog(buffer); \
|
|
} while(0)
|
|
|
|
// 错误处理函数
|
|
void HandleVehicleFixError(const std::string& errorMessage,
|
|
SDK::ASTExtraBaseCharacter* Char = nullptr,
|
|
SDK::ASTExtraVehicleBase* Vehicle = nullptr)
|
|
{
|
|
std::string errorLog = "[错误] ";
|
|
errorLog += errorMessage;
|
|
|
|
if (Char) {
|
|
errorLog += " | 角色: ";
|
|
if (Char->PlayerState) {
|
|
errorLog += Char->PlayerState->PlayerName.ToString();
|
|
} else {
|
|
errorLog += "未知";
|
|
}
|
|
}
|
|
|
|
if (Vehicle) {
|
|
char addrBuffer[20];
|
|
snprintf(addrBuffer, sizeof(addrBuffer), "%p", Vehicle);
|
|
errorLog += " | 车辆地址: ";
|
|
errorLog += addrBuffer;
|
|
}
|
|
|
|
VEHICLE_LOG("%s", errorLog.c_str());
|
|
}
|
|
|
|
// ====================================================
|
|
// 车辆修复系统数据结构
|
|
// ====================================================
|
|
|
|
struct PlayerInfo
|
|
{
|
|
SDK::APlayerState* PS;
|
|
std::string Name;
|
|
int UID;
|
|
SDK::ASTExtraBaseCharacter* Character;
|
|
|
|
// --- 车辆信息 ---
|
|
bool bInVehicle = false;
|
|
SDK::ASTExtraVehicleBase* Vehicle = nullptr;
|
|
int VehicleSeatIdx = -1;
|
|
};
|
|
|
|
std::vector<PlayerInfo> RegisteredPlayers;
|
|
|
|
// 状态跟踪
|
|
std::map<SDK::APlayerController*, int> LastSeatIndices;
|
|
std::map<SDK::APlayerController*, SDK::ASTExtraVehicleBase*> LastVehicles;
|
|
std::map<SDK::APlayerController*, bool> PlayerInVehicleState;
|
|
|
|
// ====================================================
|
|
// 辅助函数
|
|
// ====================================================
|
|
|
|
// 辅助函数:构建座位Socket名称
|
|
std::string GetSeatSocketName(int SeatIndex)
|
|
{
|
|
if (SeatIndex == 0)
|
|
return "seat_driver";
|
|
else
|
|
return "seat_passenger_" + std::to_string(SeatIndex);
|
|
}
|
|
|
|
inline std::string GetObjectClassNameEx(UObject* Object)
|
|
{
|
|
if (!Object) return "NULL_OBJ";
|
|
if (!Object->ClassPrivate) return "NULL_CLASS";
|
|
|
|
try
|
|
{
|
|
return Object->ClassPrivate->NamePrivate.GetName();
|
|
}
|
|
catch (...)
|
|
{
|
|
return "ERR";
|
|
}
|
|
}
|
|
|
|
// ====================================================
|
|
// 玩家管理函数
|
|
// ====================================================
|
|
|
|
void RegisterPlayer(SDK::APlayerState* PS, SDK::ASTExtraBaseCharacter* C)
|
|
{
|
|
PlayerInfo info;
|
|
info.PS = PS;
|
|
info.Name = PS->PlayerName.ToString();
|
|
info.UID = PS->PlayerID;
|
|
info.Character = C;
|
|
info.Vehicle = C->CurrentVehicle;
|
|
info.VehicleSeatIdx = C->VehicleSeatIdx;
|
|
info.bInVehicle = (C->CurrentVehicle != nullptr);
|
|
|
|
RegisteredPlayers.push_back(info);
|
|
|
|
VEHICLE_LOG("[玩家管理] 注册玩家: %s (UID: %d) | 车辆: %s | 座位: %d",
|
|
info.Name.c_str(),
|
|
info.UID,
|
|
info.bInVehicle ? "是" : "否",
|
|
info.VehicleSeatIdx);
|
|
}
|
|
|
|
bool IsHostPlayer(SDK::APlayerState* PS, SDK::UWorld* World)
|
|
{
|
|
if (!PS || !World) return false;
|
|
|
|
auto* GI = World->OwningGameInstance;
|
|
if (!GI || GI->LocalPlayers.Num() == 0) return false;
|
|
|
|
auto* LocalPC = GI->LocalPlayers[0]->PlayerController;
|
|
if (!LocalPC) return false;
|
|
|
|
return PS->Owner == LocalPC;
|
|
}
|
|
|
|
SDK::ASTExtraBaseCharacter* GetCharacterFromController(SDK::APlayerController* PC)
|
|
{
|
|
if (!PC) {
|
|
VEHICLE_LOG("[角色获取] 控制器为空指针");
|
|
return nullptr;
|
|
}
|
|
|
|
static SDK::ASTExtraPlayerController* CachedSTPC = nullptr;
|
|
static SDK::ASTExtraBaseCharacter* CachedCharacter = nullptr;
|
|
|
|
if (CachedSTPC == PC && CachedCharacter)
|
|
return CachedCharacter;
|
|
|
|
SDK::ASTExtraPlayerController* STPC = nullptr;
|
|
if (PC->IsA(SDK::ASTExtraPlayerController::StaticClass()))
|
|
{
|
|
STPC = (SDK::ASTExtraPlayerController*)PC;
|
|
if (STPC->STExtraBaseCharacter)
|
|
{
|
|
CachedSTPC = STPC;
|
|
CachedCharacter = STPC->STExtraBaseCharacter;
|
|
return CachedCharacter;
|
|
}
|
|
}
|
|
|
|
SDK::ASTExtraBaseCharacter* fromUtil = SDK::USTExtraVehicleUtils::GetCharacter(STPC ? STPC : (SDK::ASTExtraPlayerController*)PC);
|
|
if (fromUtil)
|
|
{
|
|
CachedSTPC = STPC;
|
|
CachedCharacter = fromUtil;
|
|
return fromUtil;
|
|
}
|
|
|
|
if (PC->Pawn && PC->Pawn->IsA(SDK::ASTExtraBaseCharacter::StaticClass()))
|
|
{
|
|
CachedSTPC = STPC;
|
|
CachedCharacter = (SDK::ASTExtraBaseCharacter*)PC->Pawn;
|
|
return CachedCharacter;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool IsKnown(SDK::APlayerState* ps)
|
|
{
|
|
for (size_t i = 0; i < RegisteredPlayers.size(); i++)
|
|
{
|
|
if (!RegisteredPlayers[i].PS)
|
|
continue;
|
|
|
|
if (RegisteredPlayers[i].PS == ps)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline float GetLocalTime()
|
|
{
|
|
static auto start_time = std::chrono::high_resolution_clock::now();
|
|
auto now = std::chrono::high_resolution_clock::now();
|
|
return std::chrono::duration<float>(now - start_time).count();
|
|
}
|
|
|
|
// ====================================================
|
|
// 车辆调试函数
|
|
// ====================================================
|
|
|
|
void DebugVehicleReplication(SDK::ASTExtraVehicleBase* Vehicle)
|
|
{
|
|
if (!Vehicle) {
|
|
VEHICLE_LOG("[车辆调试] 车辆为空指针");
|
|
return;
|
|
}
|
|
|
|
VEHICLE_LOG("[车辆调试] 地址: %p | 引擎启动: %d | 反作弊: %d | 座位数量: %d",
|
|
Vehicle,
|
|
Vehicle->bIsEngineStarted,
|
|
Vehicle->bEnableAntiCheat,
|
|
(Vehicle->VehicleSeats ? Vehicle->VehicleSeats->SeatOccupiers.Num() : 0));
|
|
}
|
|
|
|
// ====================================================
|
|
// 服务端车辆修复逻辑
|
|
// ====================================================
|
|
|
|
void ProcessServerVehicleFix(SDK::ASTExtraPlayerController* STEPC,
|
|
SDK::ASTExtraBaseCharacter* Char,
|
|
SDK::ASTExtraVehicleBase* State_Vehicle)
|
|
{
|
|
if (!STEPC) {
|
|
HandleVehicleFixError("STEPC为空指针");
|
|
return;
|
|
}
|
|
|
|
if (!Char) {
|
|
HandleVehicleFixError("角色为空指针");
|
|
return;
|
|
}
|
|
|
|
if (!State_Vehicle) {
|
|
HandleVehicleFixError("车辆为空指针", Char);
|
|
return;
|
|
}
|
|
|
|
if (!STEPC->VehicleUserComp) {
|
|
HandleVehicleFixError("VehicleUserComp为空指针", Char, State_Vehicle);
|
|
return;
|
|
}
|
|
|
|
// 确保VehicleUserComp的Character指向正确的角色
|
|
if (!STEPC->VehicleUserComp->Character && Char)
|
|
{
|
|
STEPC->VehicleUserComp->Character = (SDK::ASTExtraPlayerCharacter*)Char;
|
|
VEHICLE_LOG("[服务端修复] 设置VehicleUserComp角色");
|
|
}
|
|
|
|
/* // 调试信息:打印当前车辆状态
|
|
VEHICLE_LOG("[服务端修复] 玩家: %s | 车辆状态: %d | 当前座位: %d | 引擎已启动: %d",
|
|
Char->PlayerState ? Char->PlayerState->PlayerName.ToString().c_str() : "未知",
|
|
(int)STEPC->VehicleUserComp->VehicleUserState,
|
|
Char->VehicleSeatIdx,
|
|
State_Vehicle->bIsEngineStarted);*/
|
|
|
|
// 主要车辆修复逻辑
|
|
if (STEPC->VehicleUserComp->VehicleUserState == SDK::ESTExtraVehicleUserState::ESTExtraVehicleUserState__EVUS_AsDriver &&
|
|
State_Vehicle->VehicleSeats &&
|
|
State_Vehicle->VehicleSeats->SeatOccupiers.Num() > 0)
|
|
{
|
|
// 检查驾驶员座位是否为空
|
|
SDK::ASTExtraPlayerCharacter* CurrentDriver = State_Vehicle->VehicleSeats->SeatOccupiers[0];
|
|
|
|
if (CurrentDriver == nullptr)
|
|
{
|
|
// 驾驶员座位为空,执行完整的上车流程
|
|
VEHICLE_LOG("[服务端修复] 驾驶员座位为空,开始修复...");
|
|
|
|
// 1. 请求进入车辆
|
|
STEPC->VehicleUserComp->ReqEnterVehicle(State_Vehicle, SDK::ESTExtraVehicleSeatType::ESTExtraVehicleSeatType__ESeatType_DriversSeat);
|
|
|
|
// 2. 设置座位占用者
|
|
State_Vehicle->VehicleSeats->SeatOccupiers[0] = (SDK::ASTExtraPlayerCharacter*)Char;
|
|
|
|
// 3. 启动车辆引擎并禁用反作弊
|
|
State_Vehicle->bIsEngineStarted = true;
|
|
State_Vehicle->bEnableAntiCheat = false;
|
|
|
|
// 4. 调用车辆座位附加处理函数
|
|
State_Vehicle->HandleOnSeatAttached(
|
|
(SDK::ASTExtraPlayerCharacter*)Char,
|
|
SDK::ESTExtraVehicleSeatType::ESTExtraVehicleSeatType__ESeatType_DriversSeat,
|
|
0
|
|
);
|
|
|
|
// 5. 更新角色状态
|
|
Char->ForcePlayerUpdateAnimation();
|
|
Char->VehicleSeatIdx = 0;
|
|
Char->bWasOnVehicle = true;
|
|
Char->CurrentVehicle = State_Vehicle;
|
|
|
|
// 6. 强制网络更新
|
|
Char->ForceNetUpdate();
|
|
State_Vehicle->ForceNetUpdate();
|
|
|
|
VEHICLE_LOG("[服务端修复] 驾驶员座位已修复并占用");
|
|
}
|
|
else if (CurrentDriver != (SDK::ASTExtraPlayerCharacter*)Char)
|
|
{
|
|
// 驾驶员座位已被其他角色占用,处理座位冲突
|
|
VEHICLE_LOG("[服务端修复] 驾驶员座位已被其他玩家占用,尝试寻找空座位...");
|
|
|
|
// 寻找其他空座位
|
|
bool bFoundEmptySeat = false;
|
|
for (int seatIdx = 1; seatIdx < State_Vehicle->VehicleSeats->SeatOccupiers.Num(); seatIdx++)
|
|
{
|
|
if (State_Vehicle->VehicleSeats->SeatOccupiers[seatIdx] == nullptr)
|
|
{
|
|
// 找到空座位,作为乘客上车
|
|
STEPC->VehicleUserComp->ReqEnterVehicle(
|
|
State_Vehicle,
|
|
SDK::ESTExtraVehicleSeatType::ESTExtraVehicleSeatType__ESeatType_PassengersSeat
|
|
);
|
|
|
|
State_Vehicle->VehicleSeats->SeatOccupiers[seatIdx] = (SDK::ASTExtraPlayerCharacter*)Char;
|
|
|
|
// 调用乘客座位附加处理
|
|
State_Vehicle->HandleOnSeatAttached(
|
|
(SDK::ASTExtraPlayerCharacter*)Char,
|
|
SDK::ESTExtraVehicleSeatType::ESTExtraVehicleSeatType__ESeatType_PassengersSeat,
|
|
seatIdx
|
|
);
|
|
|
|
Char->ForcePlayerUpdateAnimation();
|
|
Char->VehicleSeatIdx = seatIdx;
|
|
Char->bWasOnVehicle = true;
|
|
Char->CurrentVehicle = State_Vehicle;
|
|
|
|
Char->ForceNetUpdate();
|
|
State_Vehicle->ForceNetUpdate();
|
|
|
|
VEHICLE_LOG("[服务端修复] 玩家已放置到乘客座位 %d", seatIdx);
|
|
bFoundEmptySeat = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bFoundEmptySeat)
|
|
{
|
|
VEHICLE_LOG("[服务端修复] 没有可用空座位");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 当前角色已经是驾驶员,确保状态正确
|
|
Char->CurrentVehicle = State_Vehicle;
|
|
Char->bWasOnVehicle = true;
|
|
Char->ForceNetUpdate();
|
|
State_Vehicle->ForceNetUpdate();
|
|
VEHICLE_LOG("[服务端修复] 玩家已经是驾驶员,状态已更新");
|
|
}
|
|
}
|
|
else if (STEPC->VehicleUserComp->VehicleUserState == SDK::ESTExtraVehicleUserState::ESTExtraVehicleUserState__EVUS_ASPassenger &&
|
|
State_Vehicle->VehicleSeats &&
|
|
State_Vehicle->VehicleSeats->SeatOccupiers.Num() > 0)
|
|
{
|
|
// 乘客状态逻辑
|
|
bool bIsInVehicle = false;
|
|
int currentSeatIdx = -1;
|
|
|
|
// 检查角色是否在车辆座位上
|
|
for (int seatIdx = 0; seatIdx < State_Vehicle->VehicleSeats->SeatOccupiers.Num(); seatIdx++)
|
|
{
|
|
if (State_Vehicle->VehicleSeats->SeatOccupiers[seatIdx] == (SDK::ASTExtraPlayerCharacter*)Char)
|
|
{
|
|
bIsInVehicle = true;
|
|
currentSeatIdx = seatIdx;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bIsInVehicle)
|
|
{
|
|
// 角色在车辆上,确保状态正确
|
|
Char->VehicleSeatIdx = currentSeatIdx;
|
|
Char->bWasOnVehicle = true;
|
|
Char->CurrentVehicle = State_Vehicle;
|
|
Char->ForceNetUpdate();
|
|
VEHICLE_LOG("[服务端修复] 玩家在乘客座位 %d,状态已更新", currentSeatIdx);
|
|
}
|
|
else
|
|
{
|
|
// 角色不在车辆上,尝试作为乘客上车
|
|
VEHICLE_LOG("[服务端修复] 玩家不在车辆上,尝试作为乘客上车...");
|
|
for (int seatIdx = 1; seatIdx < State_Vehicle->VehicleSeats->SeatOccupiers.Num(); seatIdx++)
|
|
{
|
|
if (State_Vehicle->VehicleSeats->SeatOccupiers[seatIdx] == nullptr)
|
|
{
|
|
STEPC->VehicleUserComp->ReqEnterVehicle(
|
|
State_Vehicle,
|
|
SDK::ESTExtraVehicleSeatType::ESTExtraVehicleSeatType__ESeatType_PassengersSeat
|
|
);
|
|
|
|
State_Vehicle->VehicleSeats->SeatOccupiers[seatIdx] = (SDK::ASTExtraPlayerCharacter*)Char;
|
|
|
|
State_Vehicle->HandleOnSeatAttached(
|
|
(SDK::ASTExtraPlayerCharacter*)Char,
|
|
SDK::ESTExtraVehicleSeatType::ESTExtraVehicleSeatType__ESeatType_PassengersSeat,
|
|
seatIdx
|
|
);
|
|
|
|
Char->ForcePlayerUpdateAnimation();
|
|
Char->VehicleSeatIdx = seatIdx;
|
|
Char->bWasOnVehicle = true;
|
|
Char->CurrentVehicle = State_Vehicle;
|
|
|
|
Char->ForceNetUpdate();
|
|
State_Vehicle->ForceNetUpdate();
|
|
|
|
VEHICLE_LOG("[服务端修复] 玩家已作为乘客进入座位 %d", seatIdx);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 不在车辆上的状态处理 - 修复枚举错误
|
|
VEHICLE_LOG("[服务端修复] 玩家不在车辆上,状态: %d", (int)STEPC->VehicleUserComp->VehicleUserState);
|
|
if (Char->CurrentVehicle)
|
|
{
|
|
// 清理旧车辆座位
|
|
SDK::ASTExtraVehicleBase* OldVehicle = Char->CurrentVehicle;
|
|
int OldSeatIdx = Char->VehicleSeatIdx;
|
|
|
|
if (OldSeatIdx >= 0 && OldSeatIdx < OldVehicle->VehicleSeats->SeatOccupiers.Num())
|
|
{
|
|
if (OldVehicle->VehicleSeats->SeatOccupiers[OldSeatIdx] == (SDK::ASTExtraPlayerCharacter*)Char)
|
|
{
|
|
OldVehicle->VehicleSeats->SeatOccupiers[OldSeatIdx] = nullptr;
|
|
}
|
|
}
|
|
|
|
Char->CurrentVehicle = nullptr;
|
|
Char->VehicleSeatIdx = -1;
|
|
Char->bWasOnVehicle = false;
|
|
|
|
Char->ForceNetUpdate();
|
|
OldVehicle->ForceNetUpdate();
|
|
|
|
VEHICLE_LOG("[服务端修复] 玩家已从车辆移除");
|
|
}
|
|
}
|
|
}
|
|
|
|
// ====================================================
|
|
// 客户端车辆位置和视角修复
|
|
// ====================================================
|
|
|
|
void ProcessClientVehicleFix(SDK::ASTExtraPlayerController* STEPC,
|
|
SDK::ASTExtraBaseCharacter* Char,
|
|
SDK::ASTExtraVehicleBase* Vehicle,
|
|
int SeatIdx,
|
|
bool bIsLocalPlayer)
|
|
{
|
|
if (!STEPC) {
|
|
HandleVehicleFixError("STEPC为空指针");
|
|
return;
|
|
}
|
|
|
|
if (!Char) {
|
|
HandleVehicleFixError("角色为空指针");
|
|
return;
|
|
}
|
|
|
|
if (!Vehicle) {
|
|
HandleVehicleFixError("车辆为空指针", Char);
|
|
return;
|
|
}
|
|
|
|
bool bIsDriver = (SeatIdx == 0);
|
|
|
|
// 只有本地玩家才执行客户端特定的修复
|
|
if (bIsLocalPlayer)
|
|
{
|
|
VEHICLE_LOG("[客户端修复] 本地玩家 | 座位: %d | 是驾驶员: %s",
|
|
SeatIdx,
|
|
bIsDriver ? "是" : "否");
|
|
|
|
// 1. 角色位置和附加修复
|
|
if (Vehicle->RootComponent)
|
|
{
|
|
// 先分离角色(如果已附加)
|
|
Char->K2_DetachFromActor(
|
|
(SDK::EDetachmentRule)1,
|
|
(SDK::EDetachmentRule)1,
|
|
(SDK::EDetachmentRule)1);
|
|
|
|
// 使用K2_AttachToComponent将角色附加到车辆的根组件
|
|
SDK::FName SocketName = SDK::FName();
|
|
Char->K2_AttachToComponent(Vehicle->RootComponent, SocketName,
|
|
(SDK::EAttachmentRule)1,
|
|
(SDK::EAttachmentRule)1,
|
|
(SDK::EAttachmentRule)1,
|
|
false);
|
|
|
|
// 设置相对位置和旋转
|
|
SDK::FTransform RelativeTransform;
|
|
|
|
// 根据座位设置不同的相对位置
|
|
if (bIsDriver)
|
|
{
|
|
// 驾驶员位置:使用用户调节的偏移
|
|
RelativeTransform.Translation.X = 驾驶员X偏移; // 前方
|
|
RelativeTransform.Translation.Y = 驾驶员Y偏移; // 中心
|
|
RelativeTransform.Translation.Z = 驾驶员Z偏移; // 高度
|
|
}
|
|
else
|
|
{
|
|
// 乘客位置:根据座位索引调整
|
|
RelativeTransform.Translation.X = 乘客X偏移; // 稍微靠前
|
|
RelativeTransform.Translation.Y = (SeatIdx % 2 == 0 ? 乘客Y偏移 : -乘客Y偏移); // 左右交替
|
|
RelativeTransform.Translation.Z = 乘客Z偏移; // 高度
|
|
}
|
|
|
|
// 设置旋转
|
|
RelativeTransform.Rotation.X = 0.0f;
|
|
RelativeTransform.Rotation.Y = 0.0f;
|
|
RelativeTransform.Rotation.Z = 0.0f;
|
|
RelativeTransform.Rotation.W = 1.0f;
|
|
|
|
// 缩放
|
|
RelativeTransform.Scale3D.X = 1.0f;
|
|
RelativeTransform.Scale3D.Y = 1.0f;
|
|
RelativeTransform.Scale3D.Z = 1.0f;
|
|
|
|
// 应用相对变换
|
|
SDK::FHitResult SweepHitResult;
|
|
Char->K2_SetActorRelativeTransform(RelativeTransform, false, true, &SweepHitResult);
|
|
|
|
// 更新座位索引
|
|
Char->VehicleSeatIdx = SeatIdx;
|
|
|
|
VEHICLE_LOG("[客户端修复] 角色已附加到车辆,相对位置: (%.1f, %.1f, %.1f)",
|
|
RelativeTransform.Translation.X,
|
|
RelativeTransform.Translation.Y,
|
|
RelativeTransform.Translation.Z);
|
|
}
|
|
|
|
// 2. 控制权转移
|
|
if (Char->Controller)
|
|
{
|
|
if (bIsDriver)
|
|
{
|
|
// 驾驶员:控制权转移给车辆
|
|
Char->Controller->Possess(Vehicle);
|
|
|
|
// 设置摄像机跟随车辆
|
|
STEPC->SetViewTargetWithBlend(Vehicle, 0.2f,
|
|
(SDK::EViewTargetBlendFunction)0, 0.0f, false);
|
|
|
|
VEHICLE_LOG("[客户端修复] 控制权已转移给车辆");
|
|
}
|
|
else
|
|
{
|
|
// 乘客:控制权保持在角色
|
|
Char->Controller->Possess(Char);
|
|
|
|
// 设置摄像机跟随角色
|
|
STEPC->SetViewTargetWithBlend(Char, 0.2f,
|
|
(SDK::EViewTargetBlendFunction)0, 0.0f, false);
|
|
|
|
VEHICLE_LOG("[客户端修复] 控制权保持在角色");
|
|
}
|
|
}
|
|
|
|
// 3. 视角控制设置 - 修复视角滑动问题
|
|
STEPC->SetIgnoreLookInput(false); // 允许视角滑动
|
|
STEPC->ResetIgnoreLookInput(); // 重置视角输入
|
|
// STEPC->bIgnoreLookInput = false; // 直接设置成员变量
|
|
|
|
// 4. 鼠标控制设置
|
|
STEPC->bShowMouseCursor = false; // 隐藏鼠标光标
|
|
STEPC->bEnableClickEvents = false; // 禁用点击事件
|
|
STEPC->bEnableMouseOverEvents = false; // 禁用鼠标悬停事件
|
|
|
|
VEHICLE_LOG("[客户端修复] 视角控制已启用,鼠标控制已设置");
|
|
}
|
|
}
|
|
|
|
// ====================================================
|
|
// 客户端下车处理
|
|
// ====================================================
|
|
|
|
void ProcessClientExitVehicle(SDK::ASTExtraPlayerController* STEPC,
|
|
SDK::ASTExtraBaseCharacter* Char,
|
|
SDK::ASTExtraVehicleBase* OldVehicle,
|
|
int OldSeatIdx,
|
|
bool bIsLocalPlayer)
|
|
{
|
|
if (!STEPC) {
|
|
HandleVehicleFixError("STEPC为空指针");
|
|
return;
|
|
}
|
|
|
|
if (!Char) {
|
|
HandleVehicleFixError("角色为空指针");
|
|
return;
|
|
}
|
|
|
|
// 1. 分离角色
|
|
Char->K2_DetachFromActor(
|
|
(SDK::EDetachmentRule)1,
|
|
(SDK::EDetachmentRule)1,
|
|
(SDK::EDetachmentRule)1);
|
|
|
|
VEHICLE_LOG("[客户端修复] 角色已从车辆分离");
|
|
|
|
// 只有本地玩家需要处理控制权和摄像机
|
|
if (bIsLocalPlayer)
|
|
{
|
|
VEHICLE_LOG("[客户端修复] 本地玩家下车,原座位: %d", OldSeatIdx);
|
|
|
|
// 2. 恢复控制权到角色
|
|
if (Char->Controller)
|
|
{
|
|
Char->Controller->Possess(Char);
|
|
|
|
// 3. 设置摄像机跟随角色
|
|
STEPC->SetViewTargetWithBlend(Char, 0.2f,
|
|
(SDK::EViewTargetBlendFunction)0, 0.0f, false);
|
|
|
|
// 4. 重置视角控制 - 修复下车后视角问题
|
|
STEPC->SetIgnoreLookInput(false);
|
|
STEPC->ResetIgnoreLookInput();
|
|
// STEPC->bIgnoreLookInput = false;
|
|
|
|
// 5. 重置鼠标控制
|
|
STEPC->bShowMouseCursor = false;
|
|
STEPC->bEnableClickEvents = false;
|
|
STEPC->bEnableMouseOverEvents = false;
|
|
|
|
VEHICLE_LOG("[客户端修复] 控制权已恢复,摄像机已重置");
|
|
}
|
|
}
|
|
|
|
// 6. 清理座位占用
|
|
if (OldVehicle && OldVehicle->VehicleSeats &&
|
|
OldSeatIdx >= 0 && OldSeatIdx < OldVehicle->VehicleSeats->SeatOccupiers.Num())
|
|
{
|
|
if (OldVehicle->VehicleSeats->SeatOccupiers[OldSeatIdx] == (SDK::ASTExtraPlayerCharacter*)Char)
|
|
{
|
|
OldVehicle->VehicleSeats->SeatOccupiers[OldSeatIdx] = nullptr;
|
|
OldVehicle->ForceNetUpdate();
|
|
VEHICLE_LOG("[客户端修复] 原座位 %d 已清空", OldSeatIdx);
|
|
}
|
|
}
|
|
|
|
// 7. 强制下车请求
|
|
if (STEPC->VehicleUserComp)
|
|
{
|
|
SDK::FVector ClientVehicleVelocity = {0.0f, 0.0f, 0.0f};
|
|
STEPC->VehicleUserComp->ReqExitVehicle(ClientVehicleVelocity);
|
|
VEHICLE_LOG("[客户端修复] 发送下车请求");
|
|
}
|
|
|
|
// 8. 更新角色状态
|
|
Char->CurrentVehicle = nullptr;
|
|
Char->VehicleSeatIdx = -1;
|
|
Char->bWasOnVehicle = false;
|
|
Char->ForceNetUpdate();
|
|
|
|
VEHICLE_LOG("[客户端修复] 玩家已完全下车");
|
|
}
|
|
|
|
// ====================================================
|
|
// 客户端切换座位处理
|
|
// ====================================================
|
|
|
|
void ProcessClientSwitchSeat(SDK::ASTExtraPlayerController* STEPC,
|
|
SDK::ASTExtraBaseCharacter* Char,
|
|
SDK::ASTExtraVehicleBase* Vehicle,
|
|
int OldSeatIdx,
|
|
int NewSeatIdx,
|
|
bool bIsLocalPlayer)
|
|
{
|
|
if (!STEPC) {
|
|
HandleVehicleFixError("STEPC为空指针");
|
|
return;
|
|
}
|
|
|
|
if (!Char) {
|
|
HandleVehicleFixError("角色为空指针");
|
|
return;
|
|
}
|
|
|
|
if (!Vehicle) {
|
|
HandleVehicleFixError("车辆为空指针", Char);
|
|
return;
|
|
}
|
|
|
|
if (!STEPC->VehicleUserComp) {
|
|
HandleVehicleFixError("VehicleUserComp为空指针", Char, Vehicle);
|
|
return;
|
|
}
|
|
|
|
VEHICLE_LOG("[客户端修复] 切换座位 从 %d 到 %d", OldSeatIdx, NewSeatIdx);
|
|
|
|
// 1. 清理旧座位
|
|
if (OldSeatIdx >= 0 && OldSeatIdx < Vehicle->VehicleSeats->SeatOccupiers.Num())
|
|
{
|
|
if (Vehicle->VehicleSeats->SeatOccupiers[OldSeatIdx] == (SDK::ASTExtraPlayerCharacter*)Char)
|
|
{
|
|
Vehicle->VehicleSeats->SeatOccupiers[OldSeatIdx] = nullptr;
|
|
VEHICLE_LOG("[客户端修复] 旧座位 %d 已清空", OldSeatIdx);
|
|
}
|
|
}
|
|
|
|
// 2. 请求进入新座位
|
|
if (NewSeatIdx == 0)
|
|
{
|
|
// 切换到驾驶员座位
|
|
STEPC->VehicleUserComp->ReqEnterVehicle(
|
|
Vehicle,
|
|
SDK::ESTExtraVehicleSeatType::ESTExtraVehicleSeatType__ESeatType_DriversSeat
|
|
);
|
|
|
|
Vehicle->VehicleSeats->SeatOccupiers[0] = (SDK::ASTExtraPlayerCharacter*)Char;
|
|
Vehicle->HandleOnSeatAttached(
|
|
(SDK::ASTExtraPlayerCharacter*)Char,
|
|
SDK::ESTExtraVehicleSeatType::ESTExtraVehicleSeatType__ESeatType_DriversSeat,
|
|
0
|
|
);
|
|
|
|
VEHICLE_LOG("[客户端修复] 已切换到驾驶员座位");
|
|
}
|
|
else
|
|
{
|
|
// 切换到乘客座位
|
|
STEPC->VehicleUserComp->ReqEnterVehicle(
|
|
Vehicle,
|
|
SDK::ESTExtraVehicleSeatType::ESTExtraVehicleSeatType__ESeatType_PassengersSeat
|
|
);
|
|
|
|
Vehicle->VehicleSeats->SeatOccupiers[NewSeatIdx] = (SDK::ASTExtraPlayerCharacter*)Char;
|
|
Vehicle->HandleOnSeatAttached(
|
|
(SDK::ASTExtraPlayerCharacter*)Char,
|
|
SDK::ESTExtraVehicleSeatType::ESTExtraVehicleSeatType__ESeatType_PassengersSeat,
|
|
NewSeatIdx
|
|
);
|
|
|
|
VEHICLE_LOG("[客户端修复] 已切换到乘客座位 %d", NewSeatIdx);
|
|
}
|
|
|
|
// 3. 更新角色状态
|
|
Char->VehicleSeatIdx = NewSeatIdx;
|
|
Char->CurrentVehicle = Vehicle;
|
|
Char->bWasOnVehicle = true;
|
|
Char->ForcePlayerUpdateAnimation();
|
|
|
|
// 4. 强制网络更新
|
|
Char->ForceNetUpdate();
|
|
Vehicle->ForceNetUpdate();
|
|
|
|
// 5. 客户端特定的位置和视角修复
|
|
ProcessClientVehicleFix(STEPC, Char, Vehicle, NewSeatIdx, bIsLocalPlayer);
|
|
|
|
VEHICLE_LOG("[客户端修复] 座位切换完成");
|
|
}
|
|
|
|
|
|
// ====================================================
|
|
// 服务端逻辑
|
|
// ====================================================
|
|
|
|
void ProcessServerLogic(SDK::UWorld* World, SDK::AGameStateBase* GS)
|
|
{
|
|
auto& Players = GS->PlayerArray;
|
|
|
|
static float LastVehicleFixTime = 0.0f;
|
|
float CurrentTime = GetLocalTime();
|
|
if (CurrentTime - LastVehicleFixTime < 1.0f)
|
|
return;
|
|
LastVehicleFixTime = CurrentTime;
|
|
|
|
for (int i = 0; i < Players.Num(); i++)
|
|
{
|
|
SDK::APlayerState* PS = Players[i];
|
|
if (!PS) continue;
|
|
|
|
if (IsHostPlayer(PS, World)) continue;
|
|
if (!PS->Owner) continue;
|
|
|
|
SDK::ASTExtraPlayerController* STEPC = nullptr;
|
|
SDK::APlayerController* PC = (SDK::APlayerController*)PS->Owner;
|
|
|
|
if (PS->Owner->IsA(SDK::ASTExtraPlayerController::StaticClass()))
|
|
STEPC = (SDK::ASTExtraPlayerController*)PS->Owner;
|
|
else if (PC && PC->IsA(SDK::ASTExtraPlayerController::StaticClass()))
|
|
STEPC = (SDK::ASTExtraPlayerController*)PC;
|
|
if (!STEPC) continue;
|
|
|
|
SDK::ASTExtraBaseCharacter* Char = STEPC->STExtraBaseCharacter;
|
|
if (!Char)
|
|
Char = GetCharacterFromController(PC);
|
|
if (!Char) continue;
|
|
|
|
// 注册玩家
|
|
if (!IsKnown(PS))
|
|
RegisterPlayer(PS, Char);
|
|
|
|
// 服务端车辆修复逻辑
|
|
if (!STEPC->VehicleUserComp)
|
|
continue;
|
|
|
|
SDK::ASTExtraVehicleBase* State_Vehicle = nullptr;
|
|
|
|
if (STEPC->STExtraBaseCharacter && STEPC->STExtraBaseCharacter->CurrentVehicle)
|
|
{
|
|
State_Vehicle = STEPC->STExtraBaseCharacter->CurrentVehicle;
|
|
}
|
|
else if (PC->Pawn && PC->Pawn->IsA(SDK::ASTExtraVehicleBase::StaticClass()))
|
|
{
|
|
State_Vehicle = (SDK::ASTExtraVehicleBase*)PC->Pawn;
|
|
}
|
|
|
|
if (State_Vehicle)
|
|
{
|
|
ProcessServerVehicleFix(STEPC, Char, State_Vehicle);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ====================================================
|
|
// 客户端逻辑
|
|
// ====================================================
|
|
|
|
void ProcessClientLogic(SDK::UWorld* World, SDK::AGameStateBase* GS)
|
|
{
|
|
auto& Players = GS->PlayerArray;
|
|
|
|
static float LastClientUpdateTime = 0.0f;
|
|
float CurrentTime = GetLocalTime();
|
|
if (CurrentTime - LastClientUpdateTime < 0.1f)
|
|
return;
|
|
LastClientUpdateTime = CurrentTime;
|
|
|
|
// 获取本地玩家控制器
|
|
SDK::APlayerController* LocalPC = SDK::UGameplayStatics::GetPlayerController(World, 0);
|
|
if (!LocalPC) return;
|
|
|
|
for (int i = 0; i < Players.Num(); i++)
|
|
{
|
|
SDK::APlayerState* PS = Players[i];
|
|
if (!PS) continue;
|
|
|
|
if (!PS->Owner) continue;
|
|
|
|
SDK::ASTExtraPlayerController* STEPC = nullptr;
|
|
SDK::APlayerController* PC = (SDK::APlayerController*)PS->Owner;
|
|
|
|
if (PS->Owner->IsA(SDK::ASTExtraPlayerController::StaticClass()))
|
|
STEPC = (SDK::ASTExtraPlayerController*)PS->Owner;
|
|
else if (PC && PC->IsA(SDK::ASTExtraPlayerController::StaticClass()))
|
|
STEPC = (SDK::ASTExtraPlayerController*)PC;
|
|
if (!STEPC) continue;
|
|
|
|
SDK::ASTExtraBaseCharacter* Char = STEPC->STExtraBaseCharacter;
|
|
if (!Char)
|
|
Char = GetCharacterFromController(PC);
|
|
if (!Char) continue;
|
|
|
|
// 判断是否是本地玩家
|
|
bool bIsLocalPlayer = (PC == LocalPC);
|
|
|
|
// 客户端车辆控制逻辑
|
|
if (!STEPC->VehicleUserComp)
|
|
continue;
|
|
|
|
SDK::ASTExtraVehicleBase* Vehicle = Char->CurrentVehicle;
|
|
if (!Vehicle && PC->Pawn && PC->Pawn->IsA(SDK::ASTExtraVehicleBase::StaticClass()))
|
|
Vehicle = (SDK::ASTExtraVehicleBase*)PC->Pawn;
|
|
|
|
int SeatIdx = Char->VehicleSeatIdx;
|
|
int LastSeatIdx = -1;
|
|
bool bWasInVehicle = false;
|
|
|
|
// 获取上一次的座位索引和车辆状态
|
|
if (LastSeatIndices.find(PC) != LastSeatIndices.end())
|
|
{
|
|
LastSeatIdx = LastSeatIndices[PC];
|
|
}
|
|
if (PlayerInVehicleState.find(PC) != PlayerInVehicleState.end())
|
|
{
|
|
bWasInVehicle = PlayerInVehicleState[PC];
|
|
}
|
|
|
|
bool bCurrentlyInVehicle = (Vehicle != nullptr && SeatIdx >= 0);
|
|
|
|
// 处理状态变化
|
|
if (bCurrentlyInVehicle)
|
|
{
|
|
// 角色在车辆上
|
|
if (!bWasInVehicle)
|
|
{
|
|
// 新上车
|
|
VEHICLE_LOG("[客户端修复] 新上车,座位: %d", SeatIdx);
|
|
ProcessClientVehicleFix(STEPC, Char, Vehicle, SeatIdx, bIsLocalPlayer);
|
|
}
|
|
else if (LastSeatIdx != SeatIdx)
|
|
{
|
|
// 切换座位
|
|
VEHICLE_LOG("[客户端修复] 切换座位 从 %d 到 %d", LastSeatIdx, SeatIdx);
|
|
ProcessClientSwitchSeat(STEPC, Char, Vehicle, LastSeatIdx, SeatIdx, bIsLocalPlayer);
|
|
}
|
|
else
|
|
{
|
|
// 保持原座位,修复位置和视角
|
|
ProcessClientVehicleFix(STEPC, Char, Vehicle, SeatIdx, bIsLocalPlayer);
|
|
}
|
|
}
|
|
else if (bWasInVehicle)
|
|
{
|
|
// 刚下车
|
|
SDK::ASTExtraVehicleBase* LastVehicle = nullptr;
|
|
if (LastVehicles.find(PC) != LastVehicles.end())
|
|
{
|
|
LastVehicle = LastVehicles[PC];
|
|
}
|
|
|
|
VEHICLE_LOG("[客户端修复] 下车,原座位: %d", LastSeatIdx);
|
|
ProcessClientExitVehicle(STEPC, Char, LastVehicle, LastSeatIdx, bIsLocalPlayer);
|
|
}
|
|
|
|
// 更新状态记录
|
|
LastSeatIndices[PC] = SeatIdx;
|
|
PlayerInVehicleState[PC] = bCurrentlyInVehicle;
|
|
if (Vehicle)
|
|
{
|
|
LastVehicles[PC] = Vehicle;
|
|
}
|
|
else
|
|
{
|
|
// 确保不在车上时清空车辆记录
|
|
if (LastVehicles.find(PC) != LastVehicles.end())
|
|
{
|
|
LastVehicles.erase(PC);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// ====================================================
|
|
// 主逻辑处理函数
|
|
// ====================================================
|
|
|
|
void ProcessPlayers()
|
|
{
|
|
SDK::UWorld* World = GetWorld();
|
|
if (!World) {
|
|
VEHICLE_LOG("[主逻辑] 世界为空指针");
|
|
return;
|
|
}
|
|
|
|
SDK::AGameStateBase* GS = World->GameState;
|
|
if (!GS) {
|
|
VEHICLE_LOG("[主逻辑] 游戏状态为空指针");
|
|
return;
|
|
}
|
|
|
|
static int frameCounter = 0;
|
|
frameCounter++;
|
|
|
|
// 每100帧记录一次状态
|
|
if (frameCounter % 100 == 0) {
|
|
VEHICLE_LOG("[系统状态] 处理玩家数量: %d | 已注册玩家: %d",
|
|
GS->PlayerArray.Num(),
|
|
RegisteredPlayers.size());
|
|
}
|
|
|
|
// 获取本地玩家控制器
|
|
SDK::APlayerController* MyController = SDK::UGameplayStatics::GetPlayerController(World, 0);
|
|
|
|
if (MyController && MyController->HasAuthority()) {
|
|
// 服务端逻辑
|
|
VEHICLE_LOG("[网络模式] 当前运行于: 服务端模式");
|
|
ProcessServerLogic(World, GS);
|
|
} else {
|
|
// 客户端逻辑:执行服务端和客户端逻辑
|
|
VEHICLE_LOG("[网络模式] 当前运行于: 客户端模式");
|
|
ProcessServerLogic(World, GS); // 客户端也执行服务端逻辑
|
|
ProcessClientLogic(World, GS); // 客户端执行客户端逻辑
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ==================== 头文件包含 ====================
|
|
#include <mutex>
|
|
#include <atomic>
|
|
#include <vector>
|
|
#include <thread>
|
|
#include <chrono>
|
|
#include <string>
|
|
#include <cstdlib>
|
|
|
|
// ==================== 玩家信息结构体 ====================
|
|
struct SimplePlayerInfo {
|
|
SDK::ASTExtraBaseCharacter* PlayerCharacter;
|
|
};
|
|
|
|
// ==================== 全局变量 ====================
|
|
static bool bIsTeleporting = false;
|
|
static std::string sStatusMessage = "";
|
|
static float fStatusDisplayTime = 0.0f;
|
|
static std::mutex g_TeleportMutex;
|
|
static std::mutex g_WorldAccessMutex;
|
|
static std::atomic<int> s_TeleportCounter(0);
|
|
|
|
// ==================== 辅助函数 ====================
|
|
|
|
// 创建零向量
|
|
SDK::FVector MakeZeroVector() {
|
|
SDK::FVector vec;
|
|
vec.X = 0.0f;
|
|
vec.Y = 0.0f;
|
|
vec.Z = 0.0f;
|
|
return vec;
|
|
}
|
|
|
|
// 改进的指针有效性检查
|
|
template<typename T>
|
|
bool IsValidPointer(T* ptr) {
|
|
if (!ptr) return false;
|
|
|
|
// 检查指针是否指向有效地址范围(简化版)
|
|
uintptr_t addr = reinterpret_cast<uintptr_t>(ptr);
|
|
|
|
// 基本地址范围检查
|
|
if (addr < 0x10000 || addr > 0xFFFFFFFFFFFFFF00) return false;
|
|
|
|
// 检查指针是否对齐(可选)
|
|
if (addr % sizeof(void*) != 0) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// 检查基础指针(兼容旧代码)
|
|
bool IsPointerValid(void* ptr) {
|
|
return IsValidPointer(ptr);
|
|
}
|
|
|
|
// 获取服务端玩家位置
|
|
SDK::FVector GetServerPlayerPositionSafe() {
|
|
std::lock_guard<std::mutex> lock(g_WorldAccessMutex);
|
|
|
|
try {
|
|
SDK::UWorld* world = GetWorld();
|
|
if (!IsValidPointer(world)) return MakeZeroVector();
|
|
|
|
// 获取玩家控制器
|
|
SDK::APlayerController* playerCtrl = SDK::UGameplayStatics::GetPlayerController(world, 0);
|
|
if (!IsValidPointer(playerCtrl)) return MakeZeroVector();
|
|
|
|
// 检查是否是服务端
|
|
if (!playerCtrl->HasAuthority()) return MakeZeroVector();
|
|
|
|
// 获取角色
|
|
SDK::ASTExtraBaseCharacter* character = nullptr;
|
|
if (IsValidPointer(playerCtrl->Pawn)) {
|
|
try {
|
|
if (playerCtrl->Pawn->IsA(SDK::ASTExtraBaseCharacter::StaticClass())) {
|
|
character = static_cast<SDK::ASTExtraBaseCharacter*>(playerCtrl->Pawn);
|
|
}
|
|
} catch (...) {
|
|
return MakeZeroVector();
|
|
}
|
|
}
|
|
|
|
if (!IsValidPointer(character)) return MakeZeroVector();
|
|
|
|
// 获取服务端玩家位置
|
|
SDK::FVector result;
|
|
try {
|
|
result = character->K2_GetActorLocation();
|
|
} catch (...) {
|
|
return MakeZeroVector();
|
|
}
|
|
|
|
return result;
|
|
} catch (...) {
|
|
return MakeZeroVector();
|
|
}
|
|
}
|
|
|
|
// 获取服务端玩家旋转
|
|
SDK::FRotator GetServerPlayerRotationSafe() {
|
|
std::lock_guard<std::mutex> lock(g_WorldAccessMutex);
|
|
|
|
try {
|
|
SDK::UWorld* world = GetWorld();
|
|
if (!IsValidPointer(world)) return SDK::FRotator();
|
|
|
|
// 获取玩家控制器
|
|
SDK::APlayerController* playerCtrl = SDK::UGameplayStatics::GetPlayerController(world, 0);
|
|
if (!IsValidPointer(playerCtrl)) return SDK::FRotator();
|
|
|
|
// 检查是否是服务端
|
|
if (!playerCtrl->HasAuthority()) return SDK::FRotator();
|
|
|
|
// 获取角色
|
|
SDK::ASTExtraBaseCharacter* character = nullptr;
|
|
if (IsValidPointer(playerCtrl->Pawn)) {
|
|
try {
|
|
if (playerCtrl->Pawn->IsA(SDK::ASTExtraBaseCharacter::StaticClass())) {
|
|
character = static_cast<SDK::ASTExtraBaseCharacter*>(playerCtrl->Pawn);
|
|
}
|
|
} catch (...) {
|
|
return SDK::FRotator();
|
|
}
|
|
}
|
|
|
|
if (!IsValidPointer(character)) return SDK::FRotator();
|
|
|
|
// 获取服务端玩家旋转
|
|
SDK::FRotator result;
|
|
try {
|
|
result = character->K2_GetActorRotation();
|
|
} catch (...) {
|
|
return SDK::FRotator();
|
|
}
|
|
|
|
return result;
|
|
} catch (...) {
|
|
return SDK::FRotator();
|
|
}
|
|
}
|
|
|
|
// 修复的获取所有玩家角色的函数(不跳过任何玩家)
|
|
SDK::ASTExtraBaseCharacter* GetAllPlayerCharacter(SDK::APlayerState* playerState) {
|
|
if (!IsValidPointer(playerState)) return nullptr;
|
|
|
|
try {
|
|
// 获取玩家控制器
|
|
SDK::APlayerController* playerCtrl = nullptr;
|
|
if (IsValidPointer(playerState->Owner)) {
|
|
try {
|
|
if (playerState->Owner->IsA(SDK::APlayerController::StaticClass())) {
|
|
playerCtrl = static_cast<SDK::APlayerController*>(playerState->Owner);
|
|
}
|
|
} catch (...) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
if (!IsValidPointer(playerCtrl)) return nullptr;
|
|
|
|
// 获取角色
|
|
SDK::ASTExtraBaseCharacter* character = nullptr;
|
|
if (IsValidPointer(playerCtrl->Pawn)) {
|
|
try {
|
|
if (playerCtrl->Pawn->IsA(SDK::ASTExtraBaseCharacter::StaticClass())) {
|
|
character = static_cast<SDK::ASTExtraBaseCharacter*>(playerCtrl->Pawn);
|
|
}
|
|
} catch (...) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
if (!IsValidPointer(character)) return nullptr;
|
|
|
|
return character;
|
|
} catch (...) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
// 安全的获取所有玩家列表副本(包括服务端和客户端)
|
|
std::vector<SimplePlayerInfo> GetAllPlayersList() {
|
|
std::vector<SimplePlayerInfo> safeList;
|
|
std::lock_guard<std::mutex> lock(g_WorldAccessMutex);
|
|
|
|
try {
|
|
SDK::UWorld* world = GetWorld();
|
|
if (!IsValidPointer(world)) return safeList;
|
|
|
|
SDK::AGameStateBase* gameState = world->GameState;
|
|
if (!IsValidPointer(gameState)) return safeList;
|
|
|
|
int playerCount = 0;
|
|
try {
|
|
playerCount = gameState->PlayerArray.Num();
|
|
} catch (...) {
|
|
return safeList;
|
|
}
|
|
|
|
safeList.reserve(playerCount);
|
|
|
|
// 获取当前玩家的控制器,用于判断是否是服务端
|
|
SDK::APlayerController* myCtrl = SDK::UGameplayStatics::GetPlayerController(world, 0);
|
|
bool isServer = IsValidPointer(myCtrl) && myCtrl->HasAuthority();
|
|
|
|
for (int i = 0; i < playerCount; i++) {
|
|
try {
|
|
SDK::APlayerState* playerState = gameState->PlayerArray[i];
|
|
if (!IsValidPointer(playerState)) continue;
|
|
|
|
SDK::ASTExtraBaseCharacter* character = GetAllPlayerCharacter(playerState);
|
|
if (!IsValidPointer(character)) continue;
|
|
|
|
// 如果是服务端,不要传送自己(可以跳过自己的角色)
|
|
if (isServer) {
|
|
// 检查这个角色是否属于当前玩家
|
|
SDK::APlayerController* charCtrl = SDK::UGameplayStatics::GetPlayerController(world, i);
|
|
if (IsValidPointer(charCtrl) && charCtrl == myCtrl) {
|
|
// 跳过当前玩家自己的角色
|
|
continue;
|
|
}
|
|
}
|
|
|
|
SimplePlayerInfo info;
|
|
info.PlayerCharacter = character;
|
|
safeList.push_back(info);
|
|
} catch (...) {
|
|
continue;
|
|
}
|
|
}
|
|
} catch (...) {
|
|
safeList.clear();
|
|
}
|
|
|
|
return safeList;
|
|
}
|
|
|
|
// 获取可传送的玩家列表(非服务端玩家)
|
|
std::vector<SimplePlayerInfo> GetSafePlayerList() {
|
|
std::vector<SimplePlayerInfo> safeList;
|
|
std::lock_guard<std::mutex> lock(g_WorldAccessMutex);
|
|
|
|
try {
|
|
SDK::UWorld* world = GetWorld();
|
|
if (!IsValidPointer(world)) return safeList;
|
|
|
|
SDK::AGameStateBase* gameState = world->GameState;
|
|
if (!IsValidPointer(gameState)) return safeList;
|
|
|
|
// 获取当前玩家的控制器,判断是否是服务端
|
|
SDK::APlayerController* myCtrl = SDK::UGameplayStatics::GetPlayerController(world, 0);
|
|
bool isServer = IsValidPointer(myCtrl) && myCtrl->HasAuthority();
|
|
|
|
// 如果是客户端,则不能传送任何人
|
|
if (!isServer) {
|
|
return safeList;
|
|
}
|
|
|
|
int playerCount = 0;
|
|
try {
|
|
playerCount = gameState->PlayerArray.Num();
|
|
} catch (...) {
|
|
return safeList;
|
|
}
|
|
|
|
safeList.reserve(playerCount);
|
|
|
|
for (int i = 0; i < playerCount; i++) {
|
|
try {
|
|
SDK::APlayerState* playerState = gameState->PlayerArray[i];
|
|
if (!IsValidPointer(playerState)) continue;
|
|
|
|
SDK::ASTExtraBaseCharacter* character = GetAllPlayerCharacter(playerState);
|
|
if (!IsValidPointer(character)) continue;
|
|
|
|
// 检查这个角色是否属于当前服务端玩家
|
|
SDK::APlayerController* charCtrl = SDK::UGameplayStatics::GetPlayerController(world, i);
|
|
if (IsValidPointer(charCtrl) && charCtrl == myCtrl) {
|
|
// 跳过服务端玩家自己的角色
|
|
continue;
|
|
}
|
|
|
|
SimplePlayerInfo info;
|
|
info.PlayerCharacter = character;
|
|
safeList.push_back(info);
|
|
} catch (...) {
|
|
continue;
|
|
}
|
|
}
|
|
} catch (...) {
|
|
safeList.clear();
|
|
}
|
|
|
|
return safeList;
|
|
}
|
|
|
|
// 安全的传送单个玩家
|
|
bool SafeTeleportPlayerDelayed(SDK::ASTExtraBaseCharacter* playerChar, SDK::FVector targetPos, SDK::FRotator targetRot) {
|
|
if (!IsValidPointer(playerChar)) return false;
|
|
|
|
int currentCount = s_TeleportCounter.fetch_add(1);
|
|
|
|
if (currentCount % 5 == 0) {
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
}
|
|
|
|
try {
|
|
// 尝试获取当前角色位置(确认角色可访问)
|
|
SDK::FVector currentPos;
|
|
try {
|
|
currentPos = playerChar->K2_GetActorLocation();
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
|
|
// 设置新位置(分步进行以避免碰撞)
|
|
SDK::FVector safePos = targetPos;
|
|
safePos.Z += 200.0f;
|
|
|
|
try {
|
|
playerChar->K2_SetActorLocation(safePos, false, false, nullptr);
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
|
|
std::this_thread::sleep_for(std::chrono::microseconds(500));
|
|
|
|
// 设置最终位置
|
|
try {
|
|
playerChar->K2_SetActorLocation(targetPos, false, false, nullptr);
|
|
playerChar->K2_SetActorRotation(targetRot, false);
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 兼容旧版本的传送函数
|
|
bool SafeTeleportPlayer(SDK::ASTExtraBaseCharacter* playerChar, SDK::FVector targetPos, SDK::FRotator targetRot) {
|
|
return SafeTeleportPlayerDelayed(playerChar, targetPos, targetRot);
|
|
}
|
|
|
|
// ==================== 主功能函数 ====================
|
|
|
|
// 初始化传送系统
|
|
void InitializeTeleportSystem() {
|
|
std::lock_guard<std::mutex> lock1(g_TeleportMutex);
|
|
std::lock_guard<std::mutex> lock2(g_WorldAccessMutex);
|
|
|
|
bIsTeleporting = false;
|
|
sStatusMessage = "";
|
|
fStatusDisplayTime = 0.0f;
|
|
s_TeleportCounter.store(0);
|
|
}
|
|
|
|
// 清理传送系统
|
|
void CleanupTeleportSystem() {
|
|
std::lock_guard<std::mutex> lock1(g_TeleportMutex);
|
|
std::lock_guard<std::mutex> lock2(g_WorldAccessMutex);
|
|
|
|
bIsTeleporting = false;
|
|
s_TeleportCounter.store(0);
|
|
sStatusMessage = "";
|
|
}
|
|
|
|
// 一键传送所有玩家
|
|
void SafeTeleportAllPlayers() {
|
|
if (bIsTeleporting) {
|
|
sStatusMessage = "传送已在执行中,请等待...";
|
|
fStatusDisplayTime = ImGui::GetTime();
|
|
return;
|
|
}
|
|
|
|
static std::mutex executionMutex;
|
|
if (!executionMutex.try_lock()) {
|
|
sStatusMessage = "传送已在执行中...";
|
|
fStatusDisplayTime = ImGui::GetTime();
|
|
return;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> lock(executionMutex, std::adopt_lock);
|
|
bIsTeleporting = true;
|
|
s_TeleportCounter.store(0);
|
|
|
|
sStatusMessage = "开始传送玩家...";
|
|
fStatusDisplayTime = ImGui::GetTime();
|
|
|
|
auto startTime = std::chrono::steady_clock::now();
|
|
|
|
try {
|
|
SDK::FVector serverPos = GetServerPlayerPositionSafe();
|
|
SDK::FRotator serverRot = GetServerPlayerRotationSafe();
|
|
|
|
auto currentTime = std::chrono::steady_clock::now();
|
|
auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - startTime).count();
|
|
if (elapsedMs > 5000) {
|
|
sStatusMessage = "错误: 获取服务端位置超时";
|
|
bIsTeleporting = false;
|
|
return;
|
|
}
|
|
|
|
if (serverPos.X == 0.0f && serverPos.Y == 0.0f && serverPos.Z == 0.0f) {
|
|
sStatusMessage = "错误: 无法获取服务端位置";
|
|
bIsTeleporting = false;
|
|
return;
|
|
}
|
|
|
|
sStatusMessage = "正在获取玩家列表...";
|
|
fStatusDisplayTime = ImGui::GetTime();
|
|
|
|
// 使用新的函数获取可传送的玩家列表
|
|
std::vector<SimplePlayerInfo> playerList = GetSafePlayerList();
|
|
|
|
currentTime = std::chrono::steady_clock::now();
|
|
elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - startTime).count();
|
|
if (elapsedMs > 10000) {
|
|
sStatusMessage = "错误: 获取玩家列表超时";
|
|
bIsTeleporting = false;
|
|
return;
|
|
}
|
|
|
|
if (playerList.empty()) {
|
|
sStatusMessage = "没有找到可传送的玩家";
|
|
bIsTeleporting = false;
|
|
return;
|
|
}
|
|
|
|
int teleportedCount = 0;
|
|
int totalPlayers = static_cast<int>(playerList.size());
|
|
|
|
sStatusMessage = "开始传送 " + std::to_string(totalPlayers) + " 名玩家...";
|
|
fStatusDisplayTime = ImGui::GetTime();
|
|
|
|
for (int i = 0; i < totalPlayers; i++) {
|
|
if (i % 10 == 0) {
|
|
currentTime = std::chrono::steady_clock::now();
|
|
elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - startTime).count();
|
|
if (elapsedMs > 30000) {
|
|
sStatusMessage = "警告: 传送超时,已传送 " + std::to_string(teleportedCount) + "/" + std::to_string(totalPlayers);
|
|
break;
|
|
}
|
|
}
|
|
|
|
SimplePlayerInfo& info = playerList[i];
|
|
|
|
if (!IsValidPointer(info.PlayerCharacter)) continue;
|
|
|
|
SDK::FVector finalPos = serverPos;
|
|
finalPos.X += (teleportedCount % 5) * 300.0f;
|
|
finalPos.Y += (teleportedCount / 5) * 300.0f;
|
|
finalPos.Z += 150.0f;
|
|
|
|
if (SafeTeleportPlayerDelayed(info.PlayerCharacter, finalPos, serverRot)) {
|
|
teleportedCount++;
|
|
|
|
if (teleportedCount % 10 == 0 || teleportedCount == totalPlayers) {
|
|
sStatusMessage = "已传送: " + std::to_string(teleportedCount) +
|
|
"/" + std::to_string(totalPlayers);
|
|
fStatusDisplayTime = ImGui::GetTime();
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
|
std::this_thread::yield();
|
|
}
|
|
}
|
|
}
|
|
|
|
sStatusMessage = "传送完成: " + std::to_string(teleportedCount) +
|
|
"/" + std::to_string(totalPlayers) + " 名玩家";
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
} catch (...) {
|
|
sStatusMessage = "传送过程中发生严重错误";
|
|
}
|
|
|
|
fStatusDisplayTime = ImGui::GetTime();
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
|
bIsTeleporting = false;
|
|
}
|
|
|
|
|
|
void ApplyStableActorConfig(SDK::AActor* Actor)
|
|
{
|
|
if (!Actor)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
if (Actor->bActorIsBeingDestroyed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Actor->Role != static_cast<SDK::ENetRole>(3))
|
|
{
|
|
|
|
return;
|
|
}
|
|
|
|
if (!Actor->HasAuthority())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Actor->GetGameTimeSinceCreation() < 0.1f)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool isPlayerState = Actor->IsA(SDK::APlayerState::StaticClass());
|
|
bool isController = Actor->IsA(SDK::AController::StaticClass());
|
|
|
|
if (!isPlayerState && !isController)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Actor->bReplicates = true;
|
|
Actor->bReplicateMovement = true;
|
|
|
|
Actor->Role = static_cast<SDK::ENetRole>(3);
|
|
Actor->RemoteRole = static_cast<SDK::ENetRole>(1);
|
|
|
|
Actor->NetUpdateFrequency = 100.f;
|
|
Actor->MinNetUpdateFrequency = 30.f;
|
|
|
|
Actor->NetDormancy = static_cast<SDK::ENetDormancy>(3);
|
|
|
|
Actor->bAlwaysRelevant = true;
|
|
|
|
Actor->bOnlyRelevantToOwner = false;
|
|
Actor->bNetUseOwnerRelevancy = false;
|
|
Actor->NetCullDistanceSquared = 900000000;
|
|
|
|
Actor->bAllowTickBeforeBeginPlay = true;
|
|
|
|
Actor->bActorEnableCollision = true;
|
|
Actor->bHidden = false;
|
|
|
|
Actor->SetLifeSpan(0.0f);
|
|
Actor->bAutoDestroyWhenFinished = false;
|
|
|
|
// Tick
|
|
Actor->SetActorTickEnabled(true);
|
|
Actor->SetActorTickInterval(0.0f);
|
|
Actor->SetTickableWhenPaused(true);
|
|
|
|
Actor->FlushNetDormancy();
|
|
Actor->ForceNetUpdate();
|
|
|
|
// 时间
|
|
Actor->CustomTimeDilation = 1.0f;
|
|
|
|
}
|
|
void ApplyStableConfigToWorld(SDK::UWorld* World)
|
|
{
|
|
if (!World)
|
|
return;
|
|
|
|
if (World->PersistentLevel)
|
|
{
|
|
auto Actors = getActors();
|
|
|
|
for (int i = 0; i < Actors.size(); i++)
|
|
{
|
|
SDK::AActor* Actor = Actors[i];
|
|
if (!Actor) continue;
|
|
ApplyStableActorConfig(Actor);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
struct ServerInfo {
|
|
std::string name;
|
|
std::string ipPort;
|
|
int ping = -1;
|
|
int64_t lastPingTime = 0;
|
|
};
|
|
|
|
static std::vector<ServerInfo> g_Servers;
|
|
|
|
//日志
|
|
static void log_to_file(const char* fmt, ...) {
|
|
static int fd = -1;
|
|
if (fd == -1)
|
|
fd = open("/sdcard/Android/logs.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
|
|
if (fd < 0) return;
|
|
char buf[2048];
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
va_end(ap);
|
|
strcat(buf, "\n");
|
|
write(fd, buf, strlen(buf));
|
|
fsync(fd);
|
|
}
|
|
|
|
|
|
|
|
void FetchServerListFromUrl() {
|
|
const char* filePath = "/sdcard/Download/server.txt";
|
|
FILE* file = fopen(filePath, "r");
|
|
|
|
// 如果文件不存在,则创建文件并写入示例内容
|
|
if (!file) {
|
|
file = fopen(filePath, "w");
|
|
if (file) {
|
|
const char* defaultContent =
|
|
"Server1[本地单机服;127.0.0.1:7788]\n";
|
|
|
|
fputs(defaultContent, file);
|
|
fclose(file);
|
|
|
|
// 重新以读取模式打开文件
|
|
file = fopen(filePath, "r");
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!file) {
|
|
return;
|
|
}
|
|
|
|
g_Servers.clear();
|
|
|
|
// 检查文件是否为空
|
|
fseek(file, 0, SEEK_END);
|
|
long fileSize = ftell(file);
|
|
fseek(file, 0, SEEK_SET);
|
|
|
|
if (fileSize == 0) {
|
|
fclose(file);
|
|
return;
|
|
}
|
|
|
|
// 读取文件内容
|
|
char line[256];
|
|
|
|
while (fgets(line, sizeof(line), file)) {
|
|
std::string lineStr(line);
|
|
|
|
// 移除行尾的换行符
|
|
if (!lineStr.empty() && lineStr.back() == '\n') {
|
|
lineStr.pop_back();
|
|
}
|
|
|
|
// 跳过空行
|
|
if (lineStr.empty()) {
|
|
continue;
|
|
}
|
|
|
|
// 使用英文字符替代中文字符进行解析
|
|
// 查找 [ 和 ] 的位置
|
|
size_t startBracket = lineStr.find('[');
|
|
size_t endBracket = lineStr.find(']');
|
|
size_t separator = lineStr.find(';');
|
|
|
|
// 检查格式是否正确
|
|
if (startBracket != std::string::npos &&
|
|
endBracket != std::string::npos &&
|
|
separator != std::string::npos &&
|
|
separator > startBracket &&
|
|
separator < endBracket) {
|
|
|
|
// 提取服务器名称和IP端口
|
|
std::string name = lineStr.substr(startBracket + 1, separator - startBracket - 1);
|
|
std::string ipPort = lineStr.substr(separator + 1, endBracket - separator - 1);
|
|
|
|
// 添加到服务器列表
|
|
g_Servers.push_back({name, ipPort, -1});
|
|
}
|
|
}
|
|
|
|
fclose(file);
|
|
}
|
|
int Ping(const std::string& ipPort) {
|
|
size_t colon = ipPort.find(':');
|
|
if (colon == std::string::npos) return -1;
|
|
std::string host = ipPort.substr(0, colon);
|
|
int port = std::stoi(ipPort.substr(colon + 1));
|
|
|
|
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (sock < 0) return -1;
|
|
|
|
struct sockaddr_in addr {};
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = htons(port);
|
|
if (inet_pton(AF_INET, host.c_str(), &addr.sin_addr) <= 0) {
|
|
close(sock); return -1;
|
|
}
|
|
|
|
fcntl(sock, F_SETFL, O_NONBLOCK);
|
|
auto t0 = std::chrono::steady_clock::now();
|
|
connect(sock, (sockaddr*)&addr, sizeof(addr));
|
|
|
|
fd_set wfds; FD_ZERO(&wfds); FD_SET(sock, &wfds);
|
|
struct timeval tv {1, 500000};
|
|
int ret = select(sock + 1, nullptr, &wfds, nullptr, &tv);
|
|
auto t1 = std::chrono::steady_clock::now();
|
|
close(sock);
|
|
|
|
int ms = (ret > 0) ? (int)std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0).count() : -1;
|
|
log_to_file("[Ping] %s -> %d ms", ipPort.c_str(), ms);
|
|
return ms;
|
|
}
|
|
|
|
|
|
|
|
// [NEW] 关卡管理相关变量
|
|
std::vector<ULevelStreaming*> g_LevelStreamingList;
|
|
int g_SelectedLevelIndex = -1;
|
|
|
|
// [NEW] 刷新所有 ULevelStreaming 对象列表
|
|
void RefreshLevelStreamingList() {
|
|
g_LevelStreamingList.clear();
|
|
auto objs = UObject::GetGlobalObjects();
|
|
for (int i = 0; i < objs.Num(); i++) {
|
|
auto obj = objs.GetByIndex(i);
|
|
if (!IsPtrValid(obj)) continue;
|
|
if (obj->IsA(ULevelStreaming::StaticClass())) {
|
|
g_LevelStreamingList.push_back((ULevelStreaming*)obj);
|
|
}
|
|
}
|
|
g_SelectedLevelIndex = -1;
|
|
}
|
|
|
|
// [NEW] 隐藏选中的关卡(仅不可见)
|
|
void HideSelectedLevel() {
|
|
if (g_SelectedLevelIndex >= 0 && g_SelectedLevelIndex < (int)g_LevelStreamingList.size()) {
|
|
auto level = g_LevelStreamingList[g_SelectedLevelIndex];
|
|
if (level) {
|
|
level->bShouldBeVisible = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// [NEW] 移除选中的关卡(卸载)
|
|
void UnloadSelectedLevel() {
|
|
if (g_SelectedLevelIndex >= 0 && g_SelectedLevelIndex < (int)g_LevelStreamingList.size()) {
|
|
auto level = g_LevelStreamingList[g_SelectedLevelIndex];
|
|
if (level) {
|
|
level->bShouldBeLoaded = false;
|
|
level->bShouldBeVisible = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// [NEW] 恢复选中的关卡(重新加载并显示)
|
|
void RestoreSelectedLevel() {
|
|
if (g_SelectedLevelIndex >= 0 && g_SelectedLevelIndex < (int)g_LevelStreamingList.size()) {
|
|
auto level = g_LevelStreamingList[g_SelectedLevelIndex];
|
|
if (level) {
|
|
level->bShouldBeLoaded = true;
|
|
level->bShouldBeVisible = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// [NEW] 一键隐藏所有关卡(仅不可见)
|
|
void HideAllLevels() {
|
|
for (auto level : g_LevelStreamingList) {
|
|
if (level) {
|
|
level->bShouldBeVisible = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// [NEW] 一键显示所有关卡(恢复可见,同时确保已加载)
|
|
void ShowAllLevels() {
|
|
for (auto level : g_LevelStreamingList) {
|
|
if (level) {
|
|
level->bShouldBeLoaded = true;
|
|
level->bShouldBeVisible = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// [NEW] 随机隐藏一半关卡(使用 rand 实现)
|
|
void HideHalfLevels() {
|
|
if (g_LevelStreamingList.empty()) return;
|
|
|
|
int total = (int)g_LevelStreamingList.size();
|
|
int toHide = total / 2; // 向下取整
|
|
if (toHide == 0) return;
|
|
|
|
// 生成索引列表
|
|
std::vector<int> indices(total);
|
|
for (int i = 0; i < total; ++i) indices[i] = i;
|
|
|
|
// 播种随机数(只播种一次)
|
|
static bool seeded = false;
|
|
if (!seeded) {
|
|
std::srand(static_cast<unsigned>(std::time(nullptr)));
|
|
seeded = true;
|
|
}
|
|
|
|
// Fisher-Yates 洗牌
|
|
for (int i = total - 1; i > 0; --i) {
|
|
int j = std::rand() % (i + 1);
|
|
std::swap(indices[i], indices[j]);
|
|
}
|
|
|
|
// 隐藏前 toHide 个
|
|
for (int i = 0; i < toHide; ++i) {
|
|
auto level = g_LevelStreamingList[indices[i]];
|
|
if (level) {
|
|
level->bShouldBeVisible = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct 探测到的建筑 {
|
|
std::string 名称;
|
|
std::string 路径;
|
|
UClass* 类指针;
|
|
};
|
|
|
|
inline UClass* 缓存的建筑类 = nullptr;
|
|
inline bool 建筑线程运行 = false;
|
|
inline std::thread 建筑生成线程;
|
|
|
|
static std::vector<探测到的建筑> 建筑列表;
|
|
static int 当前选中建筑索引 = -1;
|
|
|
|
std::string 获取对象路径(UObject* Obj)
|
|
{
|
|
if (!Obj) return "";
|
|
return Obj->GetFullName();
|
|
}
|
|
|
|
void 探测并添加附近建筑(float Radius)
|
|
{
|
|
获取对象();
|
|
if (!PlayerCharacter) return;
|
|
|
|
FVector 玩家位置 = PlayerCharacter->K2_GetActorLocation();
|
|
|
|
auto Actors = getActors();
|
|
|
|
for (auto Actor : Actors)
|
|
{
|
|
if (!Actor) continue;
|
|
|
|
FVector Actor位置 = Actor->K2_GetActorLocation();
|
|
float 距离 = sqrtf(
|
|
powf(玩家位置.X - Actor位置.X, 2) +
|
|
powf(玩家位置.Y - Actor位置.Y, 2) +
|
|
powf(玩家位置.Z - Actor位置.Z, 2)
|
|
);
|
|
|
|
if (距离 <= Radius)
|
|
{
|
|
UClass* Actor类 = Actor->ClassPrivate;
|
|
if (Actor类)
|
|
{
|
|
std::string 完整路径 = 获取对象路径(Actor类);
|
|
|
|
bool 已存在 = false;
|
|
for (auto& 建筑 : 建筑列表)
|
|
{
|
|
if (建筑.路径 == 完整路径)
|
|
{
|
|
已存在 = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!已存在)
|
|
{
|
|
探测到的建筑 新建筑;
|
|
新建筑.名称 = Actor->GetName();
|
|
新建筑.路径 = 完整路径;
|
|
新建筑.类指针 = Actor类;
|
|
建筑列表.push_back(新建筑);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void 清空建筑列表()
|
|
{
|
|
建筑列表.clear();
|
|
当前选中建筑索引 = -1;
|
|
}
|
|
|
|
void 加载选中的建筑()
|
|
{
|
|
if (当前选中建筑索引 >= 0 && 当前选中建筑索引 < (int)建筑列表.size())
|
|
{
|
|
缓存的建筑类 = 建筑列表[当前选中建筑索引].类指针;
|
|
}
|
|
}
|
|
|
|
void 扫描所有蓝图类()
|
|
{
|
|
建筑列表.clear();
|
|
|
|
FUObjectArray* ObjectArray = UObject::GUObjectArray;
|
|
if (!ObjectArray) return;
|
|
|
|
for (int i = 0; i < ObjectArray->ObjObjects.Num(); i++)
|
|
{
|
|
UObject* Obj = ObjectArray->ObjObjects.GetByIndex(i);
|
|
if (!Obj) continue;
|
|
|
|
if (!Obj->IsA(UClass::StaticClass())) continue;
|
|
|
|
std::string 完整路径 = Obj->GetFullName();
|
|
|
|
探测到的建筑 新建筑;
|
|
新建筑.名称 = Obj->GetName();
|
|
新建筑.路径 = 完整路径;
|
|
新建筑.类指针 = (UClass*)Obj;
|
|
建筑列表.push_back(新建筑);
|
|
}
|
|
}
|
|
|
|
UClass* 探测附近建筑类(float Radius)
|
|
{
|
|
获取对象();
|
|
if (!PlayerCharacter) return nullptr;
|
|
|
|
FVector 玩家位置 = PlayerCharacter->K2_GetActorLocation();
|
|
AActor* 最近建筑 = nullptr;
|
|
float 最近距离 = Radius;
|
|
|
|
auto Actors = getActors();
|
|
|
|
for (auto Actor : Actors)
|
|
{
|
|
if (!Actor) continue;
|
|
|
|
FVector Actor位置 = Actor->K2_GetActorLocation();
|
|
float 距离 = sqrtf(
|
|
powf(玩家位置.X - Actor位置.X, 2) +
|
|
powf(玩家位置.Y - Actor位置.Y, 2) +
|
|
powf(玩家位置.Z - Actor位置.Z, 2)
|
|
);
|
|
|
|
if (距离 <= 最近距离)
|
|
{
|
|
最近距离 = 距离;
|
|
最近建筑 = Actor;
|
|
}
|
|
}
|
|
|
|
if (最近建筑)
|
|
{
|
|
return 最近建筑->ClassPrivate;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
AActor* 生成建筑(UClass* 建筑类, const FVector& 位置)
|
|
{
|
|
if (!建筑类) return nullptr;
|
|
|
|
FTransform 生成变换;
|
|
生成变换.Translation = 位置;
|
|
生成变换.Rotation = FQuat{0, 0, 0, 1};
|
|
生成变换.Scale3D = FVector{1, 1, 1};
|
|
|
|
return UGameplayStatics::BeginDeferredActorSpawnFromClass(
|
|
GetWorld(),
|
|
建筑类,
|
|
生成变换,
|
|
ESpawnActorCollisionHandlingMethod::ESpawnActorCollisionHandlingMethod__AlwaysSpawn,
|
|
nullptr
|
|
);
|
|
}
|
|
|
|
void 在玩家前方生成建筑(UClass* 建筑类)
|
|
{
|
|
获取对象();
|
|
if (!建筑类 || !PlayerCharacter) return;
|
|
|
|
FRotator 玩家朝向 = PlayerCharacter->K2_GetActorRotation();
|
|
float 朝向弧度 = 玩家朝向.Yaw * 3.14159f / 180.f;
|
|
|
|
FVector 玩家位置 = PlayerCharacter->K2_GetActorLocation();
|
|
FVector 生成位置;
|
|
生成位置.X = 玩家位置.X + cosf(朝向弧度) * 建筑生成偏移;
|
|
生成位置.Y = 玩家位置.Y + sinf(朝向弧度) * 建筑生成偏移;
|
|
生成位置.Z = 玩家位置.Z;
|
|
|
|
生成建筑(建筑类, 生成位置);
|
|
}
|
|
|
|
UClass* 通过路径加载建筑类(const char* 路径)
|
|
{
|
|
if (!路径 || strlen(路径) == 0) return nullptr;
|
|
|
|
return UObject::FindClass(std::string(路径));
|
|
}
|
|
|
|
void 建筑生成循环()
|
|
{
|
|
while (建筑线程运行)
|
|
{
|
|
if (建筑跟随模式 && 缓存的建筑类)
|
|
{
|
|
在玩家前方生成建筑(缓存的建筑类);
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(150));
|
|
}
|
|
}
|
|
|
|
void 启动建筑线程()
|
|
{
|
|
if (建筑线程运行) return;
|
|
建筑线程运行 = true;
|
|
建筑生成线程 = std::thread(建筑生成循环);
|
|
}
|
|
|
|
void 停止建筑线程()
|
|
{
|
|
建筑线程运行 = false;
|
|
if (建筑生成线程.joinable())
|
|
{
|
|
建筑生成线程.join();
|
|
}
|
|
}
|
|
|
|
void 执行单次生成()
|
|
{
|
|
if (缓存的建筑类)
|
|
{
|
|
在玩家前方生成建筑(缓存的建筑类);
|
|
}
|
|
}
|
|
|
|
void 更新建筑跟随状态()
|
|
{
|
|
if (建筑跟随模式)
|
|
{
|
|
if (!建筑线程运行)
|
|
{
|
|
启动建筑线程();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (建筑线程运行)
|
|
{
|
|
停止建筑线程();
|
|
}
|
|
}
|
|
} |