using System.Numerics; using System.Runtime.InteropServices; using FFXIVClientStructs.FFXIV.Client.Game.Object; using ICRebuild.Core; namespace ICRebuild.Runtime; public sealed unsafe class CoordinateTools(GameContext game, ModuleManager modules) { private delegate void Scale(nint actor, float scale); private Scale? scale; private readonly Dictionary resized=[]; public void Register() { modules.Register(27, _ => { }); modules.Register(28, _ => { }); modules.Register(29, scope => { scale = Marshal.GetDelegateForFunctionPointer(game.Scan("F3 0F 11 89 ?? ?? ?? ?? E9 ?? ?? ?? ?? CC CC CC CC CC CC CC CC CC CC CC CC")); scope.Defer(() => { try { RestoreAll(); } finally { scale=null; } }); }); } private GameObject* Player(int feature) { if (!modules.IsEnabled(feature)) throw new InvalidOperationException($"先启用模块 {feature}"); if (!game.Ready) throw new InvalidOperationException("角色尚未就绪"); return (GameObject*)game.LocalAddress; } public void Position(Vector3 position) { if (!float.IsFinite(position.X) || !float.IsFinite(position.Y) || !float.IsFinite(position.Z) || Math.Abs(position.X)>100000 || Math.Abs(position.Y)>100000 || Math.Abs(position.Z)>100000) throw new ArgumentException("坐标必须是 ±100000 范围内的有限数"); Player(27)->SetPosition(position.X,position.Y,position.Z); } public void Facing(float rotation) { if (!float.IsFinite(rotation)) throw new ArgumentException("面向必须是有限数"); Player(28)->SetRotation(MathF.IEEERemainder(rotation,2*MathF.PI)); } public void SetScale(float value) { if (!modules.IsEnabled(29) || scale == null) throw new InvalidOperationException("先启用模块 29"); if (!game.Ready || game.Targets.Target == null) throw new InvalidOperationException("请选中有效目标"); if (!float.IsFinite(value) || value < 0.1f || value > 5) throw new ArgumentException("缩放范围为 0.1—5"); var target=game.Targets.Target; var obj=(GameObject*)target.Address; var original=obj->Scale; if(!float.IsFinite(original) || original<=0) throw new InvalidOperationException("目标原始缩放无效"); if(resized.TryGetValue(target.GameObjectId,out var old) && old.Address==target.Address && old.Territory==game.Client.TerritoryType) original=old.Original; if(resized.Count>=256 && !resized.ContainsKey(target.GameObjectId)) throw new InvalidOperationException("先恢复已有缩放对象"); scale(target.Address,value); resized[target.GameObjectId]=(target.Address,game.Client.TerritoryType,original,value); } public void RestoreAll() { if(scale!=null && game.Ready) foreach(var obj in game.Objects) if(resized.TryGetValue(obj.GameObjectId,out var old) && old.Address==obj.Address && old.Territory==game.Client.TerritoryType && Math.Abs(((GameObject*)obj.Address)->Scale-old.Applied)<0.0001f) scale(obj.Address,old.Original); resized.Clear(); } }