mirror of
https://github.com/oshima-studios/OshimaGameModule.git
synced 2026-06-04 19:42:13 +00:00
添加新功能
This commit is contained in:
parent
a769cdf25c
commit
67187200ca
@ -0,0 +1,89 @@
|
||||
using System.Text;
|
||||
using Milimoe.FunGame.Core.Entity;
|
||||
using Milimoe.FunGame.Core.Interface.Entity;
|
||||
using Milimoe.FunGame.Core.Library.Constant;
|
||||
using Oshima.FunGame.OshimaModules.BusinessSimulation.Interface;
|
||||
|
||||
namespace Oshima.FunGame.OshimaModules.BusinessSimulation.Entity
|
||||
{
|
||||
public class HumanResource : BaseEntity, IDescription, IBusinessSimulationEntity
|
||||
{
|
||||
public virtual QualityType QualityType { get; set; } = QualityType.White;
|
||||
public virtual HumanResourceType HumanResourceType { get; set; } = HumanResourceType.General;
|
||||
public virtual string Description { get; set; } = "";
|
||||
public virtual string GeneralDescription { get; set; } = "";
|
||||
public virtual string BackgroundStory { get; set; } = "";
|
||||
public virtual string Category { get; set; } = "";
|
||||
|
||||
public bool Enable { get; set; } = true;
|
||||
public Dictionary<string, string> SkillInfo { get; set; } = [];
|
||||
|
||||
public string ToString(bool isShowGeneralDescription, bool isShowInStore = false)
|
||||
{
|
||||
StringBuilder builder = new();
|
||||
|
||||
builder.AppendLine($"【{Name}】");
|
||||
|
||||
string itemquality = ItemSet.GetQualityTypeName(QualityType);
|
||||
string itemtype = GetHumanResourceTypeName(HumanResourceType);
|
||||
if (itemtype != "") itemtype = $" {itemtype}";
|
||||
|
||||
builder.AppendLine($"{itemquality + itemtype}");
|
||||
if (!string.IsNullOrWhiteSpace(Category)) builder.AppendLine(Category);
|
||||
|
||||
if (isShowGeneralDescription && GeneralDescription != "")
|
||||
{
|
||||
builder.AppendLine("描述:" + GeneralDescription);
|
||||
}
|
||||
else if (Description != "")
|
||||
{
|
||||
builder.AppendLine("描述:" + Description);
|
||||
}
|
||||
|
||||
if (SkillInfo.Count > 0)
|
||||
{
|
||||
builder.AppendLine("=== 技能 ===");
|
||||
foreach (var skill in SkillInfo)
|
||||
{
|
||||
builder.AppendLine($"{skill.Key}{(!string.IsNullOrWhiteSpace(skill.Value) ? $":{skill.Value}" : "")}");
|
||||
}
|
||||
}
|
||||
|
||||
if (BackgroundStory != "")
|
||||
{
|
||||
builder.AppendLine($"\"{BackgroundStory}\"");
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public override bool Equals(IBaseEntity? other) => other is HumanResource hr && GetIdName() == hr.GetIdName();
|
||||
|
||||
public static string GetHumanResourceTypeName(HumanResourceType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
HumanResourceType.General => "通用人力资源",
|
||||
HumanResourceType.Employee => "员工",
|
||||
HumanResourceType.Manager => "经理",
|
||||
HumanResourceType.Team => "团队",
|
||||
HumanResourceType.CooperationPartner => "合作伙伴",
|
||||
_ => "未知人力资源"
|
||||
};
|
||||
}
|
||||
|
||||
public virtual void UpdateSkillInfo()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public enum HumanResourceType
|
||||
{
|
||||
General,
|
||||
Employee,
|
||||
Manager,
|
||||
Team,
|
||||
CooperationPartner
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
namespace Oshima.FunGame.OshimaModules.BusinessSimulation.Entity
|
||||
{
|
||||
public class DeviceMovableProperty : MovableProperty
|
||||
{
|
||||
public override MovablePropertyType MovablePropertyType => MovablePropertyType.Device;
|
||||
|
||||
public virtual string DeviceType { get; set; } = "";
|
||||
public virtual double ProductionSpeedBonusPercent { get; set; } = 0;
|
||||
public virtual double EnergyConsumption { get; set; } = 0;
|
||||
public virtual int DeviceLevel { get; set; } = 1;
|
||||
|
||||
public List<string> CompatibleItem { get; set; } = [];
|
||||
|
||||
public DeviceMovableProperty()
|
||||
{
|
||||
UpdateSkillInfo();
|
||||
}
|
||||
|
||||
public override void UpdateSkillInfo()
|
||||
{
|
||||
base.UpdateSkillInfo();
|
||||
if (ProductionSpeedBonusPercent > 0) SkillInfo["生产速度"] = $"{ProductionSpeedBonusPercent:0.##}%";
|
||||
if (EnergyConsumption > 0) SkillInfo["能耗"] = $"{EnergyConsumption:0.##} 每日";
|
||||
if (DeviceLevel > 0) SkillInfo["设备等级"] = $"{DeviceLevel}";
|
||||
if (CompatibleItem.Count > 0) SkillInfo["适用货物"] = $"\r\n{string.Join("\r\n", CompatibleItem)}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,136 @@
|
||||
using System.Text;
|
||||
using Milimoe.FunGame.Core.Entity;
|
||||
using Milimoe.FunGame.Core.Interface.Entity;
|
||||
using Milimoe.FunGame.Core.Library.Constant;
|
||||
using Oshima.FunGame.OshimaModules.BusinessSimulation.Interface;
|
||||
|
||||
namespace Oshima.FunGame.OshimaModules.BusinessSimulation.Entity
|
||||
{
|
||||
public class MovableProperty : BaseEntity, IDescription, IBusinessSimulationEntity
|
||||
{
|
||||
public virtual QualityType QualityType { get; set; } = QualityType.White;
|
||||
public virtual MovablePropertyType MovablePropertyType { get; set; } = MovablePropertyType.General;
|
||||
public virtual string Description { get; set; } = "";
|
||||
public virtual string GeneralDescription { get; set; } = "";
|
||||
public virtual string BackgroundStory { get; set; } = "";
|
||||
public virtual double Price { get; set; } = 0;
|
||||
public virtual string Category { get; set; } = "";
|
||||
public virtual double MaintenanceCost { get; set; } = 0;
|
||||
public virtual string MaintenanceUnit { get; set; } = "每日";
|
||||
|
||||
public bool Enable { get; set; } = true;
|
||||
public bool IsPurchasable { get; set; } = true;
|
||||
public double OriginalPrice { get; set; } = 0;
|
||||
public bool IsSellable { get; set; } = true;
|
||||
public DateTime NextSellableTime { get; set; } = DateTime.MinValue;
|
||||
public Dictionary<string, string> SkillInfo { get; set; } = [];
|
||||
|
||||
public string ToString(bool isShowGeneralDescription, bool isShowInStore = false)
|
||||
{
|
||||
StringBuilder builder = new();
|
||||
|
||||
builder.AppendLine($"【{Name}】");
|
||||
|
||||
string itemquality = ItemSet.GetQualityTypeName(QualityType);
|
||||
string itemtype = GetMovablePropertyTypeName(MovablePropertyType);
|
||||
if (itemtype != "") itemtype = $" {itemtype}";
|
||||
|
||||
builder.AppendLine($"{itemquality + itemtype}");
|
||||
if (!string.IsNullOrWhiteSpace(Category)) builder.AppendLine(Category);
|
||||
|
||||
if (isShowInStore && Price > 0)
|
||||
{
|
||||
builder.AppendLine($"售价:{Price:0.##} {GameplayEquilibriumConstant.InGameCurrency}");
|
||||
}
|
||||
else if (Price > 0)
|
||||
{
|
||||
builder.AppendLine($"回收价:{Price:0.##} {GameplayEquilibriumConstant.InGameCurrency}");
|
||||
}
|
||||
if (OriginalPrice > 0) builder.AppendLine($"在商店或市场中出售时,售价不得超过原价:{OriginalPrice:0.##} {GameplayEquilibriumConstant.InGameCurrency}");
|
||||
|
||||
if (isShowInStore)
|
||||
{
|
||||
if (IsSellable)
|
||||
{
|
||||
builder.AppendLine($"购买此资产后可立即出售");
|
||||
}
|
||||
else if (NextSellableTime != DateTime.MinValue)
|
||||
{
|
||||
builder.AppendLine($"购买此资产后,将在 {NextSellableTime.ToString(General.GeneralDateTimeFormatChinese)} 后可出售");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsSellable)
|
||||
{
|
||||
builder.AppendLine("可出售");
|
||||
}
|
||||
else if (!IsSellable && NextSellableTime != DateTime.MinValue)
|
||||
{
|
||||
builder.AppendLine($"此资产将在 {NextSellableTime.ToString(General.GeneralDateTimeFormatChinese)} 后可出售");
|
||||
}
|
||||
else if (!IsSellable)
|
||||
{
|
||||
builder.AppendLine("不可出售");
|
||||
}
|
||||
}
|
||||
|
||||
if (MaintenanceCost > 0)
|
||||
{
|
||||
builder.AppendLine($"维护费:{(MaintenanceUnit != "" ? MaintenanceUnit : "每日")} {MaintenanceCost:0.##} {GameplayEquilibriumConstant.InGameCurrency}");
|
||||
}
|
||||
|
||||
if (isShowGeneralDescription && GeneralDescription != "")
|
||||
{
|
||||
builder.AppendLine("资产描述:" + GeneralDescription);
|
||||
}
|
||||
else if (Description != "")
|
||||
{
|
||||
builder.AppendLine("资产描述:" + Description);
|
||||
}
|
||||
|
||||
if (SkillInfo.Count > 0)
|
||||
{
|
||||
builder.AppendLine("=== 资产技能 ===");
|
||||
foreach (var skill in SkillInfo)
|
||||
{
|
||||
builder.AppendLine($"{skill.Key}{(!string.IsNullOrWhiteSpace(skill.Value) ? $":{skill.Value}" : "")}");
|
||||
}
|
||||
}
|
||||
|
||||
if (BackgroundStory != "")
|
||||
{
|
||||
builder.AppendLine($"\"{BackgroundStory}\"");
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public override bool Equals(IBaseEntity? other) => other is MovableProperty mp && GetIdName() == mp.GetIdName();
|
||||
|
||||
public static string GetMovablePropertyTypeName(MovablePropertyType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
MovablePropertyType.General => "通用资产",
|
||||
MovablePropertyType.OfficeAsset => "办公资产",
|
||||
MovablePropertyType.Device => "设备资产",
|
||||
MovablePropertyType.Vehicle => "载具资产",
|
||||
_ => "未知资产"
|
||||
};
|
||||
}
|
||||
|
||||
public virtual void UpdateSkillInfo()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public enum MovablePropertyType
|
||||
{
|
||||
General,
|
||||
OfficeAsset,
|
||||
Device,
|
||||
Vehicle
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
namespace Oshima.FunGame.OshimaModules.BusinessSimulation.Entity
|
||||
{
|
||||
public class OfficeAssetMovableProperty : MovableProperty
|
||||
{
|
||||
public override MovablePropertyType MovablePropertyType => MovablePropertyType.OfficeAsset;
|
||||
|
||||
public virtual string OfficeAssetType { get; set; } = "";
|
||||
public virtual int ComfortBonus { get; set; } = 0;
|
||||
public virtual double EfficiencyBonusPercent { get; set; } = 0;
|
||||
|
||||
public OfficeAssetMovableProperty()
|
||||
{
|
||||
UpdateSkillInfo();
|
||||
}
|
||||
|
||||
public override void UpdateSkillInfo()
|
||||
{
|
||||
if (ComfortBonus > 0) SkillInfo["舒适度"] = $"{ComfortBonus}";
|
||||
if (EfficiencyBonusPercent > 0) SkillInfo["办公效率"] = $"{EfficiencyBonusPercent * 100:0.##}%";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,101 @@
|
||||
using System.Text;
|
||||
using Milimoe.FunGame.Core.Entity;
|
||||
|
||||
namespace Oshima.FunGame.OshimaModules.BusinessSimulation.Entity
|
||||
{
|
||||
public class VehicleMovableProperty : MovableProperty
|
||||
{
|
||||
public override string Category => "载具";
|
||||
public override MovablePropertyType MovablePropertyType => MovablePropertyType.Vehicle;
|
||||
|
||||
public virtual string VehicleType { get; set; } = "";
|
||||
public virtual int PassengerCapacity { get; set; } = 0;
|
||||
public virtual int CargoCapacity { get; set; } = 0;
|
||||
public virtual double TransportTime { get; set; } = 5;
|
||||
public virtual double TransportCost { get; set; } = 0;
|
||||
public virtual double LoadingTime { get; set; } = 0;
|
||||
public virtual List<RealEstate> ParkingAvailable { get; set; } = [];
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => !IsParked && field;
|
||||
set;
|
||||
}
|
||||
public bool IsParked { get; set; } = false;
|
||||
public PackingRequirement ParkingIn { get; set; } = new(Guid.Empty, "");
|
||||
public List<TransportSchedule> TransportScheduleList { get; } = [];
|
||||
public List<Character> VehicleOperations { get; } = [];
|
||||
|
||||
public VehicleMovableProperty()
|
||||
{
|
||||
UpdateSkillInfo();
|
||||
}
|
||||
|
||||
public override void UpdateSkillInfo()
|
||||
{
|
||||
base.UpdateSkillInfo();
|
||||
if (PassengerCapacity > 0) SkillInfo["乘客定员"] = $"{PassengerCapacity}";
|
||||
if (CargoCapacity > 0) SkillInfo["载货容量"] = $"{CargoCapacity}";
|
||||
if (TransportTime > 0) SkillInfo["运输时间"] = $"{TransportTime:0.##} 分钟 / 次";
|
||||
if (TransportCost > 0) SkillInfo["运输成本"] = $"{TransportCost:0.##} {GameplayEquilibriumConstant.InGameCurrency} / 次";
|
||||
if (LoadingTime > 0) SkillInfo["装载时间"] = $"{LoadingTime:0.##} 分钟 / 百件货物";
|
||||
if (VehicleOperations.Count > 0) SkillInfo["车务担当"] = $"\r\n{string.Join("\r\n", VehicleOperations.Select(c => c.ToStringWithLevelWithOutUser()))}";
|
||||
if (TransportScheduleList.Count > 0)
|
||||
{
|
||||
int count = 0;
|
||||
SkillInfo["时间表"] = $"";
|
||||
foreach (TransportSchedule ts in TransportScheduleList)
|
||||
{
|
||||
SkillInfo[$"第 {++count} 站"] = $"\r\n{ts}";
|
||||
}
|
||||
}
|
||||
SkillInfo["停放于"] = $"\r\n{ParkingIn}";
|
||||
}
|
||||
}
|
||||
|
||||
public enum LoadingStrategy
|
||||
{
|
||||
Full = 1,
|
||||
Half = 2,
|
||||
AllowForTime = 3,
|
||||
SpecifiedCapacity = 4
|
||||
}
|
||||
|
||||
public class TransportSchedule(Guid esId, string esName)
|
||||
{
|
||||
public Guid TargetRealEstate { get; set; } = esId;
|
||||
public string TargetRealEstateName { get; set; } = esName;
|
||||
|
||||
public LoadingStrategy LoadingStrategy { get; set; } = LoadingStrategy.Full;
|
||||
public int WaitingTime { get; set; } = 0;
|
||||
public double SpecifiedCapacity { get; set; } = 0;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
sb.AppendLine($"目标地点:{TargetRealEstateName}");
|
||||
sb.AppendLine($"装载策略:{GetLoadingStrategyName(LoadingStrategy)}");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public string GetLoadingStrategyName(LoadingStrategy ls)
|
||||
{
|
||||
return ls switch
|
||||
{
|
||||
LoadingStrategy.Full => "满载",
|
||||
LoadingStrategy.Half => "半载",
|
||||
LoadingStrategy.AllowForTime => $"等待 {WaitingTime} 分钟",
|
||||
LoadingStrategy.SpecifiedCapacity => $"装载容量达到 {SpecifiedCapacity} 件",
|
||||
_ => "未知装载策略"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class PackingRequirement(Guid esId, string esName)
|
||||
{
|
||||
public Guid TargetRealEstate { get; set; } = esId;
|
||||
public string TargetRealEstateName { get; set; } = esName;
|
||||
|
||||
public override string ToString() => TargetRealEstateName;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
namespace Oshima.FunGame.OshimaModules.BusinessSimulation.Entity
|
||||
{
|
||||
public class CommercialRealEstate : RealEstate
|
||||
{
|
||||
public override RealEstateType RealEstateType => RealEstateType.Commercial;
|
||||
|
||||
public virtual string CommercialType { get; set; } = "";
|
||||
public virtual double Attractiveness { get; set; } = 0;
|
||||
|
||||
public CommercialRealEstate()
|
||||
{
|
||||
UpdateSkillInfo();
|
||||
}
|
||||
|
||||
public override void UpdateSkillInfo()
|
||||
{
|
||||
if (Attractiveness > 0) SkillInfo["吸引力"] = $"{Attractiveness * 100:0.##}%";
|
||||
}
|
||||
}
|
||||
|
||||
public class Shop : CommercialRealEstate
|
||||
{
|
||||
public override string CommercialType => "商铺";
|
||||
|
||||
public virtual int ShelfCount { get; set; } = 0;
|
||||
public virtual int InventoryCapacity { get; set; } = 0;
|
||||
public virtual int TruckCount { get; set; } = 0;
|
||||
|
||||
public Shop()
|
||||
{
|
||||
UpdateSkillInfo();
|
||||
}
|
||||
|
||||
public override void UpdateSkillInfo()
|
||||
{
|
||||
base.UpdateSkillInfo();
|
||||
if (ShelfCount > 0) SkillInfo["货架数量"] = $"{ShelfCount}";
|
||||
if (InventoryCapacity > 0) SkillInfo["库存容量"] = $"{InventoryCapacity}";
|
||||
if (TruckCount > 0) SkillInfo["附赠货车"] = $"{TruckCount}";
|
||||
}
|
||||
}
|
||||
|
||||
public class ParkingLot : CommercialRealEstate
|
||||
{
|
||||
public override string CommercialType => "停车场";
|
||||
|
||||
public virtual int BonusTruckCount { get; set; } = 0;
|
||||
public virtual int MaxTruckCapacity { get; set; } = 0;
|
||||
public virtual int PublicSpots { get; set; } = 0;
|
||||
public virtual double HourlyParkingFee { get; set; } = 0;
|
||||
|
||||
public int CurrentTruckCount { get; set; } = 0;
|
||||
public int CurrentPublicVehicles { get; set; } = 0;
|
||||
|
||||
public ParkingLot()
|
||||
{
|
||||
UpdateSkillInfo();
|
||||
}
|
||||
|
||||
public override void UpdateSkillInfo()
|
||||
{
|
||||
base.UpdateSkillInfo();
|
||||
if (BonusTruckCount > 0) SkillInfo["附赠货车"] = $"{BonusTruckCount}";
|
||||
if (MaxTruckCapacity > 0) SkillInfo["货车停放"] = $"{CurrentTruckCount} / {MaxTruckCapacity}";
|
||||
if (PublicSpots > 0) SkillInfo["公共车位"] = $"{CurrentPublicVehicles} / {PublicSpots}";
|
||||
if (HourlyParkingFee > 0) SkillInfo["公共停车费收入"] = $"{HourlyParkingFee:0.##} {GameplayEquilibriumConstant.InGameCurrency} / 辆每小时";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Oshima.FunGame.OshimaModules.BusinessSimulation.Entity
|
||||
{
|
||||
public class IndustrialRealEstate : RealEstate
|
||||
{
|
||||
public override RealEstateType RealEstateType => RealEstateType.Industrial;
|
||||
|
||||
public virtual string IndustryType { get; set; } = "";
|
||||
}
|
||||
|
||||
public class Factory : IndustrialRealEstate
|
||||
{
|
||||
public override string IndustryType => "工厂";
|
||||
|
||||
public virtual int TruckCount { get; set; } = 0;
|
||||
public virtual int MaxProductionLines { get; } = 1;
|
||||
public virtual double CostPerLine { get; set; } = 0;
|
||||
|
||||
public Dictionary<int, ProductionLine> ProductionLines { get; set; } = [];
|
||||
public int ActiveLines => ProductionLines.Values.Count(pl => pl.Active);
|
||||
public override double MaintenanceCost => ActiveLines * CostPerLine;
|
||||
public override string MaintenanceUnit => "每条有效流水线每日";
|
||||
|
||||
public Factory(int maxProductionLines = 1)
|
||||
{
|
||||
Category = "工厂";
|
||||
MaxProductionLines = maxProductionLines;
|
||||
// 注意:工厂一经创建,流水线数量便无法更改
|
||||
for (int i = 0; i < MaxProductionLines; i++)
|
||||
{
|
||||
ProductionLines[i] = new();
|
||||
}
|
||||
UpdateSkillInfo();
|
||||
}
|
||||
|
||||
public override void UpdateSkillInfo()
|
||||
{
|
||||
if (TruckCount > 0) SkillInfo["附赠货车"] = $"{TruckCount}";
|
||||
if (MaxProductionLines > 0)
|
||||
{
|
||||
SkillInfo["流水线数量"] = $"{ActiveLines} / {MaxProductionLines}";
|
||||
if (ProductionLines.Count > 0)
|
||||
{
|
||||
foreach (var line in ProductionLines)
|
||||
{
|
||||
SkillInfo[$"流水线 {line.Key}"] = $"\r\n{line.Value}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ProductionLine
|
||||
{
|
||||
public bool Active { get; set; } = false;
|
||||
public string ItemName { get; set; } = "";
|
||||
public double CountPerMinute => 60.0 / UnitProductionTime;
|
||||
public int UnitProductionTime { get; set; } = 0;
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
sb.AppendLine($"是否启用:{(Active ? "是" : "否")}");
|
||||
sb.AppendLine($"生产货物:{ItemName}");
|
||||
sb.AppendLine($"生产时间:{UnitProductionTime} 秒 / 件");
|
||||
sb.AppendLine($"理论产量:{CountPerMinute:0.##} 件 / 分钟");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
138
OshimaModules/BusinessSimulation/Entity/RealEstate/RealEstate.cs
Normal file
138
OshimaModules/BusinessSimulation/Entity/RealEstate/RealEstate.cs
Normal file
@ -0,0 +1,138 @@
|
||||
using System.Text;
|
||||
using Milimoe.FunGame.Core.Entity;
|
||||
using Milimoe.FunGame.Core.Interface.Entity;
|
||||
using Milimoe.FunGame.Core.Library.Constant;
|
||||
using Oshima.FunGame.OshimaModules.BusinessSimulation.Interface;
|
||||
|
||||
namespace Oshima.FunGame.OshimaModules.BusinessSimulation.Entity
|
||||
{
|
||||
public class RealEstate : BaseEntity, IDescription, IBusinessSimulationEntity
|
||||
{
|
||||
public virtual QualityType QualityType { get; set; } = QualityType.White;
|
||||
public virtual RealEstateType RealEstateType { get; set; } = RealEstateType.General;
|
||||
public virtual string Description { get; set; } = "";
|
||||
public virtual string GeneralDescription { get; set; } = "";
|
||||
public virtual string BackgroundStory { get; set; } = "";
|
||||
public virtual double Price { get; set; } = 0;
|
||||
public virtual string Category { get; set; } = "";
|
||||
public virtual double MaintenanceCost { get; set; } = 0;
|
||||
public virtual string MaintenanceUnit { get; set; } = "每日";
|
||||
|
||||
public bool Enable { get; set; } = true;
|
||||
public bool IsPurchasable { get; set; } = true;
|
||||
public double OriginalPrice { get; set; } = 0;
|
||||
public bool IsSellable { get; set; } = true;
|
||||
public DateTime NextSellableTime { get; set; } = DateTime.MinValue;
|
||||
public Dictionary<string, string> SkillInfo { get; set; } = [];
|
||||
|
||||
public string ToString(bool isShowGeneralDescription, bool isShowInStore = false)
|
||||
{
|
||||
StringBuilder builder = new();
|
||||
|
||||
builder.AppendLine($"【{Name}】");
|
||||
|
||||
string itemquality = ItemSet.GetQualityTypeName(QualityType);
|
||||
string itemtype = GetRealEstateTypeName(RealEstateType);
|
||||
if (itemtype != "") itemtype = $" {itemtype}";
|
||||
|
||||
builder.AppendLine($"{itemquality + itemtype}");
|
||||
if (!string.IsNullOrWhiteSpace(Category)) builder.AppendLine(Category);
|
||||
|
||||
if (isShowInStore && Price > 0)
|
||||
{
|
||||
builder.AppendLine($"售价:{Price:0.##} {GameplayEquilibriumConstant.InGameCurrency}");
|
||||
}
|
||||
else if (Price > 0)
|
||||
{
|
||||
builder.AppendLine($"回收价:{Price:0.##} {GameplayEquilibriumConstant.InGameCurrency}");
|
||||
}
|
||||
if (OriginalPrice > 0) builder.AppendLine($"在商店或市场中出售时,售价不得超过原价:{OriginalPrice:0.##} {GameplayEquilibriumConstant.InGameCurrency}");
|
||||
|
||||
if (isShowInStore)
|
||||
{
|
||||
if (IsSellable)
|
||||
{
|
||||
builder.AppendLine($"购买此资产后可立即出售");
|
||||
}
|
||||
else if (NextSellableTime != DateTime.MinValue)
|
||||
{
|
||||
builder.AppendLine($"购买此资产后,将在 {NextSellableTime.ToString(General.GeneralDateTimeFormatChinese)} 后可出售");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsSellable)
|
||||
{
|
||||
builder.AppendLine("可出售");
|
||||
}
|
||||
else if (!IsSellable && NextSellableTime != DateTime.MinValue)
|
||||
{
|
||||
builder.AppendLine($"此资产将在 {NextSellableTime.ToString(General.GeneralDateTimeFormatChinese)} 后可出售");
|
||||
}
|
||||
else if (!IsSellable)
|
||||
{
|
||||
builder.AppendLine("不可出售");
|
||||
}
|
||||
}
|
||||
|
||||
if (MaintenanceCost > 0)
|
||||
{
|
||||
builder.AppendLine($"维护费:{(MaintenanceUnit != "" ? MaintenanceUnit : "每日")} {MaintenanceCost:0.##} {GameplayEquilibriumConstant.InGameCurrency}");
|
||||
}
|
||||
|
||||
if (isShowGeneralDescription && GeneralDescription != "")
|
||||
{
|
||||
builder.AppendLine("资产描述:" + GeneralDescription);
|
||||
}
|
||||
else if (Description != "")
|
||||
{
|
||||
builder.AppendLine("资产描述:" + Description);
|
||||
}
|
||||
|
||||
if (SkillInfo.Count > 0)
|
||||
{
|
||||
builder.AppendLine("=== 资产技能 ===");
|
||||
foreach (var skill in SkillInfo)
|
||||
{
|
||||
builder.AppendLine($"{skill.Key}{(!string.IsNullOrWhiteSpace(skill.Value) ? $":{skill.Value}" : "")}");
|
||||
}
|
||||
}
|
||||
|
||||
if (BackgroundStory != "")
|
||||
{
|
||||
builder.AppendLine($"\"{BackgroundStory}\"");
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public override bool Equals(IBaseEntity? other) => other is RealEstate re && GetIdName() == re.GetIdName();
|
||||
|
||||
public static string GetRealEstateTypeName(RealEstateType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
RealEstateType.General => "通用资产",
|
||||
RealEstateType.Residential => "住宅资产",
|
||||
RealEstateType.Commercial => "商业资产",
|
||||
RealEstateType.Industrial => "工业资产",
|
||||
RealEstateType.Warehouse => "仓储资产",
|
||||
_ => "未知资产"
|
||||
};
|
||||
}
|
||||
|
||||
public virtual void UpdateSkillInfo()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public enum RealEstateType
|
||||
{
|
||||
General,
|
||||
Residential,
|
||||
Commercial,
|
||||
Industrial,
|
||||
Warehouse
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
using Milimoe.FunGame.Core.Entity;
|
||||
|
||||
namespace Oshima.FunGame.OshimaModules.BusinessSimulation.Entity
|
||||
{
|
||||
public class ResidentialRealEstate : RealEstate
|
||||
{
|
||||
public override RealEstateType RealEstateType => RealEstateType.Residential;
|
||||
|
||||
public virtual int MaxResidents { get; set; } = 0;
|
||||
public virtual int ExperiencePerMinute { get; set; } = 0;
|
||||
public virtual double RegenerationBonus { get; set; } = 0;
|
||||
public virtual int ExtraInventorySlots { get; set; } = 0;
|
||||
public virtual int FreeReviveCount { get; set; } = 0;
|
||||
public virtual double CostPerResident { get; set; } = 0;
|
||||
|
||||
public int CurrentUsedFreeReviveCount { get; set; } = 0;
|
||||
public override double MaintenanceCost => Characters.Count * CostPerResident;
|
||||
public override string MaintenanceUnit => "每人每日";
|
||||
public HashSet<Character> Characters { get; } = [];
|
||||
|
||||
public ResidentialRealEstate()
|
||||
{
|
||||
UpdateSkillInfo();
|
||||
}
|
||||
|
||||
public override void UpdateSkillInfo()
|
||||
{
|
||||
if (MaxResidents > 0) SkillInfo["可入住角色数量"] = $"{Characters.Count} / {MaxResidents}";
|
||||
if (ExperiencePerMinute > 0) SkillInfo["入住角色经验加成"] = $"{ExperiencePerMinute} / 人每分钟";
|
||||
if (RegenerationBonus > 0) SkillInfo["生命/魔法回复速度提升"] = $"{RegenerationBonus * 100:0.##}%";
|
||||
if (ExtraInventorySlots > 0) SkillInfo["额外玩家库存容量"] = $"{ExtraInventorySlots}";
|
||||
if (FreeReviveCount > 0) SkillInfo["免费复活次数"] = $"{CurrentUsedFreeReviveCount} / {FreeReviveCount}";
|
||||
if (Characters.Count > 0) SkillInfo["已入住角色"] = $"\r\n{string.Join("\r\n", Characters.Select(c => c.ToStringWithLevelWithOutUser()))}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
namespace Oshima.FunGame.OshimaModules.BusinessSimulation.Entity
|
||||
{
|
||||
public class WarehouseRealEstate : RealEstate
|
||||
{
|
||||
public override RealEstateType RealEstateType => RealEstateType.Warehouse;
|
||||
|
||||
public virtual int InventoryCapacity { get; set; } = 0;
|
||||
|
||||
public WarehouseRealEstate()
|
||||
{
|
||||
UpdateSkillInfo();
|
||||
}
|
||||
|
||||
public override void UpdateSkillInfo()
|
||||
{
|
||||
if (InventoryCapacity > 0) SkillInfo["库存容量"] = $"{InventoryCapacity}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
namespace Oshima.FunGame.OshimaModules.BusinessSimulation.Interface
|
||||
{
|
||||
public interface IBusinessSimulationEntity
|
||||
{
|
||||
public string Category { get; }
|
||||
public bool Enable { get; }
|
||||
public Dictionary<string, string> SkillInfo { get; }
|
||||
|
||||
public void UpdateSkillInfo();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
namespace Oshima.FunGame.OshimaModules.BusinessSimulation.Interface
|
||||
{
|
||||
public interface IDescription
|
||||
{
|
||||
public string Description { get; }
|
||||
public string GeneralDescription { get; }
|
||||
public string BackgroundStory { get; }
|
||||
}
|
||||
}
|
||||
@ -84,6 +84,15 @@ namespace Oshima.FunGame.WebAPI.Model
|
||||
|
||||
[JsonPropertyName("stage")]
|
||||
public string? Stage { get; set; }
|
||||
|
||||
[JsonPropertyName("team1")]
|
||||
public string? Team1 { get; set; }
|
||||
|
||||
[JsonPropertyName("team2")]
|
||||
public string? Team2 { get; set; }
|
||||
|
||||
[JsonPropertyName("betting_enabled")]
|
||||
public bool? BettingEnabled { get; set; }
|
||||
}
|
||||
|
||||
public class BettingEvent
|
||||
@ -165,6 +174,9 @@ namespace Oshima.FunGame.WebAPI.Model
|
||||
|
||||
[JsonPropertyName("updated_at")]
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
|
||||
[JsonPropertyName("betting_enabled")]
|
||||
public bool BettingEnabled { get; set; } = true;
|
||||
}
|
||||
|
||||
public class BettingBetRecord
|
||||
|
||||
@ -48,7 +48,7 @@ namespace Oshima.FunGame.WebAPI.Services
|
||||
int id = Convert.ToInt32(row["id"]);
|
||||
string name = row["name"].ToString() ?? "";
|
||||
int status = Convert.ToInt32(row["status"]);
|
||||
string statusStr = status switch { 0 => "未开始", 1 => "进行中", 2 => "已结束", _ => "未知" };
|
||||
string statusStr = status switch { 0 => "未开始", 1 => "进行中", 2 => "已结束", 3 => "已取消", _ => "未知" };
|
||||
DateTime startTime = Convert.ToDateTime(row["start_time"]);
|
||||
DateTime endTime = Convert.ToDateTime(row["end_time"]);
|
||||
sb.AppendLine($"🏆 [{id}] {name.CreateCmdInput($"赛事详情 {id}")} ({statusStr}:{startTime:yyyy/MM/dd} ~ {endTime:yyyy/MM/dd})");
|
||||
@ -73,7 +73,7 @@ namespace Oshima.FunGame.WebAPI.Services
|
||||
int status = Convert.ToInt32(evt["status"]);
|
||||
DateTime start = Convert.ToDateTime(evt["start_time"]);
|
||||
DateTime end = Convert.ToDateTime(evt["end_time"]);
|
||||
string statusStr = status switch { 0 => "未开始", 1 => "进行中", 2 => "已结束", _ => "未知" };
|
||||
string statusStr = status switch { 0 => "未开始", 1 => "进行中", 2 => "已结束", 3 => "已取消", _ => "未知" };
|
||||
|
||||
StringBuilder header = new();
|
||||
header.AppendLine($"赛事:{name}");
|
||||
@ -115,7 +115,7 @@ namespace Oshima.FunGame.WebAPI.Services
|
||||
DateTime deadline = Convert.ToDateTime(row["bet_deadline"]);
|
||||
string stage = row["stage"].ToString() ?? "";
|
||||
string result = row["result"] != DBNull.Value ? row["result"].ToString() ?? "" : "";
|
||||
string mStatusStr = mstatus switch { 0 => "未开始", 1 => "进行中", 2 => "已结束", _ => "未知" };
|
||||
string mStatusStr = mstatus switch { 0 => "未开始", 1 => "进行中", 2 => "已结束", 3 => "已取消", _ => "未知" };
|
||||
string matchLabel = $"{t1} vs {t2}";
|
||||
string clickableMatch = matchLabel.CreateCmdInput($"比赛详情 {mid}");
|
||||
matches.AppendLine($" [{mid}] {(stage != "" ? $"{stage} " : "")} {clickableMatch} (状态:{mStatusStr}{(result.Trim() != "" ? $", 结果:{result}" : "")}, 截止:{deadline:MM-dd HH:mm})");
|
||||
@ -179,7 +179,7 @@ namespace Oshima.FunGame.WebAPI.Services
|
||||
long eventId = Convert.ToInt64(row["event_id"]);
|
||||
string result = row["result"] != DBNull.Value ? row["result"].ToString() ?? "" : "";
|
||||
|
||||
string statusStr = status switch { 0 => "未开始", 1 => "进行中", 2 => $"已结束 | {result}", _ => "未知" };
|
||||
string statusStr = status switch { 0 => "未开始", 1 => "进行中", 2 => $"已结束 | {result}", 3 => "已取消", _ => "未知" };
|
||||
string matchLabel = $"{t1} vs {t2}".CreateCmdInput($"比赛详情 {id}");
|
||||
|
||||
sb.Append($"[{id}] {matchLabel}");
|
||||
@ -215,6 +215,7 @@ namespace Oshima.FunGame.WebAPI.Services
|
||||
long winner = row["winner"] != DBNull.Value ? Convert.ToInt64(row["winner"]) : 0;
|
||||
decimal team1Odds = Convert.ToDecimal(row["team1_win_odds"]);
|
||||
decimal team2Odds = Convert.ToDecimal(row["team2_win_odds"]);
|
||||
bool bettingEnabled = Convert.ToBoolean(row["betting_enabled"]);
|
||||
|
||||
string eventName = "";
|
||||
sql.Parameters["@eid"] = eventId;
|
||||
@ -240,7 +241,7 @@ namespace Oshima.FunGame.WebAPI.Services
|
||||
}
|
||||
}
|
||||
|
||||
string statusStr = status switch { 0 => "未开始", 1 => "进行中", 2 => "已结束", _ => "未知" };
|
||||
string statusStr = status switch { 0 => "未开始", 1 => "进行中", 2 => "已结束", 3 => "已取消", _ => "未知" };
|
||||
StringBuilder sb = new();
|
||||
sb.AppendLine($"比赛 #{matchId}");
|
||||
if (eventName.Trim() != "")
|
||||
@ -260,9 +261,10 @@ namespace Oshima.FunGame.WebAPI.Services
|
||||
}
|
||||
|
||||
DateTime now = DateTime.Now;
|
||||
bool canBet = (status == 0 || status == 1) && now < deadline;
|
||||
bool canBet = (status == 0 || status == 1) && now < deadline && bettingEnabled;
|
||||
if (canBet) sb.AppendLine($"可用选项:");
|
||||
else sb.AppendLine($"该比赛已截止预测。");
|
||||
else if (!bettingEnabled) sb.AppendLine($"🔒 该比赛不开放预测。");
|
||||
else sb.AppendLine($"🔒 预测已锁定。");
|
||||
|
||||
string GetStatString(int opt)
|
||||
{
|
||||
@ -334,9 +336,15 @@ namespace Oshima.FunGame.WebAPI.Services
|
||||
DateTime deadline = Convert.ToDateTime(row["bet_deadline"]);
|
||||
decimal team1Odds = Convert.ToDecimal(row["team1_win_odds"]);
|
||||
decimal team2Odds = Convert.ToDecimal(row["team2_win_odds"]);
|
||||
bool bettingEnabled = Convert.ToBoolean(row["betting_enabled"]);
|
||||
if (!bettingEnabled)
|
||||
{
|
||||
error = "该比赛不开放预测。";
|
||||
return false;
|
||||
}
|
||||
if (status == 2 || DateTime.Now > deadline)
|
||||
{
|
||||
error = "当前比赛已结束或非可预测阶段。";
|
||||
error = "该比赛处于不可预测阶段。";
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -429,7 +437,9 @@ namespace Oshima.FunGame.WebAPI.Services
|
||||
bool isMvp = available.Contains("mvp", StringComparison.CurrentCultureIgnoreCase);
|
||||
int status = Convert.ToInt32(row["status"]);
|
||||
if (status == 2)
|
||||
return "比赛已结算。";
|
||||
return "比赛已结算,无法再次结算。";
|
||||
if (status == 3)
|
||||
return "比赛已取消,无法结算。";
|
||||
|
||||
int winTeam = 3;
|
||||
if (!isMvp)
|
||||
@ -679,11 +689,11 @@ namespace Oshima.FunGame.WebAPI.Services
|
||||
|
||||
// 根据时间,设置比赛状态为未开始
|
||||
sql.Parameters["@now"] = now;
|
||||
sql.Execute("UPDATE csbetting_matches SET status = 0 WHERE start_time >= @now");
|
||||
sql.Execute("UPDATE csbetting_matches SET status = 0 WHERE start_time >= @now AND status != 3");
|
||||
|
||||
// 更新比赛状态:0→1 (进行中),1→2 (已结束) - 注意不要覆盖已结算的比赛(winner 为 null 时视为未结束)
|
||||
sql.Parameters["@now"] = now;
|
||||
sql.Execute("UPDATE csbetting_matches SET status = 1 WHERE status = 0 AND start_time <= @now AND bet_deadline < @now AND winner IS NULL");
|
||||
sql.Execute("UPDATE csbetting_matches SET status = 1 WHERE status = 0 AND start_time <= @now AND bet_deadline < @now AND winner IS NULL AND status != 3");
|
||||
// 对于已经过了开始时间但还没有 winner 且状态为 1 的,可保留为进行中;实际上只要 winner 为 null,状态应为 1(进行中)
|
||||
// 如果有结果但 winner 不为 null,管理员应该已经手动结算,状态会设为 2,这里不做额外修改。
|
||||
// 安全起见,只更新未开始的,以及当比赛时间已过且无 winner 时自动变成进行中。
|
||||
@ -843,12 +853,12 @@ namespace Oshima.FunGame.WebAPI.Services
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 管理员提前结束预测(将未开始比赛标记为进行中)
|
||||
/// 管理员提前结束预测
|
||||
/// </summary>
|
||||
public static bool CloseBetting(int matchId, out string message) => UpdateMatch(new()
|
||||
{
|
||||
MatchId = matchId,
|
||||
StartTime = DateTime.Now
|
||||
BetDeadline = DateTime.Now
|
||||
}, out message);
|
||||
|
||||
public static bool UpdateMatch(UpdateMatchRequest request, out string error)
|
||||
@ -946,19 +956,34 @@ namespace Oshima.FunGame.WebAPI.Services
|
||||
}
|
||||
if (request.Description != null)
|
||||
{
|
||||
sql.Parameters["@desc"] = (object?)request.Description ?? "";
|
||||
sql.Parameters["@desc"] = request.Description ?? "";
|
||||
setClause.Append("description = @desc, ");
|
||||
}
|
||||
if (request.Result != null)
|
||||
{
|
||||
sql.Parameters["@result"] = (object?)request.Result ?? "";
|
||||
sql.Parameters["@result"] = request.Result ?? "";
|
||||
setClause.Append("result = @result, ");
|
||||
}
|
||||
if (request.Stage != null)
|
||||
{
|
||||
sql.Parameters["@stage"] = (object?)request.Stage ?? "";
|
||||
sql.Parameters["@stage"] = request.Stage ?? "";
|
||||
setClause.Append("stage = @stage, ");
|
||||
}
|
||||
if (request.Team1 != null)
|
||||
{
|
||||
sql.Parameters["@t1"] = request.Team1 ?? "";
|
||||
setClause.Append("team1_name = @t1, ");
|
||||
}
|
||||
if (request.Team2 != null)
|
||||
{
|
||||
sql.Parameters["@t2"] = request.Team2 ?? "";
|
||||
setClause.Append("team2_name = @t2, ");
|
||||
}
|
||||
if (request.BettingEnabled.HasValue)
|
||||
{
|
||||
sql.Parameters["@betting_enabled"] = request.BettingEnabled.Value ? 1 : 0;
|
||||
setClause.Append("betting_enabled = @betting_enabled, ");
|
||||
}
|
||||
|
||||
if (setClause.Length == 0)
|
||||
{
|
||||
@ -980,6 +1005,95 @@ namespace Oshima.FunGame.WebAPI.Services
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消比赛(管理员操作),退还所有未结算投注的本金,标记比赛状态为已取消
|
||||
/// </summary>
|
||||
/// <param name="matchId">比赛ID</param>
|
||||
/// <param name="error">错误信息</param>
|
||||
/// <returns>是否成功</returns>
|
||||
public static bool CancelMatch(int matchId, out string error)
|
||||
{
|
||||
error = "";
|
||||
using SQLHelper? sql = Factory.OpenFactory.GetSQLHelper();
|
||||
if (sql == null)
|
||||
{
|
||||
error = "数据库连接失败。";
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
sql.NewTransaction();
|
||||
|
||||
// 1. 获取比赛信息
|
||||
sql.Parameters["@mid"] = matchId;
|
||||
sql.ExecuteDataSet("SELECT id, status FROM csbetting_matches WHERE id = @mid");
|
||||
if (!sql.Success || sql.DataSet.Tables[0].Rows.Count == 0)
|
||||
{
|
||||
error = "比赛不存在。";
|
||||
sql.Rollback();
|
||||
return false;
|
||||
}
|
||||
DataRow row = sql.DataSet.Tables[0].Rows[0];
|
||||
int status = Convert.ToInt32(row["status"]);
|
||||
if (status == 2)
|
||||
{
|
||||
error = "比赛已结束,无法取消。";
|
||||
sql.Rollback();
|
||||
return false;
|
||||
}
|
||||
if (status == 3)
|
||||
{
|
||||
error = "比赛已经是取消状态。";
|
||||
sql.Rollback();
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 获取该比赛所有未结算的投注记录
|
||||
sql.Parameters["@mid"] = matchId;
|
||||
sql.ExecuteDataSet("SELECT id, amount FROM csbetting_bet_records WHERE match_id = @mid AND is_settled = 0");
|
||||
if (sql.Success && sql.DataSet.Tables[0].Rows.Count > 0)
|
||||
{
|
||||
foreach (DataRow bet in sql.DataSet.Tables[0].Rows)
|
||||
{
|
||||
long betId = Convert.ToInt64(bet["id"]);
|
||||
long amount = Convert.ToInt64(bet["amount"]);
|
||||
|
||||
// 退还本金(payout = amount),标记为已结算,注明取消,但不自动领取
|
||||
sql.Parameters["@bid"] = betId;
|
||||
sql.Parameters["@payout"] = amount;
|
||||
sql.Execute("UPDATE csbetting_bet_records SET is_settled = 1, payout = @payout, result_note = '比赛取消,退还本金' WHERE id = @bid");
|
||||
if (!sql.Success)
|
||||
{
|
||||
sql.Rollback();
|
||||
error = "退还投注本金失败。";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 更新比赛状态为 3(已取消),并禁止继续投注
|
||||
sql.Parameters["@mid"] = matchId;
|
||||
sql.Execute("UPDATE csbetting_matches SET status = 3, betting_enabled = 0 WHERE id = @mid");
|
||||
if (!sql.Success)
|
||||
{
|
||||
sql.Rollback();
|
||||
error = "更新比赛状态失败。";
|
||||
return false;
|
||||
}
|
||||
|
||||
sql.Commit();
|
||||
error = $"比赛 {matchId} 已取消,所有未结算投注的本金已退还,请用户通过【预测领奖】领取。";
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
sql.Rollback();
|
||||
error = $"取消比赛异常:{ex.Message}";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static (decimal oddsA, decimal oddsB) CalculateOdds(decimal team1WinProbability, decimal margin = 0.08m)
|
||||
{
|
||||
if (team1WinProbability <= 0 || team1WinProbability >= 1)
|
||||
|
||||
@ -79,3 +79,8 @@ ALTER TABLE `csbetting_matches`
|
||||
-- 为投注记录表增加投注时的赔率字段,确保结算公平
|
||||
ALTER TABLE `csbetting_bet_records`
|
||||
ADD COLUMN `odds_at_bet` DECIMAL(5,2) NOT NULL DEFAULT 0.00 COMMENT '投注时的赔率' AFTER `amount`;
|
||||
|
||||
-- 增加比赛是否允许预测字段,以及状态值 3 表示已取消
|
||||
ALTER TABLE `csbetting_matches`
|
||||
MODIFY COLUMN `status` tinyint NOT NULL DEFAULT 0 COMMENT '比赛状态:0=未开始,1=进行中,2=已结束,3=已取消',
|
||||
ADD COLUMN `betting_enabled` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否允许预测:1=允许,0=禁止' AFTER `description`;
|
||||
|
||||
@ -239,7 +239,8 @@ namespace Oshima.FunGame.WebAPI.Controllers
|
||||
Team2WinOdds = Convert.ToDecimal(row["team2_win_odds"]),
|
||||
Description = row["description"]?.ToString(),
|
||||
CreatedAt = Convert.ToDateTime(row["created_at"]),
|
||||
UpdatedAt = Convert.ToDateTime(row["updated_at"])
|
||||
UpdatedAt = Convert.ToDateTime(row["updated_at"]),
|
||||
BettingEnabled = Convert.ToInt32(row["betting_enabled"]) == 1
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -338,5 +338,32 @@ namespace Oshima.FunGame.WebAPI.Controllers
|
||||
return reply;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("cancel-match")]
|
||||
public BotReply CancelMatch([FromQuery] long uid, [FromQuery] int matchId)
|
||||
{
|
||||
MarkdownMessage md = new() { Content = "服务器繁忙,请稍后再试。" };
|
||||
BotReply reply = new() { Markdown = md };
|
||||
try
|
||||
{
|
||||
if (!FunGameConstant.UserIdAndUsername.TryGetValue(uid, out User? user) || (!user.IsAdmin && !user.IsOperator))
|
||||
{
|
||||
md.Content = "你没有权限执行此操作。";
|
||||
return reply;
|
||||
}
|
||||
|
||||
if (CSBettingService.CancelMatch(matchId, out string msg))
|
||||
md.Content = msg;
|
||||
else
|
||||
md.Content = msg;
|
||||
|
||||
return reply;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError(e, "CancelMatch 异常");
|
||||
return reply;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
using System.Security.Cryptography;
|
||||
using Milimoe.FunGame.Core.Entity;
|
||||
using Milimoe.FunGame.Core.Entity;
|
||||
using Milimoe.FunGame.Core.Library.Constant;
|
||||
using Oshima.FunGame.OshimaModules.Models;
|
||||
using Oshima.FunGame.OshimaServers.Model;
|
||||
@ -295,11 +294,7 @@ namespace Oshima.FunGame.WebAPI.Services
|
||||
return true;
|
||||
}
|
||||
|
||||
// 指令:创建比赛 <赛事ID> <队伍1> <队伍2> <阶段> <开始时间> <预测截止时间> [选项列表(逗号分隔)]
|
||||
// 示例(创建默认形式的比赛):创建比赛 1 NAVI FaZe Quarter-final 2026-03-05 14:00 2026-03-05 13:55
|
||||
// 示例(提供比赛助力选项):创建比赛 1 G2 Vitality Semi-final 2026-04-02 18:00 2026-04-02 17:55 team1_win,team2_win
|
||||
// 示例(提供自定义奖励率):创建比赛 1 G2 Vitality Semi-final 2026-04-02 18:00 2026-04-02 17:55 team1_win=2.5 team2_win=2.5 team1_win,team2_win
|
||||
// 示例(提供队伍1的胜率自动计算奖励率):创建比赛 1 Vitality FaZe Final 2026-05-12 20:00 2026-05-12 19:55 prob=0.6
|
||||
// 指令:创建比赛 <赛事ID> <队伍1> <队伍2> <阶段> <开始时间> [选项列表(key=value格式,空格分隔)]
|
||||
if (e.Detail.StartsWith("创建比赛"))
|
||||
{
|
||||
e.UseNotice = false;
|
||||
@ -311,13 +306,15 @@ namespace Oshima.FunGame.WebAPI.Services
|
||||
|
||||
string detail = e.Detail.Replace("创建比赛", "").Trim();
|
||||
string[] parts = detail.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length < 7) // 最少需要 eventId, team1, team2, stage, start date, start time, deadline date, deadline time
|
||||
if (parts.Length < 6) // 最少需要 eventId, team1, team2, stage, start date, start time
|
||||
{
|
||||
await SendAsync(e, "创建比赛",
|
||||
"格式:创建比赛 <赛事ID> <队伍1> <队伍2> <阶段> <开始时间> <预测截止时间> [选项列表(逗号分隔)]\r\n" +
|
||||
"时间格式:yyyy-MM-dd HH:mm(开始/截止各占两段)\r\n" +
|
||||
"示例:创建比赛 1 NAVI FaZe Quarter-final 2026-03-05 14:00 2026-03-05 13:55\r\n" +
|
||||
"选项默认 team1_win,team2_win ,比分和MVP选项只允许独立添加:score,mvp");
|
||||
"格式:创建比赛 <赛事ID> <队伍1> <队伍2> <阶段> <开始时间> [选项列表(key=value格式,空格分隔)]\r\n" +
|
||||
"开始时间格式:yyyy-MM-dd HH:mm(两段)\r\n" +
|
||||
"可选参数:ddl=截止时间(需要用双引号包围),opts=选项列表(逗号分隔,默认team1_win,team2_win),team1_win=奖励率,team2_win=奖励率,prob=胜率\r\n" +
|
||||
"示例:创建比赛 1 NAVI FaZe Quarter-final 2026-03-05 14:00\r\n" +
|
||||
"示例:创建比赛 1 NAVI FaZe Quarter-final 2026-03-05 14:00 ddl=\"2026-03-05 13:55\" opts=team1_win,team2_win\r\n" +
|
||||
"示例:创建比赛 1 G2 Vitality Semi-final 2026-04-02 18:00 ddl=\"2026-04-02 17:55\" opts=team1_win,team2_win team1_win=2.5 prob=0.6");
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -334,54 +331,90 @@ namespace Oshima.FunGame.WebAPI.Services
|
||||
// 开始时间(parts[4] + parts[5])
|
||||
string startDate = parts[4];
|
||||
string startTime = parts[5];
|
||||
// 截止时间(parts[6] + parts[7] 如果存在)
|
||||
if (parts.Length < 8)
|
||||
if (!DateTime.TryParseExact(startDate + " " + startTime, "yyyy-MM-dd HH:mm", null, System.Globalization.DateTimeStyles.None, out DateTime startDt))
|
||||
{
|
||||
await SendAsync(e, "创建比赛", "预测截止时间需要完整日期和时间,示例:2026-03-05 13:55");
|
||||
return true;
|
||||
}
|
||||
string deadlineDate = parts[6];
|
||||
string deadlineTime = parts[7];
|
||||
|
||||
if (!DateTime.TryParseExact(startDate + " " + startTime, "yyyy-MM-dd HH:mm", null, System.Globalization.DateTimeStyles.None, out DateTime startDt) ||
|
||||
!DateTime.TryParseExact(deadlineDate + " " + deadlineTime, "yyyy-MM-dd HH:mm", null, System.Globalization.DateTimeStyles.None, out DateTime deadlineDt))
|
||||
{
|
||||
await SendAsync(e, "创建比赛", "时间格式错误,请使用 yyyy-MM-dd HH:mm(开始时间和截止时间各两段)。");
|
||||
await SendAsync(e, "创建比赛", "开始时间格式错误,请使用 yyyy-MM-dd HH:mm(日期和时间分为两段)。");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 解析剩余参数:选项和奖励率
|
||||
List<string> optionParts = [];
|
||||
// 解析剩余参数(从第6段开始,key=value格式,value支持双引号包围)
|
||||
int spaceCount = 0, idx = 0;
|
||||
for (; idx < detail.Length && spaceCount < 6; idx++)
|
||||
{
|
||||
if (detail[idx] == ' ') spaceCount++;
|
||||
}
|
||||
string paramString = detail[idx..].Trim();
|
||||
System.Text.RegularExpressions.MatchCollection matches = GetParamValue().Matches(paramString);
|
||||
|
||||
// 解析剩余可选参数:key=value 格式,支持双引号包围含空格的value
|
||||
Dictionary<string, string> paramDict = new(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (System.Text.RegularExpressions.Match m in matches)
|
||||
{
|
||||
string key = m.Groups[1].Value;
|
||||
string value = m.Groups[2].Success ? m.Groups[2].Value : m.Groups[3].Value;
|
||||
if (!paramDict.TryAdd(key, value))
|
||||
{
|
||||
await SendAsync(e, "创建比赛", $"参数 '{key}' 重复。");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 解析截止时间 (ddl=),默认与开始时间相同
|
||||
DateTime deadlineDt = startDt;
|
||||
if (paramDict.TryGetValue("ddl", out string? ddlStr))
|
||||
{
|
||||
if (!DateTime.TryParseExact(ddlStr, "yyyy-MM-dd HH:mm", null, System.Globalization.DateTimeStyles.None, out deadlineDt))
|
||||
{
|
||||
await SendAsync(e, "创建比赛", "截止时间格式错误,请使用 yyyy-MM-dd HH:mm。");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 解析选项列表 (opts=)
|
||||
string options = "team1_win,team2_win";
|
||||
if (paramDict.TryGetValue("opts", out string? optsStr))
|
||||
{
|
||||
options = optsStr; // 预期逗号分隔的列表
|
||||
}
|
||||
|
||||
// 解析奖励率与胜率
|
||||
decimal? team1Odds = null, team2Odds = null, team1WinProbability = null;
|
||||
for (int i = 8; i < parts.Length; i++)
|
||||
if (paramDict.TryGetValue("team1_win", out string? t1OddsStr))
|
||||
{
|
||||
string segment = parts[i];
|
||||
if (segment.StartsWith("team1_win="))
|
||||
if (decimal.TryParse(t1OddsStr, out decimal o1))
|
||||
{
|
||||
if (decimal.TryParse(segment[11..], out decimal odds1))
|
||||
team1Odds = odds1;
|
||||
team1Odds = o1;
|
||||
}
|
||||
else if (segment.StartsWith("team2_win="))
|
||||
else
|
||||
{
|
||||
if (decimal.TryParse(segment[11..], out decimal odds2))
|
||||
team2Odds = odds2;
|
||||
await SendAsync(e, "创建比赛", "team1_win 奖励率无效。");
|
||||
return true;
|
||||
}
|
||||
else if (segment.StartsWith("prob="))
|
||||
}
|
||||
if (paramDict.TryGetValue("team2_win", out string? t2OddsStr))
|
||||
{
|
||||
if (decimal.TryParse(t2OddsStr, out decimal o2))
|
||||
{
|
||||
team2Odds = o2;
|
||||
}
|
||||
else
|
||||
{
|
||||
await SendAsync(e, "创建比赛", "team2_win 奖励率无效。");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (paramDict.TryGetValue("prob", out string? probStr))
|
||||
{
|
||||
if (decimal.TryParse(probStr, out decimal prob) && prob > 0 && prob < 1)
|
||||
{
|
||||
if (decimal.TryParse(segment[5..], out decimal prob))
|
||||
team1WinProbability = prob;
|
||||
}
|
||||
else
|
||||
{
|
||||
await SendAsync(e, "创建比赛", "胜率值无效,应为0~1之间的小数。");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
optionParts.Add(segment);
|
||||
}
|
||||
}
|
||||
string options = optionParts.Count > 0 ? string.Join(",", optionParts) : "team1_win,team2_win";
|
||||
|
||||
BotReply reply = BettingController.CreateMatch(new CreateMatchRequest
|
||||
{
|
||||
@ -497,6 +530,24 @@ namespace Oshima.FunGame.WebAPI.Services
|
||||
case "result":
|
||||
request.Result = val;
|
||||
break;
|
||||
case "stage":
|
||||
request.Stage = val;
|
||||
break;
|
||||
case "t1":
|
||||
request.Team1 = val;
|
||||
break;
|
||||
case "t2":
|
||||
request.Team2 = val;
|
||||
break;
|
||||
case "be":
|
||||
if (val == "0" || val == "1")
|
||||
request.BettingEnabled = val == "1";
|
||||
else
|
||||
{
|
||||
await SendAsync(e, "修改比赛", "enabled 值必须为 0 或 1。");
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
await SendAsync(e, "修改比赛", $"未知参数:{key}");
|
||||
return true;
|
||||
@ -508,11 +559,34 @@ namespace Oshima.FunGame.WebAPI.Services
|
||||
reply.Keyboard = new KeyboardMessage()
|
||||
.AppendButtons(2,
|
||||
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
|
||||
Button.CreateCmdButton("📅 比赛列表", "比赛列表"),
|
||||
Button.CreateCmdButton("🔍 比赛详情", $"比赛详情 {matchId}"));
|
||||
await SendAsync(e, "修改比赛", reply);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 取消比赛指令
|
||||
if (e.Detail.StartsWith("取消比赛"))
|
||||
{
|
||||
e.UseNotice = false;
|
||||
string detail = e.Detail.Replace("取消比赛", "").Trim();
|
||||
if (!int.TryParse(detail, out int matchId))
|
||||
{
|
||||
await SendAsync(e, "取消比赛", "格式:取消比赛 <比赛ID>");
|
||||
return true;
|
||||
}
|
||||
|
||||
BotReply reply = BettingController.CancelMatch(uid, matchId);
|
||||
reply.Keyboard = new KeyboardMessage()
|
||||
.AppendButtons(2,
|
||||
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
|
||||
Button.CreateCmdButton("📅 比赛列表", "比赛列表"),
|
||||
Button.CreateCmdButton("🔍 比赛详情", $"比赛详情 {matchId}"),
|
||||
Button.CreateCmdButton("💰 预测领奖", "预测领奖"));
|
||||
await SendAsync(e, "取消比赛", reply);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user