ace/Source/ACE.Server/WorldObjects/Creature_Networking.cs
OptimShi 0720d32092
Handle some outlier instances where AltSetup models have different appearances (#4401)
Co-authored-by: OptimShi <26606778+OptimShi@users.noreply.github.com>
2025-11-28 12:38:59 -05:00

267 lines
13 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ACE.DatLoader;
using ACE.DatLoader.Entity;
using ACE.DatLoader.FileTypes;
using ACE.Entity.Enum;
using ACE.Entity.Models;
using ACE.Server.Entity.Actions;
using ACE.Server.Managers;
using ACE.Server.Network.GameMessages.Messages;
using ACE.Server.Network.Structure;
namespace ACE.Server.WorldObjects
{
partial class Creature
{
public void HandleActionWorldBroadcast(string message, ChatMessageType messageType)
{
ActionChain chain = new ActionChain();
chain.AddAction(this, () => DoWorldBroadcast(message, messageType));
chain.EnqueueChain();
}
public void DoWorldBroadcast(string message, ChatMessageType messageType)
{
GameMessageSystemChat sysMessage = new GameMessageSystemChat(message, messageType);
PlayerManager.BroadcastToAll(sysMessage);
PlayerManager.LogBroadcastChat(Channel.AllBroadcast, this, message);
}
public override ACE.Entity.ObjDesc CalculateObjDesc()
{
ACE.Entity.ObjDesc objDesc = new ACE.Entity.ObjDesc();
ClothingTable item;
AddBaseModelData(objDesc);
var coverage = new List<uint>();
uint thisSetupId = SetupTableId;
bool showHelm = true;
bool showCloak = true;
if (this is Player player)
{
showHelm = player.GetCharacterOption(CharacterOption.ShowYourHelmOrHeadGear);
showCloak = player.GetCharacterOption(CharacterOption.ShowYourCloak);
}
// Some player races use an AlternateSetupDid, either at creation or via Barber options.
// BUT -- those values do not correspond with entries in the Clothing Table.
// So, we need to make some adjustments to look up something that DOES exist and is appropriate for the AlternateSetup model.
switch (thisSetupId)
{
//case (uint)SetupConst.UmbraenMaleCrown:
case (uint)SetupConst.UmbraenMaleCrownGen:
case (uint)SetupConst.UmbraenMaleNoCrown:
case (uint)SetupConst.UmbraenMaleVoid:
thisSetupId = (uint)SetupConst.UmbraenMaleCrown;
break;
//case (uint)SetupConst.UmbraenFemaleCrown:
//case (uint)SetupConst.UmbraenFemaleCrownGen:
case (uint)SetupConst.UmbraenFemaleNoCrown:
case (uint)SetupConst.UmbraenFemaleVoid:
thisSetupId = (uint)SetupConst.UmbraenFemaleCrown;
break;
//case (uint)SetupConst.PenumbraenMaleCrown:
case (uint)SetupConst.PenumbraenMaleCrownGen:
case (uint)SetupConst.PenumbraenMaleNoCrown:
case (uint)SetupConst.PenumbraenMaleVoid:
thisSetupId = (uint)SetupConst.PenumbraenMaleCrown;
break;
//case (uint)SetupConst.PenumbraenFemaleCrown:
//case (uint)SetupConst.PenumbraenFemaleCrownGen:
case (uint)SetupConst.PenumbraenFemaleNoCrown:
case (uint)SetupConst.PenumbraenFemaleVoid:
thisSetupId = (uint)SetupConst.PenumbraenFemaleCrown;
break;
case (uint)SetupConst.UndeadMaleUndeadGen:
case (uint)SetupConst.UndeadMaleSkeleton:
case (uint)SetupConst.UndeadMaleSkeletonNoFlame:
case (uint)SetupConst.UndeadMaleZombie:
case (uint)SetupConst.UndeadMaleZombieNoFlame:
thisSetupId = (uint)SetupConst.UndeadMaleUndead;
break;
case (uint)SetupConst.UndeadFemaleUndeadGen:
case (uint)SetupConst.UndeadFemaleSkeleton:
case (uint)SetupConst.UndeadFemaleSkeletonNoFlame:
case (uint)SetupConst.UndeadFemaleZombie:
case (uint)SetupConst.UndeadFemaleZombieNoFlame:
thisSetupId = (uint)SetupConst.UndeadFemaleUndead;
break;
case (uint)SetupConst.AnakshayMale:
thisSetupId = (uint)SetupConst.HumanMale;
break;
case (uint)SetupConst.AnakshayFemale:
thisSetupId = (uint)SetupConst.HumanFemale;
break;
}
// get all the Armor Items, and any Clothing items that might be equipped (robes, slippers, gloves, kasa, etc) so we can calculate their priority
var armorItems = EquippedObjects.Values.Where(x => (x.ItemType == ItemType.Armor || (x.CurrentWieldedLocation & (EquipMask.Armor | EquipMask.Extremity)) != 0)).ToList();
foreach (var w in armorItems)
w.setVisualClothingPriority();
// sort the armor into the proper order... TopLayerPriority first, then no priority, then TopLayerPriority=false.
// Secondary sort field is the calculated "VisualClothingPriority"
var top = armorItems.Where(x => x.TopLayerPriority == true).OrderBy(x => x.VisualClothingPriority);
var noLayer = armorItems.Where(x => x.TopLayerPriority == null).OrderBy(x => x.VisualClothingPriority);
var bottom = armorItems.Where(x => x.TopLayerPriority == false).OrderBy(x => x.VisualClothingPriority);
var sortedArmorItems = bottom.Concat(noLayer).Concat(top).ToList();
var clothesAndCloaks = EquippedObjects.Values
.Where(x => (x.ItemType == ItemType.Clothing) && (x.CurrentWieldedLocation & (EquipMask.Armor | EquipMask.Extremity)) == 0) // Extremity, Head/Foot/Hands, is included in the ArmorItems above
.OrderBy(x => x.ClothingPriority);
var eo = clothesAndCloaks.Concat(sortedArmorItems).ToList();
if (eo.Count == 0)
{
// Check if there is any defined ObjDesc in the Biota and, if so, apply them
if (Biota.PropertiesAnimPart.GetCount(BiotaDatabaseLock) > 0 || Biota.PropertiesPalette.GetCount(BiotaDatabaseLock) > 0 || Biota.PropertiesTextureMap.GetCount(BiotaDatabaseLock) > 0)
{
Biota.PropertiesAnimPart.CopyTo(objDesc.AnimPartChanges, BiotaDatabaseLock);
Biota.PropertiesPalette.CopyTo(objDesc.SubPalettes, BiotaDatabaseLock);
Biota.PropertiesTextureMap.CopyTo(objDesc.TextureChanges, BiotaDatabaseLock);
return objDesc;
}
}
foreach (var w in eo)
{
if ((w.CurrentWieldedLocation == EquipMask.HeadWear) && !showHelm && (this is Player))
continue;
if ((w.CurrentWieldedLocation == EquipMask.Cloak) && !showCloak && (this is Player))
continue;
// We can wield things that are not part of our model, only use those items that can cover our model.
if ((w.CurrentWieldedLocation & (EquipMask.Clothing | EquipMask.Armor | EquipMask.Cloak)) != 0)
{
if (w.ClothingBase.HasValue)
item = DatManager.PortalDat.ReadFromDat<ClothingTable>((uint)w.ClothingBase);
else
{
objDesc = AddSetupAsClothingBase(objDesc, w);
// Add any potentially added parts back into the coverage list
foreach(var a in objDesc.AnimPartChanges)
if (!coverage.Contains(a.Index))
coverage.Add(a.Index);
continue;
}
if (item.ClothingBaseEffects.ContainsKey(SetupTableId) || item.ClothingBaseEffects.ContainsKey(thisSetupId))
// Check if the player model has data. Gear Knights, this is usually you.
{
// Add the model and texture(s). Check if the original model has AnimParts defined, otherwise use the fallback if different
ClothingBaseEffect clothingBaseEffect;
if (item.ClothingBaseEffects.ContainsKey(SetupTableId))
clothingBaseEffect = item.ClothingBaseEffects[SetupTableId];
else
clothingBaseEffect = item.ClothingBaseEffects[thisSetupId];
foreach (CloObjectEffect t in clothingBaseEffect.CloObjectEffects)
{
byte partNum = (byte)t.Index;
coverage.Add(partNum);
objDesc.AddAnimPartChange(new PropertiesAnimPart { Index = (byte)t.Index, AnimationId = t.ModelId });
foreach (CloTextureEffect t1 in t.CloTextureEffects)
objDesc.AddTextureChange(new PropertiesTextureMap { PartIndex = (byte)t.Index, OldTexture = t1.OldTexture, NewTexture = t1.NewTexture });
}
if (item.ClothingSubPalEffects.Count > 0)
{
int size = item.ClothingSubPalEffects.Count;
int palCount = size;
CloSubPalEffect itemSubPal;
int palOption = 0;
if (w.PaletteTemplate.HasValue)
palOption = (int)w.PaletteTemplate;
if (item.ClothingSubPalEffects.ContainsKey((uint)palOption))
{
itemSubPal = item.ClothingSubPalEffects[(uint)palOption];
}
else
{
itemSubPal = item.ClothingSubPalEffects[item.ClothingSubPalEffects.Keys.ElementAt(0)];
}
float shade = 0;
if (w.Shade.HasValue)
shade = (float)w.Shade;
for (int i = 0; i < itemSubPal.CloSubPalettes.Count; i++)
{
var itemPalSet = DatManager.PortalDat.ReadFromDat<PaletteSet>(itemSubPal.CloSubPalettes[i].PaletteSet);
ushort itemPal = (ushort)itemPalSet.GetPaletteID(shade);
for (int j = 0; j < itemSubPal.CloSubPalettes[i].Ranges.Count; j++)
{
ushort palOffset = (ushort)(itemSubPal.CloSubPalettes[i].Ranges[j].Offset / 8);
ushort numColors = (ushort)(itemSubPal.CloSubPalettes[i].Ranges[j].NumColors / 8);
objDesc.SubPalettes.Add(new PropertiesPalette { SubPaletteId = itemPal, Offset = palOffset, Length = numColors });
}
}
}
}
}
}
// Add the "naked" body parts. These are the ones not already covered.
// Note that this is the original SetupTableId, not thisSetupId.
if (SetupTableId > 0)
{
var baseSetup = DatManager.PortalDat.ReadFromDat<SetupModel>(SetupTableId);
for (byte i = 0; i < baseSetup.Parts.Count; i++)
{
if (!coverage.Contains(i) && i != 0x10) // Don't add body parts for those that are already covered. Also don't add the head, that was already covered by AddCharacterBaseModelData()
objDesc.AnimPartChanges.Add(new PropertiesAnimPart { Index = i, AnimationId = baseSetup.Parts[i] });
//AddModel(i, baseSetup.Parts[i]);
}
}
if (coverage.Count == 0 && ClothingBase.HasValue)
return base.CalculateObjDesc();
return objDesc;
}
/// <summary>
/// Certain items do not contain a ClothingBase. Ursuin Guise, WCID 32155 is one of them. This function will use the Setup of the weenie as a pseudo-ClothingBase.
/// </summary>
protected ACE.Entity.ObjDesc AddSetupAsClothingBase(ACE.Entity.ObjDesc objDesc, WorldObject wo)
{
// Loop over the parts in the Setup of the WorldObject
for (var i = 0; i < wo.CSetup.Parts.Count; i++)
{
if(wo.CSetup.Parts[i] != 0x010001EC || i != 16) // This is essentially a "null" part, so do not add it for the head
objDesc.AnimPartChanges.Add(new PropertiesAnimPart { Index = (byte)i, AnimationId = wo.CSetup.Parts[i] });
}
return objDesc;
}
protected static void WriteIdentifyObjectCreatureProfile(BinaryWriter writer, Creature creature, bool success)
{
var creatureProfile = new CreatureProfile(creature, success);
writer.Write(creatureProfile);
}
}
}