Initial commit
This commit is contained in:
45
mod/PLibLighting/ILightShape.cs
Normal file
45
mod/PLibLighting/ILightShape.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2022 Peter Han
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
namespace PeterHan.PLib.Lighting {
|
||||
/// <summary>
|
||||
/// An interface describing local and remote instances of PLightShape.
|
||||
/// </summary>
|
||||
public interface ILightShape {
|
||||
/// <summary>
|
||||
/// The light shape identifier.
|
||||
/// </summary>
|
||||
string Identifier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The Klei LightShape represented by this light shape, used in Light2D definitions.
|
||||
/// </summary>
|
||||
LightShape KleiLightShape { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The raycast mode used by this light shape. (-1) if no rays are to be emitted.
|
||||
/// </summary>
|
||||
LightShape RayMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the light handler with the provided light information.
|
||||
/// </summary>
|
||||
/// <param name="args">The arguments passed to the user light handler.</param>
|
||||
void FillLight(LightingArgs args);
|
||||
}
|
||||
}
|
||||
133
mod/PLibLighting/LightingArgs.cs
Normal file
133
mod/PLibLighting/LightingArgs.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2022 Peter Han
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace PeterHan.PLib.Lighting {
|
||||
/// <summary>
|
||||
/// Arguments which are passed to lighting callbacks to perform lighting calculations.
|
||||
///
|
||||
/// The range is the light radius supplied during the Light2D creation; do not light up
|
||||
/// tiles outside of this radius (measured by a square around SourceCell)!
|
||||
///
|
||||
/// The source cell is the cell nearest to where the Light2D is currently located.
|
||||
///
|
||||
/// Use the IDictionary interface to store the relative brightness of cells by their cell
|
||||
/// location. These values should be between 0 and 1 normally, with the maximum brightness
|
||||
/// being set by the intensity parameter of the Light2D. The user is responsible for
|
||||
/// ensuring that cells are valid before lighting them up.
|
||||
/// </summary>
|
||||
public sealed class LightingArgs : EventArgs, IDictionary<int, float> {
|
||||
/// <summary>
|
||||
/// The location where lighting results are stored.
|
||||
/// </summary>
|
||||
public IDictionary<int, float> Brightness { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The maximum range to use for cell lighting. Do not light up cells beyond this
|
||||
/// range from SourceCell.
|
||||
/// </summary>
|
||||
public int Range { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The source of the light.
|
||||
/// </summary>
|
||||
public GameObject Source { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The originating cell. Actual lighting can begin elsewhere, but the range limit is
|
||||
/// measured from this cell.
|
||||
/// </summary>
|
||||
public int SourceCell { get; }
|
||||
|
||||
internal LightingArgs(GameObject source, int cell, int range,
|
||||
IDictionary<int, float> output) {
|
||||
if (source == null)
|
||||
// Cannot use "throw" expression because of UnityEngine.Object.operator==
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
Brightness = output ?? throw new ArgumentNullException(nameof(output));
|
||||
Range = range;
|
||||
Source = source;
|
||||
SourceCell = cell;
|
||||
}
|
||||
|
||||
#region IDictionary
|
||||
|
||||
public ICollection<int> Keys => Brightness.Keys;
|
||||
|
||||
public ICollection<float> Values => Brightness.Values;
|
||||
|
||||
public int Count => Brightness.Count;
|
||||
|
||||
public bool IsReadOnly => Brightness.IsReadOnly;
|
||||
|
||||
public float this[int key] { get => Brightness[key]; set => Brightness[key] = value; }
|
||||
|
||||
public bool ContainsKey(int key) {
|
||||
return Brightness.ContainsKey(key);
|
||||
}
|
||||
|
||||
public void Add(int key, float value) {
|
||||
Brightness.Add(key, value);
|
||||
}
|
||||
|
||||
public bool Remove(int key) {
|
||||
return Brightness.Remove(key);
|
||||
}
|
||||
|
||||
public bool TryGetValue(int key, out float value) {
|
||||
return Brightness.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<int, float> item) {
|
||||
Brightness.Add(item);
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
Brightness.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<int, float> item) {
|
||||
return Brightness.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<int, float>[] array, int arrayIndex) {
|
||||
Brightness.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<int, float> item) {
|
||||
return Brightness.Remove(item);
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<int, float>> GetEnumerator() {
|
||||
return Brightness.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
return Brightness.GetEnumerator();
|
||||
}
|
||||
#endregion
|
||||
|
||||
public override string ToString() {
|
||||
return string.Format("LightingArgs[source={0:D},range={1:D}]", SourceCell, Range);
|
||||
}
|
||||
}
|
||||
}
|
||||
170
mod/PLibLighting/LightingPatches.cs
Normal file
170
mod/PLibLighting/LightingPatches.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright 2022 Peter Han
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
using HarmonyLib;
|
||||
using PeterHan.PLib.Core;
|
||||
using PeterHan.PLib.Detours;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
using IntHandle = HandleVector<int>.Handle;
|
||||
using LightGridEmitter = LightGridManager.LightGridEmitter;
|
||||
using TranspiledMethod = System.Collections.Generic.IEnumerable<HarmonyLib.CodeInstruction>;
|
||||
|
||||
namespace PeterHan.PLib.Lighting {
|
||||
/// <summary>
|
||||
/// Contains all patches (many!) required by the PLib Lighting subsystem. Only applied by
|
||||
/// the latest version of PLightManager.
|
||||
/// </summary>
|
||||
internal static class LightingPatches {
|
||||
private delegate IntHandle AddToLayerDelegate(Light2D instance, Extents ext,
|
||||
ScenePartitionerLayer layer);
|
||||
|
||||
private static readonly DetouredMethod<AddToLayerDelegate> ADD_TO_LAYER =
|
||||
typeof(Light2D).DetourLazy<AddToLayerDelegate>("AddToLayer");
|
||||
|
||||
private static readonly IDetouredField<Light2D, int> ORIGIN = PDetours.
|
||||
DetourFieldLazy<Light2D, int>("origin");
|
||||
|
||||
private static readonly IDetouredField<LightShapePreview, int> PREVIOUS_CELL =
|
||||
PDetours.DetourFieldLazy<LightShapePreview, int>("previousCell");
|
||||
|
||||
private static bool ComputeExtents_Prefix(Light2D __instance, ref Extents __result) {
|
||||
var lm = PLightManager.Instance;
|
||||
bool cont = true;
|
||||
if (lm != null && __instance != null) {
|
||||
var shape = __instance.shape;
|
||||
int rad = Mathf.CeilToInt(__instance.Range), cell;
|
||||
lm.AddLight(__instance.emitter, __instance.gameObject);
|
||||
if (shape > LightShape.Cone && rad > 0 && Grid.IsValidCell(cell = ORIGIN.Get(
|
||||
__instance))) {
|
||||
Grid.CellToXY(cell, out int x, out int y);
|
||||
// Better safe than sorry, check whole possible radius
|
||||
__result = new Extents(x - rad, y - rad, 2 * rad, 2 * rad);
|
||||
cont = false;
|
||||
}
|
||||
}
|
||||
return cont;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the required lighting related patches.
|
||||
/// </summary>
|
||||
/// <param name="plibInstance">The Harmony instance to use for patching.</param>
|
||||
public static void ApplyPatches(Harmony plibInstance) {
|
||||
// Light2D
|
||||
plibInstance.Patch(typeof(Light2D), "ComputeExtents",
|
||||
prefix: PatchMethod(nameof(ComputeExtents_Prefix)));
|
||||
plibInstance.Patch(typeof(Light2D), nameof(Light2D.FullRemove), postfix:
|
||||
PatchMethod(nameof(Light2D_FullRemove_Postfix)));
|
||||
plibInstance.Patch(typeof(Light2D), nameof(Light2D.RefreshShapeAndPosition),
|
||||
postfix: PatchMethod(nameof(Light2D_RefreshShapeAndPosition_Postfix)));
|
||||
|
||||
// LightBuffer
|
||||
try {
|
||||
plibInstance.PatchTranspile(typeof(LightBuffer), "LateUpdate", PatchMethod(
|
||||
nameof(LightBuffer_LateUpdate_Transpile)));
|
||||
} catch (Exception e) {
|
||||
// Only visual, log as warning
|
||||
PUtil.LogExcWarn(e);
|
||||
}
|
||||
|
||||
// LightGridEmitter
|
||||
plibInstance.Patch(typeof(LightGridEmitter), "ComputeLux", prefix:
|
||||
PatchMethod(nameof(ComputeLux_Prefix)));
|
||||
plibInstance.Patch(typeof(LightGridEmitter), nameof(LightGridEmitter.
|
||||
UpdateLitCells), prefix: PatchMethod(nameof(UpdateLitCells_Prefix)));
|
||||
|
||||
// LightGridManager
|
||||
plibInstance.Patch(typeof(LightGridManager), nameof(LightGridManager.
|
||||
CreatePreview), prefix: PatchMethod(nameof(CreatePreview_Prefix)));
|
||||
|
||||
// LightShapePreview
|
||||
plibInstance.Patch(typeof(LightShapePreview), "Update", prefix:
|
||||
PatchMethod(nameof(LightShapePreview_Update_Prefix)));
|
||||
|
||||
// Rotatable
|
||||
plibInstance.Patch(typeof(Rotatable), "OrientVisualizer", postfix:
|
||||
PatchMethod(nameof(OrientVisualizer_Postfix)));
|
||||
}
|
||||
|
||||
private static bool ComputeLux_Prefix(LightGridEmitter __instance, int cell,
|
||||
LightGridEmitter.State ___state, ref int __result) {
|
||||
var lm = PLightManager.Instance;
|
||||
return lm == null || !lm.GetBrightness(__instance, cell, ___state, out __result);
|
||||
}
|
||||
|
||||
private static bool CreatePreview_Prefix(int origin_cell, float radius,
|
||||
LightShape shape, int lux) {
|
||||
var lm = PLightManager.Instance;
|
||||
return lm == null || !lm.PreviewLight(origin_cell, radius, shape, lux);
|
||||
}
|
||||
|
||||
private static void Light2D_FullRemove_Postfix(Light2D __instance) {
|
||||
var lm = PLightManager.Instance;
|
||||
if (lm != null && __instance != null)
|
||||
lm.DestroyLight(__instance.emitter);
|
||||
}
|
||||
|
||||
private static void Light2D_RefreshShapeAndPosition_Postfix(Light2D __instance,
|
||||
Light2D.RefreshResult __result) {
|
||||
var lm = PLightManager.Instance;
|
||||
if (lm != null && __instance != null && __result == Light2D.RefreshResult.Updated)
|
||||
lm.AddLight(__instance.emitter, __instance.gameObject);
|
||||
}
|
||||
|
||||
private static TranspiledMethod LightBuffer_LateUpdate_Transpile(
|
||||
TranspiledMethod body) {
|
||||
var target = typeof(Light2D).GetPropertySafe<LightShape>(nameof(Light2D.shape),
|
||||
false)?.GetGetMethod(true);
|
||||
return (target == null) ? body : PPatchTools.ReplaceMethodCallSafe(body, target,
|
||||
typeof(PLightManager).GetMethodSafe(nameof(PLightManager.LightShapeToRayShape),
|
||||
true, typeof(Light2D)));
|
||||
}
|
||||
|
||||
private static void LightShapePreview_Update_Prefix(LightShapePreview __instance) {
|
||||
var lm = PLightManager.Instance;
|
||||
// Pass the preview object into LightGridManager
|
||||
if (lm != null && __instance != null)
|
||||
lm.PreviewObject = __instance.gameObject;
|
||||
}
|
||||
|
||||
private static void OrientVisualizer_Postfix(Rotatable __instance) {
|
||||
// Force regeneration on next Update()
|
||||
if (__instance != null && __instance.TryGetComponent(out LightShapePreview
|
||||
preview))
|
||||
PREVIOUS_CELL.Set(preview, -1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a HarmonyMethod instance for manual patching using a method from this class.
|
||||
/// </summary>
|
||||
/// <param name="name">The method name.</param>
|
||||
/// <returns>A reference to that method as a HarmonyMethod for patching.</returns>
|
||||
private static HarmonyMethod PatchMethod(string name) {
|
||||
return new HarmonyMethod(typeof(LightingPatches), name);
|
||||
}
|
||||
|
||||
private static bool UpdateLitCells_Prefix(LightGridEmitter __instance,
|
||||
List<int> ___litCells, LightGridEmitter.State ___state) {
|
||||
var lm = PLightManager.Instance;
|
||||
return lm == null || !lm.UpdateLitCells(__instance, ___state, ___litCells);
|
||||
}
|
||||
}
|
||||
}
|
||||
116
mod/PLibLighting/OctantBuilder.cs
Normal file
116
mod/PLibLighting/OctantBuilder.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2022 Peter Han
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
using PeterHan.PLib.Core;
|
||||
using PeterHan.PLib.Detours;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BrightnessDict = System.Collections.Generic.IDictionary<int, float>;
|
||||
using Octant = DiscreteShadowCaster.Octant;
|
||||
|
||||
namespace PeterHan.PLib.Lighting {
|
||||
/// <summary>
|
||||
/// A builder class which creates default light patterns based on octants.
|
||||
/// </summary>
|
||||
public sealed class OctantBuilder {
|
||||
/// <summary>
|
||||
/// The delegate type called to run the default DiscreteShadowCaster.ScanOctant.
|
||||
/// </summary>
|
||||
private delegate void ScanOctantFunc(Vector2I cellPos, int range, int depth,
|
||||
Octant octant, double startSlope, double endSlope, List<int> visiblePoints);
|
||||
|
||||
/// <summary>
|
||||
/// The method to call to scan octants.
|
||||
/// </summary>
|
||||
private static readonly ScanOctantFunc OCTANT_SCAN;
|
||||
|
||||
static OctantBuilder() {
|
||||
// Cache the method for faster execution
|
||||
OCTANT_SCAN = typeof(DiscreteShadowCaster).Detour<ScanOctantFunc>("ScanOctant");
|
||||
if (OCTANT_SCAN == null)
|
||||
PLightManager.LogLightingWarning("OctantBuilder cannot find default octant scanner!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The fallout to use when building the light.
|
||||
/// </summary>
|
||||
public float Falloff { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If false, uses the default game smoothing. If true, uses better smoothing.
|
||||
/// </summary>
|
||||
public bool SmoothLight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The origin cell.
|
||||
/// </summary>
|
||||
public int SourceCell { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The location where light cells are added.
|
||||
/// </summary>
|
||||
private readonly BrightnessDict destination;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new octant builder.
|
||||
/// </summary>
|
||||
/// <param name="destination">The location where the lit cells will be placed.</param>
|
||||
/// <param name="sourceCell">The origin cell of the light.</param>
|
||||
public OctantBuilder(BrightnessDict destination, int sourceCell) {
|
||||
if (!Grid.IsValidCell(sourceCell))
|
||||
throw new ArgumentOutOfRangeException(nameof(sourceCell));
|
||||
this.destination = destination ?? throw new ArgumentNullException(nameof(
|
||||
destination));
|
||||
destination[sourceCell] = 1.0f;
|
||||
Falloff = 0.5f;
|
||||
// Use the default game's light algorithm
|
||||
SmoothLight = false;
|
||||
SourceCell = sourceCell;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an octant of light.
|
||||
/// </summary>
|
||||
/// <param name="range">The range of the light.</param>
|
||||
/// <param name="octant">The octant to scan.</param>
|
||||
/// <returns>This object, for call chaining.</returns>
|
||||
public OctantBuilder AddOctant(int range, Octant octant) {
|
||||
var points = ListPool<int, OctantBuilder>.Allocate();
|
||||
OCTANT_SCAN?.Invoke(Grid.CellToXY(SourceCell), range, 1, octant, 1.0, 0.0, points);
|
||||
// Transfer to our array using:
|
||||
foreach (int cell in points) {
|
||||
float intensity;
|
||||
if (SmoothLight)
|
||||
// Better, not rounded falloff
|
||||
intensity = PLightManager.GetSmoothFalloff(Falloff, cell, SourceCell);
|
||||
else
|
||||
// Default falloff
|
||||
intensity = PLightManager.GetDefaultFalloff(Falloff, cell, SourceCell);
|
||||
destination[cell] = intensity;
|
||||
}
|
||||
points.Recycle();
|
||||
return this;
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return string.Format("OctantBuilder[Cell {0:D}, {1:D} lit]", SourceCell,
|
||||
destination.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
mod/PLibLighting/PLibLighting.csproj
Normal file
20
mod/PLibLighting/PLibLighting.csproj
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Title>PLib Lighting</Title>
|
||||
<AssemblyTitle>PLib.Lighting</AssemblyTitle>
|
||||
<Version>4.11.0.0</Version>
|
||||
<UsesPLib>false</UsesPLib>
|
||||
<RootNamespace>PeterHan.PLib.Lighting</RootNamespace>
|
||||
<AssemblyVersion>4.11.0.0</AssemblyVersion>
|
||||
<DistributeMod>false</DistributeMod>
|
||||
<PLibCore>true</PLibCore>
|
||||
<Platforms>Vanilla;Mergedown</Platforms>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DocumentationFile>bin\$(Platform)\Release\PLibLighting.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../PLibCore/PLibCore.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
378
mod/PLibLighting/PLightManager.cs
Normal file
378
mod/PLibLighting/PLightManager.cs
Normal file
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
* Copyright 2022 Peter Han
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
using HarmonyLib;
|
||||
using PeterHan.PLib.Core;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
using BrightnessDict = System.Collections.Generic.IDictionary<int, float>;
|
||||
using LightGridEmitter = LightGridManager.LightGridEmitter;
|
||||
|
||||
namespace PeterHan.PLib.Lighting {
|
||||
/// <summary>
|
||||
/// Manages lighting. Instantiated only by the latest PLib version.
|
||||
/// </summary>
|
||||
public sealed class PLightManager : PForwardedComponent {
|
||||
/// <summary>
|
||||
/// Implemented by classes which want to handle lighting calls.
|
||||
/// </summary>
|
||||
/// <param name="args">The parameters to use for lighting, and the location to
|
||||
/// store results. See the LightingArgs class documentation for details.</param>
|
||||
public delegate void CastLightDelegate(LightingArgs args);
|
||||
|
||||
/// <summary>
|
||||
/// A singleton empty list instance for default values.
|
||||
/// </summary>
|
||||
private static readonly List<object> EMPTY_SHAPES = new List<object>(1);
|
||||
|
||||
/// <summary>
|
||||
/// The version of this component. Uses the running PLib version.
|
||||
/// </summary>
|
||||
internal static readonly Version VERSION = new Version(PVersion.VERSION);
|
||||
|
||||
/// <summary>
|
||||
/// If true, enables the smooth light falloff mode even on vanilla lights.
|
||||
/// </summary>
|
||||
internal static bool ForceSmoothLight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The instantiated copy of this class.
|
||||
/// </summary>
|
||||
internal static PLightManager Instance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the brightness falloff as it would be in the stock game.
|
||||
/// </summary>
|
||||
/// <param name="falloffRate">The falloff rate to use.</param>
|
||||
/// <param name="cell">The cell where falloff is being computed.</param>
|
||||
/// <param name="origin">The light origin cell.</param>
|
||||
/// <returns>The brightness at that location from 0 to 1.</returns>
|
||||
public static float GetDefaultFalloff(float falloffRate, int cell, int origin) {
|
||||
return 1.0f / Math.Max(1.0f, Mathf.RoundToInt(falloffRate * Math.Max(Grid.
|
||||
GetCellDistance(origin, cell), 1)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the brightness falloff similar to the default falloff, but far smoother.
|
||||
/// Slightly heavier on computation however.
|
||||
/// </summary>
|
||||
/// <param name="falloffRate">The falloff rate to use.</param>
|
||||
/// <param name="cell">The cell where falloff is being computed.</param>
|
||||
/// <param name="origin">The light origin cell.</param>
|
||||
/// <returns>The brightness at that location from 0 to 1.</returns>
|
||||
public static float GetSmoothFalloff(float falloffRate, int cell, int origin) {
|
||||
Vector2I newCell = Grid.CellToXY(cell), start = Grid.CellToXY(origin);
|
||||
return 1.0f / Math.Max(1.0f, falloffRate * PUtil.Distance(start.X, start.Y,
|
||||
newCell.X, newCell.Y));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raycasting shape to use for the given light.
|
||||
/// </summary>
|
||||
/// <param name="light">The light which is being drawn.</param>
|
||||
/// <returns>The shape to use for its rays.</returns>
|
||||
internal static LightShape LightShapeToRayShape(Light2D light) {
|
||||
var shape = light.shape;
|
||||
if (shape != LightShape.Cone && shape != LightShape.Circle)
|
||||
shape = Instance.GetRayShape(shape);
|
||||
return shape;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a message encountered by the PLib lighting system.
|
||||
/// </summary>
|
||||
/// <param name="message">The debug message.</param>
|
||||
internal static void LogLightingDebug(string message) {
|
||||
Debug.LogFormat("[PLibLighting] {0}", message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a warning encountered by the PLib lighting system.
|
||||
/// </summary>
|
||||
/// <param name="message">The warning message.</param>
|
||||
internal static void LogLightingWarning(string message) {
|
||||
Debug.LogWarningFormat("[PLibLighting] {0}", message);
|
||||
}
|
||||
|
||||
public override Version Version => VERSION;
|
||||
|
||||
/// <summary>
|
||||
/// The light brightness set by the last lighting brightness request.
|
||||
/// </summary>
|
||||
private readonly ConcurrentDictionary<LightGridEmitter, CacheEntry> brightCache;
|
||||
|
||||
/// <summary>
|
||||
/// The last object that requested a preview. Only one preview can be requested at a
|
||||
/// time, so no need for thread safety.
|
||||
/// </summary>
|
||||
internal GameObject PreviewObject { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The lighting shapes available, all in this mod's namespace.
|
||||
/// </summary>
|
||||
private readonly IList<ILightShape> shapes;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a lighting manager to register PLib lighting.
|
||||
/// </summary>
|
||||
public PLightManager() {
|
||||
// Needs to be thread safe!
|
||||
brightCache = new ConcurrentDictionary<LightGridEmitter, CacheEntry>(2, 128);
|
||||
PreviewObject = null;
|
||||
shapes = new List<ILightShape>(16);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a light to the lookup table.
|
||||
/// </summary>
|
||||
/// <param name="source">The source of the light.</param>
|
||||
/// <param name="owner">The light's owning game object.</param>
|
||||
internal void AddLight(LightGridEmitter source, GameObject owner) {
|
||||
if (owner == null)
|
||||
throw new ArgumentNullException(nameof(owner));
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
// The default equality comparer will be used; since each Light2D is supposed
|
||||
// to have exactly one LightGridEmitter, this should be fine
|
||||
brightCache.TryAdd(source, new CacheEntry(owner));
|
||||
}
|
||||
|
||||
public override void Bootstrap(Harmony plibInstance) {
|
||||
SetSharedData(new List<object>(16));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends a call to lighting update initiated by CreateLight.
|
||||
/// </summary>
|
||||
/// <param name="source">The source of the light.</param>
|
||||
internal void DestroyLight(LightGridEmitter source) {
|
||||
if (source != null)
|
||||
brightCache.TryRemove(source, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the brightness at a given cell for the specified light source.
|
||||
/// </summary>
|
||||
/// <param name="source">The source of the light.</param>
|
||||
/// <param name="location">The location to check.</param>
|
||||
/// <param name="state">The lighting state.</param>
|
||||
/// <param name="result">The brightness there.</param>
|
||||
/// <returns>true if that brightness is valid, or false otherwise.</returns>
|
||||
internal bool GetBrightness(LightGridEmitter source, int location,
|
||||
LightGridEmitter.State state, out int result) {
|
||||
bool valid;
|
||||
var shape = state.shape;
|
||||
if (shape != LightShape.Cone && shape != LightShape.Circle) {
|
||||
valid = brightCache.TryGetValue(source, out CacheEntry cacheEntry);
|
||||
if (valid) {
|
||||
valid = cacheEntry.Intensity.TryGetValue(location, out float ratio);
|
||||
if (valid)
|
||||
result = Mathf.RoundToInt(cacheEntry.BaseLux * ratio);
|
||||
else {
|
||||
#if DEBUG
|
||||
LogLightingDebug("GetBrightness for invalid cell at {0:D}".F(location));
|
||||
#endif
|
||||
result = 0;
|
||||
}
|
||||
} else {
|
||||
#if DEBUG
|
||||
LogLightingDebug("GetBrightness for invalid emitter at {0:D}".F(location));
|
||||
#endif
|
||||
result = 0;
|
||||
}
|
||||
} else if (ForceSmoothLight) {
|
||||
// Use smooth light even for vanilla Cone and Circle
|
||||
result = Mathf.RoundToInt(state.intensity * GetSmoothFalloff(state.falloffRate,
|
||||
location, state.origin));
|
||||
valid = true;
|
||||
} else {
|
||||
// Stock
|
||||
result = 0;
|
||||
valid = false;
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if a light has specified one of the built-in ray options to cast
|
||||
/// the little yellow rays around it.
|
||||
/// </summary>
|
||||
/// <param name="shape">The light shape to check.</param>
|
||||
/// <returns>The light shape to use for ray casting, or the original shape if it is
|
||||
/// a stock shape or a light shape not known to PLib Lighting.</returns>
|
||||
internal LightShape GetRayShape(LightShape shape) {
|
||||
int index = shape - LightShape.Cone - 1;
|
||||
ILightShape ps;
|
||||
if (index >= 0 && index < shapes.Count && (ps = shapes[index]) != null) {
|
||||
var newShape = ps.RayMode;
|
||||
if (newShape >= LightShape.Circle)
|
||||
shape = newShape;
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
|
||||
public override void Initialize(Harmony plibInstance) {
|
||||
Instance = this;
|
||||
|
||||
shapes.Clear();
|
||||
foreach (var light in GetSharedData(EMPTY_SHAPES)) {
|
||||
var ls = PRemoteLightWrapper.LightToInstance(light);
|
||||
shapes.Add(ls);
|
||||
if (ls == null)
|
||||
// Moe must clean it!
|
||||
LogLightingWarning("Foreign contaminant in PLightManager!");
|
||||
}
|
||||
|
||||
LightingPatches.ApplyPatches(plibInstance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the preview for a given light.
|
||||
/// </summary>
|
||||
/// <param name="origin">The starting cell.</param>
|
||||
/// <param name="radius">The light radius.</param>
|
||||
/// <param name="shape">The light shape.</param>
|
||||
/// <param name="lux">The base brightness in lux.</param>
|
||||
/// <returns>true if the lighting was handled, or false otherwise.</returns>
|
||||
internal bool PreviewLight(int origin, float radius, LightShape shape, int lux) {
|
||||
bool handled = false;
|
||||
var owner = PreviewObject;
|
||||
int index = shape - LightShape.Cone - 1;
|
||||
if (index >= 0 && index < shapes.Count && owner != null) {
|
||||
var cells = DictionaryPool<int, float, PLightManager>.Allocate();
|
||||
// Found handler!
|
||||
shapes[index]?.FillLight(new LightingArgs(owner, origin,
|
||||
(int)radius, cells));
|
||||
foreach (var pair in cells) {
|
||||
int cell = pair.Key;
|
||||
if (Grid.IsValidCell(cell)) {
|
||||
// Allow any fraction, not just linear falloff
|
||||
int lightValue = Mathf.RoundToInt(lux * pair.Value);
|
||||
LightGridManager.previewLightCells.Add(new Tuple<int, int>(cell,
|
||||
lightValue));
|
||||
LightGridManager.previewLux[cell] = lightValue;
|
||||
}
|
||||
}
|
||||
PreviewObject = null;
|
||||
handled = true;
|
||||
cells.Recycle();
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a light shape handler.
|
||||
/// </summary>
|
||||
/// <param name="identifier">A unique identifier for this shape. If another mod has
|
||||
/// already registered that identifier, the previous mod will take precedence.</param>
|
||||
/// <param name="handler">The handler for that shape.</param>
|
||||
/// <param name="rayMode">The type of visual rays that are displayed from the light.</param>
|
||||
/// <returns>The light shape which can be used.</returns>
|
||||
public ILightShape Register(string identifier, CastLightDelegate handler,
|
||||
LightShape rayMode = (LightShape)(-1)) {
|
||||
if (string.IsNullOrEmpty(identifier))
|
||||
throw new ArgumentNullException(nameof(identifier));
|
||||
if (handler == null)
|
||||
throw new ArgumentNullException(nameof(handler));
|
||||
ILightShape lightShape = null;
|
||||
RegisterForForwarding();
|
||||
// Try to find a match for this identifier
|
||||
var registered = GetSharedData(EMPTY_SHAPES);
|
||||
int n = registered.Count;
|
||||
foreach (var obj in registered) {
|
||||
var light = PRemoteLightWrapper.LightToInstance(obj);
|
||||
// Might be from another assembly so the types may or may not be compatible
|
||||
if (light != null && light.Identifier == identifier) {
|
||||
LogLightingDebug("Found existing light shape: " + identifier + " from " +
|
||||
(obj.GetType().Assembly.GetNameSafe() ?? "?"));
|
||||
lightShape = light;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (lightShape == null) {
|
||||
// Not currently existing
|
||||
lightShape = new PLightShape(n + 1, identifier, handler, rayMode);
|
||||
LogLightingDebug("Registered new light shape: " + identifier);
|
||||
registered.Add(lightShape);
|
||||
}
|
||||
return lightShape;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the lit cells list.
|
||||
/// </summary>
|
||||
/// <param name="source">The source of the light.</param>
|
||||
/// <param name="state">The light emitter state.</param>
|
||||
/// <param name="litCells">The location where lit cells will be placed.</param>
|
||||
/// <returns>true if the lighting was handled, or false otherwise.</returns>
|
||||
internal bool UpdateLitCells(LightGridEmitter source, LightGridEmitter.State state,
|
||||
IList<int> litCells) {
|
||||
bool handled = false;
|
||||
int index = state.shape - LightShape.Cone - 1;
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
if (index >= 0 && index < shapes.Count && litCells != null && brightCache.
|
||||
TryGetValue(source, out CacheEntry entry)) {
|
||||
var ps = shapes[index];
|
||||
var brightness = entry.Intensity;
|
||||
// Proper owner found
|
||||
brightness.Clear();
|
||||
entry.BaseLux = state.intensity;
|
||||
ps.FillLight(new LightingArgs(entry.Owner, state.origin, (int)state.
|
||||
radius, brightness));
|
||||
foreach (var point in brightness)
|
||||
litCells.Add(point.Key);
|
||||
handled = true;
|
||||
}
|
||||
return handled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A cache entry in the light brightness cache.
|
||||
/// </summary>
|
||||
private sealed class CacheEntry {
|
||||
/// <summary>
|
||||
/// The base intensity in lux.
|
||||
/// </summary>
|
||||
internal int BaseLux { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The relative brightness per cell.
|
||||
/// </summary>
|
||||
internal BrightnessDict Intensity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The owner which initiated the lighting call.
|
||||
/// </summary>
|
||||
internal GameObject Owner { get; }
|
||||
|
||||
internal CacheEntry(GameObject owner) {
|
||||
// Do not use the pool because these might last a long time and be numerous
|
||||
Intensity = new Dictionary<int, float>(64);
|
||||
Owner = owner;
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return "Lighting Cache Entry for " + (Owner == null ? "" : Owner.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
85
mod/PLibLighting/PLightShape.cs
Normal file
85
mod/PLibLighting/PLightShape.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2022 Peter Han
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
using BrightnessDict = System.Collections.Generic.IDictionary<int, float>;
|
||||
|
||||
namespace PeterHan.PLib.Lighting {
|
||||
/// <summary>
|
||||
/// Represents a light shape which can be used by mods.
|
||||
/// </summary>
|
||||
internal sealed class PLightShape : ILightShape {
|
||||
/// <summary>
|
||||
/// The handler for this light shape.
|
||||
/// </summary>
|
||||
private readonly PLightManager.CastLightDelegate handler;
|
||||
|
||||
public string Identifier { get; }
|
||||
|
||||
public LightShape KleiLightShape {
|
||||
get {
|
||||
return ShapeID + LightShape.Cone;
|
||||
}
|
||||
}
|
||||
|
||||
public LightShape RayMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The light shape ID.
|
||||
/// </summary>
|
||||
internal int ShapeID { get; }
|
||||
|
||||
internal PLightShape(int id, string identifier, PLightManager.CastLightDelegate handler,
|
||||
LightShape rayMode) {
|
||||
this.handler = handler ?? throw new ArgumentNullException(nameof(handler));
|
||||
Identifier = identifier ?? throw new ArgumentNullException(nameof(identifier));
|
||||
RayMode = rayMode;
|
||||
ShapeID = id;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) {
|
||||
return obj is PLightShape other && other.Identifier == Identifier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the light handler with the provided light information.
|
||||
/// </summary>
|
||||
/// <param name="source">The source of the light.</param>
|
||||
/// <param name="cell">The origin cell.</param>
|
||||
/// <param name="range">The range to fill.</param>
|
||||
/// <param name="brightness">The location where lit points will be stored.</param>
|
||||
internal void DoFillLight(GameObject source, int cell, int range,
|
||||
BrightnessDict brightness) {
|
||||
handler.Invoke(new LightingArgs(source, cell, range, brightness));
|
||||
}
|
||||
|
||||
public void FillLight(LightingArgs args) {
|
||||
handler.Invoke(args);
|
||||
}
|
||||
|
||||
public override int GetHashCode() {
|
||||
return Identifier.GetHashCode();
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return "PLightShape[ID=" + Identifier + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
83
mod/PLibLighting/PRemoteLightWrapper.cs
Normal file
83
mod/PLibLighting/PRemoteLightWrapper.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2022 Peter Han
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
* and associated documentation files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or
|
||||
* substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
* BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
using PeterHan.PLib.Core;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
using BrightnessDict = System.Collections.Generic.IDictionary<int, float>;
|
||||
|
||||
namespace PeterHan.PLib.Lighting {
|
||||
/// <summary>
|
||||
/// Wraps a lighting system call from another mod's namespace.
|
||||
/// </summary>
|
||||
internal sealed class PRemoteLightWrapper : ILightShape {
|
||||
// The delegate type covering calls to FillLight from other mods.
|
||||
private delegate void FillLightDelegate(GameObject source, int cell, int range,
|
||||
BrightnessDict brightness);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a light shape instance from another mod.
|
||||
/// </summary>
|
||||
/// <param name="other">The object to convert.</param>
|
||||
/// <returns>A light shape object in this mod's namespace that delegates lighting
|
||||
/// calls to the other mod if necessary.</returns>
|
||||
internal static ILightShape LightToInstance(object other) {
|
||||
return (other == null || other.GetType().Name != nameof(PLightShape)) ? null :
|
||||
((other is ILightShape ls) ? ls : new PRemoteLightWrapper(other));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The method to call when lighting system handling is requested.
|
||||
/// </summary>
|
||||
private readonly FillLightDelegate fillLight;
|
||||
|
||||
public string Identifier { get; }
|
||||
|
||||
public LightShape KleiLightShape { get; }
|
||||
|
||||
public LightShape RayMode { get; }
|
||||
|
||||
internal PRemoteLightWrapper(object other) {
|
||||
if (other == null)
|
||||
throw new ArgumentNullException(nameof(other));
|
||||
if (!PPatchTools.TryGetPropertyValue(other, nameof(ILightShape.KleiLightShape),
|
||||
out LightShape ls))
|
||||
throw new ArgumentException("Light shape is missing KleiLightShape");
|
||||
KleiLightShape = ls;
|
||||
if (!PPatchTools.TryGetPropertyValue(other, nameof(ILightShape.Identifier),
|
||||
out string id) || id == null)
|
||||
throw new ArgumentException("Light shape is missing Identifier");
|
||||
Identifier = id;
|
||||
if (!PPatchTools.TryGetPropertyValue(other, nameof(ILightShape.RayMode),
|
||||
out LightShape rm))
|
||||
rm = (LightShape)(-1);
|
||||
RayMode = rm;
|
||||
var otherType = other.GetType();
|
||||
fillLight = otherType.CreateDelegate<FillLightDelegate>(nameof(PLightShape.
|
||||
DoFillLight), other, typeof(GameObject), typeof(int), typeof(int),
|
||||
typeof(BrightnessDict));
|
||||
if (fillLight == null)
|
||||
throw new ArgumentException("Light shape is missing FillLight");
|
||||
}
|
||||
|
||||
public void FillLight(LightingArgs args) {
|
||||
fillLight.Invoke(args.Source, args.SourceCell, args.Range, args.Brightness);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user