Upload from upload_mods.ps1

This commit is contained in:
Nathaniel Cosford
2025-06-04 16:44:53 +09:30
commit f1fbbe67bb
1722 changed files with 165268 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
namespace Features.Fire.Harmony
{
[HarmonyPatch(typeof(Chunk))]
[HarmonyPatch("SetBlock")]
public class ChunkSetBlock
{
public static void Postfix(Chunk __instance, int ___m_X, int ___m_Z, int x, int y, int z, bool _fromReset)
{
if (!_fromReset) return;
// If the POI is being reset, clear the fire.
var vector3I = new Vector3i((___m_X << 4) + x, y, (___m_Z << 4) + z);
var fireMap = FireManager.Instance?.GetFireMap();
if (fireMap == null) return;
if (fireMap.ContainsKey(vector3I))
{
FireManager.Instance?.RemoveFire(vector3I);
}
}
}
}

View File

@@ -0,0 +1,43 @@
namespace Features.Fire.Harmony
{
// Allows the spread of the particles to catch things on fire.
[HarmonyPatch(typeof(Explosion))]
[HarmonyPatch("AttackBlocks")]
public class SCoreExplosionattackBlocks
{
public static void Postfix(Explosion __instance, int _entityThatCausedExplosion, ExplosionData ___explosionData)
{
if (FireManager.Instance == null) return;
if (FireManager.Instance.Enabled == false) return;
// BlockDamage set to 0 does nothing.
if (___explosionData.BlockDamage == 0) return;
if (___explosionData.ParticleIndex == 0) return;
var entityAlive = GameManager.Instance.World.GetEntity(_entityThatCausedExplosion) as EntityAlive;
if (entityAlive != null)
{
if (entityAlive.EntityClass.Properties.Contains("SpreadFire"))
{
if (entityAlive.EntityClass.Properties.GetBool("SpreadFire") == false)
return;
}
if (entityAlive.Buffs.HasCustomVar("SpreadFire"))
{
var spreadFire = entityAlive.Buffs.GetCustomVar("SpreadFire");
if (spreadFire == -1f) return;
}
}
foreach (var position in __instance.ChangedBlockPositions)
{
// Negative block damages extinguishes
if (___explosionData.BlockDamage < 0f)
FireManager.Instance.Extinguish(position.Key);
else
FireManager.Instance.Add(position.Key);
}
}
}
}

View File

@@ -0,0 +1,28 @@
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace Features.Fire.Harmony
{
// Allows the spread of the particles to catch things on fire.
/*[HarmonyPatch(typeof(GameStateManager))]
[HarmonyPatch("StartGame")]
public class GameStateManagerStartGame
{
public static void Postfix()
{
if (GamePrefs.GetString(EnumGamePrefs.GameWorld) == "Empty"
|| GamePrefs.GetString(EnumGamePrefs.GameWorld) == "Playtesting"
|| GamePrefs.GetString(EnumGamePrefs.GameMode) == "GameModeEditWorld")
{
Debug.Log("Disabling Fire Manager in Play Testing / Prefab editor");
return;
}
FireManager.Init();
}
}*/
}

View File

@@ -0,0 +1,34 @@
namespace Features.Fire.Harmony
{
// Light reduction patch from ocbMaurice
[HarmonyPatch(typeof(ParticleEffect))]
[HarmonyPatch("SpawnParticleEffect")]
public class ParticleEffectspawnParticleEffect
{
private const int Odds = 4;
private static int _count = Odds;
public static void Postfix(ref Transform __result)
{
if (__result == null) return;
bool optionFireManager = CustomGameOptions.GetBool("CustomFireManager");
if (!optionFireManager) return;
if (__result.GetComponentInChildren<Light>() is not Light light) return;
if (_count == Odds)
{
_count = 0;
return;
}
light.enabled = false;
var go = light.gameObject;
//Log.Out($"SpawnParticleEffect - Destroy Light gameObject: {go}, parent: {go.transform.parent}");
go.transform.parent = null;
UnityEngine.Object.Destroy(go);
_count += 1;
}
}
}

View File

@@ -0,0 +1,27 @@
namespace Features.Fire.Harmony
{
[HarmonyPatch(typeof(Block))]
[HarmonyPatch("OnEntityWalking")]
public class BlockOnEntityWalking
{
public static void Postfix(int _x, int _y, int _z, Entity entity)
{
if (FireManager.Instance == null) return;
var blockPosition = new Vector3i(_x, _y, _z);
if (!FireManager.IsBurning(blockPosition)) return;
if (!GameManager.IsDedicatedServer)
if (!GameManager.Instance.HasBlockParticleEffect(blockPosition)) return;
if (entity is not EntityAlive entityAlive) return;
var buff = "buffBurningMolotov";
if (!string.IsNullOrEmpty(buff))
{
entityAlive.Buffs.AddBuff(buff, -1, entityAlive.isEntityRemote);
}
}
}
}

259
Score/Fire/Readme.md Normal file
View File

@@ -0,0 +1,259 @@
Fire Manager
---
The Fire Manager introduces fire mechanics into the game. This includes starting, spreading and extinguishing fires, and
allowing blocks to explode.
# Table of Contents
1. [Summary](#summary)
2. [Fire Manage Global Configuration](#fire-manager-global-configuration)
3. [Block-Specific Configuration](#block-specific-configuration)
4. [Examples](#examples)
## Summary
A block may catch on fire through a variety of means, depending on the mod in question. For example, a thrown molotov would certainly catch
blocks on fire, potentially triggering a cascading fire that may burn a building down.
When a block is on fire, it will have damage dealt to it over time, using the CheckInterval time.
When a block is checked during its CheckInterval time, neighboring blocks may catch on fire if they are flammable. A FireSpread property can disable this.
When a block has taken more damage than its max damage, the block will downgrade itself to its defined Downgrade block. By default, this is an air block.
When a block has an Explosion.ParticleIndex defined, the Fire Manage will use that data to trigger an explosion.
When a block is extinguished, a smoke particle will be placed on it, showing the player that it has been extinguished. An extinguished block will not immediately
catch fire again.
To determine if a block is flammable or not, several checks are performed. The checks here are listed in the order they are checked in code. Once a block is
determined to not be flammable, then the other checks are not performed.
- Does the block have tag of "inflammable"?
- Is the position a child of a block? ( think of multi-dim's here)
- Not Flammable.
- Is the position air?
- Not Flammable.
- is the position water?
- Not Flammable.
- Does The block have the tag of "flammable"?
- Flammable
- Does the Config block have the Property "MaterialID", and does it match our Block?
- Flammable
- Does the Config block have the Property "MaterialDamage", and does it match our Block?
- Flammable
- Does the Config block have the Property "MaterialSurface", and does it match our Block?
- Flammable
The block must return a "Flammable" check to be added to the Fire Manager's system, and be lit on fire.
## Fire Manager Global Configuration
The blocks.xml contains configurations and settings to adjust the fire behaviour from its defaults.
Note: Some of these properties may be defined on the block or material entry to over-ride the default.
#### Logging
```xml
<!-- Enables or disables verbose logging for troubleshooting -->
<property name="Logging" value="false"/>
```
#### FireEnable
```xml
<!-- Enables or disables the fire manager completely -->
<property name="FireEnable" value="false"/>
```
#### CheckInterval
```xml
<!-- Determines how often the fire manager should damage a block, determine fire spreading, etc -->
<property name="CheckInterval" value="20"/>
```
#### FireDamage
```xml
<!-- Determines how much damage is done to a block per CheckInterval. -->
<!-- If this property is defined on a specific block, that value will be used, rather than the default -->
<!-- If this property is defined on a materials.xml entry, that value will be used. -->
<property name="FireDamage" value="10"/>
```
#### SmokeTime
```xml
<!-- Determines the length of time, in seconds, that the smoke particle will appear on an extinguished block -->
<!-- If this property is defined on a specific block, that value will be used, rather than the default -->
<property name="SmokeTime" value="60"/>
```
#### FireSound
```xml
<!-- Determines which SoundDataNode entry will be used to play the sound -->
<!-- If this property is defined on a specific block, that value will be used, rather than the default -->
<property name="FireSound" value="ScoreMediumLoop"/>
```
#### BuffOnFire
```xml
<!-- Defines which buff to apply to a player / zombie / NPC when they touch a block on fire -->
<property name="BuffOnFire" value="buffBurningMolotov"/>
```
#### MaterialID
```xml
<!-- Defines which materials are considered flammable, by material ID in materials.xml -->
<property name="MaterialID" value="Mplants, Mcorn, Mhay"/>
```
#### MaterialDamage
```xml
<!-- Defines which materials are considered flammable, by material Damage in materials.xml -->
<property name="MaterialDamage" value="wood, cloth, corn, grass, plastic, leaves, cactus, mushroom, hay, paper, trash, backpack, organic"/>
```
#### MaterialSurface
```xml
<!-- Defines which materials are considered flammable, by material Surface in materials.xml -->
<property name="MaterialDamage" value="wood, cloth, corn, grass, plastic, leaves, cactus, mushroom, hay, paper, trash, backpack, organic"/>
```
#### SmokeParticle
```xml
<!-- Defines the Smoke Particle to be used on an extinguished block -->
<!-- If this property is defined on a specific block, that value will be used, rather than the default -->
<property name="SmokeParticle" value="#@modfolder:Resources/PathSmoke.unity3d?P_PathSmoke_X"/>
```
#### FireParticle
```xml
<!-- Defines the Fire Particle to be used on a block that is burning. -->
<!-- If this property is defined on a specific block, that value will be used, rather than the default -->
<property name="FireParticle" value="#@modfolder:Resources/gupFireParticles.unity3d?gupBeavis05-Heavy"/>
<!-- Other particles that may be available, but may cause more performance overhead -->
<!--
<property name="FireParticle" value="#@modfolder:Resources/gupFireParticles.unity3d?gupBeavis02-CampFire" />
<property name="FireParticle" value="#@modfolder:Resources/gupFireParticles.unity3d?gupBeavis03-Cartoon" />
<property name="FireParticle" value="#@modfolder:Resources/gupFireParticles.unity3d?gupBeavis04-SlowFire" />
<property name="FireParticle" value="#@modfolder:Resources/gupFireParticles.unity3d?gupBeavis05-Heavy" />
<property name="FireParticle" value="#@modfolder:Resources/gupFireParticles.unity3d?gupBeavis06-HeavyLight" />
-->
```
#### FireSpread
```xml
<!-- Defines is fire can be spread to neighboring blocks -->
<property name="FireSpread" value="true"/>
```
#### ChanceToExtinguish
```xml
<!-- The chance, in percentage as defined from 0 to 1 to denote 0% to 100%, for a block to self-extinguish -->
<!-- This is re-checked on each CheckInterval -->
<!-- If this property is defined on a specific block, that value will be used, rather than the default -->
<property name="ChanceToExtinguish" value="0.05"/>
```
## Block-specific Configuration
In addition to the above settings in the Fire Manager's overall blocks.xml, further properties may be optionally added to blocks.xml to further define the behaviour.
These per-block settings will over-ride the global value for that block in which they are defined on.
#### FireDowngradeBlock
```xml
<!-- When a block is destroyed by fire, it will downgrade to this block -->
<!-- By default, this is air -->
<property name="FireDowngradeBlock" value="burnBlock"/>
```
#### Explosion.ParticleIndex
```xml
<!-- If a block has a defined Explosion.ParticleIndex and is destroyed by fire, the fire manager will set an explosion based on those settings. -->
<property name="Explosion.ParticleIndex" value="0"/>
```
#### ChanceToExtinguish
```xml
<!-- The chance, in percentage as defined from 0 to 1 to denote 0% to 100%, for a block to self-extinguish -->
<property name="ChanceToExtinguish" value="0.05"/>
```
#### FireDamage
```xml
<!-- Determines how much damage is done to a block per CheckInterval. -->
<property name="FireDamage" value="10"/>
```
## MinEvents
A few MinEvents have been written to help modders interact with the fire system. These MinEvents may be added to buffs or item actions.
The trigger may change depending on context and use.
#### MinEventActionAddFireDamage
This effect will set a block on fire, using the target's position, and the range. There is an optional delay time, which will delay the fire from starting.
```xml
<triggered_effect trigger="onSelfDamagedBlock" action="AddFireDamage, SCore" target="positionAOE" range="5" delayTime="10" />
```
#### MinEventActionAddFireDamageCascade
This effect can allow fire to spread to to surrounding blocks. You can set up a filter based on the type of blocks you wanted affected.
```xml
<!-- The same type of block -->
<triggered_effect trigger="onSelfDamagedBlock" action="AddFireDamageCascade, SCore" range="4" filter="Type" />
<!-- Shares the same material -->
<triggered_effect trigger="onSelfDamagedBlock" action="AddFireDamageCascade, SCore" range="4" filter="Material" />
<!-- Shares the same material damage classification -->
<triggered_effect trigger="onSelfDamagedBlock" action="AddFireDamageCascade, SCore" range="4" filter="MaterialDamage" />
<!-- Shares the same material surface classification -->
<triggered_effect trigger="onSelfDamagedBlock" action="AddFireDamageCascade, SCore" range="4" filter="MaterialSurface" />
```
#### MinEventActionCheckFireProximity
Checks to see if there's any fire blocks within the specified range, using the player's position as center.
```xml
<!-- The cvar specified, by default _closeFires, will contain the number of burning blocks in the range. -->
<triggered_effect trigger="onSelfBuffUpdate" action="CheckFireProximity, SCore" range="5" cvar="_closeFires" />
```
#### MinEventActionRemoveFire
Remove Fire within the specified range, using the specified target.
```xml
<!-- This will remove fire from all blocks with a range of 5 from the position of the target. -->
<triggered_effect trigger="onSelfDamagedBlock" action="RemoveFire, SCore" target="positionAOE" range="5"/>
```
## Examples
### XPath
Here are some examples on how to modify the ConfigFeatureBlock, which holds the Fire Management settings, from another modlet.
```xml
<!-- Enable fire -->
<set xpath="/blocks/block[@name='ConfigFeatureBlock']/property[@class='FireManagement']/property[@name='FireEnable']/@value">true</set>
<!-- Turn on verbose logging -->
<set xpath="/blocks/block[@name='ConfigFeatureBlock']/property[@class='FireManagement']/property[@name='Logging']/@value">true</set>
<!-- Adding materials -->
<set xpath="/blocks/block[@name='ConfigFeatureBlock']/property[@class='FireManagement']/property[@name='MaterialDamage']/@value">wood, cloth, corn, grass, plastic, leaves, cactus, mushroom, hay, paper, trash, backpack, organic</set>
<set xpath="/blocks/block[@name='ConfigFeatureBlock']/property[@class='FireManagement']/property[@name='MaterialSurface']/@value">wood, cloth, corn, grass, plastic, leaves, cactus, mushroom, hay, paper, trash, backpack, organic</set>
```
To adding Fire Damage to the flaming crossbow bolts
```xml
<append xpath="/items/item[@name='ammoCrossbowBoltFlaming']/effect_group" >
<triggered_effect trigger="onSelfDamagedBlock" action="AddFireDamage, SCore" delayTime="5000"/> <!-- Delay Time in ms -->
</append>
```
### Triggers
```xml
<!-- Adds fire to the block being hit, if it's flammable. -->
<triggered_effect trigger="onSelfDamagedBlock" action="AddFireDamage, SCore" />
<!-- Remove fire from an area -->
<triggered_effect trigger="onSelfPrimaryActionRayHit" action="RemoveFire, SCore" target="positionAOE" range="5"/>
<!-- Spread fire to all surrounding blocks that have the same material as the target being hit, such as cloth -->
<triggered_effect trigger="onSelfDamagedBlock" action="AddFireDamageCascade, SCore" range="4" filter="Material" />
```

View File

@@ -0,0 +1,786 @@
using Audio;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
/// <summary>
/// SCore's FireManager allows flammable blocks to catch and spread fire.
/// </summary>
/// <remarks>
/// The Fire Manager allows blocks to catch on fire, and allows them to spread to other blocks around it at a timed interval.
/// The following parameters are configured through the blocks.xml
/// </remarks>
public class FireManager
{
private const string AdvFeatureClass = "FireManagement";
private static readonly object Locker = new object();
private static readonly ConcurrentDictionary<Vector3i, BlockValue>
FireMap = new ConcurrentDictionary<Vector3i, BlockValue>();
private static readonly ConcurrentDictionary<Vector3i, float> ExtinguishPositions =
new ConcurrentDictionary<Vector3i, float>();
private float _checkTime = 120f;
private float _currentTime;
private const float CheckTimeLights = 0.8f;
private float _currentTimeLights;
private float _fireDamage = 1f;
private float _smokeTime = 60f;
private readonly GameRandom _random = GameManager.Instance.World.GetGameRandom();
private bool _fireSpread = true;
// Used to throttle sounds playing.
private static readonly List<Vector3i> SoundPlaying = new List<Vector3i>();
private static readonly List<Vector3i> ParticlePlaying = new List<Vector3i>();
private const int MaxLights = 32;
private static readonly HashSet<Light> Shutoff =
new HashSet<Light>();
private string _fireSound;
private string _fireParticle;
private string _smokeParticle;
private const string SaveFile = "FireManager.dat";
private ThreadManager.ThreadInfo _dataSaveThreadInfo;
private float _chanceToExtinguish = 0.05f;
public bool Enabled; // { private set; get; }
public static FireManager Instance { get; private set; }
public static void Init()
{
Instance = new FireManager();
Instance.ReadConfig();
}
private void ReadConfig()
{
var fireSpreadString = "true";
if (!StringParsers.ParseBool(fireSpreadString))
{
Debug.Log("Fire Spread Disabled.");
_fireSpread = false;
}
Enabled = true;
string option = "";
bool optionFireManager = CustomGameOptions.GetBool("CustomFireManager");
if (!optionFireManager)
{
Log.Out("Fire Manager is disabled.");
Enabled = false;
return;
}
option = "20";
if (!string.IsNullOrEmpty(option))
_checkTime = StringParsers.ParseFloat(option);
var strDamage = "50";
if (!string.IsNullOrWhiteSpace(strDamage))
_fireDamage = StringParsers.ParseFloat(strDamage);
_currentTime = -1;
var smoke = "60";
if (!string.IsNullOrWhiteSpace(smoke))
_smokeTime = StringParsers.ParseFloat(smoke);
var strFireSound = "SCoreMediumLoop";
if (!string.IsNullOrWhiteSpace(strFireSound))
_fireSound = strFireSound;
if (_fireSound == "None")
_fireSound = string.Empty;
Log.Out("Starting Fire Manager");
_fireParticle = "#@modfolder(zzz_REBIRTH__Utils):Resources/gupFireParticles.unity3d?gupBeavis05-Heavy";
_smokeParticle = "#@modfolder(zzz_REBIRTH__Utils):Resources/PathSmoke.unity3d?P_PathSmoke_X";
var optionChanceToExtinguish = "0.05";
if (!string.IsNullOrEmpty(option))
_chanceToExtinguish = StringParsers.ParseFloat(optionChanceToExtinguish);
// Read the FireManager
Load();
// Only run the Update on the server, then just distribute the data to the clients using NetPackages.
if (!SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer) return;
Log.Out($" :: Fire Interval Check time: {_checkTime}");
ModEvents.GameUpdate.RegisterHandler(FireUpdate);
ModEvents.GameUpdate.RegisterHandler(LightsUpdate);
}
#region LightCode
private void LightsUpdate()
{
// Make sure to only run it once
lock (Locker)
{
_currentTimeLights -= Time.deltaTime;
if (_currentTimeLights > 0f) return;
_currentTimeLights = CheckTimeLights;
ShutoffLights();
CheckLights();
}
}
private static void ShutoffLights()
{
if (Shutoff.Count == 0) return;
if (GameManager.Instance.IsPaused()) return;
Shutoff.RemoveWhere(light => light == null);
foreach (var light in Shutoff)
{
light.intensity -= Time.deltaTime;
// Log.Out("Toning down {0}", light.intensity);
if (light.intensity > 0f) continue;
// Log.Out("Culling light out of this world");
var gameObject = light.gameObject;
gameObject.transform.parent = null;
UnityEngine.Object.Destroy(gameObject);
}
Shutoff.RemoveWhere(light => light.intensity <= 0f);
}
private static void CheckLights()
{
if (GameManager.Instance.IsPaused()) return;
var allLights = UnityEngine.Object.FindObjectsOfType<Light>();
if (allLights.Length <= MaxLights) return;
var curLights = new List<Light>();
for (var n = 0; n < allLights.Length; n += 1)
{
if (allLights[n].name != "FireLight") continue;
if (Shutoff.Contains(allLights[n])) continue;
curLights.Add(allLights[n]);
}
// AdvLogging.DisplayLog(AdvFeatureClass,$"Detect currently {curLights.Count} lights, {Shutoff.Count} being culled");
// Do nothing if we are (or will be) within limits
if (curLights.Count <= MaxLights) return;
// Otherwise choose more lights to shutoff
while (curLights.Count >= MaxLights)
{
var idx = GameManager.Instance.World.GetGameRandom().RandomRange(curLights.Count);
if (Shutoff.Contains(curLights[idx])) continue;
// Log.Out("Selected {0} for culling", idx);
Shutoff.Add(curLights[idx]);
curLights.RemoveAt(idx);
}
}
#endregion
// Poor man's timed cache
private void CheckExtinguishedPosition()
{
var worldTime = GameManager.Instance.World.GetWorldTime();
foreach (var position in ExtinguishPositions)
{
Remove(position.Key);
if (!(position.Value < worldTime) &&
!GameManager.Instance.World.GetBlock(position.Key + Vector3i.down).isair) continue;
ExtinguishPositions.TryRemove(position.Key, out var _);
ClearPos(position.Key);
}
}
public ConcurrentDictionary<Vector3i, BlockValue> GetFireMap()
{
return FireMap;
}
public int CloseFires(Vector3i position, int range = 5)
{
var count = 0;
for (var x = position.x - range; x <= position.x + range; x++)
{
for (var z = position.z - range; z <= position.z + range; z++)
{
for (int y = position.y - 2; y <= position.y + 2; y++)
{
var localPosition = new Vector3i(x, y, z);
if (IsBurning(localPosition)) count++;
}
}
}
return count;
}
public bool IsPositionCloseToFire(Vector3i position, int range = 5)
{
for (var x = position.x - range; x <= position.x + range; x++)
{
for (var z = position.z - range; z <= position.z + range; z++)
{
for (var y = position.y - 2; y <= position.y + 2; y++)
{
var localPosition = new Vector3i(x, y, z);
if (IsBurning(localPosition)) return true;
}
}
}
return false;
}
private void FireUpdate()
{
// Make sure to only run it once
lock (Locker)
{
_currentTime -= Time.deltaTime;
if (_currentTime > 0f) return;
GameManager.Instance.StopCoroutine(CheckBlocks());
GameManager.Instance.StartCoroutine(CheckBlocks());
}
}
private IEnumerator CheckBlocks()
{
if (GameManager.Instance.IsPaused()) yield break;
if (!GameManager.Instance.gameStateManager.IsGameStarted()) yield break;
//Log.Out(AdvFeatureClass,
//$"Checking Blocks for Fire: {FireMap.Count} Blocks registered. Extinguished Blocks: {ExtinguishPositions.Count}");
_currentTime = _checkTime;
var changes = new List<BlockChangeInfo>();
var neighbors = new List<Vector3i>();
CheckExtinguishedPosition();
var chunkCluster = GameManager.Instance.World.ChunkClusters[0];
if (chunkCluster == null) yield break;
var counter = 0;
var rainfallValue = WeatherManager.Instance.GetCurrentRainfallValue();
foreach (var posDict in FireMap)
{
counter++;
// only process 10 blocks per frame.
if (counter % 10 == 0)
yield return null;
var blockPos = posDict.Key;
if (!IsFlammable(blockPos))
{
Remove(blockPos);
continue;
}
var block = GameManager.Instance.World.GetBlock(blockPos);
// Get block specific damages
var damage = (int)_fireDamage;
if (block.Block.Properties.Contains("FireDamage"))
damage = block.Block.Properties.GetInt("FireDamage");
if (block.Block.blockMaterial.Properties.Contains("FireDamage"))
damage = block.Block.blockMaterial.Properties.GetInt("FireDamage");
if (block.Block.Properties.Contains("ChanceToExtinguish"))
block.Block.Properties.ParseFloat("ChanceToExtinguish", ref _chanceToExtinguish);
block.damage += damage;
if (block.damage >= block.Block.MaxDamage)
{
block.Block.SpawnDestroyParticleEffect(GameManager.Instance.World, block, blockPos, 1f,
block.Block.tintColor, -1);
var blockValue2 = block.Block.DowngradeBlock;
if (block.Block.Properties.Values.ContainsKey("FireDowngradeBlock"))
blockValue2 = Block.GetBlockValue(block.Block.Properties.Values["FireDowngradeBlock"]);
if (block.Block.Properties.Values.ContainsKey("Explosion.ParticleIndex") ||
block.Block.Properties.Classes.ContainsKey("Explosion"))
block.Block.OnBlockDestroyedByExplosion(GameManager.Instance.World, 0, blockPos, block, -1);
// Check if there's another placeholder for this block.
if (!blockValue2.isair)
blockValue2 = BlockPlaceholderMap.Instance.Replace(blockValue2,
GameManager.Instance.World.GetGameRandom(), blockPos.x, blockPos.z);
blockValue2.rotation = block.rotation;
blockValue2.meta = block.meta;
block = blockValue2;
}
if (!block.isair)
{
SingletonMonoBehaviour<ConnectionManager>.Instance.SendPackage(
NetPackageManager.GetPackage<NetPackageAddFirePosition>().Setup(blockPos, -1));
}
else
{
SingletonMonoBehaviour<ConnectionManager>.Instance.SendPackage(
NetPackageManager.GetPackage<NetPackageRemoveFirePosition>().Setup(blockPos, -1));
}
changes.Add(new BlockChangeInfo(0, blockPos, block));
var rand = _random.RandomRange(0f, 1f);
var extinguishChange = _chanceToExtinguish;
if (rainfallValue > 0.25)
{
extinguishChange = _chanceToExtinguish * 2f;
}
var blchanceToExtinguish = rand < extinguishChange;
// If the new block has changed, check to make sure the new block is flammable. Note: it checks the blockValue, not blockPos, since the change hasn't been committed yet.
if (!IsFlammable(block) || block.isair || blchanceToExtinguish)
{
// queue up the change
if (DynamicMeshManager.Instance != null)
DynamicMeshManager.Instance.AddChunk(blockPos, true);
Extinguish(blockPos);
continue;
}
ToggleSound(blockPos, rand < 0.10);
ToggleParticle(blockPos, true);
// If we are damaging a block, allow the fire to spread.
if (_fireSpread)
neighbors.AddRange(CheckNeighbors(blockPos));
FireMap[blockPos] = block;
}
// Send all the changes in one shot
if (changes.Count > 0)
GameManager.Instance.SetBlocksRPC(changes);
if (_fireSpread == false) yield break;
// Spread the fire to the neighbors. We delay this here so the fire does not spread too quickly or immediately, getting stuck in the above loop.
foreach (var pos in neighbors)
Add(pos);
//Debug.Log($"Sound Counter: {SoundPlaying.Count}, Fire Counter: {FireMap.Count} Particle Count: {ParticlePlaying.Count}: Heat: {heatMeter}");
}
private void ToggleSound(Vector3i blockPos, bool turnOn)
{
//if (SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer) return;
if (!ThreadManager.IsMainThread()) return;
// If there's a lot of fires going on, turn off the sounds.
if (turnOn && FireMap.Count > 60)
{
turnOn = false;
}
var sound = GetFireSound(blockPos);
if (string.IsNullOrEmpty(sound)) return;
if (turnOn)
{
if (SoundPlaying.Contains(blockPos))
return;
SoundPlaying.Add(blockPos);
Manager.Play(blockPos, sound);
return;
}
// No sound?
if (!SoundPlaying.Contains(blockPos)) return;
Manager.Stop(blockPos, sound);
SoundPlaying.Remove(blockPos);
}
private void ToggleParticle(Vector3i blockPos, bool turnOn)
{
// if (SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer) return;
if (!ThreadManager.IsMainThread()) return;
var randomFireParticle = GetRandomFireParticle(blockPos);
if (turnOn)
{
if (ParticlePlaying.Contains(blockPos)) return;
if (GameManager.Instance.HasBlockParticleEffect(blockPos)) return;
ParticlePlaying.Add(blockPos);
RebirthUtilities.addParticlesCentered(randomFireParticle, blockPos);
return;
}
ParticlePlaying.Remove(blockPos);
RebirthUtilities.removeParticles(blockPos);
}
private string GetFireSound(Vector3i blockPos)
{
var block = GameManager.Instance.World.GetBlock(blockPos);
var fireSound = _fireSound;
if (block.Block.Properties.Contains("FireSound"))
fireSound = block.Block.Properties.GetString("FireSound");
return fireSound;
}
private string GetRandomFireParticle(Vector3i blockPos)
{
var block = GameManager.Instance.World.GetBlock(blockPos);
var fireParticle = _fireParticle;
if (block.Block.Properties.Contains("FireParticle"))
fireParticle = block.Block.Properties.GetString("FireParticle");
if (block.Block.blockMaterial.Properties.Contains("FireParticle"))
fireParticle = block.Block.blockMaterial.Properties.GetString("FireParticle");
var randomFire = ""; // Configuration.GetPropertyValue(AdvFeatureClass, "RandomFireParticle");
if (string.IsNullOrEmpty(randomFire)) return fireParticle;
var fireParticles = randomFire.Split(',');
var randomIndex = _random.RandomRange(0, fireParticles.Length);
fireParticle = fireParticles[randomIndex];
return fireParticle;
}
private string GetRandomSmokeParticle(Vector3i blockPos)
{
var block = GameManager.Instance.World.GetBlock(blockPos);
var smokeParticle = _smokeParticle;
if (block.Block.Properties.Contains("SmokeParticle"))
smokeParticle = block.Block.Properties.GetString("SmokeParticle");
if (block.Block.blockMaterial.Properties.Contains("SmokeParticle"))
smokeParticle = block.Block.blockMaterial.Properties.GetString("SmokeParticle");
var randomSmoke = ""; // Configuration.GetPropertyValue(AdvFeatureClass, "RandomSmokeParticle");
if (string.IsNullOrEmpty(randomSmoke)) return smokeParticle;
var smokeParticles = randomSmoke.Split(',');
var randomIndex = GameManager.Instance.World.GetGameRandom().RandomRange(0, smokeParticles.Length);
smokeParticle = smokeParticles[randomIndex];
return smokeParticle;
}
// Check to see if the nearby blocks can catch fire.
private static List<Vector3i> CheckNeighbors(Vector3i blockPos)
{
var neighbors = new List<Vector3i>();
foreach (var direction in Vector3i.AllDirections)
{
var position = blockPos + direction;
if (FireMap.ContainsKey(position))
continue;
if (IsFlammable(position))
neighbors.Add(position);
}
return neighbors;
}
private static bool IsNearWater(Vector3i blockPos)
{
foreach (var direction in Vector3i.AllDirections)
{
var position = blockPos + direction;
var blockValue = GameManager.Instance.World.GetBlock(position);
if (blockValue.isWater) return true;
if (blockValue.Block is BlockLiquidv2) return true;
// A21 water check.
if (GameManager.Instance.World.GetWaterPercent(position) > 0.25f) return true;
}
return false;
}
private static bool IsFlammable(BlockValue blockValue)
{
if (blockValue.Block.HasAnyFastTags(FastTags<TagGroup.Global>.Parse("inflammable"))) return false;
if (blockValue.ischild) return false;
if (blockValue.isair) return false;
if (blockValue.isWater) return false;
// if (blockValue.Block.Properties.Values.ContainsKey("Explosion.ParticleIndex")) return true;
if (blockValue.Block.HasAnyFastTags(FastTags<TagGroup.Global>.Parse("flammable"))) return true;
var blockMaterial = blockValue.Block.blockMaterial;
var matID = "Mplants, Mcorn, Mhay"; // Configuration.GetPropertyValue(AdvFeatureClass, "MaterialID");
if (matID.Contains(blockMaterial.id)) return true;
var matDamage = "wood, cloth, corn, grass, plastic, leaves, cactus, mushroom, hay, paper, trash, backpack, organic"; // Configuration.GetPropertyValue(AdvFeatureClass, "MaterialDamage");
if (!string.IsNullOrEmpty(matDamage) && blockMaterial.DamageCategory != null)
if (matDamage.Contains(blockMaterial.DamageCategory))
return true;
var matSurface = "wood, cloth, corn, grass, plastic, leaves, cactus, mushroom, hay, paper, trash, backpack, organic"; // Configuration.GetPropertyValue(AdvFeatureClass, "MaterialSurface");
if (string.IsNullOrEmpty(matSurface) || blockMaterial.SurfaceCategory == null) return false;
return matSurface.Contains(blockMaterial.SurfaceCategory);
}
private static bool IsFlammable(Vector3i blockPos)
{
if (GameManager.Instance.World.IsWithinTraderArea(blockPos)) return false;
if (ExtinguishPositions.ContainsKey(blockPos)) return false;
// If its already burning, then don't do any other check
if (IsBurning(blockPos)) return true;
if (IsNearWater(blockPos)) return false;
// Check the block value.
var blockValue = GameManager.Instance.World.GetBlock(blockPos);
return IsFlammable(blockValue);
}
private static void Write(BinaryWriter bw)
{
// Save the burning blocks.
var writeOut = "";
foreach (var temp in FireMap)
writeOut += $"{temp.Key};";
writeOut = writeOut.TrimEnd(';');
bw.Write(writeOut);
// Save the blocks we've put out
var writeOut2 = "";
foreach (var temp in ExtinguishPositions.Keys)
writeOut2 += $"{temp};";
writeOut2 = writeOut2.TrimEnd(';');
bw.Write(writeOut2);
}
private void Read(BinaryReader br)
{
// Read burning blocks
var positions = br.ReadString();
foreach (var position in positions.Split(';'))
{
if (string.IsNullOrEmpty(position)) continue;
var vector = StringParsers.ParseVector3i(position);
AddBlock(vector);
}
// Read extinguished blocks.
var extingished = br.ReadString();
foreach (var position in extingished.Split(';'))
{
if (string.IsNullOrEmpty(position)) continue;
var vector = StringParsers.ParseVector3i(position);
ExtinguishBlock(vector);
}
}
public void ClearPosOnly(Vector3i blockPos)
{
ToggleSound(blockPos, false);
ToggleParticle(blockPos, false);
}
public void ClearPos(Vector3i blockPos)
{
ClearPosOnly(blockPos);
if (!SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer)
{
SingletonMonoBehaviour<ConnectionManager>.Instance.SendToServer(
NetPackageManager.GetPackage<NetPackageRemoveParticleEffect>().Setup(blockPos, -1));
return;
}
SingletonMonoBehaviour<ConnectionManager>.Instance.SendPackage(
NetPackageManager.GetPackage<NetPackageRemoveParticleEffect>().Setup(blockPos, -1));
}
public void Add(Vector3i blockPos, int entityID = -1)
{
if (!IsFlammable(blockPos))
return;
AddBlock(blockPos);
if (!SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer)
{
SingletonMonoBehaviour<ConnectionManager>.Instance.SendToServer(
NetPackageManager.GetPackage<NetPackageAddFirePosition>().Setup(blockPos, entityID));
return;
}
SingletonMonoBehaviour<ConnectionManager>.Instance.SendPackage(
NetPackageManager.GetPackage<NetPackageAddFirePosition>().Setup(blockPos, entityID));
}
// General call to remove the fire from a block, and add an extinguished counter, so blocks can be temporarily immune to restarting.
public void Extinguish(Vector3i blockPos, int entityID = -1)
{
ExtinguishBlock(blockPos);
if (!SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer)
{
SingletonMonoBehaviour<ConnectionManager>.Instance.SendToServer(
NetPackageManager.GetPackage<NetPackageAddExtinguishPosition>().Setup(blockPos, entityID));
return;
}
SingletonMonoBehaviour<ConnectionManager>.Instance.SendPackage(
NetPackageManager.GetPackage<NetPackageAddExtinguishPosition>().Setup(blockPos, entityID));
}
public void Remove(Vector3i blockPos, int entityID = -1)
{
RemoveFire(blockPos);
if (!SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer)
{
SingletonMonoBehaviour<ConnectionManager>.Instance.SendToServer(
NetPackageManager.GetPackage<NetPackageRemoveFirePosition>().Setup(blockPos, entityID));
return;
}
SingletonMonoBehaviour<ConnectionManager>.Instance.SendPackage(
NetPackageManager.GetPackage<NetPackageRemoveFirePosition>().Setup(blockPos, entityID));
}
public void RemoveFire(Vector3i blockPos)
{
//if (!FireMap.ContainsKey(blockPos)) return;
ToggleSound(blockPos, false);
ToggleParticle(blockPos, false);
FireMap.TryRemove(blockPos, out _);
}
public void ExtinguishBlock(Vector3i blockPos)
{
//Log.Out("FireManager-ExtinguishBlock START");
var worldTime = GameManager.Instance.World.GetWorldTime();
var expiry = worldTime + _smokeTime;
// Seems like sometimes the dedicated and clients are out of sync, so this is a shot in the dark to see if we just skip the expired position check, and just
// keep resetting the expired time.
ExtinguishPositions[blockPos] = expiry;
FireMap.TryRemove(blockPos, out _);
var block = GameManager.Instance.World.GetBlock(blockPos);
ToggleSound(blockPos, false);
if (block.isair || !(_smokeTime > 0))
{
//Log.Out("FireManager-ExtinguishBlock 1");
return;
}
var randomSmokeParticle = GetRandomSmokeParticle(blockPos);
//Log.Out("FireManager-ExtinguishBlock randomSmokeParticle: " + randomSmokeParticle);
RebirthUtilities.addParticlesCentered(randomSmokeParticle, blockPos);
}
// Add flammable blocks to the Fire Map
public void AddBlock(Vector3i blockPos)
{
var block = GameManager.Instance.World.GetBlock(blockPos);
if (!FireMap.TryAdd(blockPos, block)) return;
ToggleSound(blockPos, true);
ToggleParticle(blockPos, true);
}
public static bool IsBurning(Vector3i blockPos)
{
return Instance.Enabled && FireMap.ContainsKey(blockPos);
}
private int SaveDataThreaded(ThreadManager.ThreadInfo threadInfo)
{
var pooledExpandableMemoryStream =
(PooledExpandableMemoryStream)threadInfo.parameter;
var text = $"{GameIO.GetSaveGameDir()}/{SaveFile}";
if (File.Exists(text))
File.Copy(text, $"{GameIO.GetSaveGameDir()}/{SaveFile}.bak", true);
pooledExpandableMemoryStream.Position = 0L;
StreamUtils.WriteStreamToFile(pooledExpandableMemoryStream, text);
Log.Out("FireManager saved {0} bytes", new object[]
{
pooledExpandableMemoryStream.Length
});
MemoryPools.poolMemoryStream.FreeSync(pooledExpandableMemoryStream);
return -1;
}
private void Save()
{
if (_dataSaveThreadInfo == null || !ThreadManager.ActiveThreads.ContainsKey("silent_FireDataSave"))
{
Log.Out($"FireManager saving {FireMap.Count} Fires...");
var pooledExpandableMemoryStream = MemoryPools.poolMemoryStream.AllocSync(true);
using (var pooledBinaryWriter = MemoryPools.poolBinaryWriter.AllocSync(false))
{
pooledBinaryWriter.SetBaseStream(pooledExpandableMemoryStream);
Write(pooledBinaryWriter);
}
_dataSaveThreadInfo = ThreadManager.StartThread("silent_FireDataSave", null,
SaveDataThreaded, null,
System.Threading.ThreadPriority.Normal, pooledExpandableMemoryStream);
}
else
{
Log.Out("Not Saving. Thread still running?");
}
}
private void Load()
{
var path = $"{GameIO.GetSaveGameDir()}/{SaveFile}";
if (!Directory.Exists(GameIO.GetSaveGameDir()) || !File.Exists(path)) return;
try
{
using var fileStream = File.OpenRead(path);
using var pooledBinaryReader = MemoryPools.poolBinaryReader.AllocSync(false);
pooledBinaryReader.SetBaseStream(fileStream);
Read(pooledBinaryReader);
}
catch (Exception)
{
path = $"{GameIO.GetSaveGameDir()}/{SaveFile}.bak";
if (File.Exists(path))
{
using var fileStream2 = File.OpenRead(path);
using var pooledBinaryReader2 = MemoryPools.poolBinaryReader.AllocSync(false);
pooledBinaryReader2.SetBaseStream(fileStream2);
Read(pooledBinaryReader2);
}
}
Log.Out($"Fire Manager {path} Loaded: {FireMap.Count}");
}
public void Reset()
{
//Log.Out("Removing all blocks that are on fire and smoke.");
lock (Locker)
{
foreach (var position in FireMap.Keys)
Remove(position);
foreach (var position in ExtinguishPositions.Keys)
RebirthUtilities.removeParticles(position);
FireMap.Clear();
ExtinguishPositions.Clear();
Save();
}
}
}

View File

@@ -0,0 +1,81 @@
using JetBrains.Annotations;
using System.Threading.Tasks;
using System.Xml.Linq;
/// <summary>
/// Set's a single block on fire.
/// </summary>
/// <remarks>
/// This effect will set a block on fire, using the target's position, and the range. There is an optional delay time, which will delay the fire from starting.
/// Example:
/// <triggered_effect trigger="onSelfDamagedBlock" action="AddFireDamage, SCore" target="positionAOE" range="5" delayTime="10" />
/// </remarks>
[UsedImplicitly]
public class MinEventActionAddFireDamage : MinEventActionRemoveBuff
{
private static readonly string AdvFeatureClass = "FireManagement";
private float _delayTime;
public override void Execute(MinEventParams @params)
{
if (FireManager.Instance == null) return;
if (FireManager.Instance.Enabled == false) return;
var position = @params.Position;
if (targetType != TargetTypes.positionAOE)
{
if (Voxel.voxelRayHitInfo.bHitValid)
{
var hitInfo = Voxel.voxelRayHitInfo;
if (hitInfo == null) return;
// modification from FuriousRamsay to fix issue where the lookray is behind a door, which is setting fire to blocks on the other side of metal doors.
var itemAction = @params.Self.inventory.holdingItemData.item.Actions[0];
if (itemAction is ItemActionMelee)
{
float blockRange = 0;
if (itemAction.Properties.Values.ContainsKey("Block_range"))
{
blockRange = StringParsers.ParseFloat(itemAction.Properties.Values["Block_range"]);
}
if ((blockRange > 0) && (Vector3.Distance(hitInfo.hit.blockPos, @params.Position) > blockRange))
{
return;
}
}
position = hitInfo.hit.blockPos;
}
}
//AdvLogging.DisplayLog(AdvFeatureClass, $"Executing AddFireDamage() at {position} Self: {@params.Self.position} Range: {maxRange} Delay: {_delayTime}");
Task.Delay((int)_delayTime)
.ContinueWith(_ => AddFire(position));
}
private void AddFire(Vector3 position)
{
var range = (int)maxRange;
for (var x = -range; x <= range; x++)
{
for (var z = -range; z <= range; z++)
{
for (var y = -range; y <= range; y++)
{
var vector = new Vector3i(position.x + x, position.y + y, position.z + z);
FireManager.Instance.Add(vector);
}
}
}
}
public override bool ParseXmlAttribute(XAttribute attribute)
{
var flag = base.ParseXmlAttribute(attribute);
if (flag) return true;
var name = attribute.Name.LocalName;
if (name != "delayTime") return false;
_delayTime = StringParsers.ParseFloat(attribute.Value);
return true;
}
}

View File

@@ -0,0 +1,99 @@
using JetBrains.Annotations;
using System.Xml.Linq;
/// <summary>
/// Spreads fire over a wider area than a single block.
/// </summary>
/// <remarks>
/// This effect can allow fire to spread to to surrounding blocks. You can set up a filter based on the type of blocks you wanted affected.
/// Example:
/// <!-- The same type of block -->
/// <triggered_effect trigger="onSelfDamagedBlock" action="AddFireDamageCascade, SCore" range="4" filter="Type" />
///
/// <!-- Shares the same material -->
/// <triggered_effect trigger="onSelfDamagedBlock" action="AddFireDamageCascade, SCore" range="4" filter="Material" />
///
/// <!-- Shares the same material damage classification -->
/// <triggered_effect trigger="onSelfDamagedBlock" action="AddFireDamageCascade, SCore" range="4" filter="MaterialDamage" />
///
/// <!-- Shares the same material surface classification -->
/// <triggered_effect trigger="onSelfDamagedBlock" action="AddFireDamageCascade, SCore" range="4" filter="MaterialSurface" />
/// </remarks>
[UsedImplicitly]
public class MinEventActionAddFireDamageCascade : MinEventActionRemoveBuff
{
private enum FilterTypeCascade
{
Type,
Material,
MaterialDamage,
MaterialSurface
}
private FilterTypeCascade _filterType = FilterTypeCascade.Type;
public override void Execute(MinEventParams @params)
{
var position = @params.Position;
if (targetType != TargetTypes.positionAOE)
{
if (Voxel.voxelRayHitInfo.bHitValid)
{
var hitInfo = Voxel.voxelRayHitInfo;
if (hitInfo == null) return;
position = hitInfo.hit.blockPos;
}
}
SpreadFire(position);
}
private void SpreadFire(Vector3 position)
{
var targetBlock = GameManager.Instance.World.GetBlock(new Vector3i(position));
var range = (int)maxRange;
for (var x = -range; x <= range; x++)
{
for (var z = -range; z <= range; z++)
{
for (var y = -range; y <= range; y++)
{
var vector = new Vector3i(position.x + x, position.y + y, position.z + z);
var neighborBlock = GameManager.Instance.World.GetBlock(vector);
switch (_filterType)
{
case FilterTypeCascade.Type:
if (neighborBlock.type == targetBlock.type)
FireManager.Instance.Add(vector);
break;
case FilterTypeCascade.Material:
if (neighborBlock.Block.blockMaterial.id == targetBlock.Block.blockMaterial.id)
FireManager.Instance.Add(vector);
break;
case FilterTypeCascade.MaterialDamage:
if (neighborBlock.Block.blockMaterial.DamageCategory ==
targetBlock.Block.blockMaterial.DamageCategory)
FireManager.Instance.Add(vector);
break;
case FilterTypeCascade.MaterialSurface:
if (neighborBlock.Block.blockMaterial.SurfaceCategory ==
targetBlock.Block.blockMaterial.SurfaceCategory)
FireManager.Instance.Add(vector);
break;
}
}
}
}
}
public override bool ParseXmlAttribute(XAttribute attribute)
{
var flag = base.ParseXmlAttribute(attribute);
if (flag) return true;
var name = attribute.Name.LocalName;
if (name != "filter") return false;
_filterType = EnumUtils.Parse<FilterTypeCascade>(attribute.Value, true);
return true;
}
}

View File

@@ -0,0 +1,42 @@
using JetBrains.Annotations;
using System.Xml.Linq;
/// <summary>
/// Checks to see if there's any fire blocks within the specified range, using the player's position as center.
/// </summary>
/// <remarks>
/// Example:
///
/// The cvar specified, by default _closeFires, will contain the number of burning blocks in the range.
/// <triggered_effect trigger="onSelfBuffUpdate" action="CheckFireProximity, SCore" range="5" cvar="_closeFires" />
/// </remarks>
[UsedImplicitly]
public class MinEventActionCheckFireProximity : MinEventActionRemoveBuff
{
string cvar = "_closeFires";
public override void Execute(MinEventParams @params)
{
if (FireManager.Instance.Enabled == false) return;
var position = new Vector3i(@params.Self.position);
var count = FireManager.Instance.CloseFires(position, (int)maxRange);
if (count == 0)
count = -1;
@params.Self.Buffs.SetCustomVar(cvar, count);
}
public override bool ParseXmlAttribute(XAttribute attribute)
{
var flag = base.ParseXmlAttribute(attribute);
if (flag) return true;
var name = attribute.Name.LocalName;
if (name != "cvar") return false;
cvar = attribute.Value;
return true;
}
}

View File

@@ -0,0 +1,46 @@
using JetBrains.Annotations;
/// <summary>
/// Remove Fire within the specified range, using the specified target.
/// </summary>
/// <remarks>
/// Example:
/// This will remove fire from all blocks with a range of 5 from the position of the target.
/// <triggered_effect trigger="onSelfDamagedBlock" action="RemoveFire, SCore" target="positionAOE" range="5"/>
/// </remarks>
[UsedImplicitly]
public class MinEventActionRemoveFire : MinEventActionRemoveBuff
{
private static readonly string AdvFeatureClass = "FireManagement";
public override void Execute(MinEventParams @params)
{
if (FireManager.Instance == null) return;
if (FireManager.Instance.Enabled == false) return;
var position = @params.Position;
if (targetType != TargetTypes.positionAOE)
{
if (Voxel.voxelRayHitInfo.bHitValid)
{
var hitInfo = Voxel.voxelRayHitInfo;
if (hitInfo == null) return;
position = hitInfo.hit.blockPos;
}
}
var range = (int)maxRange;
for (var x = -range; x <= range; x++)
{
for (var z = -range; z <= range; z++)
{
for (var y = -range; y <= range; y++)
{
var vector = new Vector3i(position.x + x, position.y + y, position.z + z);
if (!FireManager.IsBurning(vector)) continue;
FireManager.Instance.Extinguish(vector);
}
}
}
}
}

View File

@@ -0,0 +1,47 @@
using JetBrains.Annotations;
/// <summary>
/// Distributes the call to all clients to extinguish a block
/// </summary>
[UsedImplicitly]
public class NetPackageAddExtinguishPosition : NetPackage
{
private Vector3i _position;
private int _entityThatCausedIt;
public NetPackageAddExtinguishPosition Setup(Vector3i position, int entityThatCausedIt)
{
_position = position;
_entityThatCausedIt = entityThatCausedIt;
return this;
}
public override void read(PooledBinaryReader br)
{
_position = new Vector3i(br.ReadInt32(), br.ReadInt32(), br.ReadInt32());
_entityThatCausedIt = br.ReadInt32();
}
public override void write(PooledBinaryWriter bw)
{
base.write(bw);
bw.Write(_position.x);
bw.Write(_position.y);
bw.Write(_position.z);
bw.Write(_entityThatCausedIt);
}
public override int GetLength()
{
return 20;
}
public override void ProcessPackage(World world, GameManager callbacks)
{
if (world == null || FireManager.Instance == null) return;
FireManager.Instance.ExtinguishBlock(_position);
}
}

View File

@@ -0,0 +1,47 @@
using JetBrains.Annotations;
/// <summary>
/// Distributes the call to all clients to add a block that is considered burning.
/// </summary>
[UsedImplicitly]
public class NetPackageAddFirePosition : NetPackage
{
private Vector3i _position;
private int _entityThatCausedIt;
public NetPackageAddFirePosition Setup(Vector3i position, int entityThatCausedIt)
{
_position = position;
_entityThatCausedIt = entityThatCausedIt;
return this;
}
public override void read(PooledBinaryReader br)
{
_position = new Vector3i(br.ReadInt32(), br.ReadInt32(), br.ReadInt32());
_entityThatCausedIt = br.ReadInt32();
}
public override void write(PooledBinaryWriter bw)
{
base.write(bw);
bw.Write(_position.x);
bw.Write(_position.y);
bw.Write(_position.z);
bw.Write(_entityThatCausedIt);
}
public override int GetLength()
{
return 20;
}
public override void ProcessPackage(World world, GameManager callbacks)
{
if (world == null || FireManager.Instance == null) return;
FireManager.Instance.AddBlock(_position);
}
}

View File

@@ -0,0 +1,52 @@
using JetBrains.Annotations;
/// <summary>
/// Distributes the call to all clients to remove a block that is considered burning.
/// </summary>
[UsedImplicitly]
public class NetPackageRemoveFirePosition : NetPackage
{
private Vector3i _position;
private int _entityThatCausedIt;
public NetPackageRemoveFirePosition Setup(Vector3i position, int entityThatCausedIt)
{
_position = position;
_entityThatCausedIt = entityThatCausedIt;
return this;
}
public override void read(PooledBinaryReader br)
{
_position = new Vector3i(br.ReadInt32(), br.ReadInt32(), br.ReadInt32());
_entityThatCausedIt = br.ReadInt32();
}
public override void write(PooledBinaryWriter bw)
{
base.write(bw);
bw.Write(_position.x);
bw.Write(_position.y);
bw.Write(_position.z);
bw.Write(_entityThatCausedIt);
}
public override int GetLength()
{
return 20;
}
public override void ProcessPackage(World world, GameManager callbacks)
{
if (world == null)
{
return;
}
if (FireManager.Instance != null)
{
FireManager.Instance.RemoveFire(_position);
}
}
}

View File

@@ -0,0 +1,51 @@
using JetBrains.Annotations;
/// <summary>
/// Distributes the call to remove a particle effect to all clients. This is used by the Fire management system.
/// </summary>
/// <remarks>
/// This allows us to distribute which block is showing a fire particle.
/// </remarks>
///
[UsedImplicitly]
public class NetPackageRemoveParticleEffect : NetPackage
{
private Vector3i _position;
private int _entityThatCausedIt;
public NetPackageRemoveParticleEffect Setup(Vector3i position, int entityThatCausedIt)
{
_position = position;
_entityThatCausedIt = entityThatCausedIt;
return this;
}
public override void read(PooledBinaryReader br)
{
_position = new Vector3i(br.ReadInt32(), br.ReadInt32(), br.ReadInt32());
_entityThatCausedIt = br.ReadInt32();
}
public override void write(PooledBinaryWriter bw)
{
base.write(bw);
bw.Write(_position.x);
bw.Write(_position.y);
bw.Write(_position.z);
bw.Write(_entityThatCausedIt);
}
public override int GetLength()
{
return 20;
}
public override void ProcessPackage(World world, GameManager callbacks)
{
if (world == null || FireManager.Instance == null) return;
FireManager.Instance.ClearPosOnly(_position);
}
}

View File

@@ -0,0 +1,12 @@
namespace Harmony.GUIWindowConsolePatches
{
[HarmonyPatch(typeof(GUIWindowConsole))]
[HarmonyPatch("openConsole")]
public class DisableExceptionHijack
{
private static bool Prefix()
{
return RebirthVariables.customConsolePopUp;
}
}
}

60
Score/ILUtilities.cs Normal file
View File

@@ -0,0 +1,60 @@
public static class ILUtilities
{
// Debug to print off a list of all fields and local variables.
/* The index may change for each release.
Example Output of this method will show, in order, all the local variable types, and their index.
The IL itself does not contain the name of the variables, as that type of meta data isn't actually available.
Instead, you need to use the index of the local variable. This is done by reviewing the output of the below method:
the Index of the variable is within the ( ). You will have to review the entire list of methods, then compare back to the C# decompiled
class to find the right one. In the example above, I want to find a string called "text". I know that there's a few other local variables
of various types right above the one I want, so I use those as an anchor to find correct index.
Example output:
<snip>
EntityItem :: EntityItem (28)
BlockValue :: BlockValue (29)
ProjectileMoveScript :: ProjectileMoveScript (30)
ThrownWeaponMoveScript :: ThrownWeaponMoveScript (31)
System.String :: System.String (32) // This is the one we want
<snip>
C# Equivalent
EntityItem entityItem = null;
BlockValue blockValue = BlockValue.Air;
ProjectileMoveScript projectileMoveScript = null;
ThrownWeaponMoveScript thrownWeaponMoveScript = null;
string text = null; // Because it matches here.
*/
public static void DisplayLocalVariables(MethodBase method)
{
var body = method.GetMethodBody();
if (body == null)
{
Debug.Log($"No Body in Method: {method.Name}");
return;
}
Debug.Log($"Start Method: {method.Name}");
Debug.Log("========================");
foreach (var field in body.LocalVariables)
{
Debug.Log($"{field} :: {field.LocalIndex}");
}
Debug.Log("========================");
Debug.Log($"End Method: {method.Name}");
}
public static string FindLocalVariable(MethodBase method, int index)
{
var body = method.GetMethodBody();
foreach (var field in body.LocalVariables)
{
if (field.LocalIndex == index)
{
return field.ToString();
}
}
return string.Empty;
}
}

View File

@@ -0,0 +1,531 @@
using UnityEngine.Events;
/*
* Thank you for using my Lockpicking asset! I hope it works great in your game. You can extend it to suit your game by
* manipulating some of the code below. For instance, if your player can have a various level of "lockpicking" skill,
* you may consider multiplying the value of lockGive by their skill, so that a higher skilled player would find it
* easier to open a lock.
*
* Enjoy!
*/
namespace Lockpicking
{
//[ExecuteInEditMode]
[RequireComponent(typeof(LockEmissive))]
public class Cipher : MonoBehaviour
{
[Header("Speed Settings")]
[Tooltip("Maximum shake distance per shake change.")]
[SerializeField]
private float maxShake = 0.5f;
[Tooltip("Amount of time between shake changes when shaking.")]
[SerializeField]
private float shakeTime = 0.1f;
[Tooltip("Speed of the wheel when turning.")]
public float turnSpeed = 180f;
[Header("Lock Setup")]
[Tooltip("Number of symbols on the wheel. Should be spaced evenly.")]
public int symbolCount = 18;
[Tooltip("How close do we have to be to perfectly aligned for the placement of a wheel to count?")]
public float closeEnough = 3f;
[Tooltip("If true, wheels will \"snap\" into a new random spot on setup. If false, they will rotate to those spots.")]
public bool quickReset;
[Tooltip("If true, the active wheel will be highlighted using the emissive map of the material.")]
public bool highlightActiveWheel = true;
[Tooltip("If true, wheels will move to the closest spot when the player stops input movement.")]
public bool moveToClosestSpot;
[Header("Lock Details")]
[Tooltip("When true, the lock has been opened.")]
public bool isOpen;
[Tooltip("When true, the lock will reset itself on awake. Set this to false if you are choosing a specific combination.")]
public bool resetOnAwake;
[Header("Animation Trigger Strings")] public string openTrigger = "Open";
public string closeTrigger = "Close";
// Audio Settings
[Range(0f, 1f)] public float clickVolumeMin = 0.1f;
[Range(0f, 1f)] public float clickVolumeMax = 0.4f;
[Range(0f, 1f)] public float squeekVolumeMin = 0.1f;
[Range(0f, 1f)] public float squeekVolumeMax = 0.4f;
[Range(0, 100)] public int squeekChance = 50;
[Range(0f, 15f)] public float squeekRate = 3.5f;
public float[] unlockAngle; // All the unlock angles for each wheel
public float[] startAngle; // All the starting angles for each wheel
public float[] turnedDistance; // amount each wheel has been turned
public float[] turnedDistancePrev; // amount each wheel has been turned
public bool[] isMoving; // Records whether the wheels are moving
public bool playAudioOnSetup = true;
[Header("Plumbing")] public GameObject[] wheels;
public LocksetAudio audioClick;
public LocksetAudio audioSqueek;
public LocksetAudio audioJiggle;
public LocksetAudio audioJiggle2;
public LocksetAudio audioJiggle3;
public LocksetAudio audioOpen;
// Events
private readonly UnityEvent lockOpen = new UnityEvent();
// Private variables
private int _activeWheel;
private int _closeTrigger;
private LocksetAudio _locksetAudio;
// Private animation hashes
private int _openTrigger;
private Animator animator;
private float getReadyPostReadyTimer = 0.3f;
private bool isReady; // WHen true, player can interact
private bool isShaking; // When true, the lock is shaking (trying to be opened but in the wrong combination)
private LockEmissive lockEmissive;
private Vector3 preshake; // Saves the pre-shake angles
private float shakeTimer; // Counter for the shake Time
// private float _speed;
private float squeekTimer;
public bool VisualizeSolution { get; set; }
public bool VisualizeStart { get; set; }
public float ActiveWheelStart
{
get => startAngle[ActiveWheel()];
set => startAngle[ActiveWheel()] = value;
}
public float ActiveWheelSolution
{
get => unlockAngle[ActiveWheel()];
set => unlockAngle[ActiveWheel()] = value;
}
private void Awake()
{
squeekTimer = squeekRate;
_locksetAudio = GetComponent<LocksetAudio>();
animator = GetComponent<Animator>();
lockEmissive = GetComponent<LockEmissive>();
if (!highlightActiveWheel)
lockEmissive.highlightRenderer = null;
_openTrigger = Animator.StringToHash(openTrigger);
_closeTrigger = Animator.StringToHash(closeTrigger);
if (resetOnAwake) ResetLock();
}
private void Update()
{
// Do actions to get ready, if we are not ready
if (!isReady)
{
GetReady();
if (playAudioOnSetup)
DoAudio();
return;
}
if (getReadyPostReadyTimer > 0)
getReadyPostReadyTimer -= Time.deltaTime;
if (moveToClosestSpot)
MoveToClosestSpot();
ResetMovement();
if (highlightActiveWheel) lockEmissive.SetHighlightRenderer(wheels[_activeWheel].GetComponent<Renderer>());
DoAudio();
}
private void OnValidate()
{
#if UNITY_EDITOR
if (unlockAngle.Length != wheels.Length )
{
Debug.Log("Updating Lengths");
unlockAngle = new float[wheels.Length];
startAngle = new float[wheels.Length];
turnedDistance = new float[wheels.Length];
isMoving = new bool[wheels.Length];
}
if (_visualizeSolution)
SetRotationToSolution();
else if (_visualizeStart)
SetRotationToStart();
else
SetRotationToZero();
for (int i = 0; i < wheels.Length; i++)
{
unlockAngle[i] = ClosestSpot(unlockAngle[i]);
}
#endif
}
public int ActiveWheel()
{
return _activeWheel;
}
public void EditorOnValidate()
{
#if UNITY_EDITOR
EditorUtility.SetDirty(this);
#endif
OnValidate();
}
private void SetRotationToSolution()
{
for (var i = 0; i < wheels.Length; i++)
{
var eulerAngles = wheels[i].transform.localEulerAngles;
eulerAngles.x = unlockAngle[i];
wheels[i].transform.localEulerAngles = eulerAngles;
}
}
private void SetRotationToStart()
{
for (var i = 0; i < wheels.Length; i++)
{
var eulerAngles = wheels[i].transform.localEulerAngles;
eulerAngles.x = startAngle[i];
wheels[i].transform.localEulerAngles = eulerAngles;
}
}
private void SetRotationToZero()
{
for (var i = 0; i < wheels.Length; i++) wheels[i].transform.localEulerAngles = new Vector3();
}
public float DistanceLeft(int i)
{
if (turnedDistance.Length >= i + 1)
{
var left = unlockAngle[i] - turnedDistance[i];
if (left < -350)
left += 360;
if (left > 350)
left -= 360;
return left;
}
return 9999;
}
private void DoAudio()
{
for (var i = 0; i < turnedDistance.Length; i++)
{
var turnSpeed = 0f;
if (turnedDistance[i] != turnedDistancePrev[i])
{
if (getReadyPostReadyTimer <= 0 || playAudioOnSetup)
{
if (audioSqueek)
// Squeek
if (squeekRate > 0)
{
squeekTimer -= Time.deltaTime;
if (squeekTimer <= 0)
{
if (UnityEngine.Random.Range(0, 100) < squeekChance)
{
//Log.Out("Cipher-DoAudio PLAY SOUND: audioSqueek");
audioSqueek.PlayAudioClip(UnityEngine.Random.Range(squeekVolumeMin, squeekVolumeMax));
}
squeekTimer = squeekRate;
}
}
if (audioClick)
{
// Clicks
turnSpeed = turnedDistance[i] - turnedDistancePrev[i];
var turnedMod = turnedDistance[i] % (360 / symbolCount);
var turnedModPrev = turnedDistancePrev[i] % (360 / symbolCount);
if ((turnSpeed > 0 || turnSpeed < -350) && (turnedMod < turnedModPrev || turnedMod > 0 && turnedModPrev < 0))
{
//Log.Out("Cipher-DoAudio PLAY SOUND: audioClick 1");
audioClick.PlayAudioClip(UnityEngine.Random.Range(clickVolumeMin, clickVolumeMax));
}
else if ((turnSpeed < 0 || turnSpeed > 350) && (turnedMod > turnedModPrev || turnedMod < 0 && turnedModPrev > 0))
{
//Log.Out("Cipher-DoAudio PLAY SOUND: audioClick 2");
audioClick.PlayAudioClip(UnityEngine.Random.Range(clickVolumeMin, clickVolumeMax));
}
else if (turnedMod == 0)
{
//Log.Out("Cipher-DoAudio PLAY SOUND: audioClick 3");
audioClick.PlayAudioClip(UnityEngine.Random.Range(clickVolumeMin, clickVolumeMax));
}
}
}
turnedDistancePrev[i] = turnedDistance[i];
}
}
}
public int NextWheelIndex()
{
return _activeWheel + 1 < wheels.Length ? _activeWheel + 1 : 0;
}
public int PrevWheelIndex()
{
return _activeWheel - 1 < 0 ? wheels.Length - 1 : _activeWheel - 1;
}
public void SelectWheel(int value)
{
_activeWheel = value < wheels.Length && value >= 0 ? value : 0;
}
public void ResetMovement()
{
for (var i = 0; i < wheels.Length; i++) isMoving[i] = false;
}
public void MoveToClosestSpot()
{
for (var i = 0; i < wheels.Length; i++)
// Only run this if the wheel is not moving (i.e. being moved by the player)
if (!isMoving[i])
{
if (turnedDistance[i] < 0) turnedDistance[i] += 360;
/*
float smallestDistance = 360f;
float closestRotation = 0f;
for (int d = 0; d < 360 / symbolCount; d++)
{
float diff = d * (360 / symbolCount);
if (Mathf.Abs(Mathf.Abs(turnedDistance[i]) - diff) < smallestDistance)
{
smallestDistance = Mathf.Abs(Mathf.Abs(turnedDistance[i]) - diff);
closestRotation = diff;
}
}
MoveTowards(i, closestRotation);
*/
MoveTowards(i, ClosestSpot(Mathf.Abs(turnedDistance[i])));
}
}
private float ClosestSpot(float value)
{
var smallestDistance = 360f;
var closestRotation = 0f;
for (var d = -360; d < 360 / symbolCount; d++)
{
float diff = d * (360 / symbolCount);
if (Mathf.Abs(value - diff) < smallestDistance)
{
smallestDistance = Mathf.Abs(value - diff);
closestRotation = diff;
}
}
return closestRotation;
}
public void TryOpen()
{
if (!isShaking)
preshake = transform.localEulerAngles;
if (!isOpen)
{
for (var i = 0; i < wheels.Length; i++)
if (Mathf.Abs(DistanceLeft(i)) > closeEnough)
{
Shake();
return;
}
isOpen = true;
animator.SetTrigger(_openTrigger);
//Log.Out("Lockpicking-TryOpen PLAY SOUND: audioPadlockOpen");
audioOpen.PlayOnce();
// Invoke the event for any other scripts that are listening
lockOpen.Invoke();
}
}
public void StopShaking()
{
transform.localEulerAngles = preshake;
isShaking = false;
if (audioJiggle)
{
audioJiggle.StopLoop();
if (audioJiggle2 != null)
audioJiggle2.StopLoop();
if (audioJiggle3 != null)
audioJiggle3.StopLoop();
}
}
private void Shake()
{
if (!isShaking)
isShaking = true;
shakeTimer -= Time.deltaTime;
if (shakeTimer <= 0)
{
// Start with the current values
var newShake = preshake;
// Add some modification
//newShake.z += UnityEngine.Random.Range(-maxShake, maxShake);
newShake.x += UnityEngine.Random.Range(-maxShake, maxShake);
newShake.y += UnityEngine.Random.Range(-maxShake, maxShake);
// Set the value + modification
transform.localEulerAngles = newShake;
// Reset the timer
shakeTimer = shakeTime;
}
if (audioJiggle)
{
audioJiggle.PlayLoop();
if (audioJiggle2 != null)
audioJiggle2.PlayLoop();
if (audioJiggle3 != null)
audioJiggle3.PlayLoop();
}
}
public void GetReady()
{
var readyCount = 0;
for (var i = 0; i < wheels.Length; i++)
{
var speed = 1;
var distanceLeft = startAngle[i] - turnedDistance[i];
if (VisualizeSolution) distanceLeft = unlockAngle[i] - turnedDistance[i];
if (distanceLeft < 0) speed = -1;
var turnAmount = speed * turnSpeed * Time.deltaTime;
if (Mathf.Abs(distanceLeft) < Mathf.Abs(turnAmount))
{
turnAmount = distanceLeft;
readyCount++;
}
if (quickReset || VisualizeSolution)
{
turnAmount = distanceLeft;
readyCount++;
}
SetTurnedDistance(i, turnAmount);
RotateWheel(wheels[i].transform, turnAmount);
}
if (readyCount == wheels.Length)
isReady = true;
}
public void MoveActiveWheel(int speed)
{
MoveWheel(_activeWheel, speed);
}
public void MoveWheel(int i, int speed, float destination = 999f)
{
if (isReady)
{
isMoving[i] = true; // Mark this wheel as currently moving
var turnAmount = speed * turnSpeed * Time.deltaTime;
if (destination != 999)
if (Mathf.Abs(destination - turnedDistance[i]) < Mathf.Abs(turnAmount))
turnAmount = destination - turnedDistance[i];
SetTurnedDistance(i, turnAmount);
RotateWheel(wheels[i].transform, turnAmount);
}
}
private void RotateWheel(Transform transform, float value)
{
transform.Rotate(value, 0.0f, 0.0f, Space.Self);
}
public void MoveTowards(int i, float closestRotation)
{
if (turnedDistance[i] > closestRotation)
MoveWheel(i, -1, closestRotation);
else
MoveWheel(i, 1, closestRotation);
}
public void SetTurnedDistance(int i, float distance)
{
turnedDistance[i] += distance;
if (turnedDistance[i] > 360)
turnedDistance[i] -= 360;
if (turnedDistance[i] < -360)
turnedDistance[i] += 360;
}
public void ResetLock()
{
Debug.Log("ResetLock");
isOpen = false;
isReady = false;
animator.SetTrigger(_closeTrigger);
// Shuffle each wheel
for (var i = 0; i < wheels.Length; i++)
{
unlockAngle[i] = RandomAngle();
startAngle[i] = RandomAngle();
}
}
private float RandomAngle()
{
return UnityEngine.Random.Range(0, symbolCount) * (360 / symbolCount) - 180;
}
}
}

View File

@@ -0,0 +1,73 @@
using Lockpicking;
using UnityEngine.Serialization;
public class CipherControls : MonoBehaviour
{
[FormerlySerializedAs("cypher")] public Cipher cipher;
// Update is called once per frame
private void Update()
{
if (cipher.gameObject.activeSelf)
InputControls();
}
private void InputControls()
{
if (Input.GetKeyDown(KeyCode.Tab))
{
if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift))
cipher.SelectWheel(cipher.PrevWheelIndex());
else
cipher.SelectWheel(cipher.NextWheelIndex());
}
if (Input.GetKeyDown(KeyCode.LeftArrow))
cipher.SelectWheel(cipher.PrevWheelIndex());
if (Input.GetKeyDown(KeyCode.RightArrow))
cipher.SelectWheel(cipher.NextWheelIndex());
if (Input.GetKey(KeyCode.UpArrow))
cipher.MoveActiveWheel(-1);
if (Input.GetKey(KeyCode.DownArrow))
cipher.MoveActiveWheel(1);
if (Input.GetKey(KeyCode.Q))
cipher.MoveWheel(0, -1);
if (Input.GetKey(KeyCode.A))
cipher.MoveWheel(0, 1);
if (Input.GetKey(KeyCode.W))
cipher.MoveWheel(1, -1);
if (Input.GetKey(KeyCode.S))
cipher.MoveWheel(1, 1);
if (Input.GetKey(KeyCode.E))
cipher.MoveWheel(2, -1);
if (Input.GetKey(KeyCode.D))
cipher.MoveWheel(2, 1);
if (Input.GetKey(KeyCode.R))
cipher.MoveWheel(3, -1);
if (Input.GetKey(KeyCode.F))
cipher.MoveWheel(3, 1);
if (Input.GetKey(KeyCode.T))
cipher.MoveWheel(4, -1);
if (Input.GetKey(KeyCode.G))
cipher.MoveWheel(4, 1);
if (Input.GetKey(KeyCode.Y))
cipher.MoveWheel(5, -1);
if (Input.GetKey(KeyCode.H))
cipher.MoveWheel(5, 1);
}
}

View File

@@ -0,0 +1,79 @@
//using UnityEngine;
//using UnityEngine.UI;
//using Lockpicking;
//using UnityEngine.Serialization;
//public class CipherDemoUI : MonoBehaviour
//{
// // Set up references to the Text objects
// public Text wheel1;
// public Text wheel2;
// public Text wheel3;
// public Text wheel4;
// public Text wheel5;
// public Text wheel6;
// public Cipher cipher;
// public Color highlightColor = new Color(255f,0f,171f,1f);
// void Start()
// {
//// ResetLock();
// }
// void Update()
// {
// DrawText();
// }
// private void DrawText()
// {
// if (Input.GetKey(KeyCode.O))
// {
// cipher.TryOpen();
// }
// if (Input.GetKeyUp(KeyCode.O))
// {
// cipher.StopShaking();
// }
// if (Mathf.Abs(cipher.DistanceLeft(0)) < cipher.closeEnough)
// wheel1.color = highlightColor;
// else
// wheel1.color = Color.white;
// if (Mathf.Abs(cipher.DistanceLeft(1)) < cipher.closeEnough)
// wheel2.color = highlightColor;
// else
// wheel2.color = Color.white;
// if (Mathf.Abs(cipher.DistanceLeft(2)) < cipher.closeEnough)
// wheel3.color = highlightColor;
// else
// wheel3.color = Color.white;
// if (Mathf.Abs(cipher.DistanceLeft(3)) < cipher.closeEnough)
// wheel4.color = highlightColor;
// else
// wheel4.color = Color.white;
// if (Mathf.Abs(cipher.DistanceLeft(4)) < cipher.closeEnough)
// wheel5.color = highlightColor;
// else
// wheel5.color = Color.white;
// if (Mathf.Abs(cipher.DistanceLeft(5)) < cipher.closeEnough)
// wheel6.color = highlightColor;
// else
// wheel6.color = Color.white;
// wheel1.text = "" + cipher.DistanceLeft(0);
// wheel2.text = "" + cipher.DistanceLeft(1);
// wheel3.text = "" + cipher.DistanceLeft(2);
// wheel4.text = "" + cipher.DistanceLeft(3);
// wheel5.text = "" + cipher.DistanceLeft(4);
// wheel6.text = "" + cipher.DistanceLeft(5);
// }
// public void ResetLock()
// {
// cipher.ResetLock();
// }
//}

View File

@@ -0,0 +1,773 @@
using UnityEngine.Events;
/*
* Thank you for using my Lockpicking asset! I hope it works great in your game. You can extend it to suit your game by
* manipulating some of the code below. For instance, if your player can have a various level of "lockpicking" skill,
* you may consider multiplying the value of lockGive by their skill, so that a higher skilled player would find it
* easier to open a lock.
*
* Enjoy!
*/
namespace Lockpicking
{
[RequireComponent(typeof(LockEmissive))]
public class Keyhole : MonoBehaviour
{
// 7 Days To Die stuff
public int NumLockPicks;
public EntityPlayer player;
public BlockValue blockValue;
[Header("Plumbing")] public GameObject keyhole; // The keyhole with lockpick A that turns the entire keyhole object to open it
public GameObject lockpickObject; // The lockpick that turns to match the secret lockAngle
public Animator lockpickAnimator; // Animator on the lockpick in the lockpickObject
public Animator _padlockAnimator;
public GameObject padlock1; // Link to the padlock 1 game object
public GameObject button;
public LocksetAudio audioTurnClick;
public LocksetAudio audioSqueek;
public LocksetAudio audioOpen;
public LocksetAudio audioJiggle;
public LocksetAudio audioJiggle2;
public LocksetAudio audioJiggle3;
public LocksetAudio audioPadlockOpen;
public LocksetAudio audioPadlockJiggle;
public LocksetAudio audioLockpickBreak;
public LocksetAudio audioLockpickEnter;
public LocksetAudio audioLockpickClick;
// Audio Settings
[Range(0f, 1f)] public float clickVolumeMin = 0.1f;
[Range(0f, 1f)] public float clickVolumeMax = 0.4f;
[Range(0, 100)] public int clickChance = 100;
[Range(0f, 15f)] public float clickRate = 10f;
[Range(0f, 1f)] public float squeekVolumeMin = 0.1f;
[Range(0f, 1f)] public float squeekVolumeMax = 0.4f;
[Range(0, 100)] public int squeekChance = 50;
[Range(0f, 360f)] public float squeekRate = 20f;
public bool buttonDown;
private LockEmissive _lockEmissive; // Link to the lockEmissive script on this object
private float _lockpickAnglePrev;
// Private variables
private float breakCounter; // Counter for taking a break after a broken lockpick
public float breakTimeCounter;
private bool isShaking; // Whether we are currently shaking or not
private Vector3 preshakeKeyhole; // Saves the pre-shake angles
private Vector3 preshakeLockpick; // Saves the pre-shake angles
private float shakeTimer; // Counter for the shake Time
private float squeekTimer;
private void Awake()
{
squeekTimer = squeekRate;
_pickAnglesDefault = LockpickAngles();
_lockEmissive = gameObject.GetComponent<LockEmissive>();
if (_lockEmissive == null)
gameObject.AddComponent<LockEmissive>();
if (padlock1 != null)
{
_padlockAnimator = padlock1.gameObject.GetComponent<Animator>();
if (_padlockAnimator == null)
_padlockAnimator = padlock1.gameObject.AddComponent<Animator>();
}
_closeTrigger = Animator.StringToHash(closeTrigger);
_openTrigger = Animator.StringToHash(openTrigger);
_lockpickBreakTrigger = Animator.StringToHash(lockPickBreakTrigger);
_lockpickInsertTrigger = Animator.StringToHash(lockPickInsertTrigger);
if (resetOnAwake)
ResetLock();
}
private void Update()
{
if (NumLockPicks > 0)
{
PassValuesToEmissiveScript();
if (BreakingForAnimation())
return;
HandlePlayerInput();
}
else
{
RefreshLockPicks();
}
}
private void OnEnable()
{
RefreshLockPicks();
ResetLock();
if (NumLockPicks > 0)
UpdateLockPicks(true);
else
UpdateLockPicks(false);
}
private void OnDisable()
{
NumLockPicks = 0;
}
private void OnValidate()
{
LockAngle = LockAngle;
LockGive = LockGive;
CloseDistance = CloseDistance;
_pickAngleMin = Mathf.Clamp(_pickAngleMin, minLockAngle, maxLockAngle);
_pickAngleMax = Mathf.Clamp(_pickAngleMax, minLockAngle, maxLockAngle);
minGiveAmount = Mathf.Clamp(minGiveAmount, 1f, 360f);
maxGiveAmount = Mathf.Clamp(maxGiveAmount, 1f, 360f);
minCloseDistance = Mathf.Clamp(minCloseDistance, 5f, 360f);
maxCloseDistance = Mathf.Clamp(maxCloseDistance, 5f, 360f);
}
public float BreakTimeCounter()
{
return breakTimeCounter;
}
public float LockPickAngle()
{
return GetAngle(LockpickAngles().z);
}
public float KeyholeAngle()
{
return GetAngle(KeyholeAngles().z);
}
public void EditorOnValidate()
{
#if UNITY_EDITOR
EditorUtility.SetDirty(this);
#endif
OnValidate();
}
private void UpdateLockPicks(bool enable)
{
// Hide the lock picks or show them.
keyhole.transform.FindInChilds("LockpickB (Turnable)").gameObject.SetActive(enable);
keyhole.transform.FindInChilds("LockpickA").gameObject.SetActive(enable);
}
private void RefreshLockPicks()
{
if (player == null)
return;
var uiforPlayer = LocalPlayerUI.GetUIForPlayer(player as EntityPlayerLocal);
var playerInventory = uiforPlayer.xui.PlayerInventory;
var item = ItemClass.GetItem("resourceLockPick");
if (item != null)
NumLockPicks = playerInventory.GetItemCount(item);
if (NumLockPicks > 0)
UpdateLockPicks(true);
else
UpdateLockPicks(false);
}
private void HandlePlayerInput()
{
if (_lockIsOpen) return;
if (openPressure > 0)
{
TryToTurnKeyhole();
}
else
{
StopShaking();
ReturnKeyholeToDefaultPosition();
TurnLockpick(turnSpeedLockpick * lockpickPressure);
}
}
private bool BreakingForAnimation()
{
if (breakCounter > 0f)
{
breakCounter -= Time.deltaTime;
ReturnKeyholeToDefaultPosition();
return true;
}
return false;
}
private void ReturnKeyholeToDefaultPosition()
{
TurnKeyhole(-returnSpeedKeyhole);
}
private void TryToTurnKeyhole()
{
if (LockCanTurn())
{
TurnKeyhole(turnSpeedKeyhole * openPressure);
if (KeyholeTurnValue() <= 0 && LockpickIsInPosition())
OpenLock();
}
else
{
Shake();
}
}
private void PassValuesToEmissiveScript()
{
_lockEmissive.breakpointValue = Mathf.Clamp(breakTimeCounter / breakTime, 0, 1);
_lockEmissive.successValue = Mathf.Clamp(KeyholeTurnValue(), 0, 1);
}
private bool LockpickIsInPosition()
{
return LockPickAngle() < _lockAngle + _lockGive && LockPickAngle() > _lockAngle - _lockGive;
}
private void Shake()
{
// If we are not already shaking, save the original rotations.
if (!isShaking)
{
if (audioPadlockJiggle && padlock1 != null && padlock1.activeSelf)
{
audioPadlockJiggle.PlayLoop();
}
else
{
if (audioJiggle)
audioJiggle.PlayLoop();
if (audioJiggle2 != null)
audioJiggle2.PlayLoop();
if (audioJiggle3 != null)
audioJiggle3.PlayLoop();
}
preshakeKeyhole = KeyholeAngles();
preshakeLockpick = LockpickAngles();
isShaking = true;
}
// Check breakTimeCounter to stop shaking at the right time
breakTimeCounter += Time.deltaTime;
//Log.Out($"Break Time Counter: {breakTimeCounter} Total BreakTime: {breakTime}");
if (breakTimeCounter > breakTime)
{
StopShaking();
BreakLockpick();
return;
}
shakeTimer -= Time.deltaTime;
if (shakeTimer <= 0)
{
// Start with the current values
var newShakeKeyhole = preshakeKeyhole;
var newShakeLockpick = preshakeLockpick;
// Add some modification
newShakeKeyhole.z += UnityEngine.Random.Range(-maxShake, maxShake);
newShakeLockpick.z += UnityEngine.Random.Range(-maxShake, maxShake);
// Set the value + modification
SetKeyholeAngles(newShakeKeyhole);
SetLockpickAngles(newShakeLockpick);
// Reset the timer
shakeTimer = shakeTime;
}
}
private void StopShaking()
{
if (isShaking)
{
if (audioPadlockJiggle && padlock1 != null && padlock1.activeSelf)
{
audioPadlockJiggle.StopLoop();
}
else if (audioJiggle)
{
audioJiggle.StopLoop();
if (audioJiggle2 != null)
audioJiggle2.StopLoop();
if (audioJiggle3 != null)
audioJiggle3.StopLoop();
}
SetKeyholeAngles(preshakeKeyhole);
SetLockpickAngles(preshakeLockpick);
isShaking = false;
}
}
private void SetKeyholeAngles(Vector3 value)
{
keyhole.transform.localEulerAngles = value;
}
private void SetLockpickAngles(Vector3 value)
{
lockpickObject.transform.localEulerAngles = value;
}
public void BreakLockpick()
{
//Log.Out("Lockpicking-BreakLockpick START");
if (audioLockpickBreak)
{
//Log.Out("Lockpicking-BreakLockpick PLAY SOUND: audioLockpickBreak");
//Log.Out("Lockpicking-BreakLockpick audioLockpickBreak.name: " + audioLockpickBreak.name);
//audioLockpickBreak.DelayPlay(1f);
audioLockpickBreak.PlayOnce();
}
breakCounter = breakPause; // Set so we can't do any actions for a short time
breakTimeCounter = 0f; // Reset the breakCounter
ResetLockpickPosition(); // Reset the lockpick position
lockpickAnimator.SetTrigger(_lockpickBreakTrigger); // Play the break animation
lockpickBroke.Invoke(); // Invoke this event in case other scripts are listening
/*if (audioLockpickEnter && audioLockpickEnter.isActiveAndEnabled)
{
Log.Out("Lockpicking-BreakLockpick PLAY SOUND: audioLockpickEnter");
audioLockpickEnter.DelayPlay(1f);
}*/
// Remove the broke pick lock.
if (player != null)
{
//Log.Out("Lockpicking-BreakLockpick REMOVE LOCK PICK");
var playerUI = (player as EntityPlayerLocal).PlayerUI;
var playerInventory = playerUI.xui.PlayerInventory;
var item = ItemClass.GetItem("resourceLockPick");
var itemStack = new ItemStack(item, 1);
playerInventory.RemoveItem(itemStack);
RefreshLockPicks();
}
if (NumLockPicks > 0)
{
//Log.Out("Lockpicking-BreakLockpick 1");
UpdateLockPicks(true);
}
else
{
//Log.Out("Lockpicking-BreakLockpick 2");
UpdateLockPicks(false);
}
}
/// <summary>
/// Call this when the lock is open successfully.
/// </summary>
public void OpenLock()
{
if (!_lockIsOpen)
{
if (audioPadlockOpen && padlock1 != null && padlock1.activeInHierarchy)
{
//Log.Out("Lockpicking-OpenLock PLAY SOUND: audioPadlockOpen");
audioPadlockOpen.PlayOnce();
if (_padlockAnimator != null)
_padlockAnimator.SetTrigger(_openTrigger);
}
else if (audioOpen)
{
//Log.Out("Lockpicking-OpenLock PLAY SOUND: audioOpen");
audioOpen.PlayOnce();
}
// Invoke the event for any other scripts that are listening
lockOpen.Invoke();
_lockIsOpen = true;
}
}
private void DoSqueekAudio(float speed)
{
if (audioSqueek)
if (squeekRate > 0)
{
squeekTimer -= Mathf.Abs(speed) * Time.deltaTime;
if (squeekTimer <= 0)
{
if (UnityEngine.Random.Range(0, 100) < squeekChance)
{
//Log.Out("Keyhole-DoSqueekAudio PLAY SOUND: audioSqueek");
audioSqueek.PlayAudioClip(UnityEngine.Random.Range(squeekVolumeMin, squeekVolumeMax));
}
squeekTimer = squeekRate;
}
}
}
private float GetAngle(float eulerAngle)
{
var angle = eulerAngle;
angle %= 360;
if (angle > 180)
angle -= 360;
return angle;
}
private void TurnLockpick(float speed)
{
// If we are at or outside of our max range, return
if (LockPickAngle() >= _pickAngleMax && speed > 0 || LockPickAngle() <= _pickAngleMin && speed < 0)
return;
// Set the new angle
var newAngle = new Vector3(LockpickAngles().x, LockpickAngles().y,
LockpickAngles().z + speed * Time.deltaTime);
SetLockpickAngles(newAngle);
DoClickAudio(speed, newAngle);
}
private void DoClickAudio(float speed, Vector3 newAngle)
{
var angleMod = newAngle.z % clickRate;
var prevMod = _lockpickAnglePrev % clickRate;
if (speed > 0 && angleMod < prevMod || speed < 0 && angleMod > prevMod)
if (audioTurnClick != null)
{
//Log.Out("Keyhole-DoClickAudio PLAY SOUND: audioTurnClick");
audioTurnClick.PlayAudioClip(UnityEngine.Random.Range(clickVolumeMin, clickVolumeMax));
}
_lockpickAnglePrev = newAngle.z;
}
private Vector3 LockpickAngles()
{
return lockpickObject.transform.localEulerAngles;
}
private void TurnKeyhole(float speed)
{
// If we are at or outside of our max range, return
if (KeyholeAngle() >= _keyholeAngleMax && speed > 0 || KeyholeAngle() <= _keyholeAngleDefault && speed < 0)
return;
// Set the new angle
SetKeyholeAngles(new Vector3(KeyholeAngles().x, KeyholeAngles().y, KeyholeAngles().z + speed * Time.deltaTime));
DoSqueekAudio(speed);
}
private Vector3 KeyholeAngles()
{
return keyhole.transform.localEulerAngles;
}
public float KeyholeTurnValue()
{
return (_keyholeAngleMax - KeyholeAngle()) / (_keyholeAngleMax - _keyholeAngleDefault);
}
public void SetLock(float newLockAngle, float newLockGive, float newCloseDistance)
{
_lockAngle = newLockAngle;
_lockGive = newLockGive;
_closeDistance = newCloseDistance;
if (audioLockpickEnter && audioLockpickEnter.isActiveAndEnabled)
{
//Log.Out("Lockpicking-BreakLockpick PLAY SOUND: audioLockpickEnter");
audioLockpickEnter.DelayPlay(0.7f);
}
ResetLockpickPosition();
lockpickAnimator.SetTrigger(_lockpickInsertTrigger); // Play the animation
}
public void SetLock(float lockAngleMin, float lockAngleMax, float lockGiveMin,
float lockGiveMax, float closeDistanceMin, float closeDistanceMax)
{
SetLock(UnityEngine.Random.Range(lockAngleMin, lockAngleMax),
UnityEngine.Random.Range(lockGiveMin, lockGiveMax),
UnityEngine.Random.Range(closeDistanceMin, closeDistanceMax));
}
public void ResetLock()
{
LockIsOpen = false;
ProgressionValue progressionValue = null;
var difficulty = 0;
var prevBreakTime = breakTime;
var prevMaxGive = maxGiveAmount;
var healthLeft = blockValue.Block.MaxDamage - blockValue.damage;
// Adjust the difficulty based on the lock damage
if (healthLeft <= 10000)
difficulty = 3;
if (healthLeft <= 2500)
difficulty = 2;
if (healthLeft <= 400)
difficulty = 1;
if (healthLeft <= 200)
difficulty = 0;
// give more time to avoid breaking pick locks.
if (player != null)
{
var secureBlock = Block.list[blockValue.type];
if (secureBlock != null)
{
if (secureBlock.Properties.Values.ContainsKey("LockPickDifficulty"))
difficulty = int.Parse(secureBlock.Properties.Values["LockPickDifficulty"]);
}
var maxGiveAmounts = "10,8,6,4".Split(','); //Configuration.GetPropertyValue("AdvancedLockpicking", "MaxGiveAmount").Split(',');
var breakTimes = "1.2,1.0,.8,.6".Split(','); //Configuration.GetPropertyValue("AdvancedLockpicking", "BreakTime").Split(',');
// Default values.
maxGiveAmount = 10f;
breakTime = 2f;
if (maxGiveAmounts.Length >= difficulty)
{
maxGiveAmount = StringParsers.ParseFloat(maxGiveAmounts[difficulty]);
breakTime = StringParsers.ParseFloat(breakTimes[difficulty]);
}
progressionValue = player.Progression.GetProgressionValue("perkLockPicking");
if (progressionValue is { Level: > 0 })
{
prevBreakTime = breakTime;
prevMaxGive = maxGiveAmount;
breakTime += (float)progressionValue.Level / 5;
maxGiveAmount += (float)progressionValue.Level * 2;
}
}
SetLock(minLockAngle, maxLockAngle,
minGiveAmount, maxGiveAmount,
minCloseDistance, maxCloseDistance);
if (GamePrefs.GetBool(EnumGamePrefs.DebugMenuEnabled))
{
var progression = " Progression: ";
progression = progressionValue != null ? $"{progression} Level {progressionValue.Level} BreakTime Before: {prevBreakTime} MaxGiveAmount Before: {prevMaxGive}" : $"{progression} N/A";
/*Log.Out("");
Log.Out("-------------------------------------------");
Log.Out($"Configured Lock Pick: Break Time: {breakTime} MaxGiveAmount: {maxGiveAmount} Lock Difficulty: {difficulty} Block Damage: {healthLeft} {progression} ");
Log.Out("Lock Angle: " + _lockAngle + " Give: " + _lockGive + " Close distance: " + _closeDistance);
Log.Out($"MinLockAngle: {minLockAngle} MaxLockAngle: {maxLockAngle} Min Give Amount: {minGiveAmount} Max Give Amount: {maxGiveAmount} Min Close distance: {minCloseDistance} Max Close Distance: {maxCloseDistance}");
Log.Out(" To Adjust, run console command lock 2 34");
Log.Out(" For breaktime of 2 and maxgive of 34");
Log.Out("-------------------------------------------");*/
}
RefreshLockPicks();
}
public void ResetLockpickPosition()
{
SetLockpickAngles(_pickAnglesDefault);
if (_padlockAnimator != null)
_padlockAnimator.SetTrigger(_closeTrigger);
}
public bool LockCanTurn()
{
return !(LockPickAngle() < GetAngle(_lockAngle) - _lockGive - _closeDistance * KeyholeTurnValue()) &&
!(LockPickAngle() > GetAngle(_lockAngle) + _lockGive + _closeDistance * KeyholeTurnValue());
}
public bool LockComplete()
{
if (!LockIsOpen)
return false;
if (padlock1 != null && padlock1.activeInHierarchy)
{
if (_padlockAnimator != null)
// If the padlock is fully animated
if (_padlockAnimator.GetCurrentAnimatorStateInfo(0).IsName("Padlock1Opened"))
return true;
}
else
{
return !audioOpen.isAudioPlaying();
}
return false;
}
#region ThirdParty
// Events
private readonly UnityEvent lockpickBroke = new UnityEvent();
private readonly UnityEvent lockOpen = new UnityEvent();
[Header("Player Input")] public float openPressure;
public float lockpickPressure;
[Header("Speed Settings")]
[Tooltip("Speed of the lockpick when input value is full.")]
[Range(1f, 720f)]
public float turnSpeedLockpick = 50f;
[Tooltip("Speed of the entire keyhole when input value is full.")]
[Range(1f, 720f)]
public float turnSpeedKeyhole = 50f;
[Tooltip("Speed at which the lock will return to normal when the input value is 0.")]
[Range(1f, 720f)]
public float returnSpeedKeyhole = 150f;
[Tooltip("Maximum shake distance per shake change.")]
[SerializeField]
private float maxShake = 0.5f;
[Tooltip("Amount of time between shake changes when shaking.")]
[SerializeField]
private float shakeTime = 0.1f;
[Header("Pick Settings")]
[Tooltip("Starting angle of the lock pick.")]
[SerializeField]
private Vector3 _pickAnglesDefault;
[Tooltip("Minimum angle the lock pick can travel to.")]
[SerializeField]
private float _pickAngleMin = -90f;
[Tooltip("Maximum angle the lock pick can travel to.")]
[SerializeField]
private float _pickAngleMax = 90f;
[Header("Keyhole Settings")]
[Tooltip("Starting angle of the keyhole.")]
[SerializeField]
private float _keyholeAngleDefault = 0f;
[Tooltip("Maximum angle of the keyhole. At this angle, the lock will open.")]
[SerializeField]
private float _keyholeAngleMax = 90f;
[Header("Lock Settings")]
[Tooltip("If true, lock details will be randomized on awake")]
public bool resetOnAwake = true;
[Tooltip("Minimum angle the lock can be set to.")]
[Range(0f, 180f)]
public float minLockAngle = -90f;
[Tooltip("Maximum angle the lock can be set to.")]
[Range(0f, 180f)]
public float maxLockAngle = 90f;
[Tooltip("Minimum distance (plus and minus) from the lock angle that the lock will open.")]
[Range(1f, 180f)]
public float minGiveAmount = 1f;
[Tooltip("Maximum distance (plus and minus) from the lock angle that the lock will open.")]
[Range(1f, 180f)]
public float maxGiveAmount = 30f;
[Tooltip("Minimum distance for the pick to be in for the lock will turn partially.")]
[Range(5f, 180f)]
public float minCloseDistance = 5f;
[Tooltip("Maximum distance for the pick to be in for the lock will turn partially.")]
[Range(5f, 180f)]
public float maxCloseDistance = 10f;
[Tooltip("Amount of time to ignore player input after a lock pick breaks.")]
[Range(0f, 5f)]
public float breakPause = 2f;
[Header("Lock Details")]
[Tooltip("True if the lock is already open (unlocked).")]
[SerializeField]
private bool _lockIsOpen;
public bool LockIsOpen
{
get => _lockIsOpen;
set => _lockIsOpen = value;
}
[Tooltip("The exact angle the lock is set to.")]
private float _lockAngle;
public float LockAngle
{
get => _lockAngle;
set => _lockAngle = Mathf.Clamp(value, minLockAngle, maxLockAngle);
}
[Tooltip("The distance to/from the LockAngle the lock pick needs to be in for the lock to open successfully.")]
[SerializeField]
private float _lockGive;
public float LockGive
{
get => _lockGive;
set => _lockGive = Mathf.Clamp(value, 1, maxGiveAmount);
}
[Tooltip("If the lock pick is within this distance to the angle range which the lock will open, the lock will turn partially when an open attempt is made.")]
[SerializeField]
private float _closeDistance;
public float CloseDistance
{
get => _closeDistance;
set => _closeDistance = Mathf.Clamp(value, 5, maxCloseDistance);
}
[Tooltip("The amount of time before a lock pick breaks when the lock is unable to be opened, but the player is attempting to open it.")]
[Range(0f, 5f)]
public float breakTime = 1f;
[Header("Animation Trigger Strings")] public string openTrigger = "OpenPadlock";
public string closeTrigger = "ClosePadlock";
public string lockPickBreakTrigger = "BreakLockpick1";
public string lockPickInsertTrigger = "InsertLockpick";
// Private animation hashes
private int _openTrigger;
private int _closeTrigger;
private int _lockpickBreakTrigger;
private int _lockpickInsertTrigger;
#endregion
}
}

View File

@@ -0,0 +1,43 @@
//using UnityEngine;
//using UnityEngine.UI;
//using Lockpicking;
//public class KeyholeDemoUI : MonoBehaviour
//{
// // Set up references to the Text objects
// public Text lockAngleText;
// public Text lockRangeText;
// public Text breakTimeText;
// public Text lockpickAngleText;
// public Text keyholeAngleText;
// public Text closeDistanceText;
// public Keyhole lockpick;
// void Start()
// {
// ResetLock();
// }
// void Update()
// {
// DrawText();
// }
// private void DrawText()
// {
// lockAngleText.text = "" + lockpick.LockAngle;
// lockRangeText.text = "" + Mathf.Round(lockpick.LockAngle - lockpick.LockGive) + " to " + Mathf.Round(lockpick.LockAngle + lockpick.LockGive);
// breakTimeText.text = "" + lockpick.BreakTimeCounter;
// lockpickAngleText.text = "" + lockpick.LockPickAngle;
// keyholeAngleText.text = "" + lockpick.KeyholeTurnValue;
// closeDistanceText.text = "" + lockpick.CloseDistance;
// }
// public void ResetLock()
// {
// lockpick.ResetLock();
// }
//}

View File

@@ -0,0 +1,64 @@
using UnityEngine.Events;
[RequireComponent(typeof(Animator))]
public class LockButton : MonoBehaviour
{
[Header("Animation Trigger Strings")] public string pressDownTrigger = "ButtonPressDown";
public string pressUpTrigger = "ButtonPressUp";
[Header("Plumbing")][SerializeField] private Animator animator;
public LocksetAudio audioButtonPressed;
public LocksetAudio audioButtonReset;
private readonly UnityEvent buttonPressed = new UnityEvent();
private readonly UnityEvent buttonReset = new UnityEvent();
private bool _isUp = true;
private int _pressDownTrigger;
private int _pressUpTrigger;
private void Start()
{
animator = GetComponent<Animator>();
}
private void Awake()
{
_pressDownTrigger = Animator.StringToHash(pressDownTrigger);
_pressUpTrigger = Animator.StringToHash(pressUpTrigger);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.P))
ToggleButton();
}
public void ToggleButton()
{
if (_isUp)
{
animator.SetTrigger(_pressDownTrigger);
buttonPressed.Invoke();
//Log.Out("LockButton-ToggleButton PLAY SOUND: audioButtonPressed");
audioButtonPressed.PlayOnce();
}
else
{
animator.SetTrigger(_pressUpTrigger);
buttonReset.Invoke();
//Log.Out("LockButton-ToggleButton PLAY SOUND: audioButtonReset");
audioButtonReset.PlayOnce();
}
_isUp = !_isUp;
}
public void ToggleButton(bool up)
{
if (up && !_isUp || !up && _isUp)
ToggleButton();
}
}

View File

@@ -0,0 +1,71 @@
using Lockpicking;
public class LockControls : MonoBehaviour
{
public Keyhole lockpick;
PlayerActionsLocal playerActions;
private void Update()
{
if (!lockpick)
return;
if (playerActions == null)
{
var player = GameManager.Instance.World.GetPrimaryPlayer();
if (player != null)
playerActions = player.PlayerUI.playerInput;
}
if (lockpick.gameObject.activeSelf) InputControls();
}
private bool Left()
{
if (Input.GetKey(KeyCode.JoystickButton4)) return true;
if (Input.GetKey(KeyCode.A)) return true;
if (Input.GetKey(KeyCode.LeftArrow)) return true;
if (Input.mouseScrollDelta.y < 0) return true;
return false;
}
private bool Right()
{
if (Input.GetKey(KeyCode.JoystickButton5)) return true;
if (Input.GetKey(KeyCode.D)) return true;
if (Input.GetKey(KeyCode.RightArrow)) return true;
if (Input.mouseScrollDelta.y > 0) return true;
return false;
}
private void InputControls()
{
ResetValues();
if (Input.GetKey(KeyCode.Space))
{
if (Input.GetKey(KeyCode.LeftShift) || Input.GetKey(KeyCode.RightShift))
lockpick.openPressure = 0.4f;
else
lockpick.openPressure = 1f;
}
if (playerActions == null) return;
if (Left())
lockpick.lockpickPressure = -1f;
if (Right())
lockpick.lockpickPressure = 1f;
//if (Input.GetKey(KeyCode.LeftArrow))
//else if (Input.GetKey(KeyCode.RightArrow)) lockpick.lockpickPressure = 1f;
}
private void ResetValues()
{
lockpick.openPressure = 0f;
lockpick.lockpickPressure = 0f;
}
}

View File

@@ -0,0 +1,36 @@
//using UnityEngine;
//using UnityEngine.UI;
//public class LockDemoCommonControls : MonoBehaviour
//{
// public GameObject canvasUI;
// public Button[] allTextureButtons;
// void Update()
// {
// if (Input.GetKeyDown(KeyCode.C))
// canvasUI.SetActive(!canvasUI.activeSelf);
// if (Input.GetKeyDown(KeyCode.Alpha1))
// allTextureButtons[0].onClick.Invoke();
// if (Input.GetKeyDown(KeyCode.Alpha2))
// allTextureButtons[1].onClick.Invoke();
// if (Input.GetKeyDown(KeyCode.Alpha3))
// allTextureButtons[2].onClick.Invoke();
// if (Input.GetKeyDown(KeyCode.Alpha4))
// allTextureButtons[3].onClick.Invoke();
// if (Input.GetKeyDown(KeyCode.Alpha5))
// allTextureButtons[4].onClick.Invoke();
// if (Input.GetKeyDown(KeyCode.Alpha6))
// allTextureButtons[5].onClick.Invoke();
// if (Input.GetKeyDown(KeyCode.Alpha7))
// allTextureButtons[6].onClick.Invoke();
// }
//}

View File

@@ -0,0 +1,146 @@
public class LockEmissive : MonoBehaviour
{
[Header("Behavior")] public bool pulse = true;
public bool breakpoint;
public bool success;
public bool off;
[Header("Settings")][Range(0f, 5f)] public float pulseSpeedMod = 0.5f;
[Header("Colors")] public Color hightlightColor = Color.yellow;
public Color[] colors = { Color.cyan, Color.red, Color.yellow, Color.green, Color.blue, Color.magenta, Color.white, Color.grey, Color.yellow, Color.blue };
[Header("Renderers")][SerializeField] private Renderer[] _renderers;
public Renderer highlightRenderer;
// Hidden from Inspector
[HideInInspector] public float breakpointValue;
[HideInInspector] public float successValue;
private int _colorIndex;
// Private variables
private float _pulseValue;
private void Start()
{
}
private void Update()
{
if (off)
{
SetAllMaterials(0f);
}
else if (pulse)
{
_pulseValue = Mathf.PingPong(Time.time * pulseSpeedMod, 1); // Pulse the value over time
SetAllMaterials(_pulseValue); // Set the values
}
else if (breakpoint)
{
SetAllMaterials(breakpointValue); // Set the values
}
else if (success)
{
SetAllMaterials(Mathf.Clamp(Mathf.Abs(successValue - 1), 0, 1)); // Set the values
}
if (highlightRenderer)
SetHighlightMaterial();
}
private Color _activeColor()
{
return colors[_colorIndex];
}
public void SetRenders(Renderer[] newRenders)
{
_renderers = newRenders;
}
public void SetHighlightRenderer(Renderer value)
{
highlightRenderer = value;
}
private void SetHighlightMaterial()
{
// sphereii: disable the high light color
//highlightRenderer.material.SetColor("_EmissionColor", hightlightColor);
highlightRenderer.material.SetColor("_EmissionColor", Color.clear);
}
/// <summary>
/// Sets all materials attached to the renderers array
/// </summary>
/// <param name="value"></param>
private void SetAllMaterials(float value)
{
if (_renderers == null) return;
for (var i = 0; i < _renderers.Length; i++)
if (_renderers[i] != null && _renderers[i].gameObject != null)
if (_renderers[i].gameObject.activeSelf)
SetMaterial(_renderers[i].material, value);
}
/// <summary>
/// Sets a single material
/// </summary>
/// <param name="material"></param>
/// <param name="value"></param>
private void SetMaterial(Material material, float value)
{
var finalColor = _activeColor() * Mathf.LinearToGammaSpace(value);
//material.SetColor("_EmissionColor", finalColor);
// sphereii: give it a nice bronze color
material.SetColor("_EmissionColor", new Color(0.666f, 0.588f, 0.275f, 1f));
}
public void NextColor()
{
_colorIndex = _colorIndex + 1 >= colors.Length ? 0 : _colorIndex + 1;
}
public void PrevColor()
{
_colorIndex = _colorIndex - 1 < 0 ? colors.Length - 1 : _colorIndex - 1;
}
public void SetPulse()
{
pulse = true;
breakpoint = false;
success = false;
off = false;
}
public void SetBreakpoint()
{
pulse = false;
breakpoint = true;
success = false;
off = false;
}
public void SetSuccess()
{
pulse = false;
breakpoint = false;
success = true;
off = false;
}
public void SetOff()
{
pulse = false;
breakpoint = false;
success = false;
off = true;
}
}

View File

@@ -0,0 +1,51 @@
public class LockEmissiveControls : MonoBehaviour
{
public Renderer[] baseplate1;
public Renderer[] baseplate2;
public Renderer[] lock1;
public Renderer[] lock2;
public Renderer[] lock3;
public Renderer[] button;
public Renderer[] padlock1;
public Renderer[] padlock2;
public void SetBaseplate1(Texture newTexture)
{
for (var i = 0; i < baseplate1.Length; i++) baseplate1[i].material.SetTexture("_EmissionMap", newTexture);
}
public void SetBaseplate2(Texture newTexture)
{
for (var i = 0; i < baseplate2.Length; i++) baseplate2[i].material.SetTexture("_EmissionMap", newTexture);
}
public void SetLock1(Texture newTexture)
{
for (var i = 0; i < lock1.Length; i++) lock1[i].material.SetTexture("_EmissionMap", newTexture);
}
public void SetLock2(Texture newTexture)
{
for (var i = 0; i < lock2.Length; i++) lock2[i].material.SetTexture("_EmissionMap", newTexture);
}
public void SetLock3(Texture newTexture)
{
for (var i = 0; i < lock3.Length; i++) lock3[i].material.SetTexture("_EmissionMap", newTexture);
}
public void SetButton(Texture newTexture)
{
for (var i = 0; i < button.Length; i++) button[i].material.SetTexture("_EmissionMap", newTexture);
}
public void SetPadlock1(Texture newTexture)
{
for (var i = 0; i < padlock1.Length; i++) padlock1[i].material.SetTexture("_EmissionMap", newTexture);
}
public void SetPadlock2(Texture newTexture)
{
for (var i = 0; i < padlock2.Length; i++) padlock2[i].material.SetTexture("_EmissionMap", newTexture);
}
}

View File

@@ -0,0 +1,61 @@
public class LockObjectRotation : MonoBehaviour
{
public bool mouseMovementOn;
[SerializeField] private float maxX = 30f; // Max rotation
[SerializeField] private float maxY = 30f; // Max rotation
[SerializeField] private Vector3 mousePosition; // last position of mouse
[SerializeField] private float speed = 2160f; // speed of rotation
[SerializeField] private float startX; // Starting rotation
[SerializeField] private float startY; // Starting rotation
public Camera uiCam;
private void Start()
{
startX = GetAngle(transform.localEulerAngles.x);
startY = GetAngle(transform.localEulerAngles.y);
}
private void Update()
{
MouseMovement();
mousePosition = uiCam.ScreenToViewportPoint(Input.mousePosition); // Save mouse position every frame.
//mousePosition = Camera.main.ScreenToViewportPoint(Input.mousePosition); // Save mouse position every frame.
}
private void MouseMovement()
{
// If we are holding down the left mouse button...
if (Input.GetMouseButton(0) || mouseMovementOn)
{
// Grab the new position of the mouse, 0,0 - 1,1
var newPosition = uiCam.ScreenToViewportPoint(Input.mousePosition);
//Vector3 newPosition = Camera.main.ScreenToViewportPoint(Input.mousePosition);
// Compute the delta in the x and y positions. Do inversion for the yDelta. Note X/Y are flipped.
var horDelta = -(newPosition.x - mousePosition.x) * speed * Time.deltaTime;
var verDelta = (newPosition.y - mousePosition.y) * speed * Time.deltaTime;
// Make sure we don't go over our max range
if (GetAngle(transform.localEulerAngles.x) + verDelta > startX + maxX || GetAngle(transform.localEulerAngles.x) + verDelta < startX - maxX)
verDelta = 0;
if (GetAngle(transform.localEulerAngles.y) + horDelta > startY + maxY || GetAngle(transform.localEulerAngles.y) + horDelta < startY - maxY)
horDelta = 0;
// Compute & assign the new angle
var newAngles = new Vector3(transform.localEulerAngles.x + verDelta,
transform.localEulerAngles.y + horDelta, transform.localEulerAngles.z);
transform.localEulerAngles = newAngles;
}
}
private float GetAngle(float eulerAngle)
{
var angle = eulerAngle;
angle %= 360;
if (angle > 180)
angle -= 360;
return angle;
}
}

View File

@@ -0,0 +1,75 @@
using System.Collections;
[RequireComponent(typeof(AudioSource))]
public class LocksetAudio : MonoBehaviour
{
public AudioClip[] clips;
private float _delay;
private AudioSource audioSource;
public void Awake()
{
audioSource = GetComponent<AudioSource>();
}
public void PlayAudioClip(float volume = 0.5f)
{
if (clips == null)
return;
if (clips.Length > 0)
{
SelectRandomClip();
audioSource.volume = volume;
audioSource.Play();
}
}
public void PlayOnce()
{
SelectRandomClip();
if (audioSource.clip == null)
return;
audioSource.PlayOneShot(audioSource.clip);
}
public bool isAudioPlaying()
{
return audioSource.isPlaying;
}
public void DelayPlay(float delay)
{
_delay = delay;
StartCoroutine("Delay");
}
public IEnumerator Delay()
{
yield return new WaitForSeconds(_delay);
PlayOnce();
}
public void PlayLoop()
{
if (!audioSource.isPlaying)
{
SelectRandomClip();
audioSource.Play();
}
}
public void StopLoop()
{
audioSource.Pause();
}
public void SelectRandomClip()
{
if (clips == null)
return;
if (clips.Length > 0) audioSource.clip = clips[UnityEngine.Random.Range(0, clips.Length)];
}
}

View File

@@ -0,0 +1,175 @@
using Lockpicking;
using System.Collections.Generic;
using Object = UnityEngine.Object;
// This class sites between the thirdparty Keyhole script + prefabs and support scripts.
public class SphereLocks
{
private GameObject _lockPick;
private GameObject _lockPickAsset;
// transforms
private List<string> _transforms = new List<string>();
public void Init(BlockValue blockValue, Vector3i blockPos)
{
// Check if the current block has a pre-defined lockpick prefab
var lockPrefab = "";
if (blockValue.type != 0)
if (blockValue.Block.Properties.Contains("LockPrefab"))
lockPrefab = blockValue.Block.Properties.GetStringValue("LockPrefab");
// Load up the default.
if (string.IsNullOrEmpty(lockPrefab))
{
// If the globally configured lock pick cotnains Lockset01, assume its the default.
lockPrefab = "#@modfolder(Locks):Resources/Locks.unity3d?Lockset01"; // Configuration.GetPropertyValue("AdvancedLockpicking", "LockPrefab");
if (lockPrefab.EndsWith("Lockset01"))
{
var locks = new List<string> { "Lockset01", "Lockset02", "Lockset03", "Lockset04", "padlock01" };
var randomKey = Math.Abs(blockPos.x % 9);
var randomLock = randomKey switch
{
< 1 => "Lockset01",
< 3 => "Lockset02",
< 5 => "Lockset03",
< 7 => "Lockset04",
_ => "padlock01"
};
lockPrefab = lockPrefab.Replace("Lockset01", randomLock);
}
}
if (string.IsNullOrEmpty(lockPrefab))
return;
try
{
_lockPickAsset = DataLoader.LoadAsset<GameObject>(lockPrefab);
_lockPick = Object.Instantiate(_lockPickAsset);
}
catch (Exception ex)
{
Log.Out($"LockPrefab not valid. Falling back to vanilla.");
return;
}
Disable();
// Marked transforms
_transforms = new List<string> { "Baseplate1", "Baseplate2", "ButtonInner", "ButtonInner", "ButtonOuter", "Padlock1_low" };
_transforms.AddRange(new List<string> { "Padlock1_Latch_low", "Lock1Outer", "Lock2Outer", "Lock3Outer", "Lock1Inner", "Lock2Inner", "Lock3Inner" });
if (_lockPick.GetComponent<Keyhole>() == null)
{
// Populate the Keyhole
var keyhole = _lockPick.AddComponent<Keyhole>();
keyhole.keyhole = FindTransform("Keyhole (Turnable)").gameObject;
// attach the lock control the to top level
LockControls lockControl;
if (_lockPick.transform.parent != null)
lockControl = _lockPick.transform.parent.gameObject.AddComponent<LockControls>();
else
lockControl = _lockPick.transform.gameObject.AddComponent<LockControls>();
lockControl.lockpick = keyhole;
// Lock Pick configuration
keyhole.lockpickObject = _lockPick.transform.FindInChilds("LockpickB (Turnable)").gameObject;
keyhole.lockpickAnimator = FindTransform("LockpickB").GetComponent<Animator>();
keyhole.lockpickAnimator.gameObject.SetActive(true);
keyhole.blockValue = blockValue;
var cam = FindTransform("Cam2").GetComponentInChildren<Camera>();
if (cam != null)
{
cam.rect = new Rect(0.25f, 0.25f, 0.5f, 0.5f);
var lockObjectRotation = keyhole.lockpickObject.transform.gameObject.AddComponent<LockObjectRotation>();
lockObjectRotation.uiCam = cam;
}
var padlock = FindTransform("Padlock1");
if (padlock != null)
{
keyhole.padlock1 = padlock.gameObject;
keyhole.audioPadlockJiggle = FindTransform("Audio Padlock Jiggle").gameObject.AddComponent<LocksetAudio>();
keyhole.audioPadlockOpen = FindTransform("Audio Padlock Open").gameObject.AddComponent<LocksetAudio>();
}
// audio configuration
keyhole.audioTurnClick = FindTransform("Audio Turn Click").gameObject.AddComponent<LocksetAudio>();
keyhole.audioSqueek = FindTransform("Audio Squeek").gameObject.AddComponent<LocksetAudio>();
keyhole.audioOpen = FindTransform("Audio Open").gameObject.AddComponent<LocksetAudio>();
keyhole.audioJiggle = FindTransform("Audio Jiggle A").gameObject.AddComponent<LocksetAudio>();
keyhole.audioJiggle2 = FindTransform("Audio Jiggle B").gameObject.AddComponent<LocksetAudio>();
keyhole.audioJiggle3 = FindTransform("Audio Jiggle C").gameObject.AddComponent<LocksetAudio>();
keyhole.audioLockpickBreak = FindTransform("Audio Lockpick Break").gameObject.AddComponent<LocksetAudio>();
keyhole.audioLockpickEnter = FindTransform("Audio Lockpick Enter").gameObject.AddComponent<LocksetAudio>();
keyhole.audioLockpickClick = FindTransform("Audio Lockpick Click").gameObject.AddComponent<LocksetAudio>();
var lockEmissive = _lockPick.AddComponent<LockEmissive>();
// lockEmissive.off = true;
var lstRenders = new List<Renderer>();
var tempRender = new Renderer[12];
foreach (var transform in _transforms)
{
var temp = FindTransform(transform);
if (temp)
lstRenders.Add(FindTransform(transform).GetComponent<MeshRenderer>());
}
lockEmissive.SetRenders(lstRenders.ToArray());
}
Enable();
}
public Keyhole GetScript()
{
return _lockPick != null ? _lockPick.GetComponent<Keyhole>() : null;
}
public bool IsLockOpened()
{
return _lockPick != null && _lockPick.GetComponent<Keyhole>().LockComplete();
}
public void SetPlayer(EntityPlayer player)
{
if (_lockPick != null) _lockPick.GetComponent<Keyhole>().player = player;
}
private Transform FindTransform(string target)
{
return _lockPick.transform.FindInChilds(target);
}
public void Enable()
{
if (_lockPick == null) return;
_lockPick.SetActive(true);
_lockPick.GetComponent<Keyhole>().ResetLock();
}
public void Disable()
{
if (_lockPick != null)
{
Keyhole keyhole = _lockPick.GetComponent<Keyhole>();
if (keyhole != null && keyhole.breakTimeCounter > 0)
{
//Log.Out("Sphereii_Locks Disabled START, breakTimeCounter: " + keyhole.breakTimeCounter);
keyhole.BreakLockpick();
}
_lockPick.SetActive(false);
}
}
}

View File

@@ -0,0 +1,88 @@
// Simple XUI screen to enable the lock picking to have a window-style pop up.
public class XUiC_PickLocking : XUiController
{
public static string ID = "";
private Vector3i blockPos;
private BlockValue currentBlock;
private SphereLocks Lock;
// Reference to our current locked container
private ILockable LockedItem;
public override void Init()
{
Lock = new SphereLocks();
ID = windowGroup.ID;
base.Init();
}
public override void Update(float _dt)
{
base.Update(_dt);
if (LockedItem == null)
return;
// Check if the lock is open
if (Lock.IsLockOpened())
{
LockedItem.SetLocked(false);
OnClose();
}
}
// Set the container reference so we can unlock it.
public static void Open(LocalPlayerUI playerUi, ILockable lockedItem, BlockValue blockValue, Vector3i blockPos)
{
// Configure the lock pick
playerUi.xui.FindWindowGroupByName(ID).GetChildByType<XUiC_PickLocking>().LockedItem = lockedItem;
playerUi.xui.FindWindowGroupByName(ID).GetChildByType<XUiC_PickLocking>().currentBlock = blockValue;
playerUi.xui.FindWindowGroupByName(ID).GetChildByType<XUiC_PickLocking>().blockPos = blockPos;
playerUi.windowManager.Open(ID, true);
}
// Set the player reference and display the lock.
public override void OnOpen()
{
EntityPlayer player = xui.playerUI.entityPlayer;
base.OnOpen();
Lock = new SphereLocks();
// Pass the Player reference to the lock before we enable.
Lock.Init(currentBlock, blockPos);
Lock.SetPlayer(player);
Lock.Enable();
if (!ThreadManager.IsMainThread()) return;
xui.playerUI.entityPlayer.PlayOneShot("open_sign");
}
public override void OnClose()
{
if (Lock.IsLockOpened())
{
var blockValue = BlockValue.Air;
if (!currentBlock.Block.LockpickDowngradeBlock.isair)
{
blockValue = currentBlock.Block.LockpickDowngradeBlock;
}
else if (!currentBlock.Block.DowngradeBlock.isair)
{
blockValue = currentBlock.Block.DowngradeBlock;
}
if (!blockValue.isair)
{
blockValue = BlockPlaceholderMap.Instance.Replace(blockValue, GameManager.Instance.World.GetGameRandom(), blockPos.x, blockPos.z, false);
blockValue.rotation = currentBlock.rotation;
blockValue.meta = currentBlock.meta;
GameManager.Instance.World.SetBlockRPC(0, blockPos, blockValue, blockValue.Block.Density);
}
}
Lock.Disable();
LockedItem = null;
base.OnClose();
xui.playerUI.windowManager.Close(ID);
if (!ThreadManager.IsMainThread()) return;
xui.playerUI.entityPlayer.PlayOneShot("close_sign");
}
}

View File

@@ -0,0 +1,129 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
using Platform;
[HarmonyPatch(typeof(PlayerMoveController))]
[HarmonyPatch("Update")]
public class PlayerMoveControllerUpdateNPCv2 {
/*
* This patch is originally intended to change string text = null; into something different.
* Since our custom NPCs aren't necessarily are a type of EntityTrader, we want to pre-set the text value
* if we are looking at an EntityAliveV2.
*/
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions,
ILGenerator generator, MethodBase method) {
// To Find the index, uncomment and follow the instructions in this method:
// ILUtilities.DisplayLocalVariables(method);
// This index is not the index of the instruction list, which would include a listing of all the opcodes / operands,
// rather, this is the 32nd local variable defined. We don't know what it is, or where it's at in the instructions,
// but the variableInformation will contain the operand fingerprint
var index = 32;
var variableInformation = ILUtilities.FindLocalVariable(method, index);
// Grab all the instructions
var codes = new List<CodeInstruction>(instructions);
// startIndex is -1, so on the first loop of the first instruction, it'll be 0
// In the end, we want the instruction right before the opcode/operand we are looking for
var startIndex = -1;
foreach (var t in codes)
{
// This opcode + operand is equivalent to
// string text = null;
if (t.opcode == OpCodes.Stloc_S && t.operand.ToString() == variableInformation)
{
// We found our line, so let's break out, keeping the startIndex to the previous line, which is what we want to change
break;
}
startIndex++;
}
if (startIndex > 0 && startIndex < codes.Count)
{
// Replace the null from the "= null" into "= CheckText()". The SymbolExtensions sorts out the full path to the method.
var checkText = SymbolExtensions.GetMethodInfo(() => SetInitialText());
codes[startIndex] = new CodeInstruction(OpCodes.Call, checkText);
}
return codes.AsEnumerable();
}
// This handles displaying the radial dial of available chat commands.
// Since the Radial dial needs a TileEntity, even if it's just to use it's position, we want to use entityAliveV2's GetTileEntity() short cut.
// By default, this will return SCore's TileEntityAoE, which is just a limited Tile Entity that meets the basic requirements.
private static void Postfix() {
var localPlayerUI = LocalPlayerUI.GetUIForPrimaryPlayer();
if (localPlayerUI == null) return;
var entityPlayerLocal = localPlayerUI.entityPlayer;
if (!entityPlayerLocal.IsAlive()) return;
if (localPlayerUI.windowManager.IsInputActive()) return;
//if (localPlayerUI.windowManager.IsInputActive() && localPlayerUI.windowManager.IsFullHUDDisabled()) return;
// Check to see if the button is currently pressed, and was held down. This is to notify the system that you want
// to see the radial
var isPressed = localPlayerUI.playerInput.Activate.IsPressed ||
localPlayerUI.playerInput.PermanentActions.Activate.IsPressed;
var wasPressed = localPlayerUI.playerInput.Activate.WasPressed ||
localPlayerUI.playerInput.PermanentActions.Activate.WasPressed;
if (!isPressed || !wasPressed) return;
var entityAliveV2 = CheckForEntityAlive();
if (entityAliveV2 == null) return;
var tileEntity = entityAliveV2.GetTileEntity();
if (tileEntity == null) return;
entityPlayerLocal.AimingGun = false;
localPlayerUI.xui.RadialWindow.Open();
localPlayerUI.xui.RadialWindow.SetCurrentEntityData(entityAliveV2.world, entityAliveV2, tileEntity, entityPlayerLocal);
}
private static EntityAliveV2 CheckForEntityAlive() {
EntityAliveV2 entV2 = null;
var localPlayerUI = LocalPlayerUI.GetUIForPrimaryPlayer();
if (localPlayerUI == null) return entV2;
var entityPlayerLocal = localPlayerUI.entityPlayer;
if (!entityPlayerLocal.IsAlive()) return entV2;
var hitInfo = entityPlayerLocal.HitInfo;
if (!hitInfo.bHitValid ||
!hitInfo.tag.StartsWith("E_") ||
hitInfo.hit.distanceSq >= Constants.cCollectItemDistance * Constants.cCollectItemDistance)
return entV2;
var rootTransform = RebirthUtilities.GetHitRootTransform(hitInfo.tag, hitInfo.transform);
if (rootTransform == null) return entV2;
entV2 = rootTransform.gameObject.GetComponent<Entity>() as EntityAliveV2;
return (entV2 != null && !entV2.IsAlive()) ? null : entV2;
}
private static string SetInitialText() {
var localPlayerUI = LocalPlayerUI.GetUIForPrimaryPlayer();
if (localPlayerUI == null) return null;
var entityPlayerLocal = localPlayerUI.entityPlayer;
if (!entityPlayerLocal.IsAlive())
return null;
var entityAliveV2 = CheckForEntityAlive();
if (entityAliveV2 == null) return null;
var text10 =
localPlayerUI.playerInput.Activate.GetBindingXuiMarkupString() + localPlayerUI.playerInput.PermanentActions.Activate.GetBindingXuiMarkupString();
var text11 = Localization.Get(entityAliveV2.EntityName);
var text = string.Format(Localization.Get("npcTooltipTalk"), text10, text11);
return text;
}
}

52
Score/NPCv2/ReadMe.txt Normal file
View File

@@ -0,0 +1,52 @@
NPCv2
=====
New class format and code designed to allow a replacement EntityAliveSDX for A22.
The goal of this project is to improve performance for the custom entities, as well as clean up the code significantly.
EntityAliveV2 (prototype name only)
=============
- Inherits off EntityNPC
- Smaller code foot print
- Base class only has the basic code to:
- Add items to their inventory
- Allow Dialog
- Checks to see if immune to damage
- etc.
- Support Classes have been configured to handle additional tasks that may be optional for the NPC.
- Found under Features\NPCv2\Scripts\Entity\SupportClasses
- Examples:
- LeaderUtils handles all logic and checks if the NPC has a leader.
- MissionUtils handles all logic and checks if the NPC is away on a mission.
- NPCQuest Utils handles all logic and checks if the NPC can give quests.
- PushOutOfBounds handles all logic and checks if the Entity is out of bounds.
- NPCUtils handles misc logic for generic NPCs
- Some of these utils are added via EntityAliveV2's Init call.
- These are considered be useful for all NPCs, regardless of what the NPCs are meant for.
public override void Init(int _entityClass) {
base.Init(_entityClass);
_pushOutOfBlocksUtils = new PushOutOfBlocksUtils(this);
_npcUtils = new NPCUtils(this);
}
- For more advanced NPCs, there is now an exposed XML format for the entityclass to configure:
<property class="NPCConfiguration" >
<property name="Hireable" value="true" />
<property name="Missions" value="true"/>
</property>
- The XML configurations are handled in the CopyPropertiesFromEntityClass, via a switch statement:
case "Hireable":
if (StringParsers.ParseBool(keyValuePair.Value.ToString()))
_leaderUtils = new LeaderUtils(this);
- This will create a new reference to the LeaderUtils class.
- Elsewhere in code, the following hooks have been added. If _leaderUtils != null, then it'll execute the LeaderUpdate() call.
- If it does not exist, it does nothing.
public override void OnUpdateLive() {
_leaderUtils?.LeaderUpdate();
_pushOutOfBlocksUtils.CheckStuck(position, width, depth);
_npcUtils?.CheckCollision();
_npcUtils?.CheckFallAndGround();

View File

@@ -0,0 +1,42 @@
public class DialogActionAddItemRebirth : DialogActionAddBuff
{
public override void PerformAction(EntityPlayer player)
{
if (string.IsNullOrEmpty(Value))
Value = "1";
int.TryParse(Value, out var flValue);
var uiforPlayer = LocalPlayerUI.GetUIForPlayer(player as EntityPlayerLocal);
var playerInventory = uiforPlayer.xui.PlayerInventory;
if (playerInventory == null) return;
if (ID == "FuriousRamsaySpawnCube")
{
int entityId = (int)player.Buffs.GetCustomVar("CurrentNPC");
if (entityId > 0)
{
var myEntity = GameManager.Instance.World.GetEntity(entityId) as EntityAliveV2;
if (myEntity != null)
{
if (myEntity.EntityClass.Properties.Values.ContainsKey("SpawnBlock"))
{
ID = "FuriousRamsaySpawnCube" + myEntity.EntityClass.Properties.Values["SpawnBlock"];
}
}
}
}
var item = ItemClass.GetItem(ID);
if (item == null)
{
Log.Out("Item Not Found: " + ID);
return;
}
var itemStack = new ItemStack(item, flValue);
if (!playerInventory.AddItem(itemStack, true))
player.world.gameManager.ItemDropServer(itemStack, player.GetPosition(), Vector3.zero);
}
}

View File

@@ -0,0 +1,1355 @@
using System.Collections.Generic;
using System.Linq;
public class EntityAliveV2 : EntityNPC
{
private PushOutOfBlocksUtils _pushOutOfBlocksUtils;
private LeaderUtils _leaderUtils;
private MissionUtils _missionUtils;
private NPCUtils _npcUtils;
public string NewName = "";
public string _strMyName = "Bob";
private string _strTitle;
public Vector3 guardPosition = Vector3.zero;
public Vector3 guardLookPosition = Vector3.zero;
public List<Vector3> patrolCoordinates = new List<Vector3>();
//private bool bLastAttackReleased;
public bool isHirable = true;
public float flEyeHeight = -1f;
private string _currentWeapon = "";
private string _defaultWeapon = "";
//private string rightHandTransformName;
public QuestJournal questJournal = new QuestJournal();
//private string particleOnDestroy;
//private BlockValue corpseBlockValue;
//private float corpseBlockChance;
public bool isAlwaysAwake;
public Vector3 scale = new Vector3(1, 1, 1);
// Toggle gravity on and off depending on if the chunk is visible.
private ChunkCluster.OnChunkVisibleDelegate chunkClusterVisibleDelegate;
private TileEntity _tileEntity;
public override void Init(int _entityClass)
{
//Log.Out("EntityAliveV2-Init START");
base.Init(_entityClass);
_pushOutOfBlocksUtils = new PushOutOfBlocksUtils(this);
_npcUtils = new NPCUtils(this);
}
public LeaderUtils LeaderUtils
{
get
{
return _leaderUtils;
}
set
{
_leaderUtils = value;
}
}
public MissionUtils MissionUtils
{
get
{
return _missionUtils;
}
set
{
_missionUtils = value;
}
}
public void ConfigureBoundaryBox(Vector3 newSize, Vector3 center)
{
var component = gameObject.GetComponent<BoxCollider>();
if (!component) return;
//Log.Out(" Box Collider: " + component.size.ToCultureInvariantString());
//Log.Out(" Current Boundary Box: " + boundingBox.ToCultureInvariantString());
// Re-adjusting the box collider
component.size = newSize;
scaledExtent = new Vector3(component.size.x / 2f * transform.localScale.x,
component.size.y / 2f * transform.localScale.y, component.size.z / 2f * transform.localScale.z);
var vector = new Vector3(component.center.x * transform.localScale.x,
component.center.y * transform.localScale.y, component.center.z * transform.localScale.z);
boundingBox = BoundsUtils.BoundsForMinMax(-scaledExtent.x, -scaledExtent.y, -scaledExtent.z, scaledExtent.x,
scaledExtent.y, scaledExtent.z);
boundingBox.center = boundingBox.center + vector;
if (center != Vector3.zero)
boundingBox.center = center;
//Log.Out(" After BoundaryBox: " + boundingBox.ToCultureInvariantString());
}
public override Ray GetLookRay()
{
Vector3 myHeadPos = (transform.position + new Vector3(0f, GetEyeHeight(), 0f));
return new Ray(myHeadPos + Origin.position, GetLookVector());
}
public override float GetEyeHeight()
{
if (!IsCrouching) return height * 0.8f;
return height * 0.5f;
}
public override Vector3 GetLookVector()
{
if (attackTarget != null)
{
var col = attackTarget.GetComponentsInChildren<Collider>().Where(x => x.CompareTag(RebirthUtilities.GetRandomBodyTag())).FirstOrDefault();
if (col != null)
{
return (col.transform.position - (transform.position + new Vector3(0f, GetEyeHeight(), 0f)));
}
}
return base.GetLookVector();
}
public virtual bool IsFriendlyPlayer(EntityPlayer player)
{
FactionManager.Relationship myRelationship =
FactionManager.Instance.GetRelationshipTier(this, player);
if (myRelationship == FactionManager.Relationship.Love) return true; // <3
return false; // </3
}
public override void PostInit()
{
//Log.Out("EntityAliveV2-PostInit START");
base.PostInit();
PhysicsTransform.gameObject.SetActive(false);
Buffs.SetCustomVar("$waterStaminaRegenAmount", 0, false);
SetupStartingItems();
if (!string.IsNullOrEmpty(_currentWeapon))
UpdateWeapon(_currentWeapon);
inventory.SetHoldingItemIdx(0);
scale = transform.localScale;
this.PhysicsTransform.gameObject.SetActive(true);
SetSpawnerSource(EnumSpawnerSource.Biome);
}
public override void CopyPropertiesFromEntityClass()
{
//Log.Out("EntityAliveV2-CopyPropertiesFromEntityClass START");
base.CopyPropertiesFromEntityClass();
var _entityClass = EntityClass.list[entityClass];
// Read in a list of names then pick one at random.
if (_entityClass.Properties.Values.ContainsKey("Names"))
{
var text = _entityClass.Properties.Values["Names"];
var names = text.Split(',');
var index = UnityEngine.Random.Range(0, names.Length);
_strMyName = names[index];
}
if (_entityClass.Properties.Classes.ContainsKey("Boundary"))
{
var strBoundaryBox = "0,0,0";
var strCenter = "0,0,0";
var dynamicProperties3 = _entityClass.Properties.Classes["Boundary"];
foreach (var keyValuePair in dynamicProperties3.Values.Dict)
{
switch (keyValuePair.Key)
{
case "BoundaryBox":
strBoundaryBox = dynamicProperties3.Values[keyValuePair.Key];
continue;
case "Center":
strCenter = dynamicProperties3.Values[keyValuePair.Key];
break;
}
}
var box = StringParsers.ParseVector3(strBoundaryBox);
var center = StringParsers.ParseVector3(strCenter);
if (_npcUtils == null)
_npcUtils = new NPCUtils(this);
scaledExtent = _npcUtils.ConfigureBoundaryBox(box, center);
}
/*
* <property class="NPCConfiguration" >
* <property name="Hireable" value="true" />
* <property name="Missions" value="true"/>
* </property>
*/
if (_entityClass.Properties.Classes.ContainsKey("NPCConfiguration"))
{
var dynamicProperties3 = _entityClass.Properties.Classes["NPCConfiguration"];
foreach (var keyValuePair in dynamicProperties3.Values.Dict)
{
switch (keyValuePair.Key)
{
case "Hireable":
if (StringParsers.ParseBool(keyValuePair.Value.ToString()))
_leaderUtils = new LeaderUtils(this);
continue;
case "Missions":
if (StringParsers.ParseBool(keyValuePair.Value.ToString()))
{
_missionUtils = new MissionUtils(this);
}
break;
}
}
}
}
public bool ExecuteAction(bool _bAttackReleased, int actionIndex)
{
//Log.Out("EntityAliveV2-ExecuteAction START");
if (actionIndex == 0)
{
if (!_bAttackReleased)
{
if (this.emodel && this.emodel.avatarController && this.emodel.avatarController.IsAnimationAttackPlaying())
{
return false;
}
if (!this.IsAttackValid())
{
return false;
}
}
if (this.bLastAttackReleased && this.GetSoundAttack() != null)
{
this.PlayOneShot(this.GetSoundAttack(), false);
}
this.bLastAttackReleased = _bAttackReleased;
this.attackingTime = 60;
ItemAction itemAction = this.inventory.holdingItem.Actions[actionIndex];
if (itemAction != null)
{
itemAction.ExecuteAction(this.inventory.holdingItemData.actionData[actionIndex], _bAttackReleased);
}
}
else if (actionIndex == 1)
{
if (!_bAttackReleased && !this.IsAttackValid())
{
return false;
}
this.attackingTime = 60;
if (this.inventory.holdingItem.Actions[actionIndex] != null)
{
this.inventory.holdingItem.Actions[actionIndex].ExecuteAction(this.inventory.holdingItemData.actionData[actionIndex], _bAttackReleased);
}
}
return true;
}
public override void PlayOneShot(string clipName, bool sound_in_head = false, bool serverSignalOnly = false, bool isUnique = false)
{
//Log.Out("EntityAliveV2-PlayOneShot START");
if (IsOnMission()) return;
base.PlayOneShot(clipName, sound_in_head);
}
public override void updateStepSound(float distX, float distZ)
{
//Log.Out("EntityAliveV2-updateStepSound START");
var leader = EntityUtilities.GetLeaderOrOwner(entityId) as EntityAlive;
if (leader == null)
{
base.updateStepSound(distX, distZ);
return;
}
if (leader.Buffs.GetCustomVar("quietNPC") > 0) return;
// Mute the foot steps when crouching.
if (IsCrouching) return;
base.updateStepSound(distX, distZ);
}
private bool ShouldPushOutOfBlock(int _x, int _y, int _z, bool pushOutOfTerrain)
{
//Log.Out("EntityAliveV2-ShouldPushOutOfBlock START");
var shape = world.GetBlock(_x, _y, _z).Block.shape;
if (shape.IsSolidSpace && !shape.IsTerrain())
{
return true;
}
if (!pushOutOfTerrain || !shape.IsSolidSpace || !shape.IsTerrain()) return false;
var shape2 = this.world.GetBlock(_x, _y + 1, _z).Block.shape;
return shape2.IsSolidSpace && shape2.IsTerrain();
}
private bool PushOutOfBlocks(float _x, float _y, float _z)
{
//Log.Out("EntityAliveV2-PushOutOfBlocks START");
var num = Utils.Fastfloor(_x);
var num2 = Utils.Fastfloor(_y);
var num3 = Utils.Fastfloor(_z);
var num4 = _x - (float)num;
var num5 = _z - (float)num3;
var result = false;
if (!this.ShouldPushOutOfBlock(num, num2, num3, false) &&
(!this.ShouldPushOutOfBlock(num, num2 + 1, num3, false))) return false;
var flag2 = !this.ShouldPushOutOfBlock(num - 1, num2, num3, true) &&
!this.ShouldPushOutOfBlock(num - 1, num2 + 1, num3, true);
var flag3 = !this.ShouldPushOutOfBlock(num + 1, num2, num3, true) &&
!this.ShouldPushOutOfBlock(num + 1, num2 + 1, num3, true);
var flag4 = !this.ShouldPushOutOfBlock(num, num2, num3 - 1, true) &&
!this.ShouldPushOutOfBlock(num, num2 + 1, num3 - 1, true);
var flag5 = !this.ShouldPushOutOfBlock(num, num2, num3 + 1, true) &&
!this.ShouldPushOutOfBlock(num, num2 + 1, num3 + 1, true);
var b = byte.MaxValue;
var num6 = 9999f;
if (flag2 && num4 < num6)
{
num6 = num4;
b = 0;
}
if (flag3 && 1.0 - (double)num4 < (double)num6)
{
num6 = 1f - num4;
b = 1;
}
if (flag4 && num5 < num6)
{
num6 = num5;
b = 4;
}
if (flag5 && 1f - num5 < num6)
{
b = 5;
}
var num7 = 0.1f;
switch (b)
{
case 0:
this.motion.x = -num7;
break;
case 1:
this.motion.x = num7;
break;
case 4:
this.motion.z = -num7;
break;
case 5:
this.motion.z = num7;
break;
}
if (b != 255)
{
result = true;
}
return result;
}
private bool CheckNonSolidVertical(Vector3i blockPos, int maxY, int verticalSpace)
{
//Log.Out("EntityAliveV2-CheckNonSolidVertical START");
for (int i = 0; i < maxY; i++)
{
if (!this.world.GetBlock(blockPos.x, blockPos.y + i + 1, blockPos.z).Block.shape.IsSolidSpace)
{
bool flag = true;
for (int j = 1; j < verticalSpace; j++)
{
if (this.world.GetBlock(blockPos.x, blockPos.y + i + 1 + j, blockPos.z).Block.shape.IsSolidSpace)
{
flag = false;
break;
}
}
if (flag)
{
return true;
}
}
}
return false;
}
public override void playStepSound(string stepSound)
{
//Log.Out("EntityAliveV2-playStepSound START");
if (IsOnMission()) return;
if (HasAnyTags(FastTags<TagGroup.Global>.Parse("floating"))) return;
base.playStepSound(stepSound);
}
public override bool IsAttackValid()
{
//Log.Out("EntityAliveV2-IsAttackValid START");
if (IsOnMission()) return false;
return base.IsAttackValid();
}
// Helper Method is maintain backwards compatibility, while also pushing the code out to another class to clean it up.
// NEW - CHANGE from Private to Public for access from LeaderUtils
public bool IsOnMission()
{
//Log.Out("EntityAliveV2-IsOnMission START");
if (_missionUtils == null) return false;
return _missionUtils.IsOnMission();
}
// Helper Method is maintain backwards compatibility, while also pushing the code out to another class to clean it up.
public void SendOnMission(bool send)
{
//Log.Out("EntityAliveV2-SendOnMission START");
_npcUtils?.ToggleCollisions(!send);
_missionUtils?.SendOnMission(send);
}
public new void SetAttackTarget(EntityAlive _attackTarget, int _attackTargetTime)
{
//Log.Out("EntityAliveV2-SetAttackTarget START");
if (_attackTarget != null && _attackTarget.IsDead()) return;
if (IsOnMission())
{
//Log.Out("EntityAliveV2-SetAttackTarget IS ON MISSION");
return;
}
// Some of the AI tasks resets the attack target when it falls down stunned; this will prevent the NPC from ignoring its stunned opponent.
if (_attackTarget == null)
{
//Log.Out($"EntityAliveV2-SetAttackTarget SPECIFIED ATTACK TARGET == null this: {this}, _attackTarget: {_attackTarget}, attackTarget: {attackTarget}");
if (attackTarget != null && attackTarget.IsAlive())
{
//Log.Out($"EntityAliveV2-SetAttackTarget KEEP CURRENT TARGET: {attackTarget}");
return;
}
}
//Log.Out($"EntityAliveV2-SetAttackTarget END this: {this}, calling base.SetAttackTarget USE NEW TARGET: {_attackTarget}, _attackTargetTime: {_attackTargetTime}");
base.SetAttackTarget(_attackTarget, _attackTargetTime);
}
public new void SetRevengeTarget(EntityAlive _other)
{
//Log.Out("EntityAliveV2-SetRevengeTarget START");
if (IsOnMission()) return;
base.SetRevengeTarget(_other);
}
public override void ProcessDamageResponse(DamageResponse _dmResponse)
{
//Log.Out("EntityAliveV2-ProcessDamageResponse START");
if (IsOnMission()) return;
base.ProcessDamageResponse(_dmResponse);
}
public override bool IsImmuneToLegDamage
{
get
{
if (IsOnMission()) return true;
return base.IsImmuneToLegDamage;
}
}
public override void ProcessDamageResponseLocal(DamageResponse _dmResponse)
{
//Log.Out("EntityAliveV2-ProcessDamageResponseLocal START");
if (EntityUtilities.GetBoolValue(entityId, "Invulnerable")) return;
if (Buffs.HasBuff("buffInvulnerable")) return;
if (!isEntityRemote)
{
// If we are being attacked, let the state machine know it can fight back
emodel.avatarController.UpdateBool("IsBusy", false);
}
base.ProcessDamageResponseLocal(_dmResponse);
}
public override void MarkToUnload()
{
//Log.Out("EntityAliveV2-MarkToUnload START");
if (!bWillRespawn)
{
base.MarkToUnload();
}
}
public override bool CanBePushed()
{
return true;
}
public override float getNextStepSoundDistance()
{
return !IsRunning ? 0.5f : 0.25f;
}
public virtual bool ShouldDamage(EntityAlive sourceEntity)
{
//Log.Out("EntityAliveV2-ShouldDamage START");
return EntityTargetingUtilities.CanTakeDamage(this, sourceEntity);
}
public override int DamageEntity(DamageSource _damageSource, int _strength, bool _criticalHit, float _impulseScale)
{
//Log.Out("EntityAliveV2-DamageEntity START");
if (IsOnMission()) return 0;
if (EntityUtilities.GetBoolValue(entityId, "Invulnerable"))
{
//Log.Out("EntityAliveV2-DamageEntity 1");
return 0;
}
if (Buffs.HasBuff("buffInvulnerable"))
{
//Log.Out("EntityAliveV2-DamageEntity 2");
return 0;
}
if (_damageSource.damageType == EnumDamageTypes.Falling &&
this.Buffs.HasCustomVar("$Leader")
)
{
//Log.Out("EntityAliveV2-DamageEntity 3");
return 0;
}
// Faction check
bool shouldDamage = ShouldDamage((EntityAlive)world.GetEntity(_damageSource.getEntityId()));
//Log.Out("EntityAliveV2-DamageEntity shouldDamage: " + shouldDamage);
if (!shouldDamage)
{
//Log.Out("EntityAliveV2-DamageEntity 4");
return 0;
}
var damage = base.DamageEntity(_damageSource, _strength, _criticalHit, _impulseScale);
return damage;
}
public override void SetDead()
{
//Log.Out("EntityAliveV2-SetDead START");
_leaderUtils?.SetDead();
bWillRespawn = false;
if (NavObject != null)
{
NavObjectManager.Instance.UnRegisterNavObject(NavObject);
NavObject = null;
}
SetupDebugNameHUD(false);
lootContainer = null;
lootListAlive = null;
lootListOnDeath = null;
isCollided = false;
nativeCollider = null;
physicsCapsuleCollider = null;
var component = gameObject.GetComponentsInChildren<Collider>();
foreach (var t in component)
{
if (t.name.ToLower() == "gameobject")
{
t.enabled = false;
}
}
base.SetDead();
}
public virtual void TeleportToPlayer(EntityAlive target, bool randomPosition = false)
{
_leaderUtils?.TeleportToPlayer(target, randomPosition);
}
public override string EntityName
{
get
{
// No configured name? return the default.
if (_strMyName == "Bob")
return entityName;
if (string.IsNullOrEmpty(_strTitle))
return Localization.Get(_strMyName);
return Localization.Get(_strMyName) + " the " + Localization.Get(_strTitle);
}
}
public override void SetEntityName(string _name)
{
if (_name.Equals(entityName)) return;
entityName = _name;
// Don't set the internal name if it's the name of the entity class, since the
// EntityFactory calls the setter with the class name when it creates the entity.
// But set the internal name otherwise, because the setter is also called when the
// entity is re-created after being picked up and placed again.
if (_name?.Equals(EntityClass.list[entityClass].entityClassName) != true)
{
_strMyName = _name;
}
bPlayerStatsChanged |= !isEntityRemote;
HandleSetNavName();
}
public override void Write(BinaryWriter _bw, bool bNetworkWrite)
{
base.Write(_bw, bNetworkWrite);
if (NewName != "")
{
_bw.Write(NewName);
}
else
{
_bw.Write(_strMyName);
}
_bw.Write(factionId);
_bw.Write(scale.ToString());
var strPatrolCoordinates = "";
foreach (var temp in patrolCoordinates) strPatrolCoordinates += ";" + temp;
_bw.Write(strPatrolCoordinates);
_bw.Write(guardPosition.ToString());
_bw.Write(guardLookPosition.ToString());
Buffs.Write(_bw);
_bw.Write(inventory.holdingItem.GetItemName());
//WriteSyncData(_bw, 1);
}
public override void Read(byte _version, BinaryReader _br)
{
base.Read(_version, _br);
_strMyName = _br.ReadString();
factionId = _br.ReadByte();
scale = RebirthUtilities.StringToVector3(_br.ReadString());
patrolCoordinates.Clear();
var strPatrol = _br.ReadString();
foreach (var strPatrolPoint in strPatrol.Split(';'))
{
var temp = ModGeneralUtilities.StringToVector3(strPatrolPoint);
if (temp != Vector3.zero)
patrolCoordinates.Add(temp);
}
var strGuardPosition = _br.ReadString();
guardPosition = ModGeneralUtilities.StringToVector3(strGuardPosition);
guardLookPosition = ModGeneralUtilities.StringToVector3(_br.ReadString());
Buffs.Read(_br);
_currentWeapon = _br.ReadString();
UpdateWeapon(_currentWeapon);
//ReadSyncData(_br, 1, -1);
}
public override bool CanEntityJump()
{
return true;
}
public override void OnUpdateLive()
{
_leaderUtils?.LeaderUpdate();
_pushOutOfBlocksUtils.CheckStuck(position, width, depth);
_npcUtils?.CheckCollision();
_npcUtils?.CheckFallAndGround();
// Set CanFall and IsOnGround
if (emodel != null && emodel.avatarController != null)
{
emodel.avatarController.UpdateBool("CanFall",
!emodel.IsRagdollActive && bodyDamage.CurrentStun == EnumEntityStunType.None && !isSwimming);
emodel.avatarController.UpdateBool("IsOnGround", onGround || isSwimming);
}
if (IsOnMission())
{
//Log.Out("EntityAliveV2-OnUpdateLive ON MISSION");
if (transform.localScale != Vector3.zero)
{
//Log.Out("EntityAliveV2-OnUpdateLive A transform.localScale: " + transform.localScale);
transform.localScale = Vector3.zero;
}
}
else
{
//Log.Out("EntityAliveV2-OnUpdateLive NOT ON MISSION");
if (transform.localScale != scale)
{
//Log.Out("EntityAliveV2-OnUpdateLive B transform.localScale: " + transform.localScale);
//Log.Out("EntityAliveV2-OnUpdateLive scale: " + scale);
transform.localScale = scale;
}
}
base.OnUpdateLive();
}
public override EntityActivationCommand[] GetActivationCommands(Vector3i _tePos, EntityAlive _entityFocusing)
{
//Log.Out("EntityAliveV2-GetActivationCommands START");
if (this.IsDead() || NPCInfo == null)
{
//Debug.Log("NPC info == null.");
return new EntityActivationCommand[0];
}
return new EntityActivationCommand[] {
new EntityActivationCommand("talk", "talk", true),
new EntityActivationCommand("talk", "talk", true),
new EntityActivationCommand("talk", "talk", true)
};
}
public override bool OnEntityActivated(int _indexInBlockActivationCommands, Vector3i _tePos, EntityAlive _entityFocusing)
{
EntityPlayerLocal player = (EntityPlayerLocal)_entityFocusing;
if (!player)
{
//Log.Out("EntityAliveV2-OnEntityActivated 1");
//Log.Out($"!player");
return false;
}
if (player.PlayerUI.windowManager.IsWindowOpen("dialog"))
{
//Log.Out("EntityAliveV2-OnEntityActivated 2");
//Log.Out($"IsWindowOpen dialog");
return false;
}
if (EntityUtilities.VerifyFactionStanding(_entityFocusing, this))
{
//Log.Out($"Not VerifyFactionStanding");
return false;
}
float leader = this.Buffs.GetCustomVar("$Leader");
if (leader > 0 && leader != _entityFocusing.entityId)
{
//Log.Out($"leader: {leader}, _entityFocusing.entityId: {_entityFocusing.entityId}");
return false;
}
Buffs.AddBuff("buffTalkingTo");
RotateTo(_entityFocusing, 360f, 360f);
SetLookPosition(_entityFocusing.getHeadPosition());
//Log.Out($"{_entityFocusing} talking to {entityId}");
_entityFocusing.Buffs.SetCustomVar("CurrentNPC", this.entityId);
RebirthVariables.talkingToNPC = this.entityId;
Buffs.SetCustomVar("CurrentPlayer", _entityFocusing.entityId);
var uiforPlayer = LocalPlayerUI.GetUIForPlayer(_entityFocusing as EntityPlayerLocal);
uiforPlayer.xui.Dialog.Respondent = this;
uiforPlayer.windowManager.CloseAllOpenWindows(null, false);
uiforPlayer.windowManager.Open("dialog", true, false, true);
return false;
}
// Helper Method to access the protected HandleNavObject of the base class
public new void HandleNavObject()
{
base.HandleNavObject();
}
// We don't have access to JumpState outside of the class, so we can make a nifty public helper to get the value.
public JumpState GetJumpState()
{
return jumpState;
}
public virtual TileEntity GetTileEntity()
{
var chunk = GameManager.Instance.World.GetChunkFromWorldPos(World.worldToBlockPos(position)) as Chunk;
if (_tileEntity == null)
{
// TileEntityAoE is a basic tile entity, so we are just re-using it.
_tileEntity = new TileEntityAoE(chunk)
{
entityId = entityId
};
}
else
{
_tileEntity.SetChunk(chunk);
}
return _tileEntity;
}
/////////////////////////////////////////////////
// This is an attempt at turning off gravity for the entity when chunks are no longer visible. The hope is that it will resolve the disappearing NPCs.
public void OnChunkDisplayed(long _key, bool _bDisplayed)
{
//Log.Out("EntityAliveV2-OnChunkDisplayed START");
if (this.emodel == null) return;
var modelTransform = this.emodel.GetModelTransform();
if (modelTransform == null) return;
foreach (var rigid in modelTransform.GetComponentsInChildren<Rigidbody>())
{
rigid.useGravity = _bDisplayed;
}
}
public override void dropItemOnDeath()
{
//Log.Out("EntityAliveV2-dropItemOnDeath START");
// Don't drop your toolbelt
if (this.world.IsDark())
{
this.lootDropProb *= 1f;
}
if (this.entityThatKilledMe)
{
this.lootDropProb = EffectManager.GetValue(PassiveEffects.LootDropProb,
this.entityThatKilledMe.inventory.holdingItemItemValue, this.lootDropProb, this.entityThatKilledMe,
null, new FastTags<TagGroup.Global>(), true, true, true, true, true, 1);
}
if (this.lootDropProb > this.rand.RandomFloat)
{
//Log.Out("EntityAliveV2-dropItemOnDeath DROP LOOT CONTAINER");
GameManager.Instance.DropContentOfLootContainerServer(BlockValue.Air, new Vector3i(this.position),
this.entityId);
}
return;
}
public override void OnAddedToWorld()
{
if (isAlwaysAwake)
{
// Set the current order, defaults to "Wander"
// EntityUtilities.SetCurrentOrder(entityId, EntityUtilities.GetCurrentOrder(entityId));
// Set in EntityAlive.TriggerSleeperPose() - resetting here
IsSleeping = false;
}
this.chunkClusterVisibleDelegate = new ChunkCluster.OnChunkVisibleDelegate(this.OnChunkDisplayed);
GameManager.Instance.World.ChunkClusters[0].OnChunkVisibleDelegates += this.chunkClusterVisibleDelegate;
base.OnAddedToWorld();
AddToInventory();
if (this.lootContainer == null)
{
//Log.Out("EntityAliveV2-OnAddedToWorld this.lootContainer == null");
this.lootContainer = new TileEntityLootContainer((Chunk)null);
this.lootContainer.entityId = this.entityId;
string lootListName = this.GetLootList();
this.lootContainer.lootListName = lootListName; // lootContainer.Name;
LootContainer lootContainer = LootContainer.GetLootContainer(lootListName, true);
Vector2i size = new Vector2i(0, 0);
if (lootContainer == null)
{
//Log.Out("EntityAliveV2-OnAddedToWorld entityClassName: " + this.EntityClass.entityClassName);
//Log.Out("EntityAliveV2-OnAddedToWorld EntityName: " + this.EntityName);
//Log.Out("EntityAliveV2-OnAddedToWorld lootListName: " + lootListName);
size = new Vector2i(8, 6);
//throw new Exception("NPC LOOT LIST APPEARS TO BE INVALID");
}
else
{
size = lootContainer.size;
}
this.lootContainer.SetContainerSize(size, true);
this.bag.SetupSlots(ItemStack.CreateArray(size.x * size.y));
}
if (lootContainer != null)
{
this.lootContainer.bWasTouched = true;
}
}
public virtual void CheckStuck()
{
//Log.Out("EntityAliveV2-CheckStuck START");
IsStuck = false;
if (IsFlyMode.Value) return;
var num = boundingBox.min.y + 0.5f;
IsStuck = PushOutOfBlocks(position.x - width * 0.3f, num, position.z + depth * 0.3f);
IsStuck = (PushOutOfBlocks(position.x - width * 0.3f, num, position.z - depth * 0.3f) || IsStuck);
IsStuck = (PushOutOfBlocks(position.x + width * 0.3f, num, position.z - depth * 0.3f) || IsStuck);
IsStuck = (PushOutOfBlocks(position.x + width * 0.3f, num, position.z + depth * 0.3f) || IsStuck);
if (IsStuck) return;
var x = Utils.Fastfloor(position.x);
var num2 = Utils.Fastfloor(num);
var z = Utils.Fastfloor(position.z);
if (!ShouldPushOutOfBlock(x, num2, z, true) ||
!CheckNonSolidVertical(new Vector3i(x, num2 + 1, z), 4, 2)) return;
IsStuck = true;
motion = new Vector3(0f, 1.6f, 0f);
Log.Warning($"{EntityName} ({entityId}) is stuck. Unsticking.");
}
private void AddToInventory()
{
//Log.Out("EntityAliveV2-AddToInventory START");
// We only want to fire the initial inventory once per entity creation.
// Let's gate it using a cvar
if (Buffs.GetCustomVar("InitialInventory") > 0) return;
Buffs.SetCustomVar("InitialInventory", 1);
var currentEntityClass = EntityClass.list[entityClass];
if (currentEntityClass.Properties.Values.ContainsKey("StartingItems"))
{
var text = currentEntityClass.Properties.Values["StartingItems"];
var items = text.Split(',');
foreach (var item in items)
{
var itemStack = ItemStack.FromString(item.Trim());
if (itemStack.itemValue.IsEmpty()) continue;
var forId = ItemClass.GetForId(itemStack.itemValue.type);
if (forId.HasQuality)
{
itemStack.itemValue = new ItemValue(itemStack.itemValue.type, 1, 6, false, null, 1f);
}
lootContainer.AddItem(itemStack);
}
}
}
protected virtual void SetupStartingItems()
{
//Log.Out("EntityAliveV2-SetupStartingItems START");
for (var i = 0; i < this.itemsOnEnterGame.Count; i++)
{
var itemStack = this.itemsOnEnterGame[i];
var forId = ItemClass.GetForId(itemStack.itemValue.type);
if (forId.HasQuality)
{
itemStack.itemValue = new ItemValue(itemStack.itemValue.type, 1, 6, false, null, 1f);
}
else
{
itemStack.count = forId.Stacknumber.Value;
}
if (i == 0)
_defaultWeapon = forId.GetItemName();
inventory.SetItem(i, itemStack);
}
}
public void RefreshWeapon()
{
//Log.Out("EntityAliveV2-RefreshWeapon START");
var item = ItemClass.GetItem(_currentWeapon);
UpdateWeapon(item);
}
public void UpdateWeapon(string itemName = "")
{
//Log.Out("EntityAliveV2-UpdateWeapon START");
if (string.IsNullOrEmpty(itemName))
itemName = _currentWeapon;
var item = ItemClass.GetItem(itemName);
UpdateWeapon(item);
EntityUtilities.UpdateHandItem(entityId, itemName);
}
// Allows the NPC to change their hand items, and update their animator.
public void UpdateWeapon(ItemValue item)
{
return; // todo fix?
if (item == null) return;
if (item.GetItemId() < 0) return;
_currentWeapon = item.ItemClass.GetItemName();
// Do we have this item?
if (!FindWeapon(_currentWeapon))
{
Debug.Log($"EntityAliveV2: UpdateWeapon() Item not found: {_currentWeapon}");
if (string.IsNullOrEmpty(_defaultWeapon))
return;
// Switch to default
item = ItemClass.GetItem(_defaultWeapon);
}
if (item.GetItemId() == inventory.holdingItemItemValue.GetItemId())
{
return;
}
_currentWeapon = item.ItemClass.GetItemName();
Buffs.SetCustomVar("CurrentWeaponID", item.GetItemId());
inventory.SetItem(0, item, 1);
foreach (var action in item.ItemClass.Actions)
{
if (action is ItemActionRanged ranged)
{
ranged.AutoFire = new DataItem<bool>(true);
}
}
// Since we are potentially changing the Hand Transform, we need to set the animator it changed.
var entityClassType = EntityClass.list[entityClass];
emodel.avatarController.SwitchModelAndView(entityClassType.mesh.name, emodel.IsFPV, IsMale);
// Item update has to happen after the SwitchModelAndView, otherwise the weapon will attach to the previous hand position
inventory.OnUpdate();
inventory.ForceHoldingItemUpdate();
}
public virtual void Collect(int _playerId)
{
//Log.Out("EntityAliveV2-Collect START");
var entityPlayerLocal = world.GetEntity(_playerId) as EntityPlayerLocal;
if (entityPlayerLocal == null) return;
var uiforPlayer = LocalPlayerUI.GetUIForPlayer(entityPlayerLocal);
var itemStack = new ItemStack(GetItemValue(), 1);
if (!uiforPlayer.xui.PlayerInventory.AddItem(itemStack))
{
GameManager.Instance.ItemDropServer(itemStack, entityPlayerLocal.GetPosition(), Vector3.zero, _playerId,
60f, false);
}
}
public virtual void SetItemValue(ItemValue itemValue)
{
Log.Out("EntityAliveV2-SetItemValue START");
SetEntityName(itemValue.GetMetadata("NPCName") as string);
belongsPlayerId = (int)itemValue.GetMetadata("BelongsToPlayer");
Health = (int)itemValue.GetMetadata("health");
var leaderID = (int)itemValue.GetMetadata("Leader");
EntityUtilities.SetLeaderAndOwner(entityId, leaderID);
lootContainer.entityId = entityId;
var slots = lootContainer.items;
for (var i = 0; i < slots.Length; i++)
{
var key = $"LootContainer-{i}";
var storage = itemValue.GetMetadata(key)?.ToString();
if (string.IsNullOrEmpty(storage)) continue;
var itemId = storage.Split(',')[0];
var quality = StringParsers.ParseSInt32(storage.Split(',')[1]);
var itemCount = StringParsers.ParseSInt32(storage.Split(',')[2]);
var createItem = ItemClass.GetItem(itemId);
if (storage.Split(',').Length > 3)
{
var useTime = StringParsers.ParseFloat(storage.Split(',')[3]);
createItem.UseTimes = useTime;
}
createItem.Quality = (ushort)quality;
var stack = new ItemStack(createItem, itemCount);
lootContainer.AddItem(stack);
}
// Tool belt
slots = inventory.GetSlots();
for (var i = 0; i < slots.Length; i++)
{
var key = $"InventorySlot-{i}";
var storage = itemValue.GetMetadata(key)?.ToString();
if (string.IsNullOrEmpty(storage)) continue;
var itemId = storage.Split(',')[0];
var quality = StringParsers.ParseSInt32(storage.Split(',')[1]);
var itemCount = StringParsers.ParseSInt32(storage.Split(',')[2]);
var createItem = ItemClass.GetItem(itemId);
createItem.Quality = (ushort)quality;
var stack = new ItemStack(createItem, itemCount);
inventory.SetItem(i, stack);
}
var x = (int)itemValue.GetMetadata("TotalBuff");
for (var i = 0; i < x; i++)
{
var buffName = itemValue.GetMetadata($"Buff-{i}")?.ToString();
Buffs.AddBuff(buffName);
}
x = (int)itemValue.GetMetadata("TotalCVar");
for (var i = 0; i < x; i++)
{
var cvarData = itemValue.GetMetadata($"CVar-{i}")?.ToString();
if (cvarData == null) continue;
var cvarName = cvarData.Split(':')[0];
var cvarValue = cvarData.Split(':')[1];
Buffs.AddCustomVar(cvarName, StringParsers.ParseFloat(cvarValue));
}
var weaponType = itemValue.GetMetadata("CurrentWeapon").ToString();
_defaultWeapon = itemValue.GetMetadata("DefaultWeapon").ToString();
var item = ItemClass.GetItem(weaponType);
if (item != null)
{
UpdateWeapon(item);
}
Buffs.SetCustomVar("WeaponTypeNeedsUpdate", 1);
}
public virtual ItemValue GetItemValue()
{
//Log.Out("EntityAliveV2-GetItemValue START");
var type = 0;
var itemValue = new ItemValue(type, false);
//itemValue.SetMetadata("NPCName", EntityName, TypedMetadataValue.TypeTag.String);
itemValue.SetMetadata("EntityClassId", entityClass, TypedMetadataValue.TypeTag.Integer);
itemValue.SetMetadata("BelongsToPlayer", belongsPlayerId, TypedMetadataValue.TypeTag.Integer);
itemValue.SetMetadata("health", Health, TypedMetadataValue.TypeTag.Integer);
itemValue.SetMetadata("Leader", EntityUtilities.GetLeaderOrOwner(entityId)?.entityId,
TypedMetadataValue.TypeTag.Integer);
itemValue.SetMetadata("CurrentWeapon", inventory.holdingItem.GetItemName(), TypedMetadataValue.TypeTag.String);
itemValue.SetMetadata("DefaultWeapon", _defaultWeapon, TypedMetadataValue.TypeTag.String);
if (lootContainer == null) return itemValue;
var slots = lootContainer.items;
for (var i = 0; i < slots.Length; i++)
{
if (slots[i].IsEmpty()) continue;
var itemId = slots[i].itemValue.ItemClass.GetItemName();
var quality = slots[i].itemValue.Quality;
var itemCount = slots[i].count;
var itemUse = slots[i].itemValue.UseTimes;
var storage = $"{itemId},{quality},{itemCount},{itemUse}";
itemValue.SetMetadata($"LootContainer-{i}", storage, TypedMetadataValue.TypeTag.String);
}
// Tool belt
slots = inventory.GetSlots();
for (var i = 0; i < slots.Length; i++)
{
if (slots[i].IsEmpty()) continue;
var itemId = slots[i].itemValue.ItemClass.GetItemName();
var quality = slots[i].itemValue.Quality;
var itemCount = slots[i].count;
var itemUse = slots[i].itemValue.UseTimes;
var storage = $"{itemId},{quality},{itemCount},{itemUse}";
itemValue.SetMetadata($"InventorySlot-{i}", storage, TypedMetadataValue.TypeTag.String);
}
var x = 0;
itemValue.SetMetadata($"TotalBuff", Buffs.ActiveBuffs.Count, TypedMetadataValue.TypeTag.Integer);
foreach (var buff in Buffs.ActiveBuffs)
{
itemValue.SetMetadata($"Buff-{x}", buff.BuffName, TypedMetadataValue.TypeTag.String);
x++;
}
x = 0;
itemValue.SetMetadata($"TotalCVar", Buffs.CVars.Count, TypedMetadataValue.TypeTag.Integer);
foreach (var cvar in Buffs.CVars)
{
var value = $"{cvar.Key}:{cvar.Value}";
itemValue.SetMetadata($"CVar-{x}", value, TypedMetadataValue.TypeTag.String);
x++;
}
return itemValue;
}
public bool FindWeapon(string weapon)
{
//Log.Out("EntityAliveV2-FindWeapon START");
var currentWeapon = ItemClass.GetItem(weapon);
if (currentWeapon == null) return false;
if (!currentWeapon.ItemClass.Properties.Contains("CompatibleWeapon")) return false;
var playerWeapon = currentWeapon.ItemClass.Properties.GetStringValue("CompatibleWeapon");
if (string.IsNullOrEmpty(playerWeapon)) return false;
var playerWeaponItem = ItemClass.GetItem(playerWeapon);
if (playerWeaponItem == null) return false;
if (lootContainer != null)
{
if (lootContainer.HasItem(playerWeaponItem))
return true;
}
// If we don't have it in our loot container, check to see if we had it when we first spawned in.
for (var i = 0; i < itemsOnEnterGame.Count; i++)
{
var itemStack = itemsOnEnterGame[i];
if (itemStack.itemValue.ItemClass.GetItemName()
.Equals(weapon, StringComparison.InvariantCultureIgnoreCase)) return true;
}
if (GetHandItem().ItemClass.GetItemName().Equals(weapon, StringComparison.InvariantCultureIgnoreCase))
return true;
return false;
}
// The GetRightHandTransformName() is not virtual in the base class. There's a Harmony patch that redirects the AvatarAnimator's call here.
// This helps adjust the hand position for various weapons we can add to the NPC.
public new string GetRightHandTransformName()
{
//Log.Out("EntityAliveV2-GetRightHandTransformName START");
var currentItemHand = inventory.holdingItem;
if (currentItemHand.Properties.Contains(EntityClass.PropRightHandJointName))
{
currentItemHand.Properties.ParseString(EntityClass.PropRightHandJointName, ref rightHandTransformName);
}
else
{
rightHandTransformName = "Gunjoint";
EntityClass.list[entityClass].Properties
.ParseString(EntityClass.PropRightHandJointName, ref rightHandTransformName);
}
return rightHandTransformName;
}
public override void OnDeathUpdate()
{
//Log.Out("EntityAliveV2-OnDeathUpdate START");
if (this.deathUpdateTime < this.timeStayAfterDeath)
{
this.deathUpdateTime++;
}
int deadBodyHitPoints = EntityClass.list[this.entityClass].DeadBodyHitPoints;
if (deadBodyHitPoints > 0 && this.DeathHealth <= -deadBodyHitPoints)
{
this.deathUpdateTime = this.timeStayAfterDeath;
}
if (this.deathUpdateTime != this.timeStayAfterDeath)
{
return;
}
if (!this.isEntityRemote && !this.markedForUnload)
{
this.dropCorpseBlock();
if (this.particleOnDestroy != null && this.particleOnDestroy.Length > 0)
{
float lightBrightness = this.world.GetLightBrightness(base.GetBlockPosition());
this.world.GetGameManager().SpawnParticleEffectServer(
new ParticleEffect(this.particleOnDestroy, this.getHeadPosition(), lightBrightness, Color.white,
null, null, false), this.entityId);
}
}
}
public override Vector3i dropCorpseBlock()
{
//Log.Out("EntityAliveV2-dropCorpseBlock START");
if (lootContainer != null && lootContainer.IsUserAccessing())
{
return Vector3i.zero;
}
if (corpseBlockValue.isair)
{
return Vector3i.zero;
}
if (rand.RandomFloat > corpseBlockChance)
{
return Vector3i.zero;
}
var vector3I = World.worldToBlockPos(this.position);
while (vector3I.y < 254 && (float)vector3I.y - this.position.y < 3f &&
!this.corpseBlockValue.Block.CanPlaceBlockAt(this.world, 0, vector3I, this.corpseBlockValue, false))
{
vector3I += Vector3i.up;
}
if (vector3I.y >= 254)
{
return Vector3i.zero;
}
if ((float)vector3I.y - this.position.y >= 2.1f)
{
return Vector3i.zero;
}
this.world.SetBlockRPC(vector3I, this.corpseBlockValue);
if (vector3I == Vector3i.zero)
{
return Vector3i.zero;
}
if (world.GetTileEntity(0, vector3I) is not TileEntityLootContainer tileEntityLootContainer)
{
return Vector3i.zero;
}
if (lootContainer != null)
{
tileEntityLootContainer.CopyLootContainerDataFromOther(lootContainer);
}
else
{
tileEntityLootContainer.lootListName = this.lootListOnDeath;
tileEntityLootContainer.SetContainerSize(LootContainer.GetLootContainer(lootListOnDeath).size,
true);
}
tileEntityLootContainer.SetModified();
return vector3I;
}
}

View File

@@ -0,0 +1,326 @@
using System.Collections;
public class LeaderUtils
{
private EntityAliveV2 _entityAlive;
private EntityPlayer _owner;
private bool _hasNavObjectsEnabled;
private bool _isTeleporting;
public LeaderUtils(EntityAliveV2 entityAlive)
{
_entityAlive = entityAlive;
}
public bool IsTeleporting
{
get
{
return _isTeleporting;
}
set
{
this._isTeleporting = value;
}
}
public EntityPlayer Owner
{
get
{
return _owner;
}
set
{
this._owner = value;
}
}
private void SetOwner()
{
//Log.Out("LeaderUtils-SetOwner START");
var flLeader = _entityAlive.Buffs.GetCustomVar("$Leader");
//Log.Out("LeaderUtils-SetOwner flLeader: " + flLeader);
_entityAlive.belongsPlayerId = (int)flLeader;
var player = _entityAlive.world.GetEntity(_entityAlive.belongsPlayerId) as EntityPlayer;
if (player)
{
//Log.Out("LeaderUtils-SetOwner 1");
_owner = player;
_entityAlive.IsEntityUpdatedInUnloadedChunk = true;
_entityAlive.bWillRespawn = true;
_entityAlive.bIsChunkObserver = true;
if (!_hasNavObjectsEnabled)
{
if (_entityAlive is EntityAliveV2 entityAliveV2)
entityAliveV2.HandleNavObject();
_hasNavObjectsEnabled = true;
}
}
}
public void AddCompanion()
{
//Log.Out("LeaderUtils-AddCompanion START");
if (_owner == null)
{
//Log.Out("LeaderUtils-AddCompanion 1");
SetOwner();
}
if (_owner != null)
{
//Log.Out("LeaderUtils-AddCompanion 2");
int num2 = ((EntityPlayer)_owner).Companions.IndexOf(_entityAlive);
if (num2 < 0)
{
((EntityPlayer)_owner).Companions.Add(_entityAlive);
}
num2 = ((EntityPlayer)_owner).Companions.IndexOf(_entityAlive);
var v = Constants.TrackedFriendColors[num2 % Constants.TrackedFriendColors.Length];
if (_entityAlive.NavObject != null)
{
//Log.Out("LeaderUtils-AddCompanion 3");
_entityAlive.NavObject.UseOverrideColor = true;
_entityAlive.NavObject.OverrideColor = v;
_entityAlive.NavObject.name = _entityAlive.EntityName;
}
}
}
public void LeaderUpdate()
{
if (_entityAlive.IsDead())
{
return;
}
if (_owner == null)
{
SetOwner();
}
//Log.Out("LeaderUtils-LeaderUpdate CurrentOrder: " + _entityAlive.Buffs.GetCustomVar("CurrentOrder"));
//Log.Out("LeaderUtils-LeaderUpdate _entityAlive.emodel.visible: " + _entityAlive.emodel.visible);
if (_entityAlive.Buffs.GetCustomVar("CurrentOrder") == (int)EntityUtilities.Orders.TempStay)
{
//Log.Out("LeaderUtils-LeaderUpdate IS TEMP STAY");
if (_owner != null)
{
//Log.Out("LeaderUtils-LeaderUpdate OWNER EXISTS");
if (_owner.Spawned && _owner.AttachedToEntity)
{
//Log.Out("LeaderUtils-LeaderUpdate NOT IN A VEHICLE");
_entityAlive.SendOnMission(false);
_entityAlive.Buffs.SetCustomVar("CurrentOrder", (int)EntityUtilities.Orders.Follow);
RebirthManager.UpdateHireInfo(_entityAlive.entityId, "order", "follow", _entityAlive.position.ToString(), _entityAlive.rotation.ToString());
}
}
else
{
if (_entityAlive.onGround)
{
//Log.Out("LeaderUtils-LeaderUpdate IS ON GROUND");
_entityAlive.guardPosition = _entityAlive.position;
_entityAlive.guardLookPosition = _entityAlive.position + _entityAlive.GetLookVector();
}
}
}
else if (_entityAlive.Buffs.GetCustomVar("CurrentOrder") == (int)EntityUtilities.Orders.Follow
)
{
if (_owner != null)
{
if (_owner.Spawned)
{
if (((EntityPlayer)_owner).Companions.IndexOf(_entityAlive) < 0)
{
AddCompanion();
}
}
}
else
{
//Log.Out("LeaderUtils-LeaderUpdate OWNER DOES NOT EXIST");
if (_entityAlive.IsOnMission())
{
//Log.Out("LeaderUtils-LeaderUpdate IS NOT ON MISSION");
_entityAlive.SendOnMission(false);
}
//Log.Out("LeaderUtils-LeaderUpdate SET TO TEMP STAY");
_entityAlive.Buffs.SetCustomVar("CurrentOrder", (int)EntityUtilities.Orders.TempStay);
RebirthManager.UpdateHireInfo(_entityAlive.entityId, "order", "stay", _entityAlive.position.ToString(), _entityAlive.rotation.ToString());
}
}
if (_entityAlive.belongsPlayerId > 0 && _owner != null)
{
if (_owner.AttachedToEntity != null && _entityAlive.IsOnMission() && _entityAlive.Buffs.GetCustomVar("CurrentOrder") == (int)EntityUtilities.Orders.Follow)
{
//Log.Out("LeaderUtils-LeaderUpdate 1");
var _position = _owner.GetPosition();
_position.y += 2;
_entityAlive.SetPosition(_position);
}
}
}
public void TeleportToPlayer(EntityAlive target, bool randomPosition = false)
{
if (target == null)
{
return;
}
if (_entityAlive.Buffs.GetCustomVar("CurrentOrder") != (int)EntityUtilities.Orders.Follow)
{
return;
}
if (target.IsInElevator())
{
return;
}
var position = _entityAlive.position;
if (!(_entityAlive.HasAnyTags(FastTags<TagGroup.Global>.Parse("survivor")) || _entityAlive.HasAnyTags(FastTags<TagGroup.Global>.Parse("ally"))))
{
return;
}
var target2i = new Vector2(target.position.x, target.position.z);
var mine2i = new Vector2(position.x, position.z);
var distance = Vector2.Distance(target2i, mine2i);
if (_isTeleporting)
{
return;
}
var myPosition = target.position + Vector3.back;
var player = target as EntityPlayer;
if (player != null)
{
myPosition = player.GetBreadcrumbPos(3 * _entityAlive.rand.RandomFloat);
// If my target distance is still way off from the player, teleport randomly. That means the bread crumb isn't accurate
var distance2 = Vector3.Distance(myPosition, player.position);
if (distance2 > 40f)
{
randomPosition = true;
}
if (randomPosition)
{
Vector3 dirV = target.position - position;
myPosition = RandomPositionGenerator.CalcPositionInDirection(target, target.position, dirV, 5, 80f);
}
float positionY = player.position.y + 1f;
if (player.position.y > position.y)
{
myPosition.y = positionY;
}
else
{
if (GameManager.Instance.World.GetHeightAt(myPosition.x, myPosition.z) <= player.position.y)
{
}
else
{
positionY = GameManager.Instance.World.GetHeightAt(myPosition.x, myPosition.z) + 1f;
}
}
myPosition.y = positionY;
}
_entityAlive.SetRevengeTarget((EntityAlive) null);
_entityAlive.attackTarget = (EntityAlive) null;
_entityAlive.motion = Vector3.zero;
_entityAlive.navigator?.clearPath();
_entityAlive.moveHelper?.Stop();
_entityAlive.SetPosition(myPosition);
_entityAlive.StartCoroutine(ValidateTeleport(target, randomPosition));
}
public void SetDead()
{
var owner = EntityUtilities.GetLeaderOrOwner(_entityAlive.entityId) as EntityPlayer;
if (owner == null) return;
_entityAlive.lootDropProb = 0;
if (owner.Companions.IndexOf(_entityAlive) >= 0)
{
owner.Companions.Remove(_entityAlive);
}
//Log.Out("LeaderUtils-SetDead 1");
GameManager.Instance.DropContentOfLootContainerServer(BlockValue.Air, new Vector3i(_entityAlive.position), _entityAlive.entityId);
//EntityUtilities.DropBackpack(_entityAlive);
}
public IEnumerator ValidateTeleport(EntityAlive target, bool randomPosition = false)
{
yield return new WaitForSeconds(1f);
var position = _entityAlive.position;
var y = (int)GameManager.Instance.World.GetHeightAt(position.x, position.z);
if (position.y < y)
{
var myPosition = position;
var player = target as EntityPlayer;
if (player != null)
myPosition = player.GetBreadcrumbPos(3 * _entityAlive.rand.RandomFloat);
if (randomPosition)
{
Vector3 dirV = target.position - position;
myPosition = RandomPositionGenerator.CalcPositionInDirection(target, target.position, dirV, 5, 80f);
}
//// Find the ground.
if (player != null)
{
if (player.position.y > this._entityAlive.position.y)
{
myPosition.y = player.position.y + 1;
}
else
{
if (GameManager.Instance.World.GetHeightAt(myPosition.x, myPosition.z) <= player.position.y)
{
}
else
{
myPosition.y = GameManager.Instance.World.GetHeightAt(myPosition.x, myPosition.z) + 1f;
}
}
}
else
{
myPosition.y = (int)GameManager.Instance.World.GetHeightAt(myPosition.x, myPosition.z) + 2;
}
myPosition.y = (int)GameManager.Instance.World.GetHeightAt(myPosition.x, myPosition.z) + 2;
// Find the ground.
_entityAlive.motion = Vector3.zero;
_entityAlive.navigator?.clearPath();
_entityAlive.SetPosition(myPosition);
}
_isTeleporting = false;
yield return null;
}
}

View File

@@ -0,0 +1,56 @@
public class MissionUtils
{
private EntityAliveV2 _entityAlive;
public MissionUtils(EntityAliveV2 entityAlive)
{
_entityAlive = entityAlive;
}
public bool IsOnMission()
{
return _entityAlive.Buffs.GetCustomVar("onMission") == 1f;
}
public void SendOnMission(bool send)
{
//Log.Out("MissionUtils-SendOnMission send: " + send);
if (send)
{
var enemy = _entityAlive.GetRevengeTarget();
if (enemy != null)
{
_entityAlive.attackTarget = (EntityAlive) null;
enemy.attackTarget = (EntityAlive) null;
enemy.SetRevengeTarget((EntityAlive) null);
enemy.DoRagdoll(new DamageResponse());
_entityAlive.SetRevengeTarget((EntityAlive) null);
}
_entityAlive.SetIgnoredByAI(true);
//_entityAlive.transform.localScale = new Vector3(0, 0, 0);
_entityAlive.SetScale(0);
_entityAlive.emodel.SetVisible(false, false);
_entityAlive.Buffs.SetCustomVar("onMission", 1f);
if (_entityAlive.NavObject != null)
_entityAlive.NavObject.IsActive = false;
_entityAlive.DebugNameInfo = "";
_entityAlive.SetupDebugNameHUD(false);
}
else
{
//_entityAlive.transform.localScale = _entityAlive.scale;
_entityAlive.SetScale(_entityAlive.scale.y);
_entityAlive.emodel.SetVisible(true, true);
_entityAlive.enabled = true;
_entityAlive.Buffs.SetCustomVar("onMission", 0f);
if (_entityAlive.NavObject != null)
_entityAlive.NavObject.IsActive = true;
_entityAlive.SetIgnoredByAI(false);
}
}
}

View File

@@ -0,0 +1,13 @@
using System.Collections.Generic;
public class NPCQuestUtils
{
private EntityAlive _entityAlive;
public List<Quest> activeQuests;
public NPCQuestUtils(EntityAlive entityAlive)
{
_entityAlive = entityAlive;
}
}

View File

@@ -0,0 +1,130 @@
public class NPCUtils
{
private EntityAlive _entityAlive;
private Transform _largeEntityBlocker;
private float fallTime;
private float fallThresholdTime;
public NPCUtils(EntityAlive entityAlive)
{
_entityAlive = entityAlive;
_largeEntityBlocker = GameUtils.FindTagInChilds(_entityAlive.RootTransform, "LargeEntityBlocker");
}
public void CheckCollision(float distance = 5f)
{
var isPlayerWithin = IsAnyPlayerWithingDist(distance, _entityAlive);
ToggleCollisions(!isPlayerWithin);
}
public void ToggleCollisions(bool value)
{
ToggleCollisions(value, _entityAlive);
}
private void ToggleCollisions(bool value, EntityAlive entity)
{
if (_largeEntityBlocker)
{
_largeEntityBlocker.gameObject.SetActive(value);
}
entity.PhysicsTransform.gameObject.SetActive(value);
entity.IsNoCollisionMode.Value = !value;
}
private static bool IsAnyPlayerWithingDist(float dist, EntityAlive entity)
{
var persistentPlayerList = GameManager.Instance.GetPersistentPlayerList();
if (persistentPlayerList?.Players == null) return false;
foreach (var keyValuePair in persistentPlayerList.Players)
{
var entityPlayer = entity.world.GetEntity(keyValuePair.Value.EntityId) as EntityPlayer;
if (!entityPlayer) continue;
var magnitude = (entityPlayer.getChestPosition() - entity.position).magnitude;
if (!entityPlayer || !(magnitude <= dist)) continue;
return ((EntityAliveV2)entity).IsFriendlyPlayer(entityPlayer);
}
return false;
}
public void CheckFallAndGround()
{
if (_entityAlive.isEntityRemote) return;
if (!_entityAlive.emodel) return;
var entityAliveV2 = _entityAlive as EntityAliveV2;
var isInElevator = _entityAlive.IsInElevator();
// Jump state isn't visible, so we made a work around.
var jumpState = EntityAlive.JumpState.Off;
if (entityAliveV2 != null)
{
jumpState = entityAliveV2.GetJumpState();
}
var avatarController = _entityAlive.emodel.avatarController;
if (!avatarController) return;
var flag = _entityAlive.onGround || _entityAlive.isSwimming || isInElevator;
if (flag)
{
fallTime = 0f;
fallThresholdTime = 0f;
if (isInElevator)
{
fallThresholdTime = 0.6f;
}
}
else
{
if (fallThresholdTime == 0f)
{
fallThresholdTime = 0.1f + _entityAlive.rand.RandomFloat * 0.3f;
}
fallTime += 0.05f;
}
var canFall = !_entityAlive.emodel.IsRagdollActive &&
_entityAlive.bodyDamage.CurrentStun == EnumEntityStunType.None &&
!_entityAlive.isSwimming && !isInElevator && jumpState == EntityAlive.JumpState.Off &&
!_entityAlive.IsDead();
if (fallTime <= fallThresholdTime)
{
canFall = false;
}
avatarController.SetFallAndGround(canFall, flag);
}
public Vector3 ConfigureBoundaryBox(Vector3 newSize, Vector3 center)
{
var component = _entityAlive.gameObject.GetComponent<BoxCollider>();
if (!component) return Vector3.zero;
// Re-adjusting the box collider
component.size = newSize;
var scaledExtent = new Vector3(component.size.x / 2f * _entityAlive.transform.localScale.x,
component.size.y / 2f * _entityAlive.transform.localScale.y, component.size.z / 2f * _entityAlive.transform.localScale.z);
var vector = new Vector3(component.center.x * _entityAlive.transform.localScale.x,
component.center.y * _entityAlive.transform.localScale.y, component.center.z * _entityAlive.transform.localScale.z);
_entityAlive.boundingBox = BoundsUtils.BoundsForMinMax(-scaledExtent.x, -scaledExtent.y, -scaledExtent.z, scaledExtent.x,
scaledExtent.y, scaledExtent.z);
_entityAlive.boundingBox.center += vector;
if (center != Vector3.zero)
_entityAlive.boundingBox.center = center;
return scaledExtent;
}
}

View File

@@ -0,0 +1,137 @@
public class PushOutOfBlocksUtils
{
private EntityAlive _entityAlive;
public PushOutOfBlocksUtils(EntityAlive entityAlive)
{
_entityAlive = entityAlive;
}
private bool ShouldPushOutOfBlock(int _x, int _y, int _z, bool pushOutOfTerrain)
{
var shape = _entityAlive.world.GetBlock(_x, _y, _z).Block.shape;
if (shape.IsSolidSpace && !shape.IsTerrain())
{
return true;
}
if (!pushOutOfTerrain || !shape.IsSolidSpace || !shape.IsTerrain()) return false;
var shape2 = _entityAlive.world.GetBlock(_x, _y + 1, _z).Block.shape;
return shape2.IsSolidSpace && shape2.IsTerrain();
}
private bool PushOutOfBlocks(float _x, float _y, float _z)
{
var num = Utils.Fastfloor(_x);
var num2 = Utils.Fastfloor(_y);
var num3 = Utils.Fastfloor(_z);
var num4 = _x - (float)num;
var num5 = _z - (float)num3;
var result = false;
if (!this.ShouldPushOutOfBlock(num, num2, num3, false) &&
(!this.ShouldPushOutOfBlock(num, num2 + 1, num3, false))) return false;
var flag2 = !this.ShouldPushOutOfBlock(num - 1, num2, num3, true) &&
!this.ShouldPushOutOfBlock(num - 1, num2 + 1, num3, true);
var flag3 = !this.ShouldPushOutOfBlock(num + 1, num2, num3, true) &&
!this.ShouldPushOutOfBlock(num + 1, num2 + 1, num3, true);
var flag4 = !this.ShouldPushOutOfBlock(num, num2, num3 - 1, true) &&
!this.ShouldPushOutOfBlock(num, num2 + 1, num3 - 1, true);
var flag5 = !this.ShouldPushOutOfBlock(num, num2, num3 + 1, true) &&
!this.ShouldPushOutOfBlock(num, num2 + 1, num3 + 1, true);
var b = byte.MaxValue;
var num6 = 9999f;
if (flag2 && num4 < num6)
{
num6 = num4;
b = 0;
}
if (flag3 && 1.0 - (double)num4 < (double)num6)
{
num6 = 1f - num4;
b = 1;
}
if (flag4 && num5 < num6)
{
num6 = num5;
b = 4;
}
if (flag5 && 1f - num5 < num6)
{
b = 5;
}
var num7 = 0.1f;
switch (b)
{
case 0:
_entityAlive.motion.x = -num7;
break;
case 1:
_entityAlive.motion.x = num7;
break;
case 4:
_entityAlive.motion.z = -num7;
break;
case 5:
_entityAlive.motion.z = num7;
break;
}
if (b != 255)
{
result = true;
}
return result;
}
public virtual void CheckStuck(Vector3 position, float width, float depth)
{
_entityAlive.IsStuck = false;
if (_entityAlive.IsFlyMode.Value) return;
var num = _entityAlive.boundingBox.min.y + 0.5f;
_entityAlive.IsStuck = PushOutOfBlocks(position.x - width * 0.3f, num, position.z + depth * 0.3f);
_entityAlive.IsStuck = (PushOutOfBlocks(position.x - width * 0.3f, num, position.z - depth * 0.3f) || _entityAlive.IsStuck);
_entityAlive.IsStuck = (PushOutOfBlocks(position.x + width * 0.3f, num, position.z - depth * 0.3f) || _entityAlive.IsStuck);
_entityAlive.IsStuck = (PushOutOfBlocks(position.x + width * 0.3f, num, position.z + depth * 0.3f) || _entityAlive.IsStuck);
if (_entityAlive.IsStuck) return;
var x = Utils.Fastfloor(position.x);
var num2 = Utils.Fastfloor(num);
var z = Utils.Fastfloor(position.z);
if (!ShouldPushOutOfBlock(x, num2, z, true) ||
!CheckNonSolidVertical(new Vector3i(x, num2 + 1, z), 4, 2)) return;
_entityAlive.IsStuck = true;
_entityAlive.motion = new Vector3(0f, 1.6f, 0f);
Log.Warning($"{_entityAlive.EntityName} ({_entityAlive.entityId}) is stuck. Unsticking.");
}
private bool CheckNonSolidVertical(Vector3i blockPos, int maxY, int verticalSpace)
{
for (int i = 0; i < maxY; i++)
{
if (!_entityAlive.world.GetBlock(blockPos.x, blockPos.y + i + 1, blockPos.z).Block.shape.IsSolidSpace)
{
bool flag = true;
for (int j = 1; j < verticalSpace; j++)
{
if (_entityAlive.world.GetBlock(blockPos.x, blockPos.y + i + 1 + j, blockPos.z).Block.shape.IsSolidSpace)
{
flag = false;
break;
}
}
if (flag)
{
return true;
}
}
}
return false;
}
}

View File

@@ -0,0 +1,38 @@
public class TileEntityAoE : TileEntity
{
public TileEntityAoE(Chunk _chunk) : base(_chunk)
{
}
private TileEntityAoE(TileEntityAoE _other) : base(null)
{
Debug.Log("Creating new TileEntity");
localChunkPos = _other.localChunkPos;
}
public override void CopyFrom(TileEntity _other)
{
Debug.Log("Copy From TileEntity");
localChunkPos = _other.localChunkPos;
}
public override TileEntityType GetTileEntityType()
{
return (TileEntityType)RebirthUtilities.TileEntityRebirth.TileEntityAoE;
}
public override void Reset(FastTags<TagGroup.Global> questTags)
{
Debug.Log("Resetting TileEntity");
base.Reset(questTags);
setModified();
}
public override bool IsActive(World world)
{
return true;
}
public override TileEntity Clone()
{
Debug.Log("Cloning TileEntity");
return new TileEntityAoE(this);
}
}

View File

@@ -0,0 +1,74 @@
using System.Collections.Generic;
namespace Harmony.AvatarControllerPatch
{
public class AvatarControllerSetTrig
{
[HarmonyPatch(typeof(AvatarController))]
[HarmonyPatch("SetCrouching")]
public class AvatarControllerSetCrouching
{
private static readonly int IsCrouchingHash = Animator.StringToHash("IsCrouching");
public static bool Prefix(global::AvatarController __instance, Animator ___anim, bool _bEnable, Dictionary<int, AnimParamData> ___ChangedAnimationParameters, global::EntityAlive ___entity)
{
if (___anim == null || ___anim.GetBool(IsCrouchingHash) == _bEnable) return true;
___anim.SetBool(IsCrouchingHash, _bEnable);
if (IsCrouchingHash == AvatarController.isFPVHash)
{
return true;
}
if (!___entity.isEntityRemote)
{
___ChangedAnimationParameters[IsCrouchingHash] = new AnimParamData(IsCrouchingHash, AnimParamData.ValueTypes.Bool, _bEnable);
}
return true;
}
}
[HarmonyPatch(typeof(AvatarZombieController))]
[HarmonyPatch("FindBodyParts")]
public class AvatarControllerFindBodyParts
{
public static void Postfix(global::AvatarZombieController __instance, global::EntityAlive ___entity, Transform ___bipedT, ref Transform ___rightHandT)
{
if (___entity is not EntityAliveV2 entityAlive) return;
// Since we allow weapon switching, the right hand transform may change depending on the weapon.
// Re-read the right hand transform, which will give it the option to specify another one through a property entry on the item.
___rightHandT = ___bipedT.FindInChilds(entityAlive.GetRightHandTransformName(), false);
}
}
[HarmonyPatch(typeof(AvatarZombieController))]
[HarmonyPatch("Update")]
public class AvatarControllerUpdate
{
public static void Postfix(global::AvatarZombieController __instance, global::EntityAlive ___entity)
{
if (___entity == null) return;
if (___entity is not EntityAliveV2) return;
if (___entity.IsFlyMode.Value) return;
__instance.TryGetFloat(AvatarController.forwardHash, out var num);
__instance.TryGetFloat(AvatarController.strafeHash, out var num2);
if (num < 0.01)
{
__instance.UpdateFloat(AvatarController.forwardHash, 0f, false);
}
if (num2 < 0.01)
{
__instance.UpdateFloat(AvatarController.strafeHash, 0f, false);
}
if (num < 0.01f || num2 < 0.01f)
{
__instance.UpdateBool(AvatarController.isMovingHash, false, false);
}
}
}
}
}

View File

@@ -0,0 +1,67 @@
namespace Harmony.Dialog
{
// Removes the custom IsBusy bool, which pauses custom NPCs in their activities, allowing the player to talk to them.
[HarmonyPatch(typeof(XUiC_DialogWindowGroup))]
[HarmonyPatch("OnClose")]
public class OnClose
{
public static bool Prefix(XUiC_DialogWindowGroup __instance)
{
//if (!__instance.xui.playerUI.entityPlayer.Buffs.HasCustomVar("CurrentNPC")) return true;
int entityID = RebirthVariables.talkingToNPC; // (int)__instance.xui.playerUI.entityPlayer.Buffs.GetCustomVar("CurrentNPC");
global::EntityAliveV2 myEntity = __instance.xui.playerUI.entityPlayer.world.GetEntity(entityID) as global::EntityAliveV2;
if (myEntity == null)
{
return true;
}
myEntity.Buffs.RemoveBuff("buffTalkingTo");
myEntity.Buffs.SetCustomVar("CurrentPlayer", 0f);
myEntity.emodel.avatarController.UpdateBool("IsBusy", false);
return true;
}
}
// Removes the custom IsBusy bool, which pauses custom NPCs in their activities, allowing the player to talk to them.
[HarmonyPatch(typeof(XUiC_DialogWindowGroup))]
[HarmonyPatch("OnOpen")]
public class OnOpen
{
public static bool Prefix(XUiC_DialogWindowGroup __instance)
{
//if (!__instance.xui.playerUI.entityPlayer.Buffs.HasCustomVar("CurrentNPC")) return true;
int entityID = RebirthVariables.talkingToNPC; // (int)__instance.xui.playerUI.entityPlayer.Buffs.GetCustomVar("CurrentNPC");
global::EntityAliveV2 myEntity = __instance.xui.playerUI.entityPlayer.world.GetEntity(entityID) as global::EntityAliveV2;
if (myEntity == null)
{
return true;
}
myEntity.Buffs.AddBuff("buffTalkingTo");
myEntity.emodel.avatarController.UpdateBool("IsBusy", true);
myEntity.RotateTo(__instance.xui.playerUI.entityPlayer, 360f, 360f);
myEntity.SetLookPosition(__instance.xui.playerUI.entityPlayer.getHeadPosition());
return true;
}
}
[HarmonyPatch(typeof(XUiC_LootWindowGroup))]
[HarmonyPatch("OnClose")]
public class XUiC_LootWindowGroupOnClose
{
public static bool Prefix(XUiC_DialogWindowGroup __instance)
{
if (!__instance.xui.playerUI.entityPlayer.Buffs.HasCustomVar("CurrentNPC")) return true;
int entityID = (int)__instance.xui.playerUI.entityPlayer.Buffs.GetCustomVar("CurrentNPC");
global::EntityAliveV2 myEntity = __instance.xui.playerUI.entityPlayer.world.GetEntity(entityID) as global::EntityAliveV2;
if (myEntity == null) return true;
myEntity.UpdateWeapon();
if (!SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer)
{
//myEntity.SendSyncData();
}
return true;
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* Match at least one of these buffs, by default.
* <requirement type="HasBuffSDX, SCore" requirementtype="Hide" value="buffCursed,buffGodMode,buffImagination" />
* Do not have these buffs
* <requirement type="HasBuffSDX, SCore" requirementtype="Hide" value="!buffCursed" " />
*/
using System.Linq;
public class DialogRequirementHasBuffSDX : BaseDialogRequirement
{
public override bool CheckRequirement(EntityPlayer player, EntityNPC talkingTo)
{
var matches = -1;
// If there's more than one buff listed, loop around, recording how many we match.
string[] array = Value.Split(new char[]
{
','
});
if (Value.Contains(","))
{
matches = array.Count(t => player.Buffs.HasBuff(t));
if (matches > 0)
return true;
return false;
}
else
{
// Reverse condition on the buff.
if (Value.StartsWith("!"))
{
var tempBuff = Value.Replace("!", "");
if (!player.Buffs.HasBuff(tempBuff))
return true;
}
// If no operator, just check if we have it
if (player.Buffs.HasBuff(Value))
return true;
}
return false;
}
}

View File

@@ -0,0 +1,30 @@
class ItemActionLauncherSDX : ItemActionLauncher
{
public override void StartHolding(ItemActionData _action)
{
// Launchers being held by NPCs don't appear to be loading their ammo properly on spawn in, so they are just shooting blanks.
if (_action.invData.itemValue.Meta == 0)
_action.invData.itemValue.Meta = 1;
base.StartHolding(_action);
}
public override void ItemActionEffects(GameManager _gameManager, ItemActionData _actionData, int _firingState, Vector3 _startPos, Vector3 _direction, int _userData = 0)
{
base.ItemActionEffects(_gameManager, _actionData, _firingState, _startPos, _direction, _userData);
ItemActionRanged.ItemActionDataRanged itemActionDataRanged = (ItemActionRanged.ItemActionDataRanged)_actionData;
itemActionDataRanged.invData.itemValue.Meta = 0;
}
public override void ExecuteAction(ItemActionData _actionData, bool _bReleased)
{
base.ExecuteAction(_actionData, _bReleased);
if (_bReleased)
return;
var itemActionDataRanged = (ItemActionRanged.ItemActionDataRanged)_actionData;
if (itemActionDataRanged.isReloading)
return;
// We were having trouble getting the NPCs to properly reload, so here we go!
StartHolding(_actionData);
}
}

View File

@@ -0,0 +1,71 @@
class ItemActionMeleeSDX : ItemActionMelee
{
// This is a copy of the GetExecuteActionTarget() minus the EntityEnemy check on the line 32 (holdingEntity.IsBreakingBlocks)
public override WorldRayHitInfo GetExecuteActionTarget(ItemActionData _actionData)
{
ItemActionMelee.InventoryDataMelee inventoryDataMelee = (ItemActionMelee.InventoryDataMelee)_actionData;
EntityAlive holdingEntity = inventoryDataMelee.invData.holdingEntity;
inventoryDataMelee.ray = holdingEntity.GetLookRay();
if (holdingEntity.IsBreakingBlocks)
{
if (inventoryDataMelee.ray.direction.y < 0f)
{
inventoryDataMelee.ray.direction = new Vector3(inventoryDataMelee.ray.direction.x, 0f, inventoryDataMelee.ray.direction.z);
ItemActionMelee.InventoryDataMelee inventoryDataMelee2 = inventoryDataMelee;
inventoryDataMelee2.ray.origin = inventoryDataMelee2.ray.origin + new Vector3(0f, -0.7f, 0f);
}
}
else if (holdingEntity.GetAttackTarget() != null)
{
Vector3 direction = holdingEntity.GetAttackTargetHitPosition() - inventoryDataMelee.ray.origin;
inventoryDataMelee.ray = new Ray(inventoryDataMelee.ray.origin, direction);
}
ItemActionMelee.InventoryDataMelee inventoryDataMelee3 = inventoryDataMelee;
inventoryDataMelee3.ray.origin = inventoryDataMelee3.ray.origin - 0.15f * inventoryDataMelee.ray.direction;
int modelLayer = holdingEntity.GetModelLayer();
holdingEntity.SetModelLayer(2, false);
float distance = Utils.FastMax(this.Range, this.BlockRange) + 0.15f;
if (holdingEntity.IsBreakingBlocks)
{
Voxel.Raycast(inventoryDataMelee.invData.world, inventoryDataMelee.ray, distance, 1073807360, 128, 0.4f);
}
else
{
EntityAlive x = null;
int layerMask = -538767381;
if (Voxel.Raycast(inventoryDataMelee.invData.world, inventoryDataMelee.ray, distance, layerMask, 128, this.SphereRadius))
{
x = (ItemActionAttack.GetEntityFromHit(Voxel.voxelRayHitInfo) as EntityAlive);
}
if (x == null)
{
Voxel.Raycast(inventoryDataMelee.invData.world, inventoryDataMelee.ray, distance, -538488837, 128, this.SphereRadius);
}
}
holdingEntity.SetModelLayer(modelLayer, false);
return _actionData.GetUpdatedHitInfo();
}
/*public override void hitTheTarget(
ItemActionMelee.InventoryDataMelee _actionData,
WorldRayHitInfo hitInfo,
float damageScale)
{
EntityAlive holdingEntity = _actionData.invData.holdingEntity;
ItemValue itemValue = _actionData.invData.itemValue;
float _weaponCondition = 1f;
if (itemValue.MaxUseTimes > 0)
_weaponCondition = ((float)itemValue.MaxUseTimes - itemValue.UseTimes) / (float)itemValue.MaxUseTimes;
float _originalValue = Mathf.Clamp01(_actionData.invData.item.CritChance.Value * (holdingEntity.Stamina / holdingEntity.Stats.Stamina.Max));
float _criticalHitChanceOLD = EffectManager.GetValue(PassiveEffects.CriticalChance, itemValue, _originalValue, holdingEntity);
_actionData.attackDetails.WeaponTypeTag = ItemActionAttack.MeleeTag;
int _flags = 1;
if (this.bUseParticleHarvesting && (this.particleHarvestingCategory == null || this.particleHarvestingCategory == this.item.MadeOfMaterial.id))
_flags |= 4;
float _blockDamage = this.GetDamageBlock(itemValue, ItemActionAttack.GetBlockHit(_actionData.invData.world, hitInfo), holdingEntity, _actionData.indexInEntityOfAction) * damageScale;
float damageEntity = this.GetDamageEntity(itemValue, holdingEntity, _actionData.indexInEntityOfAction);
ItemActionAttack.Hit(hitInfo, holdingEntity.entityId, this.DamageType == EnumDamageTypes.None ? EnumDamageTypes.Bashing : this.DamageType, _blockDamage, damageEntity, holdingEntity.Stats.Stamina.ValuePercent, _weaponCondition, _criticalHitChanceOLD, ItemAction.GetDismemberChance((ItemActionData)_actionData, hitInfo), this.item.MadeOfMaterial.SurfaceCategory, this.damageMultiplier, this.getBuffActions((ItemActionData)_actionData), _actionData.attackDetails, _flags, this.ActionExp, this.ActionExpBonusMultiplier, (ItemActionAttack)this, this.ToolBonuses, _actionData.bHarvesting ? ItemActionAttack.EnumAttackMode.RealAndHarvesting : ItemActionAttack.EnumAttackMode.RealNoHarvesting);
GameUtils.HarvestOnAttack((ItemActionData)_actionData, this.ToolBonuses);
}*/
}

View File

@@ -0,0 +1,53 @@
using System.Globalization;
using System.Xml.Linq;
public class MinEventActionAnimatorSetIntSDX : MinEventActionTargetedBase
{
private string _property;
private float _value;
private bool _cvarRef;
private string _refCvarName = string.Empty;
public override void Execute(MinEventParams @params)
{
for (var i = 0; i < targets.Count; i++)
{
if (targets[i].emodel == null || this.targets[i].emodel.avatarController == null) continue;
if (_cvarRef)
{
_value = targets[i].Buffs.GetCustomVar(_refCvarName, 0f);
}
targets[i].emodel.avatarController.UpdateInt(_property, (int)_value, true);
}
}
public override bool ParseXmlAttribute(XAttribute attribute)
{
var flag = base.ParseXmlAttribute(attribute);
if (flag) return true;
var localName = attribute.Name.LocalName;
switch (localName)
{
case "property":
_property = attribute.Value;
return true;
case "value":
{
if (attribute.Value.StartsWith("@"))
{
_cvarRef = true;
_refCvarName = attribute.Value.Substring(1);
}
else
{
_value = StringParsers.ParseFloat(attribute.Value, 0, -1, NumberStyles.Any);
}
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,53 @@
public class NetPackageWeaponSwap : NetPackage
{
private int entityId;
private string item;
public NetPackageWeaponSwap Setup(EntityAlive _entity, string _item)
{
entityId = _entity.entityId;
item = _item;
return this;
}
public override void read(PooledBinaryReader _reader)
{
entityId = _reader.ReadInt32();
item = _reader.ReadString();
}
public override void write(PooledBinaryWriter _writer)
{
base.write(_writer);
_writer.Write(entityId);
_writer.Write(item);
}
public override void ProcessPackage(World _world, GameManager _callbacks)
{
if (_world == null)
{
return;
}
// If you are the server, send it out to the clients.
if (SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer)
{
var entity = _world.GetEntity(entityId) as EntityAlive;
SingletonMonoBehaviour<ConnectionManager>.Instance.SendPackage(
NetPackageManager.GetPackage<NetPackageWeaponSwap>().Setup(entity, item), false, -1,
base.Sender.entityId, -1, null, 192);
return;
}
var entityAlive = _world.GetEntity(entityId) as EntityAliveV2;
if (!entityAlive) return;
var itemValue = ItemClass.GetItem(item);
//entityAlive.UpdateWeapon(itemValue);
}
public override int GetLength()
{
return 20;
}
}

View File

@@ -0,0 +1,40 @@
using Rebirth.RemoteCrafting;
namespace Features.RemoteCrafting
{
public class DropBoxToContainersPatches
{
[HarmonyPatch(typeof(XUiC_LootContainer))]
[HarmonyPatch("OnClose")]
public class XUiCLootContainerOnClose
{
public static bool Prefix(XUiC_LootContainer __instance, BlockValue ___blockValue, TileEntityLootContainer ___localTileEntity)
{
if (!___blockValue.Block.Properties.Values.ContainsKey("DropBox")) return true;
if (___localTileEntity == null) return true;
StringParsers.TryParseBool(___blockValue.Block.Properties.Values["DropBox"], out var isDropBox);
if (!isDropBox) return true;
var distance = 150f;
var primaryPlayer = __instance.xui.playerUI.entityPlayer;
var items = ___localTileEntity.GetItems();
for (var i = 0; i < items.Length; i++)
{
if (items[i].IsEmpty()) continue;
// If we successfully added, clear the stack.
if (RemoteCraftingUtils.AddToNearbyContainer(primaryPlayer, items[i], distance))
{
Debug.Log($"Removing {items[i].itemValue.ItemClass.GetItemName()}");
items[i] = ItemStack.Empty.Clone();
}
___localTileEntity.UpdateSlot(i, items[i]);
}
___localTileEntity.SetModified();
return true;
}
}
}
}

View File

@@ -0,0 +1,243 @@
using Rebirth.RemoteCrafting;
using System.Collections.Generic;
namespace Features.RemoteCrafting
{
/// <summary>
/// Patches to support the Crafting From Remote Storage
/// </summary>
public class EnhancedRecipeLists
{
/// <summary>
/// Used to determine which recipes the player can craft based on the availability of ingredients in local containers.
/// </summary>
[HarmonyPatch(typeof(XUiC_RecipeList))]
[HarmonyPatch("BuildRecipeInfosList")]
public class BuildRecipeInfosList
{
public static bool Prefix(XUiC_RecipeList __instance, ref List<ItemStack> _items)
{
var player = __instance.xui.playerUI.entityPlayer;
_items.AddRange(RemoteCraftingUtils.SearchNearbyContainers(player));
return true;
}
}
/// <summary>
/// Extends what is considered to be in the player's backpack / tool belt to include local containers.
/// </summary>
[HarmonyPatch(typeof(XUiM_PlayerInventory))]
[HarmonyPatch("GetAllItemStacks")]
public class GetAllItemStacks
{
public static void Postfix(ref List<ItemStack> __result, EntityPlayerLocal ___localPlayer)
{
__result.AddRange(RemoteCraftingUtils.SearchNearbyContainers(___localPlayer));
}
}
/// <summary>
/// Hijacks the GetBindingValue so we can get an accurate count of all the items we have available, including from local storage.
/// </summary>
[HarmonyPatch(typeof(XUiC_IngredientEntry))]
[HarmonyPatch("GetBindingValue")]
public class GetBindingValue
{
public static bool Prefix(XUiC_IngredientEntry __instance, ref bool __result, ref string value,
string bindingName, CachedStringFormatter<int> ___needcountFormatter,
CachedStringFormatter<int> ___havecountFormatter, bool ___materialBased, ItemStack ___ingredient,
string ___material, XUiC_RecipeCraftCount ___craftCountControl,
CachedStringFormatterXuiRgbaColor ___itemicontintcolorFormatter)
{
var flag = ___ingredient != null;
switch (bindingName)
{
case "haveneedcount":
{
var text = (flag
? ___needcountFormatter.Format(___ingredient.count * ___craftCountControl.Count)
: "");
var value1 = 0;
var childByType = __instance.WindowGroup.Controller.GetChildByType<XUiC_WorkstationMaterialInputGrid>();
if (childByType != null)
{
if (___materialBased)
{
value = (flag
? (___havecountFormatter.Format(childByType.GetWeight(___material)) + "/" + text)
: "");
}
else
{
value = (flag
? (___havecountFormatter.Format(
__instance.xui.PlayerInventory.GetItemCount(___ingredient.itemValue)) + "/" +
text)
: "");
}
}
else
{
var childByType2 = __instance.WindowGroup.Controller
.GetChildByType<XUiC_WorkstationInputGrid>();
if (childByType2 != null)
{
value = (flag
? (___havecountFormatter.Format(
childByType2.GetItemCount(___ingredient.itemValue)) + "/" + text)
: "");
}
else
{
value = (flag
? (___havecountFormatter.Format(
__instance.xui.PlayerInventory.GetItemCount(___ingredient.itemValue)) + "/" +
text)
: "");
if (flag)
{
// add items from lootcontainers
value1 = __instance.xui.PlayerInventory.GetItemCount(___ingredient.itemValue);
var array = RemoteCraftingUtils.SearchNearbyContainers(__instance.xui.playerUI.entityPlayer,
___ingredient.itemValue).ToArray();
foreach (var t in array)
{
if (t != null && t.itemValue.type != 0 &&
___ingredient.itemValue.type == t.itemValue.type)
{
value1 += t.count;
}
}
value = ___havecountFormatter.Format(value1) + "/" + text;
}
}
}
__result = true;
return false;
}
default:
return true;
}
}
}
/// <summary>
/// Expands the HasItems search to include local containers.
/// </summary>
[HarmonyPatch(typeof(XUiM_PlayerInventory))]
[HarmonyPatch("HasItems")]
public class HasItems
{
public static bool Postfix(bool __result, IList<ItemStack> _itemStacks, EntityPlayerLocal ___localPlayer,
int _multiplier)
{
if (__result) return true;
// We need to make sure we satisfy all of the items.
var itemsWeHave = 0;
foreach (var itemStack in _itemStacks)
{
var totalCount = 0;
// This is how many we need.
var num = itemStack.count * _multiplier;
// check player inventory
var slots = ___localPlayer.bag.GetSlots();
foreach (var entry in slots)
{
if (entry.IsEmpty()) continue;
if (itemStack.itemValue.GetItemOrBlockId() != entry.itemValue.GetItemOrBlockId()) continue;
totalCount += entry.count;
// We have enough.
if (totalCount >= num)
{
break;
}
}
// We have enough.
if (totalCount >= num)
{
itemsWeHave++;
continue;
}
// Check the toolbelt now.
slots = ___localPlayer.inventory.GetSlots();
foreach (var entry in slots)
{
if (entry.IsEmpty()) continue;
if (itemStack.itemValue.GetItemOrBlockId() != entry.itemValue.GetItemOrBlockId()) continue;
totalCount += entry.count;
// We have enough.
if (totalCount >= num)
break;
}
// We have enough.
if (totalCount >= num)
{
itemsWeHave++;
continue;
}
// check container
var containers = RemoteCraftingUtils.SearchNearbyContainers(___localPlayer, itemStack.itemValue);
foreach (var stack in containers)
{
if (stack.IsEmpty()) continue;
if (itemStack.itemValue.GetItemOrBlockId() != stack.itemValue.GetItemOrBlockId()) continue;
totalCount += stack.count;
// We have enough.
if (totalCount >= num)
break;
}
// We don't have enough for this.
if (totalCount < num)
{
return false;
}
if (totalCount >= num)
{
itemsWeHave++;
}
}
return itemsWeHave >= _itemStacks.Count;
}
}
/// <summary>
/// Removes from local storage containers items which we've consumed.
/// </summary>
[HarmonyPatch(typeof(XUiM_PlayerInventory))]
[HarmonyPatch("RemoveItems")]
public class RemoveItems
{
public static bool Prefix(XUiM_PlayerInventory __instance, IList<ItemStack> _itemStacks, EntityPlayerLocal ___localPlayer, int _multiplier, IList<ItemStack> _removedItems
, Bag ___backpack, Inventory ___toolbelt)
{
RemoteCraftingUtils.ConsumeItem(_itemStacks, ___localPlayer, _multiplier, _removedItems, ___backpack, ___toolbelt);
return false;
}
}
// Code from OCB7D2D/OcbPinRecipes
// Patch world unload to cleanup and save on exit
[HarmonyPatch(typeof(World))]
[HarmonyPatch("UnloadWorld")]
public class WorldUnloadWorld
{
static void Postfix()
{
if (!Broadcastmanager.HasInstance) return;
Broadcastmanager.Cleanup();
}
}
}
}

View File

@@ -0,0 +1,322 @@
using HarmonyLib;
using Rebirth.RemoteCrafting;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
// todo - fix as methods have changed
namespace Features.RemoteCrafting
{
/// <summary>
/// Patches to support repairing from remote storage
/// </summary>
public class ItemActionRepairPatches
{
private struct UpgradeInfo
{
public string FromBlock;
public string ToBlock;
public string Item;
public int ItemCount;
public string Sound;
public int Hits;
}
[HarmonyPatch(typeof(ItemActionRepair))]
[HarmonyPatch("RemoveRequiredResource")]
public class RemoveRequiredResource
{
private static bool Prefix(ref bool __result, ItemActionRepair __instance, ItemInventoryData data, UpgradeInfo ___currentUpgradeInfo)
{
if (string.IsNullOrEmpty(___currentUpgradeInfo.Item))
{
__result = true;
return false;
}
var itemValue = ItemClass.GetItem(___currentUpgradeInfo.Item);
if (data.holdingEntity.inventory.DecItem(itemValue, ___currentUpgradeInfo.ItemCount) == ___currentUpgradeInfo.ItemCount)
{
var entityPlayerLocal = data.holdingEntity as EntityPlayerLocal;
if (entityPlayerLocal != null && ___currentUpgradeInfo.ItemCount != 0)
{
entityPlayerLocal.AddUIHarvestingItem(new ItemStack(itemValue, -___currentUpgradeInfo.ItemCount));
}
__result = true;
return false;
}
if (data.holdingEntity.bag.DecItem(itemValue, ___currentUpgradeInfo.ItemCount) == ___currentUpgradeInfo.ItemCount)
{
var entityPlayerLocal2 = data.holdingEntity as EntityPlayerLocal;
if (entityPlayerLocal2 != null)
{
entityPlayerLocal2.AddUIHarvestingItem(new ItemStack(itemValue, -___currentUpgradeInfo.ItemCount));
}
__result = true;
return false;
}
var primaryPlayer = GameManager.Instance.World.GetPrimaryPlayer();
var distance = 150f;
var tileEntities = RemoteCraftingUtils.GetTileEntities(primaryPlayer, distance);
// counter quantity needed from item
var q = ___currentUpgradeInfo.ItemCount;
var itemStack = new ItemStack(ItemClass.GetItem(___currentUpgradeInfo.Item, false), ___currentUpgradeInfo.ItemCount);
//check player inventory for materials and reduce counter
var slots = primaryPlayer.bag.GetSlots();
q = q - slots
.Where(x => x.itemValue.ItemClass == itemStack.itemValue.ItemClass)
.Sum(y => y.count);
// check storage boxes
foreach (var tileEntity in tileEntities)
{
if (q <= 0) break;
if (tileEntity is not TileEntityLootContainer lootTileEntity) continue;
// If there's no items in this container, skip.
if (!lootTileEntity.HasItem(itemStack.itemValue)) continue;
for (var y = 0; y < lootTileEntity.items.Length; y++)
{
var item = lootTileEntity.items[y];
if (item.IsEmpty()) continue;
if (item.itemValue.ItemClass != itemStack.itemValue.ItemClass) continue;
// If we can completely satisfy the result, let's do that.
if (item.count >= q)
{
item.count -= q;
q = 0;
}
else
{
// Otherwise, let's just count down until we meet the requirement.
while (q >= 0)
{
item.count--;
q--;
if (item.count <= 0)
break;
}
}
//Update the slot on the container, and do the Setmodified(), so that the dedis can get updated.
if (item.count < 1)
lootTileEntity.UpdateSlot(y, ItemStack.Empty.Clone());
else
lootTileEntity.UpdateSlot(y, item);
lootTileEntity.SetModified();
}
}
primaryPlayer.AddUIHarvestingItem(new ItemStack(itemValue, -___currentUpgradeInfo.ItemCount), false);
__result = true;
return false;
}
}
[HarmonyPatch(typeof(ItemActionRepair))]
[HarmonyPatch("CanRemoveRequiredResource")]
public class CanRemoveRequiredResource
{
private static bool Prefix(ref bool __result, ItemActionRepair __instance, ItemInventoryData data, BlockValue blockValue, ref UpgradeInfo ___currentUpgradeInfo, string ___allowedUpgradeItems, string ___restrictedUpgradeItems, string ___upgradeActionSound, float ___hitCountOffset)
{
var block = blockValue.Block;
var flag = block.Properties.Values.ContainsKey("UpgradeBlock.Item");
var upgradeInfo = default(UpgradeInfo);
upgradeInfo.FromBlock = block.GetBlockName();
upgradeInfo.ToBlock = block.Properties.Values[Block.PropUpgradeBlockClassToBlock];
upgradeInfo.Sound = "";
if (flag)
{
upgradeInfo.Item = block.Properties.Values["UpgradeBlock.Item"];
if (___allowedUpgradeItems.Length > 0 && !___allowedUpgradeItems.ContainsCaseInsensitive(upgradeInfo.Item))
{
__result = false;
return false;
}
if (___restrictedUpgradeItems.Length > 0 && ___restrictedUpgradeItems.ContainsCaseInsensitive(upgradeInfo.Item))
{
__result = false;
return false;
}
}
if (___upgradeActionSound.Length > 0)
{
upgradeInfo.Sound = ___upgradeActionSound;
}
else if (flag)
{
var item = ItemClass.GetItem(upgradeInfo.Item);
if (item != null)
{
var itemClass = ItemClass.GetForId(item.type);
if (itemClass != null)
{
upgradeInfo.Sound =
$"ImpactSurface/{data.holdingEntity.inventory.holdingItem.MadeOfMaterial.SurfaceCategory}hit{itemClass.MadeOfMaterial.SurfaceCategory}";
}
}
}
if (!int.TryParse(block.Properties.Values["UpgradeBlock.UpgradeHitCount"], out var num))
{
__result = false;
return false;
}
upgradeInfo.Hits = (int)(num + ___hitCountOffset < 1f ? 1f : num + ___hitCountOffset);
if (!int.TryParse(block.Properties.Values[Block.PropUpgradeBlockClassItemCount], out upgradeInfo.ItemCount) && flag)
{
__result = false;
return false;
}
___currentUpgradeInfo = upgradeInfo;
if (___currentUpgradeInfo.FromBlock != null && flag)
{
var item = ItemClass.GetItem(___currentUpgradeInfo.Item, false);
if (data.holdingEntity.inventory.GetItemCount(item) >= ___currentUpgradeInfo.ItemCount)
{
__result = true;
return false;
}
if (data.holdingEntity.bag.GetItemCount(item) >= ___currentUpgradeInfo.ItemCount)
{
__result = true;
return false;
}
}
else if (!flag)
{
__result = true;
return false;
}
var primaryPlayer = GameManager.Instance.World.GetPrimaryPlayer();
var itemStack = new ItemStack(ItemClass.GetItem(___currentUpgradeInfo.Item), ___currentUpgradeInfo.ItemCount);
var distance = 150f;
var totalCount = RemoteCraftingUtils.SearchNearbyContainers(primaryPlayer, itemStack.itemValue, distance).Sum(y => y.count);
if (totalCount >= ___currentUpgradeInfo.ItemCount)
{
__result = true;
return false;
}
__result = false;
return false;
}
}
[HarmonyPatch(typeof(ItemActionRepair))]
[HarmonyPatch("removeRequiredItem")]
public class RemoveRequiredItem
{
private static bool Prefix(ref bool __result, ItemActionRepair __instance, ItemInventoryData _data, ItemStack _itemStack)
{
__result = false;
__result = _data.holdingEntity.inventory.DecItem(_itemStack.itemValue, _itemStack.count) == _itemStack.count || _data.holdingEntity.bag.DecItem(_itemStack.itemValue, _itemStack.count, false) == _itemStack.count;
if (__result)
{
return false;
}
var primaryPlayer = GameManager.Instance.World.GetPrimaryPlayer();
var distance = 150f;
var tileEntities = RemoteCraftingUtils.GetTileEntities(primaryPlayer, distance);
// counter quantity needed from item
var q = _itemStack.count;
//check player inventory for materials and reduce counter
var slots = primaryPlayer.bag.GetSlots();
q -= slots
.Where(x => x.itemValue.ItemClass == _itemStack.itemValue.ItemClass)
.Sum(y => y.count);
// check storage boxes
foreach (var tileEntity in tileEntities)
{
if (q <= 0) break;
if (tileEntity is not TileEntityLootContainer lootTileEntity) continue;
// If there's no items in this container, skip.
if (!lootTileEntity.HasItem(_itemStack.itemValue)) continue;
for (var y = 0; y < lootTileEntity.items.Length; y++)
{
var item = lootTileEntity.items[y];
if (item.IsEmpty()) continue;
if (item.itemValue.ItemClass != _itemStack.itemValue.ItemClass) continue;
// If we can completely satisfy the result, let's do that.
if (item.count >= q)
{
item.count -= q;
q = 0;
}
else
{
// Otherwise, let's just count down until we meet the requirement.
while (q >= 0)
{
item.count--;
q--;
if (item.count <= 0)
break;
}
}
//Update the slot on the container, and do the Setmodified(), so that the dedis can get updated.
if (item.count < 1)
lootTileEntity.UpdateSlot(y, ItemStack.Empty.Clone());
else
lootTileEntity.UpdateSlot(y, item);
lootTileEntity.SetModified();
}
}
return false;
}
}
[HarmonyPatch(typeof(ItemActionRepair))]
[HarmonyPatch("canRemoveRequiredItem")]
public class CanRemoveRequiredItem
{
private static bool Prefix(ref bool __result, ItemActionRepair __instance, ItemInventoryData _data, ItemStack _itemStack)
{
__result = false;
if (_data.holdingEntity.inventory.GetItemCount(_itemStack.itemValue) >= _itemStack.count || _data.holdingEntity.bag.GetItemCount(_itemStack.itemValue) >= _itemStack.count)
{
__result = true;
return false;
}
var primaryPlayer = GameManager.Instance.World.GetPrimaryPlayer();
var distance = 150f;
var totalCount = RemoteCraftingUtils.SearchNearbyContainers(primaryPlayer, _itemStack.itemValue, distance).Sum(y => y.count);
if (totalCount <= 0) return false;
__result = true;
return false;
}
}
}
}

View File

@@ -0,0 +1,204 @@
using System.Collections.Concurrent;
// Stripped down Firemanager
public class Broadcastmanager
{
private static Broadcastmanager instance = null;
private static ConcurrentDictionary<Vector3i, BlockValue> Broadcastmap = new ConcurrentDictionary<Vector3i, BlockValue>();
private const string saveFile = "Broadcastmanager.dat";
private ThreadManager.ThreadInfo dataSaveThreadInfo;
public static bool HasInstance => instance != null;
//public bool Enabled { private set; get; }
public static Broadcastmanager Instance
{
get
{
return instance;
}
}
public static void Init()
{
Broadcastmanager.instance = new Broadcastmanager();
Log.Out("Starting Broadcast Manager");
// Read the Broadcastmanager
Broadcastmanager.Instance.Load();
}
// Save the lootcontainer location.
public void Write(BinaryWriter _bw)
{
_bw.Write("V1");
foreach (var temp in Broadcastmap)
_bw.Write(temp.Key.ToString());
}
// Read lootcontainer location.
public void Read(BinaryReader _br)
{
var version = _br.ReadString();
if (version == "V1")
{
while (_br.BaseStream.Position != _br.BaseStream.Length)
{
string container = _br.ReadString();
add(StringParsers.ParseVector3i(container));
}
}
else
{
foreach (var position in version.Split(';'))
while (_br.BaseStream.Position != _br.BaseStream.Length)
{
if (string.IsNullOrEmpty(position)) continue;
var vector = StringParsers.ParseVector3i(position);
add(vector);
add(StringParsers.ParseVector3i(_br.ReadString()));
}
}
}
// check if lootcontainer exists in dictionary
public bool Check(Vector3i _blockPos)
{
return Broadcastmap.TryGetValue(_blockPos, out _);
}
public void Add(Vector3i _blockPos, int entityID = -1)
{
if (!GameManager.IsDedicatedServer)
add(_blockPos);
if (!SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer)
{
SingletonMonoBehaviour<ConnectionManager>.Instance.SendToServer(NetPackageManager.GetPackage<NetPackageAddBroadcastPosition>().Setup(_blockPos, entityID), false);
return;
}
SingletonMonoBehaviour<ConnectionManager>.Instance.SendPackage(NetPackageManager.GetPackage<NetPackageAddBroadcastPosition>().Setup(_blockPos, entityID), false, -1, -1, -1, null, 192);
}
public void Remove(Vector3i _blockPos, int entityID = -1)
{
if (!GameManager.IsDedicatedServer)
remove(_blockPos);
if (!SingletonMonoBehaviour<ConnectionManager>.Instance.IsServer)
{
SingletonMonoBehaviour<ConnectionManager>.Instance.SendToServer(NetPackageManager.GetPackage<NetPackageRemoveBroadcastPosition>().Setup(_blockPos, entityID), false);
return;
}
SingletonMonoBehaviour<ConnectionManager>.Instance.SendPackage(NetPackageManager.GetPackage<NetPackageRemoveBroadcastPosition>().Setup(_blockPos, entityID), false, -1, -1, -1, null, 192);
}
// Remove lootcontainer from dictionary
public void remove(Vector3i _blockPos)
{
if (!Broadcastmap.ContainsKey(_blockPos)) return;
Broadcastmap.TryRemove(_blockPos, out var block);
}
// Add lootcontainer to dictionary
public void add(Vector3i _blockPos)
{
var block = GameManager.Instance.World.GetBlock(_blockPos);
Broadcastmap.TryAdd(_blockPos, block);
}
private int saveDataThreaded(ThreadManager.ThreadInfo _threadInfo)
{
PooledExpandableMemoryStream pooledExpandableMemoryStream = (PooledExpandableMemoryStream)_threadInfo.parameter;
string text = string.Format("{0}/{1}", GameIO.GetSaveGameDir(), saveFile);
if (!Directory.Exists(GameIO.GetSaveGameDir()))
{
Directory.CreateDirectory(GameIO.GetSaveGameDir());
}
if (File.Exists(text))
{
File.Copy(text, string.Format("{0}/{1}", GameIO.GetSaveGameDir(), $"{saveFile}.bak"), true);
}
pooledExpandableMemoryStream.Position = 0L;
StreamUtils.WriteStreamToFile(pooledExpandableMemoryStream, text);
MemoryPools.poolMemoryStream.FreeSync(pooledExpandableMemoryStream);
Log.Out($"Broadcast Manager {text} Saving: {Broadcastmap.Count}");
return -1;
}
public void Save()
{
if (this.dataSaveThreadInfo == null || !ThreadManager.ActiveThreads.ContainsKey("silent_BroadcastDataSave"))
{
PooledExpandableMemoryStream pooledExpandableMemoryStream = MemoryPools.poolMemoryStream.AllocSync(true);
using (PooledBinaryWriter pooledBinaryWriter = MemoryPools.poolBinaryWriter.AllocSync(false))
{
pooledBinaryWriter.SetBaseStream(pooledExpandableMemoryStream);
this.Write(pooledBinaryWriter);
}
this.dataSaveThreadInfo = ThreadManager.StartThread("silent_BroadcastDataSave", null, new ThreadManager.ThreadFunctionLoopDelegate(this.saveDataThreaded), null, System.Threading.ThreadPriority.Normal, pooledExpandableMemoryStream, null, false);
}
}
public void Load()
{
string path = string.Format("{0}/{1}", GameIO.GetSaveGameDir(), saveFile);
if (Directory.Exists(GameIO.GetSaveGameDir()) && File.Exists(path))
{
try
{
using (FileStream fileStream = File.OpenRead(path))
{
using (PooledBinaryReader pooledBinaryReader = MemoryPools.poolBinaryReader.AllocSync(false))
{
pooledBinaryReader.SetBaseStream(fileStream);
this.Read(pooledBinaryReader);
}
}
}
catch (Exception)
{
path = string.Format("{0}/{1}", GameIO.GetSaveGameDir(), $"{saveFile}.bak");
if (File.Exists(path))
{
using (FileStream fileStream2 = File.OpenRead(path))
{
using (PooledBinaryReader pooledBinaryReader2 = MemoryPools.poolBinaryReader.AllocSync(false))
{
pooledBinaryReader2.SetBaseStream(fileStream2);
this.Read(pooledBinaryReader2);
}
}
}
}
Log.Out($"Broadcast Manager {path} Loaded: {Broadcastmap.Count}");
}
}
private void WaitOnSave()
{
if (this.dataSaveThreadInfo != null)
{
this.dataSaveThreadInfo.WaitForEnd();
this.dataSaveThreadInfo = null;
}
}
public static void Cleanup()
{
if (instance != null)
{
instance.SaveAndClear();
}
}
private void SaveAndClear()
{
WaitOnSave();
Save();
WaitOnSave();
Broadcastmap.Clear();
instance = null;
Log.Out("Broadcastmanager stopped");
}
}

View File

@@ -0,0 +1,54 @@
using Rebirth.RemoteCrafting;
public class BlockDropBoxContainer : BlockSecureLootSigned
{
private float _distance;
private float _updateTime;
public override void Init()
{
base.Init();
_distance = RebirthVariables.broadcastDistance;
Properties.ParseFloat("Distance", ref _distance);
_updateTime = 100UL;
Properties.ParseFloat("UpdateTick", ref _updateTime);
}
public override ulong GetTickRate()
{
return (ulong)_updateTime;
}
public override void OnBlockAdded(WorldBase world, Chunk _chunk, Vector3i _blockPos, BlockValue _blockValue)
{
base.OnBlockAdded(world, _chunk, _blockPos, _blockValue);
if (!world.IsRemote())
{
world.GetWBT().AddScheduledBlockUpdate(0, _blockPos, this.blockID, GetTickRate());
}
}
public override bool UpdateTick(WorldBase world, int _clrIdx, Vector3i _blockPos, BlockValue _blockValue,
bool _bRandomTick, ulong _ticksIfLoaded, GameRandom _rnd)
{
var tileLootContainer = (TileEntityLootContainer)world.GetTileEntity(_clrIdx, _blockPos);
if (tileLootContainer == null) return false;
if (!tileLootContainer.IsUserAccessing())
{
var primaryPlayer = GameManager.Instance.World.GetPrimaryPlayer();
foreach (var itemStack in tileLootContainer.GetItems())
{
if (itemStack.IsEmpty()) continue;
// If we successfully added, clear the stack.
if (RemoteCraftingUtils.AddToNearbyContainer(primaryPlayer, itemStack, _distance))
itemStack.Clear();
}
tileLootContainer.bTouched = true;
tileLootContainer.SetModified();
}
world.GetWBT().AddScheduledBlockUpdate(0, _blockPos, this.blockID, GetTickRate());
return true;
}
}

View File

@@ -0,0 +1,49 @@
//copy of NetPackageAddFirePosition
public class NetPackageAddBroadcastPosition : NetPackage
{
private Vector3i position;
private int entityThatCausedIt;
public NetPackageAddBroadcastPosition Setup(Vector3i _position, int _entityThatCausedIt)
{
this.position = _position;
this.entityThatCausedIt = _entityThatCausedIt;
return this;
}
public override void read(PooledBinaryReader _br)
{
this.position = new Vector3i((float)_br.ReadInt32(), (float)_br.ReadInt32(), (float)_br.ReadInt32());
this.entityThatCausedIt = _br.ReadInt32();
}
public override void write(PooledBinaryWriter _bw)
{
base.write(_bw);
_bw.Write((int)this.position.x);
_bw.Write((int)this.position.y);
_bw.Write((int)this.position.z);
_bw.Write(this.entityThatCausedIt);
}
public override int GetLength()
{
return 20;
}
public override void ProcessPackage(World _world, GameManager _callbacks)
{
if (_world == null)
{
return;
}
if (!_world.IsRemote())
{
return;
}
Broadcastmanager.Instance.add(position);
}
}

View File

@@ -0,0 +1,49 @@
//copy of NetPackageRemoveFirePosition
public class NetPackageRemoveBroadcastPosition : NetPackage
{
private Vector3i position;
private int entityThatCausedIt;
public NetPackageRemoveBroadcastPosition Setup(Vector3i _position, int _entityThatCausedIt)
{
this.position = _position;
this.entityThatCausedIt = _entityThatCausedIt;
return this;
}
public override void read(PooledBinaryReader _br)
{
this.position = new Vector3i((float)_br.ReadInt32(), (float)_br.ReadInt32(), (float)_br.ReadInt32());
this.entityThatCausedIt = _br.ReadInt32();
}
public override void write(PooledBinaryWriter _bw)
{
base.write(_bw);
_bw.Write((int)this.position.x);
_bw.Write((int)this.position.y);
_bw.Write((int)this.position.z);
_bw.Write(this.entityThatCausedIt);
}
public override int GetLength()
{
return 20;
}
public override void ProcessPackage(World _world, GameManager _callbacks)
{
if (_world == null)
{
return;
}
if (!_world.IsRemote())
{
return;
}
Broadcastmanager.Instance.remove(position);
}
}

View File

@@ -0,0 +1,281 @@
using JetBrains.Annotations;
using System.Collections.Generic;
using System.Linq;
namespace Rebirth.RemoteCrafting
{
[UsedImplicitly]
public class RemoteCraftingUtils
{
public static List<TileEntity> GetTileEntities(EntityAlive player)
{
var distance = 150f;
var tileEntities = RebirthUtilities.GetTileEntities(player, distance);
return tileEntities;
}
public static bool DisableSender(IEnumerable<string> value, TileEntity tileEntity)
{
if (tileEntity.TryGetSelfOrFeature(out ITileEntityLootable storage))
{
if (value.All(x => x.Trim() != storage.lootListName))
{
return true;
}
}
else
{
return false;
}
return false;
}
private static bool BindToWorkstation(string value, EntityAlive player, TileEntity tileEntity)
{
var result = false;
if (player is not EntityPlayerLocal playerLocal) return false;
if (!tileEntity.TryGetSelfOrFeature(out ITileEntityLootable storage)) return false;
// TODO: we want to refactor this to remove the complex LinQ.
// what could be easier than linq?
// bind storage to workstation
if (value.Split(';').Where(x =>
x.Split(':')[0].Split(',').Any(ws => ws.Trim() == playerLocal.PlayerUI.xui.currentWorkstation))
.Any(x => x.Split(':')[1].Split(',').Any(y => y == storage.lootListName))) result = true;
// bind storage to other workstations if allowed
if (value.Split(';').Any(x =>
x.Split(':')[0].Split(',').Any(ws => ws.Trim() == playerLocal.PlayerUI.xui.currentWorkstation)))
return result;
{
if (value.Split(';').Any(x => x.Split(':')[1].Split(',').Any(y => y == storage.lootListName)))
{
result = false;
}
else
{
result = true;
}
}
return result;
}
private static bool NotToWorkstation(string value, EntityAlive player, TileEntity tileEntity)
{
var result = false;
if (player is not EntityPlayerLocal playerLocal) return false;
if (!tileEntity.TryGetSelfOrFeature(out ITileEntityLootable storage)) return false;
foreach (var bind in value.Split(';'))
{
var workstation = bind.Split(':')[0].Split(',');
var disablebinding = bind.Split(':')[1].Split(',');
if ((workstation.Any(ws => ws.Trim() == playerLocal.PlayerUI.xui.currentWorkstation)) &&
(disablebinding.Any(x => x.Trim() == storage.lootListName))) result = true;
}
return result;
}
public static List<ItemStack> SearchNearbyContainers(EntityAlive player)
{
var items = new List<ItemStack>();
var tileEntities = GetTileEntities(player);
foreach (var tileEntity in tileEntities)
{
if (!tileEntity.TryGetSelfOrFeature(out ITileEntityLootable storage)) continue;
if (storage.IsUserAccessing()) continue;
items.AddRange(storage.items);
}
return items;
}
public static List<ItemStack> SearchNearbyContainers(EntityAlive player, ItemValue itemValue)
{
var item = new List<ItemStack>();
var items = new List<ItemStack>();
var tileEntities = GetTileEntities(player);
foreach (var tileEntity in tileEntities)
{
if (!tileEntity.TryGetSelfOrFeature(out ITileEntityLootable storage)) continue;
// If the container is open, don't use it.
if (storage.IsUserAccessing()) continue;
item.AddRange(storage.items);
}
foreach (var t in item)
{
if ((!t.itemValue.HasModSlots || !t.itemValue.HasMods()) &&
t.itemValue.type == itemValue.type)
{
items.Add(t);
}
}
return items;
}
public static List<ItemStack> SearchNearbyContainers(EntityAlive player, ItemValue itemValue, float distance)
{
var item = new List<ItemStack>();
var items = new List<ItemStack>();
var tileEntities = RebirthUtilities.GetTileEntities(player, distance);
foreach (var tileEntity in tileEntities)
{
if (tileEntity.TryGetSelfOrFeature(out ITileEntityLootable storage))
{
if (storage.IsUserAccessing()) continue;
item.AddRange(storage.items);
}
}
foreach (var t in item)
{
if ((!t.itemValue.HasModSlots || !t.itemValue.HasMods()) &&
t.itemValue.type == itemValue.type)
{
items.Add(t);
}
}
return items;
}
public static bool AddToNearbyContainer(EntityAlive player, ItemStack itemStack, float distance)
{
var tileEntities = RebirthUtilities.GetTileEntities(player, distance);
foreach (var tileEntity in tileEntities)
{
if (!tileEntity.TryGetSelfOrFeature(out ITileEntityLootable storage)) continue;
// If the container is open, don't include it.
if (storage.IsUserAccessing()) continue;
// Don't try to add to a drop box.
if (storage.blockValue.Block.Properties.Values.ContainsKey("DropBox")) continue;
// Can we quickly find a incomplete stack?
//if (lootTileEntity.TryStackItem(0, itemStack)) return true;
var result = storage.TryStackItem(0, itemStack);
if (result.allMoved) return true;
var matchingItem = false;
// Loop through the items and see if we have any matching items.
foreach (var item in storage.items)
{
// We match with something.
if (item.itemValue.type != itemStack.itemValue.type) continue;
matchingItem = true;
break;
}
// If we don't match, don't try to add.
if (!matchingItem) continue;
// We added a full stack! No need to keep processing.
if (storage.AddItem(itemStack)) return true;
}
return false;
}
public static void ConsumeItem(IEnumerable<ItemStack> itemStacks, EntityPlayerLocal localPlayer, int multiplier, IList<ItemStack> _removedItems, Bag bag, Inventory toolbelt)
{
var tileEntities = GetTileEntities(localPlayer);
var enumerable = itemStacks as ItemStack[] ?? itemStacks.ToArray();
for (var i = 0; i < enumerable.Count(); i++)
{
// Grab from the backpack first.
var num = enumerable[i].count * multiplier;
if (bag != null)
{
num -= bag.DecItem(enumerable[i].itemValue, num, true, _removedItems);
if (num > 0)
{
// Check tool belt
if (toolbelt != null)
{
num -= toolbelt.DecItem(enumerable[i].itemValue, num, true, _removedItems);
}
}
}
// We've met our goals for this.
if (num <= 0) continue;
// check storage boxes
foreach (var tileEntity in tileEntities)
{
if (num <= 0) break;
if (!tileEntity.TryGetSelfOrFeature(out ITileEntityLootable storage)) continue;
// If someone is using the tool account, skip it.
if (storage.IsUserAccessing()) continue;
// If there's no items in this container, skip.
if (!storage.HasItem(enumerable[i].itemValue)) continue;
for (var y = 0; y < storage.items.Length; y++)
{
var item = storage.items[y];
if (item.IsEmpty()) continue;
if (item.itemValue.ItemClass != enumerable[i].itemValue.ItemClass) continue;
// If we can completely satisfy the result, let's do that.
if (item.count >= num)
{
item.count -= num;
// Add the item to the removed items list so we can return it.
_removedItems.Add(new ItemStack(item.itemValue.Clone(), num));
num = 0;
}
else
{
// Otherwise, let's just count down until we meet the requirement.
while (num >= 0)
{
item.count--;
num--;
if (item.count <= 0)
{
_removedItems.Add(new ItemStack(item.itemValue.Clone(), num));
break;
}
}
}
//Update the slot on the container, and do the Setmodified(), so that the dedis can get updated.
if (item.count < 1)
{
// Add it to the removed list.
_removedItems.Add(item.Clone());
storage.UpdateSlot(y, ItemStack.Empty.Clone());
}
else
{
storage.UpdateSlot(y, item);
}
}
storage.SetModified();
}
}
}
}
}

View File

@@ -0,0 +1,89 @@
// Code from Laydor slightly modified
public class XUiC_BroadcastButton : XUiController
{
private XUiV_Button _button;
public override void Init()
{
base.Init();
_button = viewComponent as XUiV_Button;
OnPress += Grab_OnPress;
}
public override void Update(float dt)
{
base.Update(dt);
if (!IsDirty) return;
IsDirty = false;
SetupButton();
}
public override void OnOpen()
{
base.OnOpen();
IsDirty = true;
}
private void Grab_OnPress(XUiController sender, int mouseButton)
{
//Check if Broadcastmanager is running
if (!Broadcastmanager.HasInstance) return;
if (Broadcastmanager.Instance.Check(xui.lootContainer.ToWorldPos()))
{
//Unselect button
_button.Selected = true;
// Remove from Broadcastmanager dictionary
Broadcastmanager.Instance.remove(xui.lootContainer.ToWorldPos());
}
else
{
//Select button
_button.Selected = false;
// Add to Broadcastmanager dictionary
Broadcastmanager.Instance.add(xui.lootContainer.ToWorldPos());
}
}
private void SetupButton()
{
//Log.Out("XUiC_BroadcastButton-SetupButton START");
//Unselect button and disable it
_button.Enabled = false;
_button.Selected = false;
_button.IsVisible = false;
if (xui.lootContainer != null)
{
Entity entity = GameManager.Instance.World.GetEntity(xui.lootContainer.EntityId) as Entity;
//Log.Out("XUiC_BroadcastButton-SetupButton entity: " + entity.EntityClass.entityClassName);
//Log.Out("XUiC_BroadcastButton-SetupButton entity is EntityAliveV2: " + (entity is EntityAliveV2));
if (entity is EntityAliveV2)
{
return;
}
}
if (xui.lootContainer == null || !Broadcastmanager.HasInstance ||
xui.vehicle != null ||
(xui.lootContainer != null && GameManager.Instance.World.GetEntity(xui.lootContainer.EntityId) is EntityDrone))
{
//Log.Out("XUiC_BroadcastButton-SetupButton xui.lootContainer == null: " + (xui.lootContainer == null));
//Log.Out("XUiC_BroadcastButton-SetupButton !Broadcastmanager.HasInstance: " + (!Broadcastmanager.HasInstance));
//Log.Out("XUiC_BroadcastButton-SetupButton xui.vehicle != null: " + (xui.vehicle != null));
//Log.Out("XUiC_BroadcastButton-SetupButton EntityNPCRebirth: " + ((xui.lootContainer != null && GameManager.Instance.World.GetEntity(xui.lootContainer.entityId)) is EntityNPCRebirth));
//Log.Out("XUiC_BroadcastButton-SetupButton EntityDrone: " + ((xui.lootContainer != null && GameManager.Instance.World.GetEntity(xui.lootContainer.entityId) is EntityDrone)));
return;
}
//Enable button and set if button is selected
_button.IsVisible = true;
_button.Enabled = true;
_button.Selected = !Broadcastmanager.Instance.Check(xui.lootContainer.ToWorldPos());
//Log.Out("XUiC_BroadcastButton-SetupButton END");
}
}