/// <summary> /// Displays a fly text in-game on the local player. /// </summary> /// <param name="kind">The FlyTextKind. See <see cref="FlyTextKind"/>.</param> /// <param name="actorIndex">The index of the actor to place flytext on. Indexing unknown. 1 places flytext on local player.</param> /// <param name="val1">Value1 passed to the native flytext function.</param> /// <param name="val2">Value2 passed to the native flytext function. Seems unused.</param> /// <param name="text1">Text1 passed to the native flytext function.</param> /// <param name="text2">Text2 passed to the native flytext function.</param> /// <param name="color">Color passed to the native flytext function. Changes flytext color.</param> /// <param name="icon">Icon ID passed to the native flytext function. Only displays with select FlyTextKind.</param> public unsafe void AddFlyText(FlyTextKind kind, uint actorIndex, uint val1, uint val2, SeString text1, SeString text2, uint color, uint icon) { // Known valid flytext region within the atk arrays var numIndex = 28; var strIndex = 25; var numOffset = 147u; var strOffset = 28u; // Get the UI module and flytext addon pointers var gameGui = Service <GameGui> .Get(); var ui = (FFXIVClientStructs.FFXIV.Client.UI.UIModule *)gameGui.GetUIModule(); var flytext = gameGui.GetAddonByName("_FlyText", 1); if (ui == null || flytext == IntPtr.Zero) { return; } // Get the number and string arrays we need var atkArrayDataHolder = ui->RaptureAtkModule.AtkModule.AtkArrayDataHolder; var numArray = atkArrayDataHolder._NumberArrays[numIndex]; var strArray = atkArrayDataHolder._StringArrays[strIndex]; // Write the values to the arrays using a known valid flytext region numArray->IntArray[numOffset + 0] = 1; // Some kind of "Enabled" flag for this section numArray->IntArray[numOffset + 1] = (int)kind; numArray->IntArray[numOffset + 2] = unchecked ((int)val1); numArray->IntArray[numOffset + 3] = unchecked ((int)val2); numArray->IntArray[numOffset + 4] = 5; // Unknown numArray->IntArray[numOffset + 5] = unchecked ((int)color); numArray->IntArray[numOffset + 6] = unchecked ((int)icon); numArray->IntArray[numOffset + 7] = 0; // Unknown numArray->IntArray[numOffset + 8] = 0; // Unknown, has something to do with yOffset fixed(byte *pText1 = text1.Encode()) { fixed(byte *pText2 = text2.Encode()) { strArray->StringArray[strOffset + 0] = pText1; strArray->StringArray[strOffset + 1] = pText2; this.addFlyTextNative( flytext, actorIndex, 1, (IntPtr)numArray, numOffset, 9, (IntPtr)strArray, strOffset, 2, 0); } } }
private bool KindCheck(ActionEffectInfo info, FlyTextKind targetKind) { var result = targetKind == info.kind; // Screenlog will log misses from enemies as Named/Miss, but they will show up to us as Named/Dodge if (!result) { return(targetKind is FlyTextKind.NamedDodge or FlyTextKind.Dodge && info.kind is FlyTextKind.NamedMiss or FlyTextKind.Miss); } return(true); }
public bool TryGetEffect(uint value, FlyTextKind targetKind, uint charaId, List <uint> petIds, out ActionEffectInfo info) { StoreLog($"Looking for effect {value} {targetKind}..."); info = default; if (!_store.TryGetValue(value, out var list)) { return(false); } var effect = list.FirstOrDefault(x => x.value == value && x.step == ActionStep.Screenlog && KindCheck(x, targetKind) && TargetCheck(x, charaId, petIds)); if (!list.Remove(effect)) { return(false); } info = effect; StoreLog($"Retrieved effect {effect}"); return(true); }
public void UpdateEffect(uint actionId, uint sourceId, uint targetId, uint value, FlyTextKind logKind) { StoreLog($"Updating effect {actionId} {sourceId} {targetId} {value} with {logKind}..."); if (!_store.TryGetValue(value, out var list)) { return; } var effect = list.FirstOrDefault(x => x.step == ActionStep.Effect && x.actionId == actionId && x.sourceId == sourceId && x.targetId == targetId); if (!list.Remove(effect)) { return; } effect.kind = logKind; effect.step = ActionStep.Screenlog; list.Add(effect); StoreLog($"Updated effect {effect}"); }
public unsafe IntPtr CreateFlyText( IntPtr flyTextMgr, // or something UInt32 kind, UInt32 val1, UInt32 val2, IntPtr text1, UInt32 color, UInt32 icon, IntPtr text2, float unk3 ) { uint tColor = color; uint tVal1 = val1; if (Randomize) { int ttVal1 = ModifyDamageALittle((int)val1); tVal1 = (uint)ttVal1; } try { if (Hijack) { string hjText1 = Marshal.PtrToStringAnsi(hijackStruct.text1); string hjText2 = Marshal.PtrToStringAnsi(hijackStruct.text2); FlyTextLog( $"flytext hijacked: kind: {hijackStruct.kind}, val1: {hijackStruct.val1}, val2: {hijackStruct.val2}, color: {hijackStruct.color:X}, icon: {hijackStruct.icon}"); FlyTextLog($"text1: {hjText1} | text2: {hjText2}"); return(createFlyTextHook.Original(flyTextMgr, hijackStruct.kind, hijackStruct.val1, hijackStruct.val2, hijackStruct.text1, hijackStruct.color, hijackStruct.icon, hijackStruct.text2, unk3)); } FlyTextKind ftKind = (FlyTextKind)kind; // wrap this here to lower overhead when not logging if (configuration.FlyTextLogEnabled) { string strText1 = Marshal.PtrToStringAnsi(text1); string strText2 = Marshal.PtrToStringAnsi(text2); strText1 = strText1?.Replace("%", "%%"); strText2 = strText2?.Replace("%", "%%"); FlyTextLog( $"flytext created: kind: {ftKind}, val1: {tVal1}, val2: {val2}, color: {color:X}, icon: {icon}"); FlyTextLog($"text1: {strText1} | text2: {strText2}"); } if (TryGetFlyTextDamageType(tVal1, out DamageType dmgType, out int sourceId)) { int charaId = GetCharacterActorId(); int petId = FindCharaPet(); if (configuration.OutgoingColorEnabled || configuration.IncomingColorEnabled) { // sourceId == GetCharacterActorId() && configuration.OutgoingColorEnabled || (sourceId != GetCharacterActorId() && sourceId != FindCharaPet() && configuration.IncomingColorEnabled) bool outPlayer = sourceId == charaId && configuration.OutgoingColorEnabled; bool outPet = sourceId == petId && configuration.PetDamageColorEnabled; bool outCheck = outPlayer || outPet; bool incCheck = sourceId != charaId && sourceId != petId && configuration.IncomingColorEnabled; // match up the condition with what to check // because right now with this OR, it doesn't care if the source is incoming and outgoing is enabled // so make sure that it oes it right if (outCheck && !incCheck || !outCheck && incCheck) { if (ftKind == FlyTextKind.AutoAttack || ftKind == FlyTextKind.CriticalHit || ftKind == FlyTextKind.DirectHit || ftKind == FlyTextKind.CriticalDirectHit || ftKind == FlyTextKind.NamedAttack || ftKind == FlyTextKind.NamedDirectHit || ftKind == FlyTextKind.NamedCriticalHit || ftKind == FlyTextKind.NamedCriticalDirectHit) { switch (dmgType) { case DamageType.Physical: tColor = ImGui.GetColorU32(configuration.PhysicalColor); break; case DamageType.Magic: tColor = ImGui.GetColorU32(configuration.MagicColor); break; case DamageType.Darkness: tColor = ImGui.GetColorU32(configuration.DarknessColor); break; } } } } if (configuration.SourceTextEnabled) { bool tgtCheck = sourceId != charaId && sourceId != petId; bool petCheck = sourceId == petId && configuration.PetSourceTextEnabled; if (tgtCheck || petCheck) { string name = GetActorName(sourceId); if (!string.IsNullOrEmpty(name)) { string existingText = ""; if (text1 != IntPtr.Zero) { existingText = Marshal.PtrToStringUni(text1); existingText = Encoding.UTF8.GetString(Encoding.Unicode.GetBytes(existingText)); } string combined = $"from {name} {existingText}"; combined = Encoding.Unicode.GetString(Encoding.UTF8.GetBytes(combined)); text1 = Marshal.StringToHGlobalUni(combined); text.Enqueue(new Tuple <IntPtr, long>(text1, Ms())); } } } } } catch (Exception e) { PluginLog.Log($"{e.Message} {e.StackTrace}"); } return(createFlyTextHook.Original(flyTextMgr, kind, tVal1, val2, text1, tColor, icon, text2, unk3)); }