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 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((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(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(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; } /// /// 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. /// 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); } } }