Merge pull request #199 from xen-42/achievements-2

Achievements 2
This commit is contained in:
Nick 2022-06-19 16:04:42 -04:00 committed by GitHub
commit c0d459c203
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 445 additions and 28 deletions

View File

@ -1,14 +1,21 @@
using NewHorizons.External.Configs;
using NewHorizons.Utility; using NewHorizons.Utility;
using OWML.ModHelper; using OWML.ModHelper;
using System; using System;
using System.Collections.Generic;
using System.Linq;
namespace NewHorizons.AchievementsPlus namespace NewHorizons.AchievementsPlus
{ {
public static class AchievementHandler public static class AchievementHandler
{ {
public static bool Enabled { get => _enabled; }
private static bool _enabled; private static bool _enabled;
private static IAchievements API; private static IAchievements API;
private static List<AchievementInfo> _achievements;
public static void Init() public static void Init()
{ {
API = Main.Instance.ModHelper.Interaction.TryGetModApi<IAchievements>("xen.AchievementTracker"); API = Main.Instance.ModHelper.Interaction.TryGetModApi<IAchievements>("xen.AchievementTracker");
@ -22,6 +29,8 @@ namespace NewHorizons.AchievementsPlus
_enabled = true; _enabled = true;
_achievements = new List<AchievementInfo>();
// Register base NH achievements // Register base NH achievements
NH.WarpDriveAchievement.Init(); NH.WarpDriveAchievement.Init();
NH.MultipleSystemAchievement.Init(); NH.MultipleSystemAchievement.Init();
@ -30,8 +39,33 @@ namespace NewHorizons.AchievementsPlus
NH.ProbeLostAchievement.Init(); NH.ProbeLostAchievement.Init();
API.RegisterTranslationsFromFiles(Main.Instance, "Assets/translations"); API.RegisterTranslationsFromFiles(Main.Instance, "Assets/translations");
GlobalMessenger<string, bool>.AddListener("DialogueConditionChanged", OnDialogueConditionChanged);
} }
public static void OnDestroy()
{
if (!_enabled) return;
GlobalMessenger<string, bool>.RemoveListener("DialogueConditionChanged", OnDialogueConditionChanged);
}
public static void RegisterAddon(AddonConfig addon, ModBehaviour mod)
{
if (addon.achievements == null) return;
if (!_enabled) return;
foreach (var achievement in addon.achievements)
{
_achievements.Add(achievement);
API.RegisterAchievement(achievement.ID, achievement.secret, mod);
}
}
public static void RegisterTranslationsFromFiles(ModBehaviour mod, string path) => API.RegisterTranslationsFromFiles(mod, path);
public static void Earn(string unique_id) public static void Earn(string unique_id)
{ {
if (!_enabled) return; if (!_enabled) return;
@ -45,5 +79,49 @@ namespace NewHorizons.AchievementsPlus
API.RegisterAchievement(unique_id, secret, mod); API.RegisterAchievement(unique_id, secret, mod);
} }
public static void OnLearnSignal()
{
if (!_enabled) return;
foreach (var achievement in _achievements.Where(x => x.signalIDs != null))
{
CheckAchievement(achievement);
}
}
public static void OnRevealFact()
{
if (!_enabled) return;
foreach (var achievement in _achievements.Where(x => x.factIDs != null))
{
CheckAchievement(achievement);
}
}
public static void OnDialogueConditionChanged(string s, bool b) => OnSetCondition();
public static void OnSetCondition()
{
if (!_enabled) return;
foreach (var achievement in _achievements.Where(x => x.conditionIDs != null))
{
CheckAchievement(achievement);
}
}
private static void CheckAchievement(AchievementInfo achievement)
{
if (!_enabled) return;
if (API.HasAchievement(achievement.ID)) return;
if (achievement.IsUnlocked())
{
Earn(achievement.ID);
}
}
} }
} }

View File

@ -0,0 +1,79 @@
using NewHorizons.Builder.Props;
using Newtonsoft.Json;
using System.Linq;
namespace NewHorizons.AchievementsPlus
{
/// <summary>
/// Info for an achievement to be used with the Achievements+ mod.
/// </summary>
[JsonObject]
public class AchievementInfo
{
/// <summary>
/// The unique ID of the achievement. This must be globally unique, meaning all achivements for
/// you mod should start with something to identify the mod they are from. For example, Real Solar System
/// uses "RSS_" and Signals+ would use "SIGNALS_PLUS_".
/// </summary>
public string ID;
/// <summary>
/// Should the name and description of the achievement be hidden until it is unlocked. Good for hiding spoilers!
/// </summary>
public bool secret;
/// <summary>
/// A list of facts that must be discovered before this achievement is unlocked. You can also set the achievement
/// to be unlocked by a reveal trigger in Props -> Reveals. Optional.
/// </summary>
public string[] factIDs;
/// <summary>
/// A list of signals that must be discovered before this achievement is unlocked. Optional.
/// </summary>
public string[] signalIDs;
/// <summary>
/// A list of conditions that must be true before this achievement is unlocked. Conditions can be set via dialogue. Optional.
/// </summary>
public string[] conditionIDs;
// Cache signal ids to the enum
[JsonIgnore]
private SignalName[] _signalIDs;
public bool IsUnlocked()
{
if (signalIDs != null)
{
if (_signalIDs == null)
{
_signalIDs = signalIDs.Select(x => SignalBuilder.StringToSignalName(x)).ToArray();
}
foreach(var signal in _signalIDs)
{
if (!PlayerData.KnowsSignal(signal)) return false;
}
}
if (factIDs != null)
{
foreach (var fact in factIDs)
{
if (!Locator.GetShipLogManager().IsFactRevealed(fact)) return false;
}
}
if (conditionIDs != null)
{
foreach (var condition in conditionIDs)
{
if (!DialogueConditionManager.SharedInstance.GetConditionState(condition)) return false;
}
}
return true;
}
}
}

View File

@ -8,5 +8,6 @@ namespace NewHorizons.AchievementsPlus
void RegisterTranslation(string uniqueID, TextTranslation.Language language, string name, string description); void RegisterTranslation(string uniqueID, TextTranslation.Language language, string name, string description);
void RegisterTranslationsFromFiles(ModBehaviour mod, string folderPath); void RegisterTranslationsFromFiles(ModBehaviour mod, string folderPath);
void EarnAchievement(string uniqueID); void EarnAchievement(string uniqueID);
bool HasAchievement(string uniqueID);
} }
} }

View File

@ -1,3 +1,4 @@
using NewHorizons.AchievementsPlus;
using NewHorizons.Components; using NewHorizons.Components;
using NewHorizons.External.Modules; using NewHorizons.External.Modules;
using NewHorizons.Utility; using NewHorizons.Utility;
@ -233,7 +234,7 @@ namespace NewHorizons.Builder.Props
return customName; return customName;
} }
private static SignalName StringToSignalName(string str) public static SignalName StringToSignalName(string str)
{ {
foreach (SignalName name in Enum.GetValues(typeof(SignalName))) foreach (SignalName name in Enum.GetValues(typeof(SignalName)))
{ {

View File

@ -1,4 +1,5 @@
using NewHorizons.External.Modules; using NewHorizons.Components.Achievement;
using NewHorizons.External.Modules;
using OWML.Common; using OWML.Common;
using UnityEngine; using UnityEngine;
using Logger = NewHorizons.Utility.Logger; using Logger = NewHorizons.Utility.Logger;
@ -46,35 +47,77 @@ namespace NewHorizons.Builder.ShipLog
private static void MakeTrigger(GameObject go, Sector sector, PropModule.RevealInfo info, IModBehaviour mod) private static void MakeTrigger(GameObject go, Sector sector, PropModule.RevealInfo info, IModBehaviour mod)
{ {
SphereShape newShape = MakeShape(go, info, Shape.CollisionMode.Volume); var shape = MakeShape(go, info, Shape.CollisionMode.Volume);
OWTriggerVolume newVolume = go.AddComponent<OWTriggerVolume>();
newVolume._shape = newShape; var volume = go.AddComponent<OWTriggerVolume>();
ShipLogFactListTriggerVolume volume = go.AddComponent<ShipLogFactListTriggerVolume>(); volume._shape = shape;
volume._factIDs = info.reveals;
if (info.reveals != null)
{
var factRevealVolume = go.AddComponent<ShipLogFactListTriggerVolume>();
factRevealVolume._factIDs = info.reveals;
}
if (!string.IsNullOrEmpty(info.achievementID))
{
var achievementVolume = go.AddComponent<AchievementVolume>();
achievementVolume.achievementID = info.achievementID;
}
} }
private static void MakeObservable(GameObject go, Sector sector, PropModule.RevealInfo info, IModBehaviour mod) private static void MakeObservable(GameObject go, Sector sector, PropModule.RevealInfo info, IModBehaviour mod)
{ {
go.layer = LayerMask.NameToLayer("Interactible"); go.layer = LayerMask.NameToLayer("Interactible");
SphereCollider newSphere = go.AddComponent<SphereCollider>();
newSphere.radius = info.radius; var sphere = go.AddComponent<SphereCollider>();
OWCollider newCollider = go.AddComponent<OWCollider>(); sphere.radius = info.radius;
ShipLogFactObserveTrigger newObserveTrigger = go.AddComponent<ShipLogFactObserveTrigger>();
newObserveTrigger._factIDs = info.reveals; var collider = go.AddComponent<OWCollider>();
newObserveTrigger._maxViewDistance = info.maxDistance == -1f ? 2f : info.maxDistance;
newObserveTrigger._maxViewAngle = info.maxAngle; var maxDistance = info.maxDistance == -1f ? 2f : info.maxDistance;
newObserveTrigger._owCollider = newCollider;
newObserveTrigger._disableColliderOnRevealFact = true; if (info.reveals != null)
{
var observeTrigger = go.AddComponent<ShipLogFactObserveTrigger>();
observeTrigger._factIDs = info.reveals;
observeTrigger._maxViewDistance = maxDistance;
observeTrigger._maxViewAngle = info.maxAngle;
observeTrigger._owCollider = collider;
observeTrigger._disableColliderOnRevealFact = true;
}
if (!string.IsNullOrEmpty(info.achievementID))
{
var achievementTrigger = go.AddComponent<AchievementObserveTrigger>();
achievementTrigger.achievementID = info.achievementID;
achievementTrigger.disableColliderOnUnlockAchievement = true;
achievementTrigger.maxViewDistance = maxDistance;
achievementTrigger.maxViewAngle = info.maxAngle;
}
} }
private static void MakeSnapshot(GameObject go, Sector sector, PropModule.RevealInfo info, IModBehaviour mod) private static void MakeSnapshot(GameObject go, Sector sector, PropModule.RevealInfo info, IModBehaviour mod)
{ {
SphereShape newShape = MakeShape(go, info, Shape.CollisionMode.Manual); var shape = MakeShape(go, info, Shape.CollisionMode.Manual);
ShapeVisibilityTracker newTracker = go.AddComponent<ShapeVisibilityTracker>();
newTracker._shapes = new Shape[] { newShape }; var visibilityTracker = go.AddComponent<ShapeVisibilityTracker>();
ShipLogFactSnapshotTrigger newSnapshotTrigger = go.AddComponent<ShipLogFactSnapshotTrigger>(); visibilityTracker._shapes = new Shape[] { shape };
newSnapshotTrigger._maxDistance = info.maxDistance == -1f ? 200f : info.maxDistance;
newSnapshotTrigger._factIDs = info.reveals; var maxDistance = info.maxDistance == -1f ? 200f : info.maxDistance;
if (info.reveals != null)
{
var snapshotTrigger = go.AddComponent<ShipLogFactSnapshotTrigger>();
snapshotTrigger._maxDistance = maxDistance;
snapshotTrigger._factIDs = info.reveals;
}
if (!string.IsNullOrEmpty(info.achievementID))
{
var achievementTrigger = go.AddComponent<AchievementSnapshotTrigger>();
achievementTrigger.maxDistance = maxDistance;
achievementTrigger.achievementID = info.achievementID;
}
} }
} }
} }

View File

@ -0,0 +1,52 @@
using NewHorizons.AchievementsPlus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace NewHorizons.Components.Achievement
{
public class AchievementObserveTrigger : MonoBehaviour, IObservable
{
public string achievementID;
public float maxViewDistance = 2f;
public float maxViewAngle = 180f;
public bool disableColliderOnUnlockAchievement;
public bool achievementUnlocked;
private OWCollider _owCollider;
private void Reset()
{
gameObject.layer = LayerMask.NameToLayer("Interactible");
}
private void Awake()
{
if (disableColliderOnUnlockAchievement)
{
_owCollider = gameObject.GetAddComponent<OWCollider>();
}
}
public void Observe(RaycastHit raycastHit, Vector3 raycastOrigin)
{
float num = Vector3.Angle(raycastHit.point - raycastOrigin, -transform.forward);
if (!achievementUnlocked && raycastHit.distance < maxViewDistance && num < maxViewAngle)
{
if (disableColliderOnUnlockAchievement)
{
_owCollider.SetActivation(false);
}
AchievementHandler.Earn(achievementID);
achievementUnlocked = true;
}
}
public void LoseFocus() { }
}
}

View File

@ -0,0 +1,38 @@
using NewHorizons.AchievementsPlus;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace NewHorizons.Components.Achievement
{
// Modified version of ShipLogFactSnapshotTrigger
public class AchievementSnapshotTrigger : MonoBehaviour
{
private void Awake()
{
_visibilityTracker = GetComponent<VisibilityTracker>();
GlobalMessenger<ProbeCamera>.AddListener("ProbeSnapshot", new Callback<ProbeCamera>(OnProbeSnapshot));
}
private void OnDestroy()
{
GlobalMessenger<ProbeCamera>.RemoveListener("ProbeSnapshot", new Callback<ProbeCamera>(OnProbeSnapshot));
}
private void OnProbeSnapshot(ProbeCamera probeCamera)
{
if (_visibilityTracker != null && _visibilityTracker.IsVisibleToProbe(probeCamera.GetOWCamera()) && (_visibilityTracker.transform.position - probeCamera.transform.position).magnitude < maxDistance)
{
AchievementHandler.Earn(achievementID);
}
}
public string achievementID;
public float maxDistance = 200f;
private VisibilityTracker _visibilityTracker;
}
}

View File

@ -0,0 +1,37 @@
using NewHorizons.AchievementsPlus;
using UnityEngine;
namespace NewHorizons.Components.Achievement
{
public class AchievementVolume : MonoBehaviour
{
private void Start()
{
_trigger = gameObject.GetRequiredComponent<OWTriggerVolume>();
_trigger.OnEntry += OnEntry;
return;
}
private void OnDestroy()
{
_trigger.OnEntry -= OnEntry;
}
private void OnEntry(GameObject hitObj)
{
if ((!player || hitObj.CompareTag("PlayerDetector")) && (!probe || hitObj.CompareTag("ProbeDetector")))
{
AchievementHandler.Earn(achievementID);
_trigger.OnEntry -= OnEntry;
}
}
public string achievementID;
public bool player = true;
public bool probe;
private OWTriggerVolume _trigger;
}
}

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using NewHorizons.AchievementsPlus;
using NewHorizons.External.Modules;
using NewHorizons.External.Modules.VariableSize;
using Newtonsoft.Json;
namespace NewHorizons.External.Configs
{
/// <summary>
/// Describes the New Horizons addon itself
/// </summary>
[JsonObject]
public class AddonConfig
{
/// <summary>
/// Achievements for this mod if the user is playing with Achievements+
/// Achievement icons must be put in a folder named "icons"
/// The icon for the mod must match the name of the mod (e.g., New Horizons.png)
/// The icons for achievements must match their unique IDs (e.g., NH_WARP_DRIVE.png)
/// </summary>
public AchievementInfo[] achievements;
}
}

View File

@ -1,9 +1,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace NewHorizons.External.Configs namespace NewHorizons.External.Configs
{ {
[JsonObject]
public class TranslationConfig public class TranslationConfig
{ {
/// <summary> /// <summary>
@ -21,6 +23,30 @@ namespace NewHorizons.External.Configs
/// </summary> /// </summary>
public Dictionary<string, string> UIDictionary; public Dictionary<string, string> UIDictionary;
// Literally only exists for the schema generation, Achievements+ handles the parsing
#region Achievements+
/// <summary>
/// Translation table for achievements. The key is the unique ID of the achievement
/// </summary>
public readonly Dictionary<string, AchievementTranslationInfo> AchievementTranslations;
[JsonObject]
public class AchievementTranslationInfo
{
/// <summary>
/// The name of the achievement.
/// </summary>
public string Name;
/// <summary>
/// The short description for this achievement.
/// </summary>
public readonly string Description;
}
#endregion
public TranslationConfig(string filename) public TranslationConfig(string filename)
{ {
var dict = JObject.Parse(File.ReadAllText(filename)).ToObject<Dictionary<string, object>>(); var dict = JObject.Parse(File.ReadAllText(filename)).ToObject<Dictionary<string, object>>();

View File

@ -385,6 +385,11 @@ namespace NewHorizons.External.Modules
/// A list of facts to reveal /// A list of facts to reveal
/// </summary> /// </summary>
public string[] reveals; public string[] reveals;
/// <summary>
/// An achievement to unlock. Optional.
/// </summary>
public string achievementID;
} }
[JsonObject] [JsonObject]

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

View File

@ -19,6 +19,7 @@ using Logger = NewHorizons.Utility.Logger;
using NewHorizons.Utility.DebugUtilities; using NewHorizons.Utility.DebugUtilities;
using Newtonsoft.Json; using Newtonsoft.Json;
using NewHorizons.Utility.DebugMenu; using NewHorizons.Utility.DebugMenu;
using NewHorizons.AchievementsPlus;
namespace NewHorizons namespace NewHorizons
{ {
@ -152,7 +153,7 @@ namespace NewHorizons
Instance.ModHelper.Events.Unity.FireOnNextUpdate(() => _firstLoad = false); Instance.ModHelper.Events.Unity.FireOnNextUpdate(() => _firstLoad = false);
Instance.ModHelper.Menus.PauseMenu.OnInit += DebugReload.InitializePauseMenu; Instance.ModHelper.Menus.PauseMenu.OnInit += DebugReload.InitializePauseMenu;
AchievementsPlus.AchievementHandler.Init(); AchievementHandler.Init();
} }
public void OnDestroy() public void OnDestroy()
@ -161,6 +162,8 @@ namespace NewHorizons
SceneManager.sceneLoaded -= OnSceneLoaded; SceneManager.sceneLoaded -= OnSceneLoaded;
GlobalMessenger<DeathType>.RemoveListener("PlayerDeath", OnDeath); GlobalMessenger<DeathType>.RemoveListener("PlayerDeath", OnDeath);
GlobalMessenger.RemoveListener("WakeUp", new Callback(OnWakeUp)); GlobalMessenger.RemoveListener("WakeUp", new Callback(OnWakeUp));
AchievementHandler.OnDestroy();
} }
private static void OnWakeUp() private static void OnWakeUp()
@ -361,10 +364,18 @@ namespace NewHorizons
} }
} }
} }
// Has to go before translations for achievements
if (File.Exists(folder + "addon-manifest.json"))
{
var addonConfig = mod.ModHelper.Storage.Load<AddonConfig>("addon-manifest.json");
AchievementHandler.RegisterAddon(addonConfig, mod as ModBehaviour);
}
if (Directory.Exists(folder + @"translations\")) if (Directory.Exists(folder + @"translations\"))
{ {
LoadTranslations(folder, mod); LoadTranslations(folder, mod);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -390,6 +401,11 @@ namespace NewHorizons
foundFile = true; foundFile = true;
TranslationHandler.RegisterTranslation(language, config); TranslationHandler.RegisterTranslation(language, config);
if (AchievementHandler.Enabled)
{
AchievementHandler.RegisterTranslationsFromFiles(mod as ModBehaviour, "translations");
}
} }
} }
if (!foundFile) Logger.LogWarning($"{mod.ModHelper.Manifest.Name} has a folder for translations but none were loaded"); if (!foundFile) Logger.LogWarning($"{mod.ModHelper.Manifest.Name} has a folder for translations but none were loaded");

View File

@ -1,4 +1,5 @@
using HarmonyLib; using HarmonyLib;
using NewHorizons.AchievementsPlus;
using NewHorizons.AchievementsPlus.NH; using NewHorizons.AchievementsPlus.NH;
using NewHorizons.Builder.Props; using NewHorizons.Builder.Props;
using NewHorizons.External; using NewHorizons.External;
@ -58,7 +59,13 @@ namespace NewHorizons.Patches
var customSignalName = SignalBuilder.GetCustomSignalName(__0); var customSignalName = SignalBuilder.GetCustomSignalName(__0);
if (customSignalName != null) if (customSignalName != null)
{ {
if (!NewHorizonsData.KnowsSignal(customSignalName)) NewHorizonsData.LearnSignal(customSignalName); if (!NewHorizonsData.KnowsSignal(customSignalName))
{
NewHorizonsData.LearnSignal(customSignalName);
}
AchievementHandler.OnLearnSignal();
return false; return false;
} }
return true; return true;

View File

@ -1,4 +1,5 @@
using HarmonyLib; using HarmonyLib;
using NewHorizons.AchievementsPlus;
using NewHorizons.Builder.ShipLog; using NewHorizons.Builder.ShipLog;
using NewHorizons.Components; using NewHorizons.Components;
using NewHorizons.Handlers; using NewHorizons.Handlers;
@ -217,6 +218,8 @@ namespace NewHorizons.Patches
public static void ShipLogManager_RevealFact(string __0) public static void ShipLogManager_RevealFact(string __0)
{ {
StarChartHandler.OnRevealFact(__0); StarChartHandler.OnRevealFact(__0);
AchievementHandler.OnRevealFact();
} }
} }
} }

View File

@ -27,6 +27,9 @@ public static class SchemaExporter
var systemSchema = var systemSchema =
new Schema<StarSystemConfig>("Star System Schema", "Schema for a star system in New Horizons", $"{folderName}/star_system_schema", settings); new Schema<StarSystemConfig>("Star System Schema", "Schema for a star system in New Horizons", $"{folderName}/star_system_schema", settings);
systemSchema.Output(); systemSchema.Output();
var addonSchema = new Schema<AddonConfig>("Addon Manifest Schema",
"Schema for an addon manifest in New Horizons", $"{folderName}/addon_manifest_schema", settings);
addonSchema.Output();
var translationSchema = var translationSchema =
new Schema<TranslationConfig>("Translation Schema", "Schema for a translation file in New Horizons", $"{folderName}/translation_schema", settings); new Schema<TranslationConfig>("Translation Schema", "Schema for a translation file in New Horizons", $"{folderName}/translation_schema", settings);
translationSchema.Output(); translationSchema.Output();