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 OWML.ModHelper;
using System;
using System.Collections.Generic;
using System.Linq;
namespace NewHorizons.AchievementsPlus
{
public static class AchievementHandler
{
public static bool Enabled { get => _enabled; }
private static bool _enabled;
private static IAchievements API;
private static List<AchievementInfo> _achievements;
public static void Init()
{
API = Main.Instance.ModHelper.Interaction.TryGetModApi<IAchievements>("xen.AchievementTracker");
@ -22,6 +29,8 @@ namespace NewHorizons.AchievementsPlus
_enabled = true;
_achievements = new List<AchievementInfo>();
// Register base NH achievements
NH.WarpDriveAchievement.Init();
NH.MultipleSystemAchievement.Init();
@ -30,8 +39,33 @@ namespace NewHorizons.AchievementsPlus
NH.ProbeLostAchievement.Init();
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)
{
if (!_enabled) return;
@ -45,5 +79,49 @@ namespace NewHorizons.AchievementsPlus
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 RegisterTranslationsFromFiles(ModBehaviour mod, string folderPath);
void EarnAchievement(string uniqueID);
bool HasAchievement(string uniqueID);
}
}

View File

@ -1,3 +1,4 @@
using NewHorizons.AchievementsPlus;
using NewHorizons.Components;
using NewHorizons.External.Modules;
using NewHorizons.Utility;
@ -233,7 +234,7 @@ namespace NewHorizons.Builder.Props
return customName;
}
private static SignalName StringToSignalName(string str)
public static SignalName StringToSignalName(string str)
{
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 UnityEngine;
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)
{
SphereShape newShape = MakeShape(go, info, Shape.CollisionMode.Volume);
OWTriggerVolume newVolume = go.AddComponent<OWTriggerVolume>();
newVolume._shape = newShape;
ShipLogFactListTriggerVolume volume = go.AddComponent<ShipLogFactListTriggerVolume>();
volume._factIDs = info.reveals;
var shape = MakeShape(go, info, Shape.CollisionMode.Volume);
var volume = go.AddComponent<OWTriggerVolume>();
volume._shape = shape;
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)
{
go.layer = LayerMask.NameToLayer("Interactible");
SphereCollider newSphere = go.AddComponent<SphereCollider>();
newSphere.radius = info.radius;
OWCollider newCollider = go.AddComponent<OWCollider>();
ShipLogFactObserveTrigger newObserveTrigger = go.AddComponent<ShipLogFactObserveTrigger>();
newObserveTrigger._factIDs = info.reveals;
newObserveTrigger._maxViewDistance = info.maxDistance == -1f ? 2f : info.maxDistance;
newObserveTrigger._maxViewAngle = info.maxAngle;
newObserveTrigger._owCollider = newCollider;
newObserveTrigger._disableColliderOnRevealFact = true;
var sphere = go.AddComponent<SphereCollider>();
sphere.radius = info.radius;
var collider = go.AddComponent<OWCollider>();
var maxDistance = info.maxDistance == -1f ? 2f : info.maxDistance;
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)
{
SphereShape newShape = MakeShape(go, info, Shape.CollisionMode.Manual);
ShapeVisibilityTracker newTracker = go.AddComponent<ShapeVisibilityTracker>();
newTracker._shapes = new Shape[] { newShape };
ShipLogFactSnapshotTrigger newSnapshotTrigger = go.AddComponent<ShipLogFactSnapshotTrigger>();
newSnapshotTrigger._maxDistance = info.maxDistance == -1f ? 200f : info.maxDistance;
newSnapshotTrigger._factIDs = info.reveals;
var shape = MakeShape(go, info, Shape.CollisionMode.Manual);
var visibilityTracker = go.AddComponent<ShapeVisibilityTracker>();
visibilityTracker._shapes = new Shape[] { shape };
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 Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace NewHorizons.External.Configs
{
[JsonObject]
public class TranslationConfig
{
/// <summary>
@ -21,6 +23,30 @@ namespace NewHorizons.External.Configs
/// </summary>
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)
{
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
/// </summary>
public string[] reveals;
/// <summary>
/// An achievement to unlock. Optional.
/// </summary>
public string achievementID;
}
[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 Newtonsoft.Json;
using NewHorizons.Utility.DebugMenu;
using NewHorizons.AchievementsPlus;
namespace NewHorizons
{
@ -152,7 +153,7 @@ namespace NewHorizons
Instance.ModHelper.Events.Unity.FireOnNextUpdate(() => _firstLoad = false);
Instance.ModHelper.Menus.PauseMenu.OnInit += DebugReload.InitializePauseMenu;
AchievementsPlus.AchievementHandler.Init();
AchievementHandler.Init();
}
public void OnDestroy()
@ -161,6 +162,8 @@ namespace NewHorizons
SceneManager.sceneLoaded -= OnSceneLoaded;
GlobalMessenger<DeathType>.RemoveListener("PlayerDeath", OnDeath);
GlobalMessenger.RemoveListener("WakeUp", new Callback(OnWakeUp));
AchievementHandler.OnDestroy();
}
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\"))
{
LoadTranslations(folder, mod);
}
}
catch (Exception ex)
{
@ -390,6 +401,11 @@ namespace NewHorizons
foundFile = true;
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");

View File

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

View File

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

View File

@ -27,6 +27,9 @@ public static class SchemaExporter
var systemSchema =
new Schema<StarSystemConfig>("Star System Schema", "Schema for a star system in New Horizons", $"{folderName}/star_system_schema", settings);
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 =
new Schema<TranslationConfig>("Translation Schema", "Schema for a translation file in New Horizons", $"{folderName}/translation_schema", settings);
translationSchema.Output();