diff --git a/src/ClassicUO.Client/ClassicUO.Client.csproj b/src/ClassicUO.Client/ClassicUO.Client.csproj index 58bb7751e..1a16d085f 100644 --- a/src/ClassicUO.Client/ClassicUO.Client.csproj +++ b/src/ClassicUO.Client/ClassicUO.Client.csproj @@ -94,6 +94,10 @@ + + + + ..\..\external\cuoapi\cuoapi.dll diff --git a/src/ClassicUO.Client/Game/GameObjects/Views/LandView.cs b/src/ClassicUO.Client/Game/GameObjects/Views/LandView.cs index 10a4658c3..74774f580 100644 --- a/src/ClassicUO.Client/Game/GameObjects/Views/LandView.cs +++ b/src/ClassicUO.Client/Game/GameObjects/Views/LandView.cs @@ -91,7 +91,7 @@ public override bool Draw(UltimaBatcher2D batcher, int posX, int posY, float dep } if (ProfileManager.CurrentProfile.HighlightTileAtRangeSpell) { - if (GameCursor._spellTime >= 1 && Distance == ProfileManager.CurrentProfile.HighlightTileAtRangeRangeSpell) + if (GameActions.LastSpellIndexCursor > 0 && Distance == ProfileManager.CurrentProfile.HighlightTileAtRangeRangeSpell) { hueVec.X = ProfileManager.CurrentProfile.HighlightTileRangeHueSpell; hueVec.Y = 1; diff --git a/src/ClassicUO.Client/Game/GameObjects/Views/MultiView.cs b/src/ClassicUO.Client/Game/GameObjects/Views/MultiView.cs index 0f64b2bd9..74c2da8f9 100644 --- a/src/ClassicUO.Client/Game/GameObjects/Views/MultiView.cs +++ b/src/ClassicUO.Client/Game/GameObjects/Views/MultiView.cs @@ -123,7 +123,7 @@ public override bool Draw(UltimaBatcher2D batcher, int posX, int posY, float dep } if (ProfileManager.CurrentProfile.HighlightTileAtRangeSpell) { - if (GameCursor._spellTime >= 1 && Distance == ProfileManager.CurrentProfile.HighlightTileAtRangeRangeSpell) + if (GameActions.LastSpellIndexCursor > 0 && Distance == ProfileManager.CurrentProfile.HighlightTileAtRangeRangeSpell) { hueVec.X = ProfileManager.CurrentProfile.HighlightTileRangeHueSpell; hueVec.Y = 1; diff --git a/src/ClassicUO.Client/Game/GameObjects/Views/StaticView.cs b/src/ClassicUO.Client/Game/GameObjects/Views/StaticView.cs index e1a49f165..892e5233a 100644 --- a/src/ClassicUO.Client/Game/GameObjects/Views/StaticView.cs +++ b/src/ClassicUO.Client/Game/GameObjects/Views/StaticView.cs @@ -102,7 +102,7 @@ public override bool Draw(UltimaBatcher2D batcher, int posX, int posY, float dep } if (ProfileManager.CurrentProfile.HighlightTileAtRangeSpell) { - if (GameCursor._spellTime >= 1 && Distance == ProfileManager.CurrentProfile.HighlightTileAtRangeRangeSpell) + if (GameActions.LastSpellIndexCursor > 0 && Distance == ProfileManager.CurrentProfile.HighlightTileAtRangeRangeSpell) { hueVec.X = ProfileManager.CurrentProfile.HighlightTileRangeHueSpell; hueVec.Y = 1; diff --git a/src/ClassicUO.Client/Game/Managers/WorldMapEntityManager.cs b/src/ClassicUO.Client/Game/Managers/WorldMapEntityManager.cs index 81e86ce84..74aad5cc3 100644 --- a/src/ClassicUO.Client/Game/Managers/WorldMapEntityManager.cs +++ b/src/ClassicUO.Client/Game/Managers/WorldMapEntityManager.cs @@ -30,6 +30,8 @@ #endregion +using System; +using Microsoft.Extensions.Caching.Memory; using System.Collections.Generic; using ClassicUO.Configuration; using ClassicUO.Game.Data; @@ -43,33 +45,46 @@ namespace ClassicUO.Game.Managers { internal class WMapEntity { + public bool IsGuild; + public uint LastUpdate; + public string Name; + public uint Serial; + public int X, Y, HP, Map; + public static readonly Dictionary nameCache = new Dictionary(); + public WMapEntity(uint serial) { Serial = serial; - //var mob = World.Mobiles.Get(serial); + var mob = World.Mobiles.Get(serial); - //if (mob != null) - // GetName(); + if (mob != null) + { + GetName(serial); + } + } - public bool IsGuild; - public uint LastUpdate; - public string Name; - public readonly uint Serial; - public int X, Y, HP, Map; - - //public string GetName() - //{ - // Entity e = World.Get(Serial); + public string GetName(uint Serial) + { + Entity e = World.Get(Serial); - // if (e != null && !e.IsDestroyed && !string.IsNullOrEmpty(e.Name) && Name != e.Name) - // { - // Name = e.Name; - // } + if (e != null && !e.IsDestroyed && !string.IsNullOrEmpty(e.Name) && Name != e.Name) + { + Name = e.Name; + nameCache[Serial] = Name; + } - // return string.IsNullOrEmpty(Name) ? "" : Name; - //} + if (nameCache.TryGetValue(Serial, out string cachedName)) + { + var teste = string.IsNullOrEmpty(Name) ? cachedName : Name; + return string.IsNullOrEmpty(Name) ? cachedName : Name; + } + else + { + return string.IsNullOrEmpty(Name) ? "" : Name; + } + } } internal class WorldMapEntityManager diff --git a/src/ClassicUO.Client/Game/UI/Controls/PaperDollInteractable.cs b/src/ClassicUO.Client/Game/UI/Controls/PaperDollInteractable.cs index 797c262f1..027d2c0d4 100644 --- a/src/ClassicUO.Client/Game/UI/Controls/PaperDollInteractable.cs +++ b/src/ClassicUO.Client/Game/UI/Controls/PaperDollInteractable.cs @@ -354,7 +354,7 @@ public void RequestUpdate() _updateUI = true; } - private static ushort GetAnimID(ushort graphic, ushort animID, bool isfemale) + public static ushort GetAnimID(ushort graphic, ushort animID, bool isfemale) { int offset = isfemale ? Constants.FEMALE_GUMP_OFFSET : Constants.MALE_GUMP_OFFSET; @@ -395,7 +395,7 @@ private static ushort GetAnimID(ushort graphic, ushort animID, bool isfemale) return (ushort) (animID + offset); } - private class GumpPicEquipment : GumpPic + public class GumpPicEquipment : GumpPic { private readonly Layer _layer; diff --git a/src/ClassicUO.Client/Game/UI/Gumps/RaceChangeGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/RaceChangeGump.cs new file mode 100644 index 000000000..afa2cd1bb --- /dev/null +++ b/src/ClassicUO.Client/Game/UI/Gumps/RaceChangeGump.cs @@ -0,0 +1,813 @@ +using ClassicUO.Assets; +using ClassicUO.Configuration; +using ClassicUO.Game.Data; +using ClassicUO.Game.GameObjects; +using ClassicUO.Game.Managers; +using ClassicUO.Game.UI.Controls; +using ClassicUO.Input; +using ClassicUO.Network; +using System; +using System.Collections.Generic; + +namespace ClassicUO.Game.UI.Gumps +{ + internal class RaceChangeGump : Gump + { + private bool isFemale { get; } = false; + private RaceType selectedRace { get; } = RaceType.HUMAN; + private PlayerMobile fakeMobile; + private CustomPaperDollGump paperDollInteractable; + private readonly Dictionary> CurrentColorOption = new Dictionary>(); + private readonly Dictionary CurrentOption = new Dictionary() + { + { + Layer.Hair, 1 + }, + { + Layer.Beard, 0 + } + }; + private Item hair, beard; + + #region + private ushort raceTextGraphic + { + get + { + switch (selectedRace) + { + case RaceType.HUMAN: + return 0x702; + case RaceType.ELF: + return 0x705; + case RaceType.GARGOYLE: + return 0x7D4; + } + return 0; + } + } + private int raceTextWidth + { + get + { + switch (selectedRace) + { + case RaceType.HUMAN: + return 79; + case RaceType.ELF: + return 79; + case RaceType.GARGOYLE: + return 99; + } + return 0; + } + } + private ushort genderTextGraphic + { + get + { + return (ushort)(isFemale ? 0x70D : 0x710); + } + } + #endregion + + public RaceChangeGump(bool isFemale, byte race) : base(0, 0) + { + if (race <= 0 || race > (int)RaceType.GARGOYLE) + { + //Invalid race byte + Dispose(); + return; + } + + selectedRace = (RaceType)race; + this.isFemale = isFemale; + + X = 50; + Y = 50; + CanMove = false; + CanCloseWithRightClick = true; + AcceptMouseInput = true; + + BuildGump(); + + WantUpdateSize = true; + } + + private void BuildGump() + { + #region Background elements + Add + ( + new ResizePic(0x0E10) + { + X = 0, + Y = 0, + Width = 595, + Height = 400 + }, + 1 + ); //Main background + + Add + ( + new ResizePic(0x0E10) + { + X = 25, + Y = 45, + Width = 151, + Height = 310 + }, + 1 + ); //Left side, hair style etc + + Add + ( + new ResizePic(0x0E10) + { + X = 419, + Y = 45, + Width = 151, + Height = 310 + }, + 1 + ); //Right side tone/colors + #endregion + + Add(new GumpPic(176 - raceTextWidth, 360, raceTextGraphic, 0)); //non-functional "Button" that says Human, Elf, or Gargoyle + + Add(new GumpPic(419, 360, genderTextGraphic, 0)); //non-functional "Button" that says Male or Female + + Button confirmButton; + Add(confirmButton = new Button(0, 0x15A4, 0x15A6, 0x15A5) { X = 560, Y = 360 }); //Button to confirm, in classic client it is an arrow pointing right. + confirmButton.MouseUp += ConfirmButton_MouseUp; + + //Add hair styles + BuildHairStyles(40, 60); + + //Add color pickers + BuildColorOptions(434, 60); + + //Create fake character + CreateCharacter(); + UpdateEquipments(); + + //Add the main paperdoll graphic + Add(new GumpPic(185, 25, 0x708, 0)); + Add + ( + paperDollInteractable = new CustomPaperDollGump(210, 75, fakeMobile, hair, beard) + { + AcceptMouseInput = false + } + ); + + paperDollInteractable.RequestUpdate(); + + Add(new GumpPic(211, 15, 0x769, 0)); //Character Race Changer text + } + + /// + /// Build hair options for race change gump. + /// + /// The starting point for these ui elements + /// The starting point for these ui elements + private void BuildHairStyles(int x, int y) + { + #region TextSetup + bool isAsianLang = string.Compare(Settings.GlobalSettings.Language, "CHT", StringComparison.InvariantCultureIgnoreCase) == 0 || + string.Compare(Settings.GlobalSettings.Language, "KOR", StringComparison.InvariantCultureIgnoreCase) == 0 || + string.Compare(Settings.GlobalSettings.Language, "JPN", StringComparison.InvariantCultureIgnoreCase) == 0; + + bool unicode = isAsianLang; + byte font = (byte)(isAsianLang ? 3 : 9); + ushort hue = (ushort)(isAsianLang ? 0xFFFF : 0); + #endregion + + CharacterCreationValues.ComboContent content = CharacterCreationValues.GetHairComboContent(isFemale, selectedRace); + + CurrentOption[Layer.Beard] = 0; + CurrentOption[Layer.Hair] = 1; + + #region Hair style + Add + ( + new Label(ClilocLoader.Instance.GetString(selectedRace == RaceType.GARGOYLE ? 1112309 : 3000121), unicode, hue, font: font) + { + X = x + 1, + Y = y + } + ); + y += 15; + + Combobox hair; + Add + (hair = + new Combobox + ( + x, + y, + 120, + content.Labels, + CurrentOption[Layer.Hair] + ) + ); + hair.OnOptionSelected += (s, e) => + { + CurrentOption[Layer.Hair] = e; + UpdateEquipments(); + paperDollInteractable.RequestUpdate(this.hair, beard); + }; + y += 30; + #endregion + + #region Facial Hair + if (!isFemale && selectedRace != RaceType.ELF) + { + content = CharacterCreationValues.GetFacialHairComboContent(selectedRace); + + Add + ( + new Label(ClilocLoader.Instance.GetString(selectedRace == RaceType.GARGOYLE ? 1112511 : 3000122), unicode, hue, font: font) + { + X = x + 1, + Y = y + } + ); + y += 15; + + Combobox facialHair; + Add + (facialHair = + new Combobox + ( + x, + y, + 120, + content.Labels, + CurrentOption[Layer.Beard] + ) + ); + facialHair.OnOptionSelected += (s, e) => + { + CurrentOption[Layer.Beard] = e; + UpdateEquipments(); + paperDollInteractable.RequestUpdate(this.hair, beard); + }; + } + #endregion + } + + /// + /// Build color options for race change gump + /// + /// The starting point for these ui elements + /// The starting point for these ui elements + private void BuildColorOptions(int x, int y) + { + ushort[] pallet = CharacterCreationValues.GetSkinPallet(selectedRace); + + AddCustomColorPicker + ( + x, + y, + pallet, + Layer.Invalid, + 3000183, + 8, + pallet.Length >> 3 + ); + y += 42; + + // Hair + pallet = CharacterCreationValues.GetHairPallet(selectedRace); + + AddCustomColorPicker + ( + x, + y, + pallet, + Layer.Hair, + selectedRace == RaceType.GARGOYLE ? 1112322 : 3000184, + 8, + pallet.Length >> 3 + ); + y += 42; + + if (!isFemale && selectedRace != RaceType.ELF) + { + // Facial + pallet = CharacterCreationValues.GetHairPallet(selectedRace); + + AddCustomColorPicker + ( + x, + y, + pallet, + Layer.Beard, + selectedRace == RaceType.GARGOYLE ? 1112512 : 3000446, + 8, + pallet.Length >> 3 + ); + } + } + + /// + /// Must be called *after* color options have been set up. + /// + private void CreateCharacter() + { + #region Create a fake character to use for the gump + if (fakeMobile == null || fakeMobile.IsDestroyed) + { + fakeMobile = new PlayerMobile(1); + World.Mobiles.Add(fakeMobile); + } + + LinkedObject first = fakeMobile.Items; + + while (first != null) + { + LinkedObject next = first.Next; + + World.RemoveItem((Item)first, true); + + first = next; + } + + fakeMobile.Clear(); + fakeMobile.Race = selectedRace; + fakeMobile.IsFemale = isFemale; + + if (isFemale) + { + fakeMobile.Flags |= Flags.Female; + } + else + { + fakeMobile.Flags &= ~Flags.Female; + } + #endregion + + if (selectedRace == RaceType.ELF) + { + fakeMobile.Graphic = (ushort)(isFemale ? 0x025E : 0x025D); + } + else + { + fakeMobile.Graphic = (ushort)(isFemale ? 0x0191 : 0x0190); + } + } + + private void AddCustomColorPicker + ( + int x, + int y, + ushort[] pallet, + Layer layer, + int clilocLabel, + int rows, + int columns + ) + { + CustomColorPicker colorPicker; + + Add + ( + colorPicker = new CustomColorPicker + ( + layer, + clilocLabel, + pallet, + rows, + columns + ) + { + X = x, + Y = y + }, + 1 + ); + + if (!CurrentColorOption.ContainsKey(layer)) + { + CurrentColorOption[layer] = new Tuple(0, colorPicker.HueSelected); + } + else + { + colorPicker.SetSelectedIndex(CurrentColorOption[layer].Item1); + } + + colorPicker.ColorSelected += ColorPicker_ColorSelected; + } + + private void ColorPicker_ColorSelected(object sender, ColorSelectedEventArgs e) + { + if (e.SelectedIndex == 0xFFFF) + { + return; + } + + CurrentColorOption[e.Layer] = new Tuple(e.SelectedIndex, e.SelectedHue); + + if (e.Layer != Layer.Invalid) + { + Item item = fakeMobile.FindItemByLayer(e.Layer); + + if (item != null) + { + item.Hue = e.SelectedHue; + } + } + else + { + fakeMobile.Hue = e.SelectedHue; + } + + paperDollInteractable.RequestUpdate(hair, beard); + } + + private void ConfirmButton_MouseUp(object sender, Input.MouseEventArgs e) + { + if (e.Button == Input.MouseButtonType.Left) + { + NetClient.Socket.Send_ChangeRaceRequest( + CurrentColorOption[Layer.Invalid].Item2, + (ushort)CharacterCreationValues.GetHairComboContent(isFemale, selectedRace).GetGraphic(CurrentOption[Layer.Hair]), + CurrentColorOption[Layer.Hair].Item2, + (ushort)CharacterCreationValues.GetFacialHairComboContent(selectedRace).GetGraphic(CurrentOption[Layer.Beard]), + CurrentColorOption[Layer.Beard].Item2 + ); + Dispose(); + } + } + + private void UpdateEquipments() + { + Layer layer; + CharacterCreationValues.ComboContent content; + + fakeMobile.Hue = CurrentColorOption[Layer.Invalid].Item2; + + if (!isFemale && selectedRace != RaceType.ELF) + { + layer = Layer.Beard; + content = CharacterCreationValues.GetFacialHairComboContent(selectedRace); + + beard = CreateItem(content.GetGraphic(CurrentOption[layer]), CurrentColorOption[layer].Item2, layer); + } + + layer = Layer.Hair; + content = CharacterCreationValues.GetHairComboContent(isFemale, selectedRace); + + hair = CreateItem(content.GetGraphic(CurrentOption[layer]), CurrentColorOption[layer].Item2, layer); + } + + private Item CreateItem(int id, ushort hue, Layer layer) + { + Item existsItem = fakeMobile.FindItemByLayer(layer); + + if (existsItem != null) + { + World.RemoveItem(existsItem, true); + fakeMobile.Remove(existsItem); + } + + if (id == 0) + { + return null; + } + // This is a workaround to avoid to see naked guy + // We are simulating server objects into World.Items map. + Item item = World.GetOrCreateItem(0x4000_0000 + (uint)layer); // use layer as unique Serial + fakeMobile.Remove(item); + item.Graphic = (ushort)id; + item.Hue = hue; + item.Layer = layer; + item.Container = fakeMobile; + fakeMobile.PushToBack(item); + // + + return item; + } + + #region Classes + private class ColorSelectedEventArgs : EventArgs + { + public ColorSelectedEventArgs(Layer layer, ushort[] pallet, int selectedIndex) + { + Layer = layer; + Pallet = pallet; + SelectedIndex = selectedIndex; + } + + public Layer Layer { get; } + + private ushort[] Pallet { get; } + + public int SelectedIndex { get; } + + public ushort SelectedHue => Pallet != null && SelectedIndex >= 0 && SelectedIndex < Pallet.Length ? Pallet[SelectedIndex] : (ushort)0xFFFF; + } + + private class CustomColorPicker : Control + { + //private readonly ColorBox _box; + private readonly int _cellH; + private readonly int _cellW; + private readonly ColorBox _colorPicker; + private ColorPickerBox _colorPickerBox; + private readonly int _columns, _rows; + private int _lastSelectedIndex; + private readonly Layer _layer; + private readonly ushort[] _pallet; + + public CustomColorPicker(Layer layer, int label, ushort[] pallet, int rows, int columns) + { + Width = 121; + Height = 25; + _cellW = 125 / columns; + _cellH = 280 / rows; + _columns = columns; + _rows = rows; + _layer = layer; + _pallet = pallet; + + bool isAsianLang = string.Compare(Settings.GlobalSettings.Language, "CHT", StringComparison.InvariantCultureIgnoreCase) == 0 || + string.Compare(Settings.GlobalSettings.Language, "KOR", StringComparison.InvariantCultureIgnoreCase) == 0 || + string.Compare(Settings.GlobalSettings.Language, "JPN", StringComparison.InvariantCultureIgnoreCase) == 0; + + bool unicode = isAsianLang; + byte font = (byte)(isAsianLang ? 3 : 9); + ushort hue = (ushort)(isAsianLang ? 0xFFFF : 0); + + Add + ( + new Label(ClilocLoader.Instance.GetString(label), unicode, hue, font: font) + { + X = 0, + Y = 0 + } + ); + + Add + ( + _colorPicker = new ColorBox(121, 23, (ushort)((pallet?[0] ?? 1) + 1)) + { + X = 1, + Y = 15 + } + ); + + _colorPicker.MouseUp += ColorPicker_MouseClick; + } + + public ushort HueSelected => _colorPicker.Hue; + + public event EventHandler ColorSelected; + + public void SetSelectedIndex(int index) + { + if (_colorPickerBox != null) + { + _colorPickerBox.SelectedIndex = index; + + SetCurrentHue(); + } + } + + private void SetCurrentHue() + { + _colorPicker.Hue = _colorPickerBox.SelectedHue; + _lastSelectedIndex = _colorPickerBox.SelectedIndex; + + _colorPickerBox.Dispose(); + } + + private void ColorPickerBoxOnMouseUp(object sender, MouseEventArgs e) + { + int column = e.X / _cellW; + int row = e.Y / _cellH; + int selectedIndex = row * _columns + column; + + if (selectedIndex >= 0 && selectedIndex < _colorPickerBox.Hues.Length) + { + ColorSelected?.Invoke(this, new ColorSelectedEventArgs(_layer, _colorPickerBox.Hues, selectedIndex)); + SetCurrentHue(); + } + } + + private void ColorPicker_MouseClick(object sender, MouseEventArgs e) + { + if (e.Button == MouseButtonType.Left) + { + _colorPickerBox?.Dispose(); + _colorPickerBox = null; + + if (_colorPickerBox == null) + { + _colorPickerBox = new ColorPickerBox + ( + 485, + 109, + _rows, + _columns, + _cellW, + _cellH, + _pallet + ) + { + IsModal = true, + LayerOrder = UILayer.Over, + ModalClickOutsideAreaClosesThisControl = true, + ShowLivePreview = false, + SelectedIndex = _lastSelectedIndex + }; + + UIManager.Add(_colorPickerBox); + + _colorPickerBox.ColorSelectedIndex += ColorPickerBoxOnColorSelectedIndex; + _colorPickerBox.MouseUp += ColorPickerBoxOnMouseUp; + } + } + } + + private void ColorPickerBoxOnColorSelectedIndex(object sender, EventArgs e) + { + ColorSelected?.Invoke(this, new ColorSelectedEventArgs(_layer, _colorPickerBox.Hues, _colorPickerBox.SelectedIndex)); + } + } + + /// + /// Partially custom paperdoll gump required, when in-game the fake character created gets automatically removed and breaks the original paperdoll gump. + /// + private class CustomPaperDollGump : PaperDollInteractable + { + private readonly Mobile playerMobile; + private Item hair; + private Item beard; + private bool requestUpdate = false; + + public CustomPaperDollGump(int x, int y, Mobile playerMobile, Item hair, Item beard) : base(x, y, playerMobile, null) + { + this.playerMobile = playerMobile; + this.hair = hair; + this.beard = beard; + } + + private void UpdateUI() + { + if (IsDisposed) + { + return; + } + + Mobile mobile = playerMobile; + + if (mobile == null) + { + Dispose(); + + return; + } + + Clear(); + + #region Add the base gump - the semi-naked paper doll. + ushort body; + ushort hue = mobile.Hue; + + if (mobile.Graphic == 0x0191 || mobile.Graphic == 0x0193) + { + body = 0x000D; + } + else if (mobile.Graphic == 0x025D) + { + body = 0x000E; + } + else if (mobile.Graphic == 0x025E) + { + body = 0x000F; + } + else if (mobile.Graphic == 0x029A || mobile.Graphic == 0x02B6) + { + body = 0x029A; + } + else if (mobile.Graphic == 0x029B || mobile.Graphic == 0x02B7) + { + body = 0x0299; + } + else if (mobile.Graphic == 0x04E5) + { + body = 0xC835; + } + else if (mobile.Graphic == 0x03DB) + { + body = 0x000C; + hue = 0x03EA; + } + else if (mobile.IsFemale) + { + body = 0x000D; + } + else + { + body = 0x000C; + } + + // body + Add + ( + new GumpPic(0, 0, body, hue) + { + IsPartialHue = true + } + ); + + + if (mobile.Graphic == 0x03DB) + { + Add + ( + new GumpPic(0, 0, 0xC72B, mobile.Hue) + { + AcceptMouseInput = true, + IsPartialHue = true + } + ); + } + #endregion + + // equipment + + if (hair != null) + { + ushort id = GetAnimID(mobile.Graphic, hair.ItemData.AnimID, mobile.IsFemale); + + Add + ( + new GumpPicEquipment + ( + hair.Serial, + 0, + 0, + id, + (ushort)(hair.Hue & 0x3FFF), + Layer.Hair + ) + { + AcceptMouseInput = true, + IsPartialHue = hair.ItemData.IsPartialHue, + CanLift = false + } + ); + } + + if (beard != null) + { + ushort id = GetAnimID(mobile.Graphic, beard.ItemData.AnimID, mobile.IsFemale); + + Add + ( + new GumpPicEquipment + ( + beard.Serial, + 0, + 0, + id, + (ushort)(beard.Hue & 0x3FFF), + Layer.Beard + ) + { + AcceptMouseInput = true, + IsPartialHue = beard.ItemData.IsPartialHue, + CanLift = false + } + ); + } + } + + public void RequestUpdate(Item hair, Item beard) + { + this.hair = hair; + this.beard = beard; + requestUpdate = true; + } + + public new void RequestUpdate() + { + requestUpdate = true; + } + + public override void Update() + { + if (requestUpdate) + { + UpdateUI(); + requestUpdate = false; + } + } + } + #endregion + } +} \ No newline at end of file diff --git a/src/ClassicUO.Client/Game/UI/Gumps/WorldMapGump.cs b/src/ClassicUO.Client/Game/UI/Gumps/WorldMapGump.cs index 3bbad18e1..3f0b663a3 100644 --- a/src/ClassicUO.Client/Game/UI/Gumps/WorldMapGump.cs +++ b/src/ClassicUO.Client/Game/UI/Gumps/WorldMapGump.cs @@ -3316,7 +3316,7 @@ out rot.Y if (_showGroupName) { - string name = entity.Name ?? ResGumps.OutOfRange; + string name = entity.Name ?? entity.GetName(entity.Serial); Vector2 size = Fonts.Regular.MeasureString(entity.Name ?? name); if (rot.X + size.X / 2 > x + Width - 8) diff --git a/src/ClassicUO.Client/Network/PacketHandlers.cs b/src/ClassicUO.Client/Network/PacketHandlers.cs index da51ac3d5..e162bb2de 100644 --- a/src/ClassicUO.Client/Network/PacketHandlers.cs +++ b/src/ClassicUO.Client/Network/PacketHandlers.cs @@ -4787,9 +4787,10 @@ private static void ExtendedCommand(ref StackDataReader p) bool isfemale = p.ReadBool(); byte race = p.ReadUInt8(); - // TODO: gump race request + // Add race change - GameActions.Print("[DEBUG]: change-race gump is not implemented yet.", 34); + UIManager.GetGump()?.Dispose(); + UIManager.Add(new RaceChangeGump(isfemale, race)); break;