fix: 修复多个组件的空引用、调试日志和编码问题

### HediffComp_GestaltNode
- 添加 pawn 空引用检查,防止 NullReferenceException
- 将 Log.Message 改为 ArachnaeLog.Debug 统一日志管理
- 新增 Notify_PawnDied 方法处理 Pawn 死亡时的过渡状态清理
- 修复 UpdateTransitionState 中重复声明 pawn 变量的问题

### CompAbilityEffect_LaunchMultiProjectile
- 添加目标有效性检查,处理目标死亡或消失的情况
- 实现动态目标追踪,更新目标位置
- 移除未使用的 parametersInitialized 字段
- 新增 ForceReinitialize 方法支持状态变化时重新初始化

### CompHediffGiver
- 改进异常处理,记录警告日志而非静默吞掉异常
- 重构 IsSymmetricalPart 方法,使用翻译键和 BodyPartTagDef 支持本地化

### HediffComp_Spawner
- 将 DebugSettings.debugLogging 改为 Prefs.DevMode
- 修复所有 UTF-8 编码乱码注释(约30处)

### Comp_PawnResearchBlueprintReader
- 修复灵能科研点消耗时机,确保先检查→再消耗→最后添加进度
- 提高研究进度添加的原子性
This commit is contained in:
2026-02-15 00:24:08 +08:00
parent ae7a72fd27
commit f9624818f5
6 changed files with 148 additions and 65 deletions

Binary file not shown.

View File

@@ -13,7 +13,6 @@ namespace ArachnaeSwarm
private int nextProjectileTick; private int nextProjectileTick;
private int projectilesFired; private int projectilesFired;
private IntVec3? targetCell; private IntVec3? targetCell;
private bool parametersInitialized;
// 缓存当前状态的参数 // 缓存当前状态的参数
private int currentNumProjectiles; private int currentNumProjectiles;
@@ -139,17 +138,20 @@ namespace ArachnaeSwarm
/// </summary> /// </summary>
private void InitializeParameters() private void InitializeParameters()
{ {
if (parametersInitialized)
return;
var state = GetCurrentState(); var state = GetCurrentState();
currentNumProjectiles = state?.numProjectiles ?? Props.numProjectiles; currentNumProjectiles = state?.numProjectiles ?? Props.numProjectiles;
currentProjectileDef = state?.projectileDef ?? Props.projectileDef; currentProjectileDef = state?.projectileDef ?? Props.projectileDef;
currentOffsetRadius = state?.offsetRadius ?? Props.offsetRadius; currentOffsetRadius = state?.offsetRadius ?? Props.offsetRadius;
currentShotIntervalTicks = state?.shotIntervalTicks ?? Props.shotIntervalTicks; currentShotIntervalTicks = state?.shotIntervalTicks ?? Props.shotIntervalTicks;
}
parametersInitialized = true;
/// <summary>
/// 强制重新初始化参数(当状态变化时调用)
/// </summary>
public void ForceReinitialize()
{
InitializeParameters();
} }
/// <summary> /// <summary>
@@ -182,7 +184,31 @@ namespace ArachnaeSwarm
return; return;
Pawn pawn = parent.pawn; Pawn pawn = parent.pawn;
// 检查目标有效性(目标可能已移动或消失)
LocalTargetInfo finalTarget = target; LocalTargetInfo finalTarget = target;
if (Props.useSustainedJob && target.Pawn != null)
{
// 如果是持续模式且有目标Pawn尝试追踪目标
if (target.Pawn.Dead || !target.Pawn.Spawned)
{
// 目标已死亡或消失,使用最后已知位置
if (targetCell.HasValue)
{
finalTarget = new LocalTargetInfo(targetCell.Value);
}
else
{
return; // 无法发射
}
}
else
{
// 更新目标位置
targetCell = target.Pawn.Position;
finalTarget = target;
}
}
// 如果有偏移范围,计算偏移后的目标 // 如果有偏移范围,计算偏移后的目标
if (Props.useRandomOffset && currentOffsetRadius > 0) if (Props.useRandomOffset && currentOffsetRadius > 0)
@@ -205,7 +231,6 @@ namespace ArachnaeSwarm
nextProjectileTick = 0; nextProjectileTick = 0;
projectilesFired = 0; projectilesFired = 0;
targetCell = null; targetCell = null;
parametersInitialized = false;
} }
/// <summary> /// <summary>

View File

@@ -144,7 +144,7 @@ namespace ArachnaeSwarm
return comp.GestaltTracker; return comp.GestaltTracker;
} }
if (tracker == null && NodeType == GestaltNodeType.OverlordNode) if (tracker == null && NodeType == GestaltNodeType.OverlordNode && pawn != null)
{ {
tracker = new Pawn_GestaltTracker(pawn); tracker = new Pawn_GestaltTracker(pawn);
} }
@@ -247,7 +247,7 @@ namespace ArachnaeSwarm
TryFindOverlord(); TryFindOverlord();
} }
Log.Message($"[GestaltNode] {pawn.LabelShort} (HiveNode) 延迟初始化完成,当前连接状态: {IsConnectedToOverlord()}"); ArachnaeLog.Debug($"[GestaltNode] {pawn.LabelShort} (HiveNode) 延迟初始化完成,当前连接状态: {IsConnectedToOverlord()}");
} }
} }
@@ -256,6 +256,15 @@ namespace ArachnaeSwarm
/// </summary> /// </summary>
private void UpdateTransitionState() private void UpdateTransitionState()
{ {
// 检查Pawn是否死亡或无效
var pawn = Pawn;
if (pawn == null || pawn.Dead)
{
isTransitioning = false;
transitionStartTick = -1;
return;
}
if (!isTransitioning || transitionStartTick < 0) if (!isTransitioning || transitionStartTick < 0)
return; return;
@@ -267,14 +276,13 @@ namespace ArachnaeSwarm
{ {
parent.Severity = targetSeverityAfterTransition; parent.Severity = targetSeverityAfterTransition;
// 通知Pawn状态改变 // 通知Pawn状态改变使用方法开头声明的pawn变量
var pawn = Pawn;
if (pawn?.health != null) if (pawn?.health != null)
{ {
pawn.health.Notify_HediffChanged(parent); pawn.health.Notify_HediffChanged(parent);
} }
Log.Message($"[GestaltNode] {Pawn?.LabelShort} 过渡完成,严重性: {targetSeverityAfterTransition}"); ArachnaeLog.Debug($"[GestaltNode] {pawn?.LabelShort} 过渡完成,严重性: {targetSeverityAfterTransition}");
} }
// 重置过渡状态 // 重置过渡状态
@@ -316,7 +324,7 @@ namespace ArachnaeSwarm
} }
} }
Log.Message($"[GestaltNode] {Pawn?.LabelShort} 开始过渡,目标: {(isConnecting ? "" : "")} ({targetSeverityAfterTransition})"); ArachnaeLog.Debug($"[GestaltNode] {Pawn?.LabelShort} 开始过渡,目标: {(isConnecting ? "" : "")} ({targetSeverityAfterTransition})");
} }
/// <summary> /// <summary>
@@ -434,7 +442,7 @@ namespace ArachnaeSwarm
} }
UpdateSeverityBasedOnConnection(); UpdateSeverityBasedOnConnection();
Log.Message($"[GestaltNode] {pawn.LabelShort} 连接到 Overlord: {bestOverlord.LabelShort}"); ArachnaeLog.Debug($"[GestaltNode] {pawn.LabelShort} 连接到 Overlord: {bestOverlord.LabelShort}");
} }
} }
@@ -466,6 +474,10 @@ namespace ArachnaeSwarm
{ {
base.CompPostPostRemoved(); base.CompPostPostRemoved();
// 清理过渡状态
isTransitioning = false;
transitionStartTick = -1;
// 当Hediff被移除时如果连接到Overlord需要断开连接 // 当Hediff被移除时如果连接到Overlord需要断开连接
if (NodeType == GestaltNodeType.HiveNode && IsConnectedToOverlord()) if (NodeType == GestaltNodeType.HiveNode && IsConnectedToOverlord())
{ {
@@ -483,6 +495,20 @@ namespace ArachnaeSwarm
} }
} }
/// <summary>
/// Pawn 死亡时的处理
/// </summary>
public override void Notify_PawnDied(DamageInfo? dinfo, Hediff culprit = null)
{
base.Notify_PawnDied(dinfo, culprit);
// 清理过渡状态
isTransitioning = false;
transitionStartTick = -1;
ArachnaeLog.Debug($"[GestaltNode] {Pawn?.LabelShort} 死亡,清理过渡状态");
}
/// <summary> /// <summary>
/// 序列化 /// 序列化
/// </summary> /// </summary>

View File

@@ -350,7 +350,7 @@ namespace ArachnaeSwarm
if (this.Props.animalThing) if (this.Props.animalThing)
{ {
// 鍔ㄧ墿鐢熸垚閫昏緫淇濇寔涓嶅彉 // 动物生成逻辑保持不变
if (this.Props.spawnMaxAdjacent > 0 && pawn.Map.mapPawns.AllPawns.Where(delegate(Pawn mP) if (this.Props.spawnMaxAdjacent > 0 && pawn.Map.mapPawns.AllPawns.Where(delegate(Pawn mP)
{ {
ThingDef defToCompare = this.Props.animalThing ? this.Props.animalToSpawn?.race : this.Props.thingToSpawn; ThingDef defToCompare = this.Props.animalThing ? this.Props.animalToSpawn?.race : this.Props.thingToSpawn;
@@ -402,10 +402,10 @@ namespace ArachnaeSwarm
} }
else else
{ {
// 閲嶆柊璁捐鐗╁搧鐢熸垚閫昏緫锛氭寜浼樺厛绾ч『搴忓皾璇? // 重新设计物品生成逻辑:按优先级顺序尝试
bool success = TrySpawnItemWithPriority(pawn); bool success = TrySpawnItemWithPriority(pawn);
// === 鏂板锛氬鏋滈厤缃簡閿€姣侀殢鏈洪儴浣嶏紝鍦ㄦ垚鍔熺敓鎴愮墿鍝佸悗鎵ц === // === 新增:如果配置了销毁随机部位,在成功生成物品后执行 ===
if (success && this.Props.destroyRandomBodyPart) if (success && this.Props.destroyRandomBodyPart)
{ {
DestroyRandomBodyPart(pawn); DestroyRandomBodyPart(pawn);
@@ -415,7 +415,7 @@ namespace ArachnaeSwarm
} }
} }
// 鏂板锛氶攢姣侀殢鏈鸿韩浣撻儴浣嶏紙鍙傝€?CompAbilityEffect_DestroyOwnBodyPart锛? // 新增:销毁随机身体部位(参考 CompAbilityEffect_DestroyOwnBodyPart
private void DestroyRandomBodyPart(Pawn pawn) private void DestroyRandomBodyPart(Pawn pawn)
{ {
try try
@@ -426,7 +426,7 @@ namespace ArachnaeSwarm
return; return;
} }
// 鑾峰彇鎵€鏈夊彲浠ラ攢姣佺殑韬綋閮ㄤ綅 // 获取所有可以销毁的身体部位
List<BodyPartRecord> possibleParts = GetDestroyableBodyParts(pawn); List<BodyPartRecord> possibleParts = GetDestroyableBodyParts(pawn);
if (possibleParts.Count == 0) if (possibleParts.Count == 0)
@@ -435,7 +435,7 @@ namespace ArachnaeSwarm
return; return;
} }
// 闅忔満閫夋嫨涓€涓儴浣? // 随机选择一个部位
BodyPartRecord partToDestroy = possibleParts.RandomElement(); BodyPartRecord partToDestroy = possibleParts.RandomElement();
if (partToDestroy == null) if (partToDestroy == null)
@@ -444,10 +444,10 @@ namespace ArachnaeSwarm
return; return;
} }
// 璁板綍閮ㄤ綅鍚嶇О // 记录部位名称
string partName = partToDestroy.def?.label ?? "鏈煡閮ㄤ綅"; string partName = partToDestroy.def?.label ?? "未知部位";
// 娣诲姞缂哄け閮ㄤ綅hediff // 添加缺失部位hediff
pawn.health.AddHediff(HediffDefOf.MissingBodyPart, partToDestroy); pawn.health.AddHediff(HediffDefOf.MissingBodyPart, partToDestroy);
Warn($"Destroyed {partName} on {pawn.LabelShort}", this.myDebug); Warn($"Destroyed {partName} on {pawn.LabelShort}", this.myDebug);
@@ -458,7 +458,7 @@ namespace ArachnaeSwarm
} }
} }
// 鏂板锛氳幏鍙栧彲浠ラ攢姣佺殑韬綋閮ㄤ綅鍒楄〃 // 新增:获取可以销毁的身体部位列表
private List<BodyPartRecord> GetDestroyableBodyParts(Pawn pawn) private List<BodyPartRecord> GetDestroyableBodyParts(Pawn pawn)
{ {
List<BodyPartRecord> destroyableParts = new List<BodyPartRecord>(); List<BodyPartRecord> destroyableParts = new List<BodyPartRecord>();
@@ -466,20 +466,20 @@ namespace ArachnaeSwarm
if (pawn?.health?.hediffSet == null) if (pawn?.health?.hediffSet == null)
return destroyableParts; return destroyableParts;
// 鑾峰彇鎵€鏈夎韩浣撻儴浣? // 获取所有身体部位
List<BodyPartRecord> allParts = pawn.RaceProps.body.AllParts; List<BodyPartRecord> allParts = pawn.RaceProps.body.AllParts;
foreach (BodyPartRecord part in allParts) foreach (BodyPartRecord part in allParts)
{ {
// 鎺掗櫎鏍稿績閮ㄤ綅锛堥伩鍏嶆浜★級 // 排除核心部位(避免死亡)
if (IsCriticalBodyPart(part)) if (IsCriticalBodyPart(part))
continue; continue;
// 鎺掗櫎宸茬粡缂哄け鐨勯儴浣? // 排除已经缺失的部位
if (pawn.health.hediffSet.PartIsMissing(part)) if (pawn.health.hediffSet.PartIsMissing(part))
continue; continue;
// 鎺掗櫎宸茬粡鏈変弗閲嶄激瀹崇殑閮ㄤ綅锛堝彲閫夛級 // 排除已经有严重伤害的部位(可选)
if (pawn.health.hediffSet.PartOrAnyAncestorHasDirectlyAddedParts(part)) if (pawn.health.hediffSet.PartOrAnyAncestorHasDirectlyAddedParts(part))
continue; continue;
@@ -492,34 +492,34 @@ namespace ArachnaeSwarm
return destroyableParts; return destroyableParts;
} }
// 鏂板锛氭鏌ユ槸鍚︽槸鍏抽敭韬綋閮ㄤ綅 // 新增:检查是否是关键身体部位
private bool IsCriticalBodyPart(BodyPartRecord part) private bool IsCriticalBodyPart(BodyPartRecord part)
{ {
if (part == null) if (part == null)
return false; return false;
// 鏍规嵁鏍囩鍒ゆ柇鏄惁涓哄叧閿儴浣? // 根据标签判断是否为关键部位
if (part.def.tags != null) if (part.def.tags != null)
{ {
// 杩欎簺鏍囩閫氬父琛ㄧず鍏抽敭閮ㄤ綅 // 这些标签通常表示关键部位
if (part.def.tags.Contains(BodyPartTagDefOf.ConsciousnessSource) || // 鎰忚瘑婧愶紙澶ц剳锛? if (part.def.tags.Contains(BodyPartTagDefOf.ConsciousnessSource) || // 意识源(大脑)
part.def.tags.Contains(BodyPartTagDefOf.BloodPumpingSource) || // 琛€娑叉车婧愶紙蹇冭剰锛? part.def.tags.Contains(BodyPartTagDefOf.BloodPumpingSource) || // 血液泵源(心脏)
part.def.tags.Contains(BodyPartTagDefOf.BreathingSource) || // 鍛煎惛婧愶紙鑲猴級 part.def.tags.Contains(BodyPartTagDefOf.BreathingSource) || // 呼吸源(肺)
part.def.tags.Contains(BodyPartTagDefOf.MetabolismSource) // 浠h阿婧愶紙鑲濊剰锛? part.def.tags.Contains(BodyPartTagDefOf.MetabolismSource) // 代谢源(肝脏)
) )
{ {
return true; return true;
} }
} }
// 鏍规嵁娣卞害鍒ゆ柇锛堟繁搴﹁緝娣辩殑閫氬父鏄唴閮ㄥ櫒瀹橈級 // 根据深度判断(深度较深的通常是内部器官)
if (part.depth == BodyPartDepth.Inside && part.parent == null) if (part.depth == BodyPartDepth.Inside && part.parent == null)
return true; return true;
return false; return false;
} }
// 鏂板锛氭寜浼樺厛绾ч『搴忓皾璇曠敓鎴愮墿鍝? // 新增:按优先级顺序尝试生成物品
private bool TrySpawnItemWithPriority(Pawn pawn) private bool TrySpawnItemWithPriority(Pawn pawn)
{ {
Thing thing = ThingMaker.MakeThing(this.Props.thingToSpawn, null); Thing thing = ThingMaker.MakeThing(this.Props.thingToSpawn, null);
@@ -739,19 +739,19 @@ namespace ArachnaeSwarm
} }
else else
{ {
// 淇敼杩欓噷锛氬皢鍗婂緞浠?鍑忓皯鍒?锛岃鐢熸垚浣嶇疆鏇撮潬杩憄awn // 修改这里将半径从3减少到2让生成位置更靠近Pawn
int searchRadius = 2; int searchRadius = 2;
// 棣栧厛灏濊瘯鍦╬awn鐨勭浉閭诲崟鍏冩牸鐢熸垚锛堝崐寰勪负1锛? // 首先尝试在Pawn的相邻单元格生成半径为1
result = CellFinder.RandomClosewalkCellNear(this.pawn.Position, map, 1, null); result = CellFinder.RandomClosewalkCellNear(this.pawn.Position, map, 1, null);
// 濡傛灉鐩搁偦鍗曞厓鏍兼壘涓嶅埌鍚堥€備綅缃紝鍐嶅皾璇曠◢杩滀竴鐐癸紙鍗婂緞涓?锛? // 如果相邻单元格找不到合适位置再尝试稍远一点半径为2
if (!result.IsValid) if (!result.IsValid)
{ {
result = CellFinder.RandomClosewalkCellNear(this.pawn.Position, map, searchRadius, null); result = CellFinder.RandomClosewalkCellNear(this.pawn.Position, map, searchRadius, null);
} }
// 濡傛灉杩樻槸鎵句笉鍒帮紝灏濊瘯pawn褰撳墠浣嶇疆锛堜綔涓烘渶鍚庢墜娈碉級 // 如果还是找不到尝试Pawn当前位置作为最后手段
if (!result.IsValid && this.pawn.Position.IsValid && this.pawn.Position.Walkable(map)) if (!result.IsValid && this.pawn.Position.IsValid && this.pawn.Position.Walkable(map))
{ {
result = this.pawn.Position; result = this.pawn.Position;
@@ -848,7 +848,7 @@ namespace ArachnaeSwarm
} }
} }
// === 鏁村悎鐨?Tools 鏂规硶 === // === 整合的 Tools 方法 ===
private void DestroyParentHediff(Hediff parentHediff, bool debug = false) private void DestroyParentHediff(Hediff parentHediff, bool debug = false)
{ {
@@ -968,7 +968,7 @@ namespace ArachnaeSwarm
} }
private void Warn(string warning, bool debug = false) private void Warn(string warning, bool debug = false)
{ {
if (debug) if (debug && Prefs.DevMode)
{ {
Log.Message($"[HediffComp_Spawner] {warning}"); Log.Message($"[HediffComp_Spawner] {warning}");
} }
@@ -990,4 +990,3 @@ namespace ArachnaeSwarm
private readonly int errorSpawnCount = 750; private readonly int errorSpawnCount = 750;
} }
} }

View File

@@ -117,8 +117,9 @@ namespace ArachnaeSwarm
ArachnaeLog.Debug($"Added hediff {hediffDef.defName} to {pawn.Label} " + ArachnaeLog.Debug($"Added hediff {hediffDef.defName} to {pawn.Label} " +
$"{(bodyPart != null ? $"on {bodyPart.def.defName}" : "to whole body")}"); $"{(bodyPart != null ? $"on {bodyPart.def.defName}" : "to whole body")}");
} }
catch (Exception) catch (Exception ex)
{ {
Log.Warning($"[CompHediffGiver] Failed to add hediff {hediffDef?.defName ?? "null"} to {pawn?.Label ?? "null"}: {ex.Message}");
} }
} }
@@ -252,27 +253,50 @@ namespace ArachnaeSwarm
/// </summary> /// </summary>
private bool IsSymmetricalPart(BodyPartRecord part1, BodyPartRecord part2) private bool IsSymmetricalPart(BodyPartRecord part1, BodyPartRecord part2)
{ {
// 简单的对称检测逻辑 // 优先使用 BodyPartDef 的对称标签检测
if (part1.def == part2.def)
{
// 检查是否有对称标签
if (part1.def.tags != null)
{
// 使用 BodyPartTagDef 进行对称检测
bool part1Left = part1.def.tags.Any(t => t.defName.Contains("Left"));
bool part1Right = part1.def.tags.Any(t => t.defName.Contains("Right"));
bool part2Left = part2.def.tags.Any(t => t.defName.Contains("Left"));
bool part2Right = part2.def.tags.Any(t => t.defName.Contains("Right"));
if ((part1Left && part2Right) || (part1Right && part2Left))
return true;
}
}
// 备用方案:使用自定义标签检测(支持本地化)
string label1 = part1.customLabel ?? part1.def.label; string label1 = part1.customLabel ?? part1.def.label;
string label2 = part2.customLabel ?? part2.def.label; string label2 = part2.customLabel ?? part2.def.label;
// 检查是否包含对称标识符 // 使用翻译键检测,而非硬编码英文
string[] symmetryKeywords = { "Left", "Right", "Left", "Right", "Front", "Back", "Upper", "Lower" }; string leftKey = "Left".Translate();
string rightKey = "Right".Translate();
foreach (var keyword in symmetryKeywords) bool label1HasLeft = label1.Contains(leftKey) || label1.Contains("Left");
{ bool label1HasRight = label1.Contains(rightKey) || label1.Contains("Right");
if (label1.Contains(keyword) && label2.Contains(keyword)) bool label2HasLeft = label2.Contains(leftKey) || label2.Contains("Left");
{ bool label2HasRight = label2.Contains(rightKey) || label2.Contains("Right");
// 检查是否是对称的关键词对
if ((keyword == "Left" && label2.Contains("Right")) || if ((label1HasLeft && label2HasRight) || (label1HasRight && label2HasLeft))
(keyword == "Right" && label2.Contains("Left")) || return true;
(keyword == "Front" && label2.Contains("Back")) ||
(keyword == "Back" && label2.Contains("Front"))) // 检查前后对称
{ string frontKey = "Front".Translate();
return true; string backKey = "Back".Translate();
}
} bool label1HasFront = label1.Contains(frontKey) || label1.Contains("Front");
} bool label1HasBack = label1.Contains(backKey) || label1.Contains("Back");
bool label2HasFront = label2.Contains(frontKey) || label2.Contains("Front");
bool label2HasBack = label2.Contains(backKey) || label2.Contains("Back");
if ((label1HasFront && label2HasBack) || (label1HasBack && label2HasFront))
return true;
return false; return false;
} }

View File

@@ -345,10 +345,11 @@ namespace ArachnaeSwarm
{ {
int pointsToAdd = Mathf.FloorToInt(accumulatedResearchPoints); int pointsToAdd = Mathf.FloorToInt(accumulatedResearchPoints);
// 消耗灵能科研点 // 先检查并消耗灵能科研点,再添加进度(确保原子性)
if (Props.usePsychicResearchPoints && spellHolder != null) if (Props.usePsychicResearchPoints && spellHolder != null)
{ {
if (!spellHolder.ConsumePsychicResearchPoints(pointsToAdd, $"研究 {currentResearch.LabelCap}")) // 先检查是否有足够的灵能科研点
if (!spellHolder.HasEnoughPsychicResearchPoints(pointsToAdd))
{ {
// 灵能科研点不足,停止研究 // 灵能科研点不足,停止研究
StopResearching(); StopResearching();
@@ -356,9 +357,17 @@ namespace ArachnaeSwarm
Pawn, MessageTypeDefOf.NegativeEvent); Pawn, MessageTypeDefOf.NegativeEvent);
return; return;
} }
// 消耗灵能科研点
if (!spellHolder.ConsumePsychicResearchPoints(pointsToAdd, $"研究 {currentResearch.LabelCap}"))
{
// 消耗失败(理论上不应该发生,因为已经检查过了)
StopResearching();
return;
}
} }
// 添加到全局研究进度 // 添加到全局研究进度(在消耗成功后)
AddResearchProgress(pointsToAdd); AddResearchProgress(pointsToAdd);
// 减去已添加的点数 // 减去已添加的点数