Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Time and weather implementation #96

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions Ktisis/Interface/Components/TimeControls.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;

using ImGuiNET;

using FFXIVClientStructs.FFXIV.Client.System.Framework;

using Ktisis.Interop.Hooks;
using Ktisis.Structs.FFXIV;
using Ktisis.Util;

namespace Ktisis.Interface.Components {
public static class TimeControls {
public unsafe static void Draw() {
if (ImGui.CollapsingHeader("Time Control")) {
Framework* framework = Framework.Instance();

ImGui.Checkbox("Lock Time", ref WorldHooks.TimeUpdateDisabled);

bool isLocked = WorldHooks.TimeUpdateDisabled;
bool isOverridden = framework->IsEorzeaTimeOverridden;
long currentTime = isOverridden ? framework->EorzeaTimeOverride : framework->EorzeaTime;

long timeVal = currentTime % 2764800;
long secondInDay = timeVal % 86400;
int timeOfDay = (int)(secondInDay / 60f);
int dayOfMonth = (int)(Math.Floor(timeVal / 86400f) + 1);
var displayTime = TimeSpan.FromMinutes(timeOfDay);

int originalTime = timeOfDay;
int originalDay = dayOfMonth;

if (!isLocked) ImGui.BeginDisabled();

ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X - GuiHelpers.WidthMargin() - GuiHelpers.GetRightOffset(ImGui.CalcTextSize("Day of Month").X));
ImGui.SliderInt("Time of Day", ref timeOfDay, 0, 1439, $"{displayTime.Hours:D2}:{displayTime.Minutes:D2}");
ImGui.SliderInt("Day of Month", ref dayOfMonth, 1, 31);
ImGui.PopItemWidth();

if (!isLocked) ImGui.EndDisabled();


if (originalTime != timeOfDay || originalDay != dayOfMonth) {
long newTime = ((timeOfDay * 60) + (86400 * ((byte)(dayOfMonth) - 1)));

if (isOverridden) framework->EorzeaTimeOverride = newTime;
framework->EorzeaTime = newTime;
}
}
}
}
}
124 changes: 124 additions & 0 deletions Ktisis/Interface/Components/WeatherControls.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.Linq;

using Dalamud.Interface;

using ImGuiNET;

using Lumina.Excel.GeneratedSheets;

using Ktisis.Interop.Hooks;
using Ktisis.Util;

namespace Ktisis.Interface.Components {
public static class WeatherControls {
private static uint CachedTerritory = 0xFFFFFFFF;
private static readonly List<(byte Id, string Name)> ZoneValidWeatherList = new();
private static readonly Lazy<List<Weather>> WeatherSheet = new(() => Services.DataManager.GameData.GetExcelSheet<Weather>()!.Where(i => !string.IsNullOrEmpty(i.Name)).ToList());
private static bool SearchOpen = false;
private static string SearchTerm = string.Empty;

public unsafe static void Draw() {
if (ImGui.CollapsingHeader("Weather Control")) {
int weatherId = WorldHooks.WeatherSystem->CurrentWeather;
int originalWeatherId = weatherId;

UpdateCache();

ImGui.Checkbox("Lock Weather", ref WorldHooks.WeatherUpdateDisabled);

bool isLocked = WorldHooks.WeatherUpdateDisabled;

if (!isLocked) ImGui.BeginDisabled();

ImGui.SetNextItemWidth(130f);
ImGui.InputInt("Weather ID", ref weatherId, 0, 0);
ImGui.SameLine();
if (GuiHelpers.IconButtonTooltip(FontAwesomeIcon.Search, "Search"))
SearchOpen = true;

ImGui.PushItemWidth(ImGui.GetContentRegionAvail().X - GuiHelpers.WidthMargin());
if (ImGui.BeginListBox("###weather_list")) {

foreach (var weather in ZoneValidWeatherList) {
bool isSelected = weather.Id == weatherId;
if (ImGui.Selectable($"{weather.Name} ({weather.Id})###weather_{weather.Id}", isSelected)) {
weatherId = weather.Id;
}
}

ImGui.EndListBox();
}
ImGui.PopItemWidth();

if (isLocked && weatherId != originalWeatherId)
SetWeather((ushort)weatherId);

if (!isLocked) ImGui.EndDisabled();

if (SearchOpen)
DrawWeatherSearchPopup();
}
}

private static void UpdateCache() {

ushort territoryId = Services.ClientState.TerritoryType;

if (CachedTerritory == territoryId)
return;

ZoneValidWeatherList.Clear();
CachedTerritory = territoryId; // We set this here so if there is a failure we don't try again until a rezone

var territory = Services.DataManager.GameData.GetExcelSheet<TerritoryType>()!.GetRow(territoryId);

if (territory == null)
return;

var rate = Services.DataManager.GameData.GetExcelSheet<WeatherRate>()!.GetRow(territory.WeatherRate);

if (rate == null)
return;

foreach (var wr in rate!.UnkData0) {
if (wr.Weather == 0)
continue;

var weather = WeatherSheet.Value.SingleOrDefault(i => i.RowId == wr.Weather);

if (weather == null)
continue;

if (ZoneValidWeatherList.Count(x => x.Id == (byte)weather.RowId) == 0)
ZoneValidWeatherList.Add(((byte)weather.RowId, weather.Name));

}

ZoneValidWeatherList.Sort((x, y) => x.Id.CompareTo(y.Id));
}

private unsafe static void DrawWeatherSearchPopup() {
PopupSelect.HoverPopupWindow(
PopupSelect.HoverPopupWindowFlags.SelectorList | PopupSelect.HoverPopupWindowFlags.SearchBar,
WeatherSheet.Value!,
(e, input) => e.Where(t => $"{t.Name} ({t.RowId})".Contains(input, StringComparison.OrdinalIgnoreCase)),
(t, a) => {
// draw Line
bool selected = ImGui.Selectable($"{t.Name} ({t.RowId})###weather_item_{t.RowId}", a);
bool focus = ImGui.IsItemFocused();
return (selected, focus);
},
(t) => SetWeather((ushort)t.RowId),
() => SearchOpen = false,
ref SearchTerm,
"Weather Select",
"##weather_select",
"##weather_search");
;
}

private unsafe static void SetWeather(ushort weatherId) => WorldHooks.WeatherSystem->CurrentWeather = weatherId;
}
}
7 changes: 7 additions & 0 deletions Ktisis/Interface/Windows/Workspace/Workspace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ public static void Draw() {
SceneTab();*/
if (ImGui.BeginTabItem(Locale.GetString("Pose")))
PoseTab(target);
if (ImGui.BeginTabItem(Locale.GetString("World")))
WorldTab();
}
}

Expand Down Expand Up @@ -214,6 +216,11 @@ private unsafe static void PoseTab(GameObject target) {
ImGui.EndTabItem();
}

private unsafe static void WorldTab() {
TimeControls.Draw();
WeatherControls.Draw();
}

public static unsafe void DrawAdvancedDebugOptions(Actor* actor) {
if(ImGui.Button("Reset Current Pose") && actor->Model != null)
actor->Model->SyncModelSpace();
Expand Down
69 changes: 69 additions & 0 deletions Ktisis/Interop/Hooks/WorldHooks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System;

using Dalamud.Hooking;

using Ktisis.Events;
using Ktisis.Structs.Actor.State;
using Ktisis.Structs.FFXIV;

namespace Ktisis.Interop.Hooks {
internal static class WorldHooks {
public static bool TimeUpdateDisabled = false;
public static bool WeatherUpdateDisabled = false;


internal delegate void UpdateEorzeaTimeDelegate(IntPtr a1, IntPtr a2);
internal static Hook<UpdateEorzeaTimeDelegate> UpdateEorzeaTimeHook = null!;

internal delegate void UpdateTerritoryWeatherDelegate(IntPtr a1, IntPtr a2);
internal static Hook<UpdateTerritoryWeatherDelegate> UpdateTerritoryWeatherHook = null!;

public static unsafe WeatherSystem* WeatherSystem;

internal unsafe static void UpdateEorzeaTime(IntPtr a1, IntPtr a2) {
if (TimeUpdateDisabled)
return;

UpdateEorzeaTimeHook.Original(a1, a2);
}

internal unsafe static void UpdateTerritoryWeather(IntPtr a1, IntPtr a2) {
if (WeatherUpdateDisabled)
return;

UpdateTerritoryWeatherHook.Original(a1, a2);
}

internal unsafe static void Init() {
var etAddress = Services.SigScanner.ScanText("48 89 5C 24 ?? 57 48 83 EC ?? 48 8B F9 48 8B DA 48 81 C1 ?? ?? ?? ?? E8 ?? ?? ?? ?? 4C 8B 87 ?? ?? ?? ??");
UpdateEorzeaTimeHook = Hook<UpdateEorzeaTimeDelegate>.FromAddress(etAddress, UpdateEorzeaTime);
UpdateEorzeaTimeHook.Enable();

var twAddress = Services.SigScanner.ScanText("48 89 5C 24 ?? 55 56 57 48 83 EC ?? 48 8B F9 48 8D 0D ?? ?? ?? ??");
UpdateTerritoryWeatherHook = Hook<UpdateTerritoryWeatherDelegate>.FromAddress(twAddress, UpdateTerritoryWeather);
UpdateTerritoryWeatherHook.Enable();

IntPtr rawWeather = Services.SigScanner.GetStaticAddressFromSig("4C 8B 05 ?? ?? ?? ?? 41 8B 80 ?? ?? ?? ?? C1 E8 02");
WeatherSystem = *(WeatherSystem**) rawWeather;

EventManager.OnGPoseChange += OnGPoseChange;
}

internal static void OnGPoseChange(ActorGposeState _state) {
if (_state == ActorGposeState.OFF) {
TimeUpdateDisabled = false;
WeatherUpdateDisabled = false;
}
}

internal static void Dispose() {
EventManager.OnGPoseChange -= OnGPoseChange;

UpdateEorzeaTimeHook.Disable();
UpdateEorzeaTimeHook.Dispose();

UpdateTerritoryWeatherHook.Disable();
UpdateTerritoryWeatherHook.Dispose();
}
}
}
2 changes: 2 additions & 0 deletions Ktisis/Ktisis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public Ktisis(DalamudPluginInterface pluginInterface) {
Interop.Hooks.EventsHooks.Init();
Interop.Hooks.GuiHooks.Init();
Interop.Hooks.PoseHooks.Init();
Interop.Hooks.WorldHooks.Init();

EventManager.OnGPoseChange += Workspace.OnEnterGposeToggle; // must be placed before ActorStateWatcher.Init()

Expand Down Expand Up @@ -100,6 +101,7 @@ public void Dispose() {
Interop.Hooks.EventsHooks.Dispose();
Interop.Hooks.GuiHooks.Dispose();
Interop.Hooks.PoseHooks.Dispose();
Interop.Hooks.WorldHooks.Dispose();

Interop.Alloc.Dispose();
ActorStateWatcher.Instance.Dispose();
Expand Down
9 changes: 9 additions & 0 deletions Ktisis/Structs/FFXIV/WeatherSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Runtime.InteropServices;

namespace Ktisis.Structs.FFXIV {
[StructLayout(LayoutKind.Explicit)]
public struct WeatherSystem {
[FieldOffset(0x27)]
public ushort CurrentWeather;
}
}