Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Client/Localization/Chinese.json
Original file line number Diff line number Diff line change
Expand Up @@ -855,7 +855,9 @@
"MaxAcPlusPercent": "最大防御 + {0}%",
"MaxMacPlusPercent": "最大魔法防御 + {0}%",
"HealthRecoveryPlus": "生命恢复 + {0}",
"HealthRecoveryPlusPercent": "生命恢复 + {0}%",
"ManaRecoveryPlus": "魔法恢复 + {0}",
"ManaRecoveryPlusPercent": "魔法恢复 + {0}%",
"PoisonRecoveryPlus": "中毒恢复 + {0}",
"AddsAgilityPlus": "敏捷 +{0}",
"StrongPlus": "力量 + {0}",
Expand Down
2 changes: 2 additions & 0 deletions Client/Localization/English.json
Original file line number Diff line number Diff line change
Expand Up @@ -855,7 +855,9 @@
"MaxAcPlusPercent": "Max AC + {0}%",
"MaxMacPlusPercent": "Max MAC + {0}%",
"HealthRecoveryPlus": "Health Recovery + {0}",
"HealthRecoveryPlusPercent": "Health Recovery + {0}%",
"ManaRecoveryPlus": "Mana Recovery + {0}",
"ManaRecoveryPlusPercent": "Mana Recovery + {0}%",
"PoisonRecoveryPlus": "Poison Recovery + {0}",
"AddsAgilityPlus": "Adds +{0} Agility",
"StrongPlus": "Strong + {0}",
Expand Down
47 changes: 38 additions & 9 deletions Client/MirScenes/GameScene.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3412,7 +3412,18 @@ private void DamageIndicator(S.DamageIndicator p)
switch (p.Type)
{
case DamageType.Hit: //add damage level colours
obj.Damages.Add(new Damage(p.Damage.ToString("#,##0"), 1000, obj.Race == ObjectType.Player ? Color.Red : Color.White, 50));
{
// Negative values are damage, positive values are healing/regen.
var value = p.Damage;
if (value == 0) break;
string text = value > 0 ? $"+{value:#,##0}" : value.ToString("#,##0");

Color colour;
// Keep legacy colour scheme (player red, others white) for consistency.
colour = obj.Race == ObjectType.Player ? Color.Red : Color.White;

obj.Damages.Add(new Damage(text, 1000, colour, 50));
}
break;
case DamageType.Miss:
obj.Damages.Add(new Damage(GameLanguage.ClientTextMap.GetLocalization(ClientTextKeys.Miss), 1200, obj.Race == ObjectType.Player ? Color.LightCoral : Color.LightGray, 50));
Expand Down Expand Up @@ -7749,7 +7760,10 @@ public MirControl DefenceInfoLabel(UserItem item, bool Inspect = false, bool hid
maxValue = 0;
addValue = (!hideAdded && (!HoverItem.Info.NeedIdentify || HoverItem.Identified)) ? addedStats[Stat.HP] : 0;

if (minValue > 0 || maxValue > 0 || addValue > 0)
bool isPotionHP = realItem.Type == ItemType.Potion;
string hpRange = realItem.HPRollRaw; // e.g. "10~100" or empty

if (minValue > 0 || maxValue > 0 || addValue > 0 || (isPotionHP && !string.IsNullOrWhiteSpace(hpRange)))
{
count++;
MirLabel MAXHPLabel = new MirLabel
Expand All @@ -7759,8 +7773,12 @@ public MirControl DefenceInfoLabel(UserItem item, bool Inspect = false, bool hid
Location = new Point(4, ItemLabel.DisplayRectangle.Bottom),
OutLine = true,
Parent = ItemLabel,
//Text = string.Format(realItem.Type == ItemType.Potion ? "HP + {0} Recovery" : "MAXHP + {0}", minValue + addValue)
Text = GameLanguage.ClientTextMap.GetLocalization((ClientTextKeys.MaxHpPlus), minValue + addValue) + (addValue > 0 ? $" (+{addValue})" : String.Empty)
Text = isPotionHP
? GameLanguage.ClientTextMap.GetLocalization(
ClientTextKeys.HealthRecoveryPlus,
string.IsNullOrWhiteSpace(hpRange) ? (minValue + addValue).ToString() : hpRange
) + (addValue > 0 ? $" (+{addValue})" : String.Empty)
: GameLanguage.ClientTextMap.GetLocalization((ClientTextKeys.MaxHpPlus), minValue + addValue) + (addValue > 0 ? $" (+{addValue})" : String.Empty)
};

ItemLabel.Size = new Size(Math.Max(ItemLabel.Size.Width, MAXHPLabel.DisplayRectangle.Right + 4),
Expand All @@ -7776,7 +7794,10 @@ public MirControl DefenceInfoLabel(UserItem item, bool Inspect = false, bool hid
maxValue = 0;
addValue = (!hideAdded && (!HoverItem.Info.NeedIdentify || HoverItem.Identified)) ? addedStats[Stat.MP] : 0;

if (minValue > 0 || maxValue > 0 || addValue > 0)
bool isPotionMP = realItem.Type == ItemType.Potion;
string mpRange = realItem.MPRollRaw; // e.g. "10~100" or empty

if (minValue > 0 || maxValue > 0 || addValue > 0 || (isPotionMP && !string.IsNullOrWhiteSpace(mpRange)))
{
count++;
MirLabel MAXMPLabel = new MirLabel
Expand All @@ -7786,8 +7807,12 @@ public MirControl DefenceInfoLabel(UserItem item, bool Inspect = false, bool hid
Location = new Point(4, ItemLabel.DisplayRectangle.Bottom),
OutLine = true,
Parent = ItemLabel,
//Text = string.Format(realItem.Type == ItemType.Potion ? "MP + {0} Recovery" : "MAXMP + {0}", minValue + addValue)
Text = GameLanguage.ClientTextMap.GetLocalization((ClientTextKeys.MaxMpPlus), minValue + addValue) + (addValue > 0 ? $" (+{addValue})" : String.Empty)
Text = isPotionMP
? GameLanguage.ClientTextMap.GetLocalization(
ClientTextKeys.ManaRecoveryPlus,
string.IsNullOrWhiteSpace(mpRange) ? (minValue + addValue).ToString() : mpRange
) + (addValue > 0 ? $" (+{addValue})" : String.Empty)
: GameLanguage.ClientTextMap.GetLocalization((ClientTextKeys.MaxMpPlus), minValue + addValue) + (addValue > 0 ? $" (+{addValue})" : String.Empty)
};

ItemLabel.Size = new Size(Math.Max(ItemLabel.Size.Width, MAXMPLabel.DisplayRectangle.Right + 4),
Expand All @@ -7812,7 +7837,9 @@ public MirControl DefenceInfoLabel(UserItem item, bool Inspect = false, bool hid
Location = new Point(4, ItemLabel.DisplayRectangle.Bottom),
OutLine = true,
Parent = ItemLabel,
Text = GameLanguage.ClientTextMap.GetLocalization((ClientTextKeys.MaxHpPlusPercent), minValue + addValue)
Text = realItem.Type == ItemType.Potion
? GameLanguage.ClientTextMap.GetLocalization((ClientTextKeys.HealthRecoveryPlusPercent), minValue + addValue)
: GameLanguage.ClientTextMap.GetLocalization((ClientTextKeys.MaxHpPlusPercent), minValue + addValue)
};

ItemLabel.Size = new Size(Math.Max(ItemLabel.Size.Width, MAXHPRATELabel.DisplayRectangle.Right + 4),
Expand All @@ -7837,7 +7864,9 @@ public MirControl DefenceInfoLabel(UserItem item, bool Inspect = false, bool hid
Location = new Point(4, ItemLabel.DisplayRectangle.Bottom),
OutLine = true,
Parent = ItemLabel,
Text = GameLanguage.ClientTextMap.GetLocalization((ClientTextKeys.MaxMpPlusPercent), minValue + addValue)
Text = realItem.Type == ItemType.Potion
? GameLanguage.ClientTextMap.GetLocalization((ClientTextKeys.ManaRecoveryPlusPercent), minValue + addValue)
: GameLanguage.ClientTextMap.GetLocalization((ClientTextKeys.MaxMpPlusPercent), minValue + addValue)
};

ItemLabel.Size = new Size(Math.Max(ItemLabel.Size.Width, MAXMPRATELabel.DisplayRectangle.Right + 4),
Expand Down
92 changes: 87 additions & 5 deletions Server.MirForms/Database/ItemInfoFormNew.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,8 @@ private void CreateDynamicColumns()
{
HeaderText = $"{strKey} {sign}",
Name = "Stat" + stat.ToString(),
ValueType = typeof(int),
// HP/MP support fixed number OR "min~max" range strings.
ValueType = (stat == Stat.HP || stat == Stat.MP) ? typeof(string) : typeof(int),
DataPropertyName = "Stat" + stat.ToString()
};

Expand Down Expand Up @@ -268,7 +269,12 @@ private void PopulateTable()
{
if (stat == Stat.Unknown) continue;

row["Stat" + stat.ToString()] = item.Stats[stat];
if (stat == Stat.HP)
row["StatHP"] = string.IsNullOrWhiteSpace(item.HPRollRaw) ? item.Stats[Stat.HP].ToString() : item.HPRollRaw;
else if (stat == Stat.MP)
row["StatMP"] = string.IsNullOrWhiteSpace(item.MPRollRaw) ? item.Stats[Stat.MP].ToString() : item.MPRollRaw;
else
row["Stat" + stat.ToString()] = item.Stats[stat];
}

foreach (BindMode bind in BindEnums)
Expand Down Expand Up @@ -382,18 +388,51 @@ private void SaveForm()
item.ToolTip = row.Cells["ItemToolTip"].Value.ToString();

item.Stats.Clear();
item.HPRollRaw = string.Empty;
item.MPRollRaw = string.Empty;
item.Bind = BindMode.None;
item.Unique = SpecialItemMode.None;

foreach (DataGridViewColumn col in itemInfoGridView.Columns)
{
if (col.Name.StartsWith("Stat"))
{
var stat = col.Name.Substring(4);
// HP/MP accept "N" or "N~M" (stored as strings on ItemInfo)
if (col.Name == "StatHP" || col.Name == "StatMP")
{
var text = (row.Cells[col.Name].Value?.ToString() ?? "").Trim();

Stat enumStat = (Stat)Enum.Parse(typeof(Stat), stat);
if (string.IsNullOrWhiteSpace(text))
{
if (col.Name == "StatHP") { item.HPRollRaw = ""; item.Stats[Stat.HP] = 0; }
else { item.MPRollRaw = ""; item.Stats[Stat.MP] = 0; }
}
else if (TryParseRangeOrNumber(text, out var isRange, out var min, out var max))
{
if (isRange)
{
if (col.Name == "StatHP") { item.HPRollRaw = $"{min}~{max}"; item.Stats[Stat.HP] = 0; }
else { item.MPRollRaw = $"{min}~{max}"; item.Stats[Stat.MP] = 0; }
}
else
{
if (col.Name == "StatHP") { item.HPRollRaw = ""; item.Stats[Stat.HP] = min; }
else { item.MPRollRaw = ""; item.Stats[Stat.MP] = min; }
}
}
else
{
// Shouldn't happen due to validation
if (col.Name == "StatHP") { item.HPRollRaw = ""; item.Stats[Stat.HP] = 0; }
else { item.MPRollRaw = ""; item.Stats[Stat.MP] = 0; }
}

item.Stats[enumStat] = (int)row.Cells[col.Name].Value;
continue;
}

var stat = col.Name.Substring(4);
Stat enumStat = (Stat)Enum.Parse(typeof(Stat), stat);
item.Stats[enumStat] = Convert.ToInt32(row.Cells[col.Name].Value ?? 0);
}
else if (col.Name.StartsWith("Bind"))
{
Expand Down Expand Up @@ -467,6 +506,22 @@ private void itemInfoGridView_CellValidating(object sender, DataGridViewCellVali

itemInfoGridView.Rows[e.RowIndex].ErrorText = "";

// HP/MP accept "N" or "N~M"
if (col.Name == "StatHP" || col.Name == "StatMP")
{
var s = (val ?? "").Trim();
if (!string.IsNullOrEmpty(s) && !TryParseRangeOrNumber(s, out _, out _, out _))
{
e.Cancel = true;
itemInfoGridView.Rows[e.RowIndex].ErrorText = "Enter a number or min~max (e.g. 20 or 20~40).";
}

if (!e.Cancel)
itemInfoGridView.Rows[e.RowIndex].Cells["Modified"].Value = true;

return;
}

if (cell.OwningColumn.Name == "ItemName")
{
var existingRow = FindRowByItemName(val);
Expand Down Expand Up @@ -519,6 +574,33 @@ private void itemInfoGridView_CellValidating(object sender, DataGridViewCellVali
}
}

// "N" or "N~M" (negatives allowed)
private static bool TryParseRangeOrNumber(string s, out bool isRange, out int min, out int max)
{
isRange = false;
min = max = 0;
if (string.IsNullOrWhiteSpace(s)) return false;

if (s.Contains("~"))
{
var parts = s.Split('~');
if (parts.Length != 2) return false;
if (!int.TryParse(parts[0].Trim(), out min)) return false;
if (!int.TryParse(parts[1].Trim(), out max)) return false;
if (min > max) (min, max) = (max, min);
isRange = true;
return true;
}

if (int.TryParse(s.Trim(), out var v))
{
min = max = v;
return true;
}

return false;
}

private void rbtnViewAll_CheckedChanged(object sender, EventArgs e)
{
if (rbtnViewAll.Checked)
Expand Down
31 changes: 27 additions & 4 deletions Server/MirObjects/HeroObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -354,12 +354,35 @@ public override void UseItem(ulong id)
switch (item.Info.Shape)
{
case 0: //NormalPotion
PotHealthAmount = (ushort)Math.Min(ushort.MaxValue, PotHealthAmount + item.Info.Stats[Stat.HP]);
PotManaAmount = (ushort)Math.Min(ushort.MaxValue, PotManaAmount + item.Info.Stats[Stat.MP]);
{
GetPotionRecovery(item, out int hpVal, out int mpVal);

int newHP = PotHealthAmount + hpVal;
int newMP = PotManaAmount + mpVal;

newHP = Math.Max(0, newHP);
newMP = Math.Max(0, newMP);

PotHealthAmount = (ushort)Math.Min(ushort.MaxValue, newHP);
PotManaAmount = (ushort)Math.Min(ushort.MaxValue, newMP);
}
break;
case 1: //SunPotion
ChangeHP(item.Info.Stats[Stat.HP]);
ChangeMP(item.Info.Stats[Stat.MP]);
{
GetPotionRecovery(item, out int hpVal, out int mpVal);

if (hpVal != 0)
{
ChangeHP(hpVal);
BroadcastDamageIndicator(DamageType.Hit, hpVal);
}

if (mpVal != 0)
{
ChangeMP(mpVal);
BroadcastDamageIndicator(DamageType.Hit, mpVal);
}
}
break;
case 2: //MysteryWater
if (UnlockCurse)
Expand Down
28 changes: 27 additions & 1 deletion Server/MirObjects/HumanObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,11 @@ private void ProcessRegen()
HealAmount = 0;
}

if (manaRegen > 0) ChangeMP(manaRegen);
if (manaRegen > 0)
{
ChangeMP(manaRegen);
BroadcastDamageIndicator(DamageType.Hit, manaRegen);
}
if (MP == Stats[Stat.MP]) PotManaAmount = 0;
}
private void ProcessPoison()
Expand Down Expand Up @@ -1343,6 +1347,28 @@ protected bool CanUseItem(UserItem item)
return true;
}
public virtual void UseItem(ulong id) { }

protected static int CalcPercentAmount(int maxValue, int percent)
{
if (percent == 0 || maxValue <= 0) return 0;

int absPercent = Math.Abs(percent);
long raw = (long)maxValue * absPercent;

// Ceil to avoid 1% on small pools rounding down to 0.
int amount = (int)((raw + 99) / 100);
return percent > 0 ? amount : -amount;
}

protected void GetPotionRecovery(UserItem item, out int hpVal, out int mpVal)
{
hpVal = item?.Info?.RollHP() ?? 0;
mpVal = item?.Info?.RollMP() ?? 0;

// Percent-based recovery (for potions): add % of max HP/MP.
hpVal += CalcPercentAmount(Stats[Stat.HP], item?.GetTotal(Stat.HPRatePercent) ?? 0);
mpVal += CalcPercentAmount(Stats[Stat.MP], item?.GetTotal(Stat.MPRatePercent) ?? 0);
}
protected void ConsumeItem(UserItem item, byte cost)
{
item.Count -= cost;
Expand Down
31 changes: 27 additions & 4 deletions Server/MirObjects/PlayerObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5788,12 +5788,35 @@ public override void UseItem(ulong id)
switch (item.Info.Shape)
{
case 0: //NormalPotion
PotHealthAmount = (ushort)Math.Min(ushort.MaxValue, PotHealthAmount + item.Info.Stats[Stat.HP]);
PotManaAmount = (ushort)Math.Min(ushort.MaxValue, PotManaAmount + item.Info.Stats[Stat.MP]);
{
GetPotionRecovery(item, out int hpVal, out int mpVal);

int newHP = PotHealthAmount + hpVal;
int newMP = PotManaAmount + mpVal;

newHP = Math.Max(0, newHP);
newMP = Math.Max(0, newMP);

PotHealthAmount = (ushort)Math.Min(ushort.MaxValue, newHP);
PotManaAmount = (ushort)Math.Min(ushort.MaxValue, newMP);
}
break;
case 1: //SunPotion
ChangeHP(item.Info.Stats[Stat.HP]);
ChangeMP(item.Info.Stats[Stat.MP]);
{
GetPotionRecovery(item, out int hpVal, out int mpVal);

if (hpVal != 0)
{
ChangeHP(hpVal);
BroadcastDamageIndicator(DamageType.Hit, hpVal);
}

if (mpVal != 0)
{
ChangeMP(mpVal);
BroadcastDamageIndicator(DamageType.Hit, mpVal);
}
}
break;
case 2: //MysteryWater
if (UnlockCurse)
Expand Down
Loading