Upload from upload_mods.ps1
This commit is contained in:
22
Score/Fire/Harmony/ChunkSetBlock.cs
Normal file
22
Score/Fire/Harmony/ChunkSetBlock.cs
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Score/Fire/Harmony/Explosion.cs
Normal file
43
Score/Fire/Harmony/Explosion.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Score/Fire/Harmony/FireInit.cs
Normal file
28
Score/Fire/Harmony/FireInit.cs
Normal 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();
|
||||
}
|
||||
}*/
|
||||
}
|
||||
34
Score/Fire/Harmony/PerformanceTweaks.cs
Normal file
34
Score/Fire/Harmony/PerformanceTweaks.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Score/Fire/Harmony/UpdateCurrentBlockposAndValueFire.cs
Normal file
27
Score/Fire/Harmony/UpdateCurrentBlockposAndValueFire.cs
Normal 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
259
Score/Fire/Readme.md
Normal 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" />
|
||||
```
|
||||
|
||||
786
Score/Fire/Scripts/FireManager.cs
Normal file
786
Score/Fire/Scripts/FireManager.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
81
Score/Fire/Scripts/MinEvent/MinEventActionAddFireDamage.cs
Normal file
81
Score/Fire/Scripts/MinEvent/MinEventActionAddFireDamage.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
46
Score/Fire/Scripts/MinEvent/MinEventActionRemoveFire.cs
Normal file
46
Score/Fire/Scripts/MinEvent/MinEventActionRemoveFire.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
47
Score/Fire/Scripts/NetPackage/NetPackageAddFirePosition.cs
Normal file
47
Score/Fire/Scripts/NetPackage/NetPackageAddFirePosition.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
12
Score/Harmony/Harmony_GUIWindowConsole.cs
Normal file
12
Score/Harmony/Harmony_GUIWindowConsole.cs
Normal 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
60
Score/ILUtilities.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
531
Score/LockPicking/Scripts/Cipher.cs
Normal file
531
Score/LockPicking/Scripts/Cipher.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Score/LockPicking/Scripts/CipherControls.cs
Normal file
73
Score/LockPicking/Scripts/CipherControls.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
79
Score/LockPicking/Scripts/CipherDemoUI.cs
Normal file
79
Score/LockPicking/Scripts/CipherDemoUI.cs
Normal 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();
|
||||
// }
|
||||
//}
|
||||
|
||||
773
Score/LockPicking/Scripts/Keyhole.cs
Normal file
773
Score/LockPicking/Scripts/Keyhole.cs
Normal 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
|
||||
}
|
||||
}
|
||||
43
Score/LockPicking/Scripts/KeyholeDemoUI.cs
Normal file
43
Score/LockPicking/Scripts/KeyholeDemoUI.cs
Normal 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();
|
||||
// }
|
||||
//}
|
||||
|
||||
64
Score/LockPicking/Scripts/LockButton.cs
Normal file
64
Score/LockPicking/Scripts/LockButton.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
71
Score/LockPicking/Scripts/LockControls.cs
Normal file
71
Score/LockPicking/Scripts/LockControls.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
36
Score/LockPicking/Scripts/LockDemoCommonControls.cs
Normal file
36
Score/LockPicking/Scripts/LockDemoCommonControls.cs
Normal 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();
|
||||
// }
|
||||
//}
|
||||
|
||||
146
Score/LockPicking/Scripts/LockEmissive.cs
Normal file
146
Score/LockPicking/Scripts/LockEmissive.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
51
Score/LockPicking/Scripts/LockEmissiveControls.cs
Normal file
51
Score/LockPicking/Scripts/LockEmissiveControls.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
61
Score/LockPicking/Scripts/LockObjectRotation.cs
Normal file
61
Score/LockPicking/Scripts/LockObjectRotation.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
75
Score/LockPicking/Scripts/LocksetAudio.cs
Normal file
75
Score/LockPicking/Scripts/LocksetAudio.cs
Normal 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)];
|
||||
}
|
||||
}
|
||||
175
Score/LockPicking/Scripts/SphereII_Locks.cs
Normal file
175
Score/LockPicking/Scripts/SphereII_Locks.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
88
Score/LockPicking/XUiC/XUiC_PickLocking.cs
Normal file
88
Score/LockPicking/XUiC/XUiC_PickLocking.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
129
Score/NPCv2/Harmony/Entity/PlayerControllerPatch.cs
Normal file
129
Score/NPCv2/Harmony/Entity/PlayerControllerPatch.cs
Normal 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
52
Score/NPCv2/ReadMe.txt
Normal 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();
|
||||
|
||||
42
Score/NPCv2/Scripts/Dialog/DialogActionAddItemRebirth.cs
Normal file
42
Score/NPCv2/Scripts/Dialog/DialogActionAddItemRebirth.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
1355
Score/NPCv2/Scripts/Entity/EntityAliveV2.cs
Normal file
1355
Score/NPCv2/Scripts/Entity/EntityAliveV2.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
326
Score/NPCv2/Scripts/Entity/SupportClasses/LeaderUtils.cs
Normal file
326
Score/NPCv2/Scripts/Entity/SupportClasses/LeaderUtils.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
56
Score/NPCv2/Scripts/Entity/SupportClasses/MissionUtils.cs
Normal file
56
Score/NPCv2/Scripts/Entity/SupportClasses/MissionUtils.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Score/NPCv2/Scripts/Entity/SupportClasses/NPCQuestUtils.cs
Normal file
13
Score/NPCv2/Scripts/Entity/SupportClasses/NPCQuestUtils.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
130
Score/NPCv2/Scripts/Entity/SupportClasses/NPCUtils.cs
Normal file
130
Score/NPCv2/Scripts/Entity/SupportClasses/NPCUtils.cs
Normal 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;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
38
Score/NPCv2/Scripts/TileEntity/TileEntityAoE.cs
Normal file
38
Score/NPCv2/Scripts/TileEntity/TileEntityAoE.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
74
Score/Other/AvatarController.cs
Normal file
74
Score/Other/AvatarController.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
67
Score/Other/DialogClose.cs
Normal file
67
Score/Other/DialogClose.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
50
Score/Other/DialogRequirementHasBuffSDX.cs
Normal file
50
Score/Other/DialogRequirementHasBuffSDX.cs
Normal 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
30
Score/Other/ItemActionLauncherSDX.cs
Normal file
30
Score/Other/ItemActionLauncherSDX.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
71
Score/Other/ItemActionMeleeSDX.cs
Normal file
71
Score/Other/ItemActionMeleeSDX.cs
Normal 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);
|
||||
}*/
|
||||
}
|
||||
53
Score/Other/MinEventActionAnimatorSetIntSDX.cs
Normal file
53
Score/Other/MinEventActionAnimatorSetIntSDX.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
53
Score/Other/NetPackageWeaponSwap.cs
Normal file
53
Score/Other/NetPackageWeaponSwap.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
40
Score/RemoteCrafting/Harmony/DropBoxToContainers.cs
Normal file
40
Score/RemoteCrafting/Harmony/DropBoxToContainers.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
243
Score/RemoteCrafting/Harmony/IngredientsFromContainers.cs
Normal file
243
Score/RemoteCrafting/Harmony/IngredientsFromContainers.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
322
Score/RemoteCrafting/Harmony/ItemActionRepairPatches.cs
Normal file
322
Score/RemoteCrafting/Harmony/ItemActionRepairPatches.cs
Normal 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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
204
Score/RemoteCrafting/Manager/Broadcastmanager.cs
Normal file
204
Score/RemoteCrafting/Manager/Broadcastmanager.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
54
Score/RemoteCrafting/Scripts/BlockDropBoxContainer.cs
Normal file
54
Score/RemoteCrafting/Scripts/BlockDropBoxContainer.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
281
Score/RemoteCrafting/Scripts/RemoteCraftingUtils.cs
Normal file
281
Score/RemoteCrafting/Scripts/RemoteCraftingUtils.cs
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
89
Score/RemoteCrafting/Scripts/XUiC/XUiC_BroadcastButton.cs
Normal file
89
Score/RemoteCrafting/Scripts/XUiC/XUiC_BroadcastButton.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user