```
feat(fight): 实现玩家间战斗邀请与处理功能 新增战斗邀请与处理逻辑,包括邀请发送、邀请接受/拒绝流程。 添加战斗模式支持(1v1 和 6v6)及相关数据结构定义。 优化玩家战斗准备逻辑,完善战斗初始化流程。 修复玩家离线保存数据时的空指针问题。 调整战斗相关枚举类型,统一管理战斗模式。 完善邀请战斗消息结构体及通信协议。 ```
This commit is contained in:
@@ -74,9 +74,19 @@ func (h Controller) OnReadyToFight(data *fight.ReadyToFightInboundInfo, c *playe
|
||||
// 接收战斗或者取消战斗的包
|
||||
func (h Controller) OnPlayerHandleFightInvite(data *fight.HandleFightInviteInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
|
||||
if ok, p1 := c.AgreeBattle(data.UserID, data.Flag, data.Mode); ok {
|
||||
fight.NewFight(info.EnumBattleMode(data.Mode), p1, c) ///开始对战,房主方以及被邀请方
|
||||
}
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
// 邀请其他人进行战斗
|
||||
func (h Controller) OnPlayerInviteOtherFight(data *fight.InviteToFightInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
c.InvitePlayerToBattle(data.UserID, info.EnumBattleMode(data.Mode))
|
||||
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// 使用技能包
|
||||
func (h Controller) UseSkill(data *fight.UseSkillInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
c.FightC.UseSkill(c, int32(data.SkillId))
|
||||
|
||||
@@ -88,7 +88,7 @@ func (h *Controller) MapList(data *maps.ListMapPlayerInboundInfo, c *player.Play
|
||||
|
||||
space.GetSpace(c.Info.MapID).Range(func(userID uint32, player common.PlayerI) bool {
|
||||
result1 := maps.NewOutInfo()
|
||||
copier.Copy(result1, player)
|
||||
copier.Copy(result1, player.GetInfo())
|
||||
result.Player = append(result.Player, *result1)
|
||||
return true
|
||||
})
|
||||
|
||||
24
logic/controller/user.go
Normal file
24
logic/controller/user.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"blazing/common/socket/errorcode"
|
||||
"blazing/logic/service/player"
|
||||
"blazing/logic/service/user"
|
||||
|
||||
"github.com/jinzhu/copier"
|
||||
)
|
||||
|
||||
func (h Controller) UserSimInfo(data *user.SimUserInfoInboundInfo, c *player.Player) (result *user.SimUserInfoOutboundInfo, err errorcode.ErrorCode) {
|
||||
ret := &user.SimUserInfoOutboundInfo{}
|
||||
|
||||
copier.Copy(ret, c.Service.PersonOther(data.UserId))
|
||||
return ret, 0
|
||||
}
|
||||
func (h Controller) UserMoreInfo(data *user.MoreUserInfoInboundInfo, c *player.Player) (result *user.MoreUserInfoOutboundInfo, err errorcode.ErrorCode) {
|
||||
ret := &user.MoreUserInfoOutboundInfo{}
|
||||
info := c.Service.PersonOther(data.UserId)
|
||||
copier.Copy(ret, info)
|
||||
|
||||
//todo 待实现
|
||||
return ret, 0
|
||||
}
|
||||
@@ -38,6 +38,19 @@ type HandleFightInviteInboundInfo struct {
|
||||
Mode uint32 `json:"mode" codec:"mode,uint"` // 战斗类型 1 = 1v1 2 = 6v6
|
||||
}
|
||||
|
||||
// InviteToFightInboundInfo 对应 Java 的 InviteToFightInboundInfo 类
|
||||
// 实现 InboundMessage 接口
|
||||
type InviteToFightInboundInfo struct {
|
||||
Head player.TomeeHeader `cmd:"2401" struc:"[0]pad"`
|
||||
// UserID 被邀请人的userid
|
||||
// 对应 Java 注解: @FieldDescription("被邀请人的userid") @UInt
|
||||
UserID uint32 `codec:"true"`
|
||||
|
||||
// Mode 战斗类型 1 = 1v1 2 = 6v6
|
||||
// 对应 Java 注解: @FieldDescription("战斗类型 1 = 1v1 2 = 6v6") @UInt
|
||||
Mode uint32 `codec:"true"`
|
||||
}
|
||||
|
||||
// 2502的回复包 PVP邀请消息
|
||||
type NoteHandleFightInviteOutboundInfo struct {
|
||||
UserID uint32
|
||||
|
||||
@@ -142,7 +142,13 @@ func (f *FightC) initplayer(c common.PlayerI, opp bool) {
|
||||
func NewFight(i info.EnumBattleMode, p1 common.PlayerI, p2 common.PlayerI) *FightC {
|
||||
f := &FightC{}
|
||||
f.ownerID = p1.ID()
|
||||
f.Info.FightId = i //房主
|
||||
f.Info.FightId = i //房主
|
||||
switch i {
|
||||
case info.BattleMode.PVP_6V6:
|
||||
f.Info.MAXPET = 6
|
||||
case info.BattleMode.PVP_1V1:
|
||||
f.Info.MAXPET = 1
|
||||
}
|
||||
seed := f.StartTime.UnixNano() ^ int64(p1.ID()) ^ int64(p2.ID()) // ^ int64(f.Round) // 用异或运算混合多维度信息
|
||||
f.rand = rand.New(rand.NewSource(seed))
|
||||
f.Info = info.NoteReadyToFightInfo{
|
||||
|
||||
@@ -26,8 +26,9 @@ import (
|
||||
type EnumBattleMode int
|
||||
|
||||
var BattleMode = enum.New[struct {
|
||||
PVE EnumBattleMode `enum:"3"`
|
||||
PVP EnumBattleMode `enum:"1"`
|
||||
PVE EnumBattleMode `enum:"3"`
|
||||
PVP_6V6 EnumBattleMode `enum:"2"`
|
||||
PVP_1V1 EnumBattleMode `enum:"1"`
|
||||
}]()
|
||||
|
||||
// 玩家离线数据
|
||||
|
||||
@@ -20,6 +20,32 @@ type ChangePetInfo struct {
|
||||
MaxHp uint32 `json:"maxHp"`
|
||||
CatchTime uint32 `fieldDesc:"捕捉时间" `
|
||||
}
|
||||
type NoteInviteToFightOutboundInfo struct {
|
||||
// UserID 邀请我对战人的userid
|
||||
// 对应 Java 注解: @FieldDescription("邀请我对战人的userid") @UInt
|
||||
UserID uint32 `codec:"true"`
|
||||
|
||||
// Nickname 邀请我对战人的玩家名称
|
||||
// 对应 Java 注解: @FieldDescription("邀请我对战人的玩家名称") @ArraySerialize(value = ArraySerializeType.FIXED_LENGTH, fixedLength = 16)
|
||||
Nick string `struc:"[16]byte" default:"seer" json:"nick"` // 16字节昵称
|
||||
|
||||
// Mode 战斗模式 1 = 1v1 2 = 6v6
|
||||
// 对应 Java 注解: @FieldDescription("战斗模式 1 = 1v1 2 = 6v6") @UInt
|
||||
Mode EnumBattleMode `codec:"true"`
|
||||
}
|
||||
type S2C_NOTE_HANDLE_FIGHT_INVITE struct {
|
||||
// UserID 对方的userid
|
||||
UserID uint32
|
||||
// 对应 Java 注解: @FieldDescription("邀请我对战人的玩家名称") @ArraySerialize(value = ArraySerializeType.FIXED_LENGTH, fixedLength = 16)
|
||||
Nick string `struc:"[16]byte" default:"seer" json:"nick"` // 16字节昵称
|
||||
// Result 处理结果
|
||||
// 0=对方拒绝了对战
|
||||
// 1=对方同意了对战
|
||||
// 2=对方在线时长超过6小时
|
||||
// 3=对方没有可出战的精灵
|
||||
// 4=对方不在线
|
||||
Result uint32
|
||||
}
|
||||
|
||||
// FightPetInfo 战斗精灵信息结构体,FightPetInfo类
|
||||
type FightPetInfo struct {
|
||||
|
||||
15
logic/service/fight/node/attr.go
Normal file
15
logic/service/fight/node/attr.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package node
|
||||
|
||||
// 返回false阻止继续运行
|
||||
// 回合开始
|
||||
func (this *EffectNode) AfterAttr() {
|
||||
|
||||
}
|
||||
|
||||
// 返回false阻止继续运行
|
||||
// 回合开始
|
||||
func (this *EffectNode) BeferAttr() {
|
||||
|
||||
}
|
||||
|
||||
//回合结束前
|
||||
@@ -100,7 +100,12 @@ func (f *FightC) ReadyFight(c common.PlayerI) {
|
||||
if f.GetInputByPlayer(c, true).Finished {
|
||||
rrsult()
|
||||
}
|
||||
case 2: // 6v6
|
||||
|
||||
f.GetInputByPlayer(c, false).Finished = true
|
||||
if f.GetInputByPlayer(c, true).Finished {
|
||||
rrsult()
|
||||
}
|
||||
case 3: // 野怪战斗
|
||||
|
||||
//判断捕捉率大于0
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"blazing/common/utils"
|
||||
|
||||
"blazing/logic/service/common"
|
||||
|
||||
"blazing/logic/service/fight/info"
|
||||
"blazing/logic/service/space"
|
||||
|
||||
@@ -20,7 +21,6 @@ import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
"github.com/panjf2000/gnet/pkg/logging"
|
||||
"github.com/tnnmigga/enum"
|
||||
)
|
||||
|
||||
func ConutPlayer() int {
|
||||
@@ -102,6 +102,7 @@ type Player struct {
|
||||
OgreInfo *OgreInfo
|
||||
FightC common.FightI //绑定战斗标识 替代本身的是否战斗标记 //IsFighting bool
|
||||
Service *blservice.UserService
|
||||
PVPinfo map[uint32]info.EnumBattleMode
|
||||
//FightInfo info.NoteReadyToFightInfo
|
||||
}
|
||||
|
||||
@@ -118,6 +119,7 @@ func WithConn(c *Conn) PlayerOption {
|
||||
func NewPlayer(opts ...PlayerOption) *Player {
|
||||
p := &Player{
|
||||
loginChan: make(chan struct{}),
|
||||
PVPinfo: make(map[uint32]info.EnumBattleMode),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(p)
|
||||
@@ -235,7 +237,11 @@ func LeaveMap(c common.PlayerI) {
|
||||
|
||||
// Save 保存玩家数据
|
||||
func (p *Player) Save() {
|
||||
glog.Debug(context.Background(), p.Info.UserID, "断开连接")
|
||||
if p.Info == nil {
|
||||
return
|
||||
|
||||
}
|
||||
//glog.Debug(context.Background(), p.Info.UserID, "断开连接")
|
||||
|
||||
LeaveMap(p)
|
||||
|
||||
@@ -326,31 +332,30 @@ func (lw *Player) IsNewPlayer() bool {
|
||||
return false // 全部等于3则返回false
|
||||
}
|
||||
|
||||
// 战斗模式
|
||||
type EnumBattleMode int
|
||||
|
||||
var BattleMode_PVP = enum.New[struct {
|
||||
PVP_1V1 EnumBattleMode `enum:"1"`
|
||||
PVP_6V6 EnumBattleMode `enum:"2"`
|
||||
}]()
|
||||
var Playerinvitemap map[uint32][]Playerinvite = make(map[uint32][]Playerinvite) //玩家邀请信息 ,比如一个玩家被多人邀请对战
|
||||
|
||||
type Playerinvite struct { //挂载到[]Playerinvite上? 被邀请者->邀请者
|
||||
InviteID uint32 // 邀请者
|
||||
InviteTime EnumBattleMode //游戏模式
|
||||
}
|
||||
// type Playerinvite struct { //挂载到[]Playerinvite上? 被邀请者->邀请者
|
||||
// InviteID uint32 // 邀请者
|
||||
// InviteTime EnumBattleMode //游戏模式
|
||||
// }
|
||||
|
||||
// 邀请玩家加入战斗 邀请者,被邀请者,邀请模式
|
||||
func (lw *Player) InvitePlayerToBattle(target int64, mode EnumBattleMode) {
|
||||
t, ok := Playerinvitemap[uint32(target)] //被邀请者是否被邀请过
|
||||
if ok { //说明存在被邀请
|
||||
t = append(t, Playerinvite{uint32(lw.Info.UserID), mode})
|
||||
Playerinvitemap[uint32(target)] = t
|
||||
} else {
|
||||
Playerinvitemap[uint32(target)] = []Playerinvite{{uint32(lw.Info.UserID), mode}}
|
||||
}
|
||||
|
||||
func (lw *Player) InvitePlayerToBattle(target uint32, mode info.EnumBattleMode) {
|
||||
lw.Playerinvite = uint32(target)
|
||||
Mainplayer.Range(func(key uint32, value *Player) bool {
|
||||
|
||||
if key == uint32(target) {
|
||||
value.PVPinfo[uint32(lw.Info.UserID)] = mode
|
||||
t1 := NewTomeeHeader(2501, value.Info.UserID)
|
||||
value.SendPack(t1.Pack(&info.NoteInviteToFightOutboundInfo{
|
||||
UserID: lw.ID(),
|
||||
Nick: lw.Info.Nick,
|
||||
Mode: mode,
|
||||
}))
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// 取消对战邀请
|
||||
@@ -359,6 +364,72 @@ func (lw *Player) CancelBattle() {
|
||||
if lw.Playerinvite == 0 {
|
||||
return
|
||||
}
|
||||
delete(Playerinvitemap, uint32(lw.Playerinvite)) //删除玩家邀请信息
|
||||
Mainplayer.Range(func(key uint32, value *Player) bool {
|
||||
|
||||
if key == uint32(lw.Playerinvite) {
|
||||
delete(value.PVPinfo, uint32(lw.Playerinvite)) //删除玩家邀请信息
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
lw.Playerinvite = 0
|
||||
}
|
||||
func (lw *Player) CanBattle() bool {
|
||||
|
||||
for _, v := range lw.Info.PetList {
|
||||
if v.Hp > 0 {
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 同意对战
|
||||
func (lw *Player) AgreeBattle(userid, flag, mode uint32) (bool, common.PlayerI) {
|
||||
|
||||
otherpp, ok := Mainplayer.Load(userid)
|
||||
defer delete(lw.PVPinfo, userid) //删除对方的邀请信息
|
||||
if ok { //获取到对方
|
||||
t1 := NewTomeeHeader(2502, otherpp.Info.UserID)
|
||||
ret := &info.S2C_NOTE_HANDLE_FIGHT_INVITE{
|
||||
UserID: lw.ID(),
|
||||
Nick: lw.Info.Nick,
|
||||
}
|
||||
|
||||
if flag == 0 { //拒绝对战
|
||||
|
||||
otherpp.SendPack(t1.Pack(ret))
|
||||
return false, nil
|
||||
}
|
||||
if !lw.IsLogin { //玩家未登录
|
||||
ret.Result = 4
|
||||
otherpp.SendPack(t1.Pack(ret))
|
||||
return false, nil
|
||||
}
|
||||
for t, v := range lw.PVPinfo {
|
||||
if t == userid && uint32(v) == mode { //成功找到,同意对战
|
||||
if lw.CanBattle() {
|
||||
ret.Result = 1
|
||||
//todo 待发送2503开始对战
|
||||
otherpp.SendPack(t1.Pack(ret))
|
||||
return true, otherpp
|
||||
|
||||
} else {
|
||||
ret.Result = 3
|
||||
otherpp.SendPack(t1.Pack(ret))
|
||||
return false, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
return false, nil
|
||||
|
||||
} //如果对方掉线
|
||||
return false, nil
|
||||
}
|
||||
|
||||
123
logic/service/user/user.go
Normal file
123
logic/service/user/user.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"blazing/logic/service/player"
|
||||
"blazing/modules/blazing/model"
|
||||
)
|
||||
|
||||
// SimUserInfoInboundInfo 对应 Java 的 SimUserInfoInboundInfo 类
|
||||
type SimUserInfoInboundInfo struct {
|
||||
Head player.TomeeHeader `cmd:"2051" struc:"[0]pad"`
|
||||
UserId uint32 `fieldDescription:"米米号" uint:"true" codec:"true"`
|
||||
}
|
||||
type SimUserInfoOutboundInfo struct {
|
||||
// UserID 米米号
|
||||
// 对应 Java 注解: @FieldDescription("米米号") @UInt
|
||||
UserID uint32 `codec:"true"`
|
||||
|
||||
Nick string `struc:"[16]byte" default:"seer" json:"nick"` // 16字节昵称
|
||||
|
||||
// Color rgb颜色
|
||||
// 对应 Java 注解: @FieldDescription("rgb颜色") @UInt
|
||||
Color uint32 `codec:"true"`
|
||||
|
||||
// Texture 纹理 0
|
||||
// 对应 Java 注解: @FieldDescription("纹理 0") @UInt
|
||||
Texture uint32 `codec:"true"`
|
||||
|
||||
// Vip 默认值 1 位置
|
||||
// 对应 Java 注解: @FieldDescription("默认值 1 位置") @UInt
|
||||
Vip uint32 `codec:"true"`
|
||||
|
||||
// Status 默认值0 未知
|
||||
// 对应 Java 注解: @FieldDescription("默认值0 未知") @UInt
|
||||
Status uint32 `codec:"true"`
|
||||
|
||||
// MapType 地图类型 进入地图包传入的字段
|
||||
// 对应 Java 注解: @FieldDescription("地图类型 进入地图包传入的字段") @UInt
|
||||
MapType uint32 `codec:"true"`
|
||||
|
||||
// MapID 地图id
|
||||
// 对应 Java 注解: @FieldDescription("地图id") @UInt
|
||||
MapID uint32 `codec:"true"`
|
||||
|
||||
// IsCanBeTeacher 能不能做教官
|
||||
// 对应 Java 注解: @FieldDescription("能不能做教官") @UInt
|
||||
IsCanBeTeacher uint32 `codec:"true"`
|
||||
|
||||
// TeacherID 教官id
|
||||
// 对应 Java 注解: @FieldDescription("教官id") @UInt
|
||||
TeacherID uint32 `codec:"true"`
|
||||
|
||||
// StudentID 学生id
|
||||
// 对应 Java 注解: @FieldDescription("学生id") @UInt
|
||||
StudentID uint32 `codec:"true"`
|
||||
|
||||
// GraduationCount 教官成绩
|
||||
// 对应 Java 注解: @FieldDescription("教官成绩") @UInt
|
||||
GraduationCount uint32 `codec:"true"`
|
||||
|
||||
// VipLevel vip等级 8
|
||||
// 对应 Java 注解: @FieldDescription("vip等级 8") @UInt
|
||||
VipLevel uint32 `codec:"true"`
|
||||
|
||||
// TeamId 战队id
|
||||
// 对应 Java 注解: @FieldDescription("战队id") @UInt
|
||||
TeamId uint32 `codec:"true"`
|
||||
|
||||
// IsShow 有没有跟随的精灵
|
||||
// 对应 Java 注解: @FieldDescription("有没有跟随的精灵") @UInt
|
||||
IsShow uint32 `codec:"true"`
|
||||
ClothesCount uint32 `struc:"sizeof=Clothes" json:"clothes_count"` // 穿戴装备数量
|
||||
// Clothes 穿戴装备
|
||||
// 对应 Java 注解: @FieldDescription("穿戴装备") @Builder.Default
|
||||
Clothes []model.PeopleItemInfo `codec:"true"`
|
||||
}
|
||||
type MoreUserInfoInboundInfo struct {
|
||||
Head player.TomeeHeader `cmd:"2052" struc:"[0]pad"`
|
||||
UserId uint32 `fieldDescription:"米米号" uint:"true" codec:"true"`
|
||||
}
|
||||
type MoreUserInfoOutboundInfo struct {
|
||||
// UserID 米米号
|
||||
// 对应 Java 注解: @FieldDescription("米米号") @UInt
|
||||
UserID uint32 `codec:"true"`
|
||||
|
||||
Nick string `struc:"[16]byte" default:"seer" json:"nick"` // 16字节昵称
|
||||
RegisterTime uint32 `struc:"uint32" json:"register_time"` // 注册时间(秒时间戳)
|
||||
|
||||
// PetAllNum 所有精灵数量
|
||||
// 对应 Java 注解: @FieldDescription("所有精灵数量") @UInt
|
||||
PetAllNum uint32 `codec:"true"`
|
||||
|
||||
// PetMaxLev 精灵最大等级
|
||||
// 对应 Java 注解: @FieldDescription("精灵最大等级") @UInt
|
||||
PetMaxLev uint32 `codec:"true"`
|
||||
|
||||
// BossAchievement spt boos成就 20字节 以任务状态完成 3为击败该boss, 目前spt只有12个, 剩下的长度以3填充
|
||||
// 对应 Java 注解: @FieldDescription(...) @ArraySerialize(value = ArraySerializeType.FIXED_LENGTH, fixedLength = 20) @Builder.Default
|
||||
BossAchievement []byte `codec:"true"`
|
||||
|
||||
// GraduationCount 教官成绩
|
||||
// 对应 Java 注解: @FieldDescription("教官成绩") @UInt
|
||||
GraduationCount uint32 `codec:"true"`
|
||||
|
||||
// MonKingWin 精灵王胜场
|
||||
// 对应 Java 注解: @FieldDescription("精灵王胜场") @UInt
|
||||
MonKingWin uint32 `codec:"true"`
|
||||
|
||||
// MessWin 大乱斗胜场
|
||||
// 对应 Java 注解: @FieldDescription("大乱斗胜场") @UInt
|
||||
MessWin uint32 `codec:"true"`
|
||||
|
||||
// MaxStage 勇者之塔层数
|
||||
// 对应 Java 注解: @FieldDescription("勇者之塔层数") @UInt
|
||||
MaxStage uint32 `codec:"true"`
|
||||
|
||||
// MaxArenaWins 星际擂台胜场
|
||||
// 对应 Java 注解: @FieldDescription("星际擂台胜场") @UInt
|
||||
MaxArenaWins uint32 `codec:"true"`
|
||||
|
||||
// CurrentTitleID 当前称号ID
|
||||
// 对应 Java 注解: @FieldDescription("当前称号ID") @UInt
|
||||
CurrentTitleID uint32 `codec:"true"`
|
||||
}
|
||||
@@ -45,7 +45,7 @@ func (s *UserService) Reg(nick string, color uint32) {
|
||||
}
|
||||
|
||||
t.Data = string(t22)
|
||||
_, err = cool.DBM(s.reg.Model).Data(t).Insert()
|
||||
_, err = cool.DBM(s.reg.Model).Data(t).FieldsEx("id").Insert()
|
||||
if err != nil {
|
||||
glog.Error(context.Background(), err)
|
||||
return
|
||||
@@ -62,6 +62,16 @@ func (s *UserService) Person() (ret *model.PlayerInfo) {
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
func (s *UserService) PersonOther(userid uint32) (ret *model.PlayerInfo) {
|
||||
|
||||
m := cool.DBM(s.reg.Model).Where("player_id", userid)
|
||||
var tt model.Player
|
||||
m.Scan(&tt)
|
||||
json.Unmarshal([]byte(tt.Data), &ret)
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
func (s *UserService) Save(data *model.PlayerInfo) {
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user