From 0f540c296afa09f3f78bc5ef0cc34769b3dd5451 Mon Sep 17 00:00:00 2001 From: "zolantris@vllc.dev" <151822540+zolantris@users.noreply.github.com> Date: Tue, 21 May 2024 03:00:16 -0700 Subject: [PATCH] Add the drakkal mast (#82) * Add the drakkal mast, which is super similar to viking mast, use vikingmast icon for now * fix gameobject for sailcloth on drakkal * add prefabname for drakkal mast, and fix a few more render problems * fix tier4 prefab references * fix wind direction for moder --- .../Translations/English/valheimraft.json | 4 +- src/ValheimRAFT/ValheimRAFT.csproj | 1 - .../ValheimRAFT/MoveableBaseRootComponent.cs | 2 +- src/ValheimRAFT/ValheimRAFT/SailAreaForce.cs | 16 --- .../ValheimRAFT/ValheimRaftPlugin.cs | 6 ++ .../LoadValheimAssets.cs | 2 + .../ValheimVehicles.Prefabs/PrefabNames.cs | 1 + .../PrefabRegistryController.cs | 3 + .../Registry/SailPrefabs.cs | 98 +++++++++++++++---- .../ValheimVehicles.Prefabs/SpriteNames.cs | 1 + .../Sail/SailAreaForce.cs | 5 +- .../BaseVehicleController.cs | 20 +++- .../ValheimVehicles.Vehicles/VehicleShip.cs | 2 +- 13 files changed, 118 insertions(+), 43 deletions(-) delete mode 100644 src/ValheimRAFT/ValheimRAFT/SailAreaForce.cs diff --git a/src/ValheimRAFT/Assets/Translations/English/valheimraft.json b/src/ValheimRAFT/Assets/Translations/English/valheimraft.json index acc01b05..5207675b 100644 --- a/src/ValheimRAFT/Assets/Translations/English/valheimraft.json +++ b/src/ValheimRAFT/Assets/Translations/English/valheimraft.json @@ -53,7 +53,9 @@ "mb_sail_desc": "Customizable sail.", "mb_sail_edit": "Customize sail", "mb_vikingship_mast": "Longship mast", - "mb_vikingship_mast_desc": "A mast for your base, in the style of the longship", + "mb_vikingship_mast_desc": "A mast for your base, in the style of the Longship", + "valheim_vehicles_drakkalship_mast": "Drakkal Mast", + "valheim_vehicles_drakkalship_mast_desc": "A mast for your base, in the style of the Drakkal Longship", "valheim_vehicles_water_vehicle": "A Water Vehicle", "valheim_vehicles_water_vehicle_desc": "A raft (>=v2.0.0)...that you can build on!", "valheim_vehicles_wheel": "Vehicle Wheel", diff --git a/src/ValheimRAFT/ValheimRAFT.csproj b/src/ValheimRAFT/ValheimRAFT.csproj index a13bfb0a..3eca8dd5 100644 --- a/src/ValheimRAFT/ValheimRAFT.csproj +++ b/src/ValheimRAFT/ValheimRAFT.csproj @@ -59,7 +59,6 @@ - diff --git a/src/ValheimRAFT/ValheimRAFT/MoveableBaseRootComponent.cs b/src/ValheimRAFT/ValheimRAFT/MoveableBaseRootComponent.cs index a6223c3e..ed8010f4 100644 --- a/src/ValheimRAFT/ValheimRAFT/MoveableBaseRootComponent.cs +++ b/src/ValheimRAFT/ValheimRAFT/MoveableBaseRootComponent.cs @@ -1 +1 @@ -using System; using System.Collections; using System.Collections.Generic; using SentryUnityWrapper; using UnityEngine; using ValheimRAFT.Util; using ValheimVehicles.Propulsion.Rudder; using ValheimVehicles.Vehicles; using ValheimVehicles.Vehicles.Interfaces; using Logger = Jotunn.Logger; namespace ValheimRAFT; public class MoveableBaseRootComponent : MonoBehaviour { public static readonly KeyValuePair MBParentHash = ZDO.GetHashZDOID("MBParent"); public static readonly int MBCharacterParentHash = "MBCharacterParent".GetStableHashCode(); public static readonly int MBCharacterOffsetHash = "MBCharacterOFfset".GetStableHashCode(); public static readonly int MBParentIdHash = "MBParentId".GetStableHashCode(); public static readonly int MBPositionHash = "MBPosition".GetStableHashCode(); public static readonly int MBRotationHash = "MBRotation".GetStableHashCode(); public static readonly int MBRotationVecHash = "MBRotationVec".GetStableHashCode(); internal static Dictionary> m_pendingPieces = new(); internal static Dictionary> m_allPieces = new(); internal static Dictionary> m_dynamicObjects = new(); public MoveableBaseShipComponent shipController; private IVehicleShip? ShipInstance => m_ship as IVehicleShip; public MoveableBaseRootComponent instance; internal Rigidbody m_rigidbody; public ZNetView m_nview; internal Rigidbody m_syncRigidbody; public Ship m_ship; public List m_pieces = new(); internal List m_mastPieces = new(); internal List m_sailPiece = new(); internal List m_wheelPieces = new(); internal List m_portals = new(); internal List m_ladders = new(); internal List m_boardingRamps = new(); internal float ShipContainerMass = 0f; internal float ShipMass = 0f; public static bool hasDebug = false; internal float TotalMass => ShipContainerMass + ShipMass; /* * sail calcs */ public int numberOfTier1Sails = 0; public int numberOfTier2Sails = 0; public int numberOfTier3Sails = 0; public float customSailsArea = 0f; public float totalSailArea = 0f; /* end sail calcs */ private Vector2i m_sector; private Vector2i m_serverSector; private Bounds m_bounds = default; internal BoxCollider m_blockingcollider; internal BoxCollider m_floatcollider; internal BoxCollider m_onboardcollider; internal int m_id; public bool m_statsOverride; private static bool itemsRemovedDuringWait; private Coroutine? pendingPiecesCoroutine; private Coroutine? server_UpdatePiecesCoroutine; public int GetPersistentId() { return m_id; } // compatibility with v2. public ShipControlls MovementController; public void Awake() { instance = this; hasDebug = ValheimRaftPlugin.Instance.HasDebugBase.Value; m_rigidbody = gameObject.AddComponent(); m_rigidbody.isKinematic = true; m_rigidbody.interpolation = RigidbodyInterpolation.Interpolate; m_rigidbody.mass = 99999f; /* * This should work on both client and server, but the garbage collecting should only apply if the ZDOs are not persistent */ if (!(bool)ZNet.instance) { // prevents NRE from next command return; } if (ZNet.instance.IsDedicated()) { server_UpdatePiecesCoroutine = StartCoroutine(nameof(UpdatePiecesInEachSectorWorker)); } } public void CleanUp() { if (pendingPiecesCoroutine != null) { StopCoroutine(pendingPiecesCoroutine); } if (!ZNetScene.instance || m_id == 0) return; for (var i = 0; i < m_pieces.Count; i++) { var piece = m_pieces[i]; if ((bool)piece) { piece.transform.SetParent(null); AddInactivePiece(m_id, piece); } } var players = Player.GetAllPlayers(); for (var j = 0; j < players.Count; j++) if ((bool)players[j] && players[j].transform.parent == transform) players[j].transform.SetParent(null); } private void Sync() { if ((bool)m_syncRigidbody) { m_rigidbody.MovePosition(m_syncRigidbody.transform.position); m_rigidbody.MoveRotation(m_syncRigidbody.transform.rotation); } } public void FixedUpdate() { Sync(); } /* * @important, server does not have access to lifecycle methods so a coroutine is required to update things */ public void LateUpdate() { Sync(); if (!(bool)ZNet.instance) { // prevents NRE from next command Client_UpdateAllPieces(); return; } if (ZNet.instance.IsDedicated() == false) Client_UpdateAllPieces(); } /** * @warning this must only be called on the client */ public void Client_UpdateAllPieces() { var sector = ZoneSystem.instance.GetZone(transform.position); if (sector == m_sector) return; if (m_sector != m_serverSector) ServerSyncAllPieces(); m_sector = sector; for (var i = 0; i < m_pieces.Count; i++) { var netview = m_pieces[i]; if (!netview) { Logger.LogError($"Error found with m_pieces: netview {netview}"); m_pieces.RemoveAt(i); i--; } else { if (transform.position != netview.transform.position) { netview.m_zdo.SetPosition(transform.position); } } } } public void ServerSyncAllPieces() { if (server_UpdatePiecesCoroutine != null) StopCoroutine(server_UpdatePiecesCoroutine); StartCoroutine(UpdatePiecesInEachSectorWorker()); } public void UpdatePieces(List list) { var pos = transform.position; var sector = ZoneSystem.instance.GetZone(pos); if (m_serverSector == sector) return; if (!sector.Equals(m_sector)) m_sector = sector; m_serverSector = sector; for (var i = 0; i < list.Count; i++) { var zdo = list[i]; // This could also be a problem. If the zdo is created but the ship is in part of another sector it gets cut off. if (zdo.GetSector() == sector) continue; var id = zdo.GetInt(MBParentIdHash); if (id != m_id) { list.FastRemoveAt(i); i--; continue; } zdo.SetPosition(pos); } } /** * large ships need additional threads to render the ship quickly * * @todo setPosition should not need to be called unless the item is out of alignment. In theory it should be relative to parent so it never should be out of alignment. */ private IEnumerator UpdatePiecesWorker(List list) { UpdatePieces(list); yield return null; } /* * This method IS important, but it also seems heavily related to causing the raft to disappear if it fails. * * - Apparently to get this working this method must also fire on the client & on server. * * - This method must fire when a zone loads, otherwise the items will be in a box position until they are renders. * - For larger ships, this can take up to 20 seconds. Yikes. * * Outside of this problem, this script repeatedly calls (but stays on a separate thread) which may be related to fps drop. */ public IEnumerator UpdatePiecesInEachSectorWorker() { while (true) { /* * wait for the pending pieces coroutine to complete before updating */ if (pendingPiecesCoroutine != null) yield return pendingPiecesCoroutine; var time = Time.realtimeSinceStartup; var output = m_allPieces.TryGetValue(m_id, out var list); if (!output) { yield return new WaitForSeconds(Math.Max(2f, ValheimRaftPlugin.Instance.ServerRaftUpdateZoneInterval .Value)); continue; } yield return UpdatePiecesWorker(list); list = null; yield return new WaitForEndOfFrame(); } } internal float GetColliderBottom() { return m_blockingcollider.transform.position.y + m_blockingcollider.center.y - m_blockingcollider.size.y / 2f; } public static void AddInactivePiece(int id, ZNetView netView) { if (hasDebug) Logger.LogDebug($"addInactivePiece called with {id} for {netView.name}"); if (!m_pendingPieces.TryGetValue(id, out var list)) { list = new List(); m_pendingPieces.Add(id, list); } list.Add(netView); var wnt = netView.GetComponent(); if ((bool)wnt) wnt.enabled = false; } /* * deltaMass can be positive or negative number */ public void UpdateMass(ZNetView netView, bool isRemoving = false) { if (!(bool)netView) { if (hasDebug) { Logger.LogDebug("NetView is invalid skipping mass update"); } return; } var piece = netView.GetComponent(); if (!(bool)piece) { if (hasDebug) Logger.LogDebug( "unable to fetch piece data from netViewPiece this could be a raft piece erroring."); return; } var pieceWeight = ComputePieceWeight(piece, isRemoving); if (isRemoving) { ShipMass -= pieceWeight; } else { ShipMass += pieceWeight; } if ((bool)m_rigidbody) { m_rigidbody.mass = 1000f + TotalMass; } if ((bool)m_syncRigidbody) { m_syncRigidbody.mass = 1000f + TotalMass; } } public void RemovePiece(ZNetView netView) { if (m_pieces.Remove(netView)) { UpdateMass(netView, true); var sail = netView.GetComponent(); if ((bool)sail) { m_sailPiece.Remove(sail); } var mast = netView.GetComponent(); if ((bool)mast) { m_mastPieces.Remove(mast); } var wheel = netView.GetComponent(); if ((bool)wheel) { m_wheelPieces.Remove(wheel); } var ramp = netView.GetComponent(); if ((bool)ramp) m_boardingRamps.Remove(ramp); var portal = netView.GetComponent(); if ((bool)portal) m_portals.Remove(netView); var ladder = netView.GetComponent(); if ((bool)ladder) { m_ladders.Remove(ladder); ladder.m_mbroot = null; } } } /** * this will recalculate only when the ship speed changes. */ public void ComputeAllShipContainerItemWeight() { if (!ValheimRaftPlugin.Instance.HasShipContainerWeightCalculations.Value && ShipContainerMass != 0f) { ShipContainerMass = 0f; return; } var containers = GetComponentsInChildren(); float totalContainerMass = 0f; foreach (var container in containers) { totalContainerMass += ComputeContainerWeight(container); } ShipContainerMass = totalContainerMass; } private float ComputeContainerWeight(Container container, bool isRemoving = false) { var inventory = container.GetInventory(); if (inventory != null) { var containerWeight = inventory.GetTotalWeight(); if (hasDebug) Logger.LogDebug($"containerWeight {containerWeight} name: {container.name}"); if (isRemoving) { return -containerWeight; } return containerWeight; } return 0f; } /* * this function must be used on additional and removal of items to avoid retaining item weight */ private float ComputePieceWeight(Piece piece, bool isRemoving) { if (!(bool)piece) { return 0f; } var pieceName = piece.name; if (ValheimRaftPlugin.Instance.HasShipContainerWeightCalculations.Value) { var container = piece.GetComponent(); if ((bool)container) { ShipContainerMass += ComputeContainerWeight(container, isRemoving); } } var baseMultiplier = 1f; /* * locally scaled pieces should have a mass multiplier. * * For now assuming everything is a rectangular prism L*W*H */ if (piece.transform.localScale != new Vector3(1, 1, 1)) { baseMultiplier = piece.transform.localScale.x * piece.transform.localScale.y * piece.transform.localScale.z; if (hasDebug) Logger.LogDebug( $"ValheimRAFT ComputeShipItemWeight() found piece that does not have a 1,1,1 local scale piece: {pieceName} scale: {piece.transform.localScale}, the 3d localScale will be multiplied by the area of this vector instead of 1x1x1"); } if (pieceName == "wood_floor_1x1") { return 1f * baseMultiplier; } /* * wood_log/wood_core may be split out to a lower ratio */ if (pieceName.Contains("wood")) { return MaterialWeight.Wood * baseMultiplier; } if (pieceName.Contains("stone_")) { return MaterialWeight.Stone * baseMultiplier; } if (pieceName.Contains("blackmarble")) { return MaterialWeight.BlackMarble * baseMultiplier; } if (pieceName.Contains("blastfurnace") || pieceName.Contains("charcoal_kiln") || pieceName.Contains("forge") || pieceName.Contains("smelter")) { return 20f * baseMultiplier; } // default return is the weight of wood 1x1 return 2f * baseMultiplier; } public void DestroyPiece(WearNTear wnt) { if (!(bool)wnt) { return; } var netview = wnt.GetComponent(); RemovePiece(netview); UpdatePieceCount(); totalSailArea = 0f; if (GetPieceCount() == 0) { var wntShip = m_ship.GetComponent(); if ((bool)wntShip) wntShip.Destroy(); if (gameObject) { Destroy(gameObject); } } } public void DestroyBoat() { var wntShip = m_ship.GetComponent(); if ((bool)wntShip) wntShip.Destroy(); else if (m_ship) Destroy(m_ship); Destroy(gameObject); } public void ActivatePendingPiecesCoroutine() { if (hasDebug) Logger.LogDebug( $"ActivatePendingPiecesCoroutine(): pendingPieces count: {m_pendingPieces.Count}"); if (pendingPiecesCoroutine != null) StopCoroutine(pendingPiecesCoroutine); pendingPiecesCoroutine = StartCoroutine(nameof(ActivatePendingPieces)); } public IEnumerator ActivatePendingPieces() { if (!m_nview || m_nview.m_zdo == null) { if (hasDebug) Logger.LogDebug( $"ActivatePendingPieces early exit due to m_nview: {m_nview} m_nview.m_zdo {(m_nview != null ? m_nview.m_zdo : null)}"); yield return null; } var id = ZDOPersistentID.Instance.GetOrCreatePersistentID(m_nview.m_zdo); m_pendingPieces.TryGetValue(id, out var list); if (list is { Count: > 0 }) { // var stopwatch = new Stopwatch(); // stopwatch.Start(); for (var j = 0; j < list.Count; j++) { var obj = list[j]; if ((bool)obj) { if (hasDebug) { Logger.LogDebug($"ActivatePendingPieces obj: {obj} {obj.name}"); } ActivatePiece(obj); } else { if (hasDebug) { Logger.LogDebug($"ActivatePendingPieces obj is not valid {obj}"); } } } list.Clear(); m_pendingPieces.Remove(id); } if (hasDebug) Logger.LogDebug($"Ship Size calc is: m_bounds {m_bounds} bounds size {m_bounds.size}"); m_dynamicObjects.TryGetValue(m_id, out var objectList); var ObjectListHasNoValidItems = true; if (objectList is { Count: > 0 }) { for (var i = 0; i < objectList.Count; i++) { var go = ZNetScene.instance.FindInstance(objectList[i]); if (!go) continue; var nv = go.GetComponentInParent(); if (!nv || nv.m_zdo == null) continue; else ObjectListHasNoValidItems = false; if (ZDOExtraData.s_vec3.TryGetValue(nv.m_zdo.m_uid, out var dic)) { if (dic.TryGetValue(MBCharacterOffsetHash, out var offset)) nv.transform.position = offset + transform.position; offset = default; } ZDOExtraData.RemoveInt(nv.m_zdo.m_uid, MBCharacterParentHash); ZDOExtraData.RemoveVec3(nv.m_zdo.m_uid, MBCharacterOffsetHash); dic = null; } m_dynamicObjects.Remove(m_id); } /* * This prevents empty Prefabs of MBRaft from existing * @todo make this only apply for boats with no objects in any list */ if (list == null || (list.Count == 0 && (m_dynamicObjects.Count == 0 || ObjectListHasNoValidItems)) ) { Logger.LogError($"found boat without any items attached {m_ship} {m_nview}"); DestroyBoat(); } yield return null; } public static void AddDynamicParent(ZNetView source, GameObject target) { var mbroot = target.GetComponentInParent(); if ((bool)mbroot) { source.m_zdo.Set(MBCharacterParentHash, mbroot.m_id); source.m_zdo.Set(MBCharacterOffsetHash, source.transform.position - mbroot.transform.position); } } /** * A cached getter for sail size. Cache invalidates when a piece is added or removed * * This method calls so frequently outside of the scope of ValheimRaftPlugin.Instance so the Config values cannot be fetched for some reason. */ public float GetTotalSailArea() { if (totalSailArea != 0f || !ValheimRaftPlugin.Instance || m_mastPieces.Count == 0 && m_sailPiece.Count == 0) { return totalSailArea; } totalSailArea = 0; customSailsArea = 0; numberOfTier1Sails = 0; numberOfTier2Sails = 0; numberOfTier3Sails = 0; var hasConfigOverride = ValheimRaftPlugin.Instance.EnableCustomPropulsionConfig.Value; foreach (var mMastPiece in m_mastPieces) { if (mMastPiece.name.Contains("MBRaftMast")) { ++numberOfTier1Sails; var multiplier = hasConfigOverride ? ValheimRaftPlugin.Instance.SailTier1Area.Value : SailAreaForce.Tier1; totalSailArea += numberOfTier1Sails * multiplier; } else if (mMastPiece.name.Contains("MBKarveMast")) { ++numberOfTier2Sails; var multiplier = hasConfigOverride ? ValheimRaftPlugin.Instance.SailTier2Area.Value : SailAreaForce.Tier2; totalSailArea += numberOfTier2Sails * multiplier; } else if (mMastPiece.name.Contains("MBVikingShipMast")) { ++numberOfTier3Sails; var multiplier = hasConfigOverride ? ValheimRaftPlugin.Instance.SailTier3Area.Value : SailAreaForce.Tier3; totalSailArea += numberOfTier3Sails * multiplier; ; } } var sailComponents = GetComponentsInChildren(); if (sailComponents.Length != 0) { foreach (var sailComponent in sailComponents) { if ((bool)sailComponent) { customSailsArea += sailComponent.GetSailArea(); } } if (hasDebug) Logger.LogDebug($"CustomSailsArea {customSailsArea}"); var multiplier = hasConfigOverride ? ValheimRaftPlugin.Instance.SailCustomAreaTier1Multiplier.Value : SailAreaForce.CustomTier1AreaForceMultiplier; totalSailArea += (customSailsArea * Math.Max(0.1f, multiplier)); } /* * Clamps everything by the maxSailSpeed */ if (totalSailArea != 0 && !ValheimRaftPlugin.Instance.HasShipWeightCalculations.Value) { totalSailArea = Math.Min(ValheimRaftPlugin.Instance.MaxSailSpeed.Value, totalSailArea); } return totalSailArea; } public float GetSailingForce() { var area = GetTotalSailArea(); if (!ValheimRaftPlugin.Instance.HasShipWeightCalculations.Value) return area; var mpFactor = ValheimRaftPlugin.Instance.MassPercentageFactor.Value; var speedCapMultiplier = ValheimRaftPlugin.Instance.SpeedCapMultiplier.Value; var sailForce = speedCapMultiplier * area / (TotalMass / mpFactor); if (ValheimRaftPlugin.Instance.HasDebugSails.Value) { Logger.LogDebug( $"GetSailingForce() = speedCapMultiplier * area /(totalMass / mpFactor); {speedCapMultiplier} * ({area}/({TotalMass}/{mpFactor})) = {sailForce}"); } var maxSailForce = Math.Min(ValheimRaftPlugin.Instance.MaxSailSpeed.Value, sailForce); var maxPropulsion = Math.Min(ValheimRaftPlugin.Instance.MaxPropulsionSpeed.Value, maxSailForce); return maxPropulsion; } public static void AddDynamicParent(ZNetView source, GameObject target, Vector3 offset) { var mbroot = target.GetComponentInParent(); if ((bool)mbroot) { source.m_zdo.Set(MBCharacterParentHash, mbroot.m_id); source.m_zdo.Set(MBCharacterOffsetHash, offset); } } public static void InitZDO(ZDO zdo) { // does not init the MBRaft as it should never exist as a piece within the raft items if (zdo.m_prefab == "MBRaft".GetStableHashCode()) { return; } var id = GetParentID(zdo); if (id != 0) { if (!m_allPieces.TryGetValue(id, out var list)) { list = new List(); m_allPieces.Add(id, list); } list.Add(zdo); } var cid = zdo.GetInt(MBCharacterParentHash); if (cid != 0) { if (!m_dynamicObjects.TryGetValue(cid, out var objectList)) { objectList = new List(); m_dynamicObjects.Add(cid, objectList); } objectList.Add(zdo.m_uid); } } public static void RemoveZDO(ZDO zdo) { var id = GetParentID(zdo); if (id != 0 && m_allPieces.TryGetValue(id, out var list)) { list.FastRemove(zdo); itemsRemovedDuringWait = true; } } private static int GetParentID(ZDO zdo) { var id = zdo.GetInt(MBParentIdHash); if (id == 0) { var zdoid = zdo.GetZDOID(MBParentHash); if (zdoid != ZDOID.None) { var zdoparent = ZDOMan.instance.GetZDO(zdoid); id = zdoparent == null ? ZDOPersistentID.ZDOIDToId(zdoid) : ZDOPersistentID.Instance.GetOrCreatePersistentID(zdoparent); zdo.Set(MBParentIdHash, id); zdo.Set(MBRotationVecHash, zdo.GetQuaternion(MBRotationHash, Quaternion.identity).eulerAngles); zdo.RemoveZDOID(MBParentHash); ZDOExtraData.s_quats.Remove(zdoid, MBRotationHash); } } return id; } public static void InitPiece(ZNetView netview) { var rb = netview.GetComponentInChildren(); if ((bool)rb && !rb.isKinematic) return; var id = GetParentID(netview.m_zdo); if (id == 0) return; var parentObj = ZDOPersistentID.Instance.GetGameObject(id); if ((bool)parentObj) { var mb = parentObj.GetComponent(); if ((bool)mb && (bool)mb.m_baseRoot) { mb.m_baseRoot.ActivatePiece(netview); } } else { AddInactivePiece(id, netview); } } public void ActivatePiece(ZNetView netview) { if ((bool)netview) { netview.transform.SetParent(transform); netview.transform.localPosition = netview.m_zdo.GetVec3(MBPositionHash, Vector3.zero); netview.transform.localRotation = Quaternion.Euler(netview.m_zdo.GetVec3(MBRotationVecHash, Vector3.zero)); var wnt = netview.GetComponent(); if ((bool)wnt) wnt.enabled = true; AddPiece(netview); } } public void AddTemporaryPiece(Piece piece) { piece.transform.SetParent(transform); } public void AddNewPiece(Piece piece) { if ((bool)piece && (bool)piece.m_nview) { if (hasDebug) Logger.LogDebug("Added new piece is valid"); AddNewPiece(piece.m_nview); } } public void AddNewPiece(ZNetView netView) { netView.transform.SetParent(transform); if (netView.m_zdo != null) { netView.m_zdo.Set(MBParentIdHash, ZDOPersistentID.Instance.GetOrCreatePersistentID(m_nview.m_zdo)); netView.m_zdo.Set(MBRotationVecHash, netView.transform.localRotation.eulerAngles); netView.m_zdo.Set(MBPositionHash, netView.transform.localPosition); } AddPiece(netView); InitZDO(netView.m_zdo); } public void AddPiece(ZNetView netView) { totalSailArea = 0; m_pieces.Add(netView); UpdatePieceCount(); EncapsulateBounds(netView); var wnt = netView.GetComponent(); if ((bool)wnt && ValheimRaftPlugin.Instance.MakeAllPiecesWaterProof.Value) wnt.m_noRoofWear = false; var cultivatable = netView.GetComponent(); if ((bool)cultivatable) cultivatable.UpdateMaterial(); var mast = netView.GetComponent(); if ((bool)mast) { m_mastPieces.Add(mast); } var sail = netView.GetComponent(); if ((bool)sail) { m_sailPiece.Add(sail); } var ramp = netView.GetComponent(); if ((bool)ramp) { ramp.ForceRampUpdate(); m_boardingRamps.Add(ramp); } var wheel = netView.GetComponent(); if ((bool)wheel) { wheel.DEPRECATED_InitializeControls(netView); if (!wheel.deprecatedShipControls) wheel.deprecatedShipControls = netView.GetComponentInChildren(); wheel.deprecatedShipControls.m_ship = m_ship; m_wheelPieces.Add(wheel); } var portal = netView.GetComponent(); if ((bool)portal) m_portals.Add(netView); var ladder = netView.GetComponent(); if ((bool)ladder) { m_ladders.Add(ladder); ladder.m_mbroot = this; } var meshes = netView.GetComponentsInChildren(true); foreach (var meshRenderer in meshes) if ((bool)meshRenderer.sharedMaterial) { var sharedMaterials = meshRenderer.sharedMaterials; for (var j = 0; j < sharedMaterials.Length; j++) { var material = new Material(sharedMaterials[j]); material.SetFloat("_RippleDistance", 0f); material.SetFloat("_ValueNoise", 0f); sharedMaterials[j] = material; } meshRenderer.sharedMaterials = sharedMaterials; } UpdateMass(netView); /* * @todo investigate why this is called. Determine if it is needed * * - most likely this is to prevent other rigidbody nodes from interacting with unity world physics within the ship */ var rbs = netView.GetComponentsInChildren(); for (var i = 0; i < rbs.Length; i++) if (rbs[i].isKinematic) Destroy(rbs[i]); } private void UpdatePieceCount() { if ((bool)m_nview && m_nview.m_zdo != null) m_nview.m_zdo.Set("MBPieceCount", m_pieces.Count); } public void EncapsulateBounds(ZNetView netview) { var piece = netview.GetComponent(); var colliders = piece ? piece.GetAllColliders() : [..netview.GetComponentsInChildren()]; var door = netview.GetComponentInChildren(); var ladder = netview.GetComponent(); var rope = netview.GetComponent(); if (!door && !ladder && !rope) m_bounds.Encapsulate(netview.transform.localPosition); for (var i = 0; i < colliders.Count; i++) { Physics.IgnoreCollision(colliders[i], m_blockingcollider, true); Physics.IgnoreCollision(colliders[i], m_floatcollider, true); Physics.IgnoreCollision(colliders[i], m_onboardcollider, true); } m_blockingcollider.size = new Vector3(m_bounds.size.x, 1.5f, m_bounds.size.z); m_blockingcollider.center = new Vector3(m_bounds.center.x, 0.2f, m_bounds.center.z); m_floatcollider.size = new Vector3(m_bounds.size.x, 3f, m_bounds.size.z); m_floatcollider.center = new Vector3(m_bounds.center.x, -0.2f, m_bounds.center.z); m_onboardcollider.size = m_bounds.size; m_onboardcollider.center = m_bounds.center; } internal int GetPieceCount() { if (!m_nview || m_nview.m_zdo == null) return m_pieces.Count; return m_nview.m_zdo.GetInt("MBPieceCount", m_pieces.Count); } } \ No newline at end of file +using System; using System.Collections; using System.Collections.Generic; using SentryUnityWrapper; using UnityEngine; using ValheimRAFT.Util; using ValheimVehicles.Propulsion.Rudder; using ValheimVehicles.Propulsion.Sail; using ValheimVehicles.Vehicles; using ValheimVehicles.Vehicles.Interfaces; using Logger = Jotunn.Logger; namespace ValheimRAFT; public class MoveableBaseRootComponent : MonoBehaviour { public static readonly KeyValuePair MBParentHash = ZDO.GetHashZDOID("MBParent"); public static readonly int MBCharacterParentHash = "MBCharacterParent".GetStableHashCode(); public static readonly int MBCharacterOffsetHash = "MBCharacterOFfset".GetStableHashCode(); public static readonly int MBParentIdHash = "MBParentId".GetStableHashCode(); public static readonly int MBPositionHash = "MBPosition".GetStableHashCode(); public static readonly int MBRotationHash = "MBRotation".GetStableHashCode(); public static readonly int MBRotationVecHash = "MBRotationVec".GetStableHashCode(); internal static Dictionary> m_pendingPieces = new(); internal static Dictionary> m_allPieces = new(); internal static Dictionary> m_dynamicObjects = new(); public MoveableBaseShipComponent shipController; private IVehicleShip? ShipInstance => m_ship as IVehicleShip; public MoveableBaseRootComponent instance; internal Rigidbody m_rigidbody; public ZNetView m_nview; internal Rigidbody m_syncRigidbody; public Ship m_ship; public List m_pieces = new(); internal List m_mastPieces = new(); internal List m_sailPiece = new(); internal List m_wheelPieces = new(); internal List m_portals = new(); internal List m_ladders = new(); internal List m_boardingRamps = new(); internal float ShipContainerMass = 0f; internal float ShipMass = 0f; public static bool hasDebug = false; internal float TotalMass => ShipContainerMass + ShipMass; /* * sail calcs */ public int numberOfTier1Sails = 0; public int numberOfTier2Sails = 0; public int numberOfTier3Sails = 0; public float customSailsArea = 0f; public float totalSailArea = 0f; /* end sail calcs */ private Vector2i m_sector; private Vector2i m_serverSector; private Bounds m_bounds = default; internal BoxCollider m_blockingcollider; internal BoxCollider m_floatcollider; internal BoxCollider m_onboardcollider; internal int m_id; public bool m_statsOverride; private static bool itemsRemovedDuringWait; private Coroutine? pendingPiecesCoroutine; private Coroutine? server_UpdatePiecesCoroutine; public int GetPersistentId() { return m_id; } // compatibility with v2. public ShipControlls MovementController; public void Awake() { instance = this; hasDebug = ValheimRaftPlugin.Instance.HasDebugBase.Value; m_rigidbody = gameObject.AddComponent(); m_rigidbody.isKinematic = true; m_rigidbody.interpolation = RigidbodyInterpolation.Interpolate; m_rigidbody.mass = 99999f; /* * This should work on both client and server, but the garbage collecting should only apply if the ZDOs are not persistent */ if (!(bool)ZNet.instance) { // prevents NRE from next command return; } if (ZNet.instance.IsDedicated()) { server_UpdatePiecesCoroutine = StartCoroutine(nameof(UpdatePiecesInEachSectorWorker)); } } public void CleanUp() { if (pendingPiecesCoroutine != null) { StopCoroutine(pendingPiecesCoroutine); } if (!ZNetScene.instance || m_id == 0) return; for (var i = 0; i < m_pieces.Count; i++) { var piece = m_pieces[i]; if ((bool)piece) { piece.transform.SetParent(null); AddInactivePiece(m_id, piece); } } var players = Player.GetAllPlayers(); for (var j = 0; j < players.Count; j++) if ((bool)players[j] && players[j].transform.parent == transform) players[j].transform.SetParent(null); } private void Sync() { if ((bool)m_syncRigidbody) { m_rigidbody.MovePosition(m_syncRigidbody.transform.position); m_rigidbody.MoveRotation(m_syncRigidbody.transform.rotation); } } public void FixedUpdate() { Sync(); } /* * @important, server does not have access to lifecycle methods so a coroutine is required to update things */ public void LateUpdate() { Sync(); if (!(bool)ZNet.instance) { // prevents NRE from next command Client_UpdateAllPieces(); return; } if (ZNet.instance.IsDedicated() == false) Client_UpdateAllPieces(); } /** * @warning this must only be called on the client */ public void Client_UpdateAllPieces() { var sector = ZoneSystem.instance.GetZone(transform.position); if (sector == m_sector) return; if (m_sector != m_serverSector) ServerSyncAllPieces(); m_sector = sector; for (var i = 0; i < m_pieces.Count; i++) { var netview = m_pieces[i]; if (!netview) { Logger.LogError($"Error found with m_pieces: netview {netview}"); m_pieces.RemoveAt(i); i--; } else { if (transform.position != netview.transform.position) { netview.m_zdo.SetPosition(transform.position); } } } } public void ServerSyncAllPieces() { if (server_UpdatePiecesCoroutine != null) StopCoroutine(server_UpdatePiecesCoroutine); StartCoroutine(UpdatePiecesInEachSectorWorker()); } public void UpdatePieces(List list) { var pos = transform.position; var sector = ZoneSystem.instance.GetZone(pos); if (m_serverSector == sector) return; if (!sector.Equals(m_sector)) m_sector = sector; m_serverSector = sector; for (var i = 0; i < list.Count; i++) { var zdo = list[i]; // This could also be a problem. If the zdo is created but the ship is in part of another sector it gets cut off. if (zdo.GetSector() == sector) continue; var id = zdo.GetInt(MBParentIdHash); if (id != m_id) { list.FastRemoveAt(i); i--; continue; } zdo.SetPosition(pos); } } /** * large ships need additional threads to render the ship quickly * * @todo setPosition should not need to be called unless the item is out of alignment. In theory it should be relative to parent so it never should be out of alignment. */ private IEnumerator UpdatePiecesWorker(List list) { UpdatePieces(list); yield return null; } /* * This method IS important, but it also seems heavily related to causing the raft to disappear if it fails. * * - Apparently to get this working this method must also fire on the client & on server. * * - This method must fire when a zone loads, otherwise the items will be in a box position until they are renders. * - For larger ships, this can take up to 20 seconds. Yikes. * * Outside of this problem, this script repeatedly calls (but stays on a separate thread) which may be related to fps drop. */ public IEnumerator UpdatePiecesInEachSectorWorker() { while (true) { /* * wait for the pending pieces coroutine to complete before updating */ if (pendingPiecesCoroutine != null) yield return pendingPiecesCoroutine; var time = Time.realtimeSinceStartup; var output = m_allPieces.TryGetValue(m_id, out var list); if (!output) { yield return new WaitForSeconds(Math.Max(2f, ValheimRaftPlugin.Instance.ServerRaftUpdateZoneInterval .Value)); continue; } yield return UpdatePiecesWorker(list); list = null; yield return new WaitForEndOfFrame(); } } internal float GetColliderBottom() { return m_blockingcollider.transform.position.y + m_blockingcollider.center.y - m_blockingcollider.size.y / 2f; } public static void AddInactivePiece(int id, ZNetView netView) { if (hasDebug) Logger.LogDebug($"addInactivePiece called with {id} for {netView.name}"); if (!m_pendingPieces.TryGetValue(id, out var list)) { list = new List(); m_pendingPieces.Add(id, list); } list.Add(netView); var wnt = netView.GetComponent(); if ((bool)wnt) wnt.enabled = false; } /* * deltaMass can be positive or negative number */ public void UpdateMass(ZNetView netView, bool isRemoving = false) { if (!(bool)netView) { if (hasDebug) { Logger.LogDebug("NetView is invalid skipping mass update"); } return; } var piece = netView.GetComponent(); if (!(bool)piece) { if (hasDebug) Logger.LogDebug( "unable to fetch piece data from netViewPiece this could be a raft piece erroring."); return; } var pieceWeight = ComputePieceWeight(piece, isRemoving); if (isRemoving) { ShipMass -= pieceWeight; } else { ShipMass += pieceWeight; } if ((bool)m_rigidbody) { m_rigidbody.mass = 1000f + TotalMass; } if ((bool)m_syncRigidbody) { m_syncRigidbody.mass = 1000f + TotalMass; } } public void RemovePiece(ZNetView netView) { if (m_pieces.Remove(netView)) { UpdateMass(netView, true); var sail = netView.GetComponent(); if ((bool)sail) { m_sailPiece.Remove(sail); } var mast = netView.GetComponent(); if ((bool)mast) { m_mastPieces.Remove(mast); } var wheel = netView.GetComponent(); if ((bool)wheel) { m_wheelPieces.Remove(wheel); } var ramp = netView.GetComponent(); if ((bool)ramp) m_boardingRamps.Remove(ramp); var portal = netView.GetComponent(); if ((bool)portal) m_portals.Remove(netView); var ladder = netView.GetComponent(); if ((bool)ladder) { m_ladders.Remove(ladder); ladder.m_mbroot = null; } } } /** * this will recalculate only when the ship speed changes. */ public void ComputeAllShipContainerItemWeight() { if (!ValheimRaftPlugin.Instance.HasShipContainerWeightCalculations.Value && ShipContainerMass != 0f) { ShipContainerMass = 0f; return; } var containers = GetComponentsInChildren(); float totalContainerMass = 0f; foreach (var container in containers) { totalContainerMass += ComputeContainerWeight(container); } ShipContainerMass = totalContainerMass; } private float ComputeContainerWeight(Container container, bool isRemoving = false) { var inventory = container.GetInventory(); if (inventory != null) { var containerWeight = inventory.GetTotalWeight(); if (hasDebug) Logger.LogDebug($"containerWeight {containerWeight} name: {container.name}"); if (isRemoving) { return -containerWeight; } return containerWeight; } return 0f; } /* * this function must be used on additional and removal of items to avoid retaining item weight */ private float ComputePieceWeight(Piece piece, bool isRemoving) { if (!(bool)piece) { return 0f; } var pieceName = piece.name; if (ValheimRaftPlugin.Instance.HasShipContainerWeightCalculations.Value) { var container = piece.GetComponent(); if ((bool)container) { ShipContainerMass += ComputeContainerWeight(container, isRemoving); } } var baseMultiplier = 1f; /* * locally scaled pieces should have a mass multiplier. * * For now assuming everything is a rectangular prism L*W*H */ if (piece.transform.localScale != new Vector3(1, 1, 1)) { baseMultiplier = piece.transform.localScale.x * piece.transform.localScale.y * piece.transform.localScale.z; if (hasDebug) Logger.LogDebug( $"ValheimRAFT ComputeShipItemWeight() found piece that does not have a 1,1,1 local scale piece: {pieceName} scale: {piece.transform.localScale}, the 3d localScale will be multiplied by the area of this vector instead of 1x1x1"); } if (pieceName == "wood_floor_1x1") { return 1f * baseMultiplier; } /* * wood_log/wood_core may be split out to a lower ratio */ if (pieceName.Contains("wood")) { return MaterialWeight.Wood * baseMultiplier; } if (pieceName.Contains("stone_")) { return MaterialWeight.Stone * baseMultiplier; } if (pieceName.Contains("blackmarble")) { return MaterialWeight.BlackMarble * baseMultiplier; } if (pieceName.Contains("blastfurnace") || pieceName.Contains("charcoal_kiln") || pieceName.Contains("forge") || pieceName.Contains("smelter")) { return 20f * baseMultiplier; } // default return is the weight of wood 1x1 return 2f * baseMultiplier; } public void DestroyPiece(WearNTear wnt) { if (!(bool)wnt) { return; } var netview = wnt.GetComponent(); RemovePiece(netview); UpdatePieceCount(); totalSailArea = 0f; if (GetPieceCount() == 0) { var wntShip = m_ship.GetComponent(); if ((bool)wntShip) wntShip.Destroy(); if (gameObject) { Destroy(gameObject); } } } public void DestroyBoat() { var wntShip = m_ship.GetComponent(); if ((bool)wntShip) wntShip.Destroy(); else if (m_ship) Destroy(m_ship); Destroy(gameObject); } public void ActivatePendingPiecesCoroutine() { if (hasDebug) Logger.LogDebug( $"ActivatePendingPiecesCoroutine(): pendingPieces count: {m_pendingPieces.Count}"); if (pendingPiecesCoroutine != null) StopCoroutine(pendingPiecesCoroutine); pendingPiecesCoroutine = StartCoroutine(nameof(ActivatePendingPieces)); } public IEnumerator ActivatePendingPieces() { if (!m_nview || m_nview.m_zdo == null) { if (hasDebug) Logger.LogDebug( $"ActivatePendingPieces early exit due to m_nview: {m_nview} m_nview.m_zdo {(m_nview != null ? m_nview.m_zdo : null)}"); yield return null; } var id = ZDOPersistentID.Instance.GetOrCreatePersistentID(m_nview.m_zdo); m_pendingPieces.TryGetValue(id, out var list); if (list is { Count: > 0 }) { // var stopwatch = new Stopwatch(); // stopwatch.Start(); for (var j = 0; j < list.Count; j++) { var obj = list[j]; if ((bool)obj) { if (hasDebug) { Logger.LogDebug($"ActivatePendingPieces obj: {obj} {obj.name}"); } ActivatePiece(obj); } else { if (hasDebug) { Logger.LogDebug($"ActivatePendingPieces obj is not valid {obj}"); } } } list.Clear(); m_pendingPieces.Remove(id); } if (hasDebug) Logger.LogDebug($"Ship Size calc is: m_bounds {m_bounds} bounds size {m_bounds.size}"); m_dynamicObjects.TryGetValue(m_id, out var objectList); var ObjectListHasNoValidItems = true; if (objectList is { Count: > 0 }) { for (var i = 0; i < objectList.Count; i++) { var go = ZNetScene.instance.FindInstance(objectList[i]); if (!go) continue; var nv = go.GetComponentInParent(); if (!nv || nv.m_zdo == null) continue; else ObjectListHasNoValidItems = false; if (ZDOExtraData.s_vec3.TryGetValue(nv.m_zdo.m_uid, out var dic)) { if (dic.TryGetValue(MBCharacterOffsetHash, out var offset)) nv.transform.position = offset + transform.position; offset = default; } ZDOExtraData.RemoveInt(nv.m_zdo.m_uid, MBCharacterParentHash); ZDOExtraData.RemoveVec3(nv.m_zdo.m_uid, MBCharacterOffsetHash); dic = null; } m_dynamicObjects.Remove(m_id); } /* * This prevents empty Prefabs of MBRaft from existing * @todo make this only apply for boats with no objects in any list */ if (list == null || (list.Count == 0 && (m_dynamicObjects.Count == 0 || ObjectListHasNoValidItems)) ) { Logger.LogError($"found boat without any items attached {m_ship} {m_nview}"); DestroyBoat(); } yield return null; } public static void AddDynamicParent(ZNetView source, GameObject target) { var mbroot = target.GetComponentInParent(); if ((bool)mbroot) { source.m_zdo.Set(MBCharacterParentHash, mbroot.m_id); source.m_zdo.Set(MBCharacterOffsetHash, source.transform.position - mbroot.transform.position); } } /** * A cached getter for sail size. Cache invalidates when a piece is added or removed * * This method calls so frequently outside of the scope of ValheimRaftPlugin.Instance so the Config values cannot be fetched for some reason. */ public float GetTotalSailArea() { if (totalSailArea != 0f || !ValheimRaftPlugin.Instance || m_mastPieces.Count == 0 && m_sailPiece.Count == 0) { return totalSailArea; } totalSailArea = 0; customSailsArea = 0; numberOfTier1Sails = 0; numberOfTier2Sails = 0; numberOfTier3Sails = 0; var hasConfigOverride = ValheimRaftPlugin.Instance.EnableCustomPropulsionConfig.Value; foreach (var mMastPiece in m_mastPieces) { if (mMastPiece.name.Contains("MBRaftMast")) { ++numberOfTier1Sails; var multiplier = hasConfigOverride ? ValheimRaftPlugin.Instance.SailTier1Area.Value : SailAreaForce.Tier1; totalSailArea += numberOfTier1Sails * multiplier; } else if (mMastPiece.name.Contains("MBKarveMast")) { ++numberOfTier2Sails; var multiplier = hasConfigOverride ? ValheimRaftPlugin.Instance.SailTier2Area.Value : SailAreaForce.Tier2; totalSailArea += numberOfTier2Sails * multiplier; } else if (mMastPiece.name.Contains("MBVikingShipMast")) { ++numberOfTier3Sails; var multiplier = hasConfigOverride ? ValheimRaftPlugin.Instance.SailTier3Area.Value : SailAreaForce.Tier3; totalSailArea += numberOfTier3Sails * multiplier; ; } } var sailComponents = GetComponentsInChildren(); if (sailComponents.Length != 0) { foreach (var sailComponent in sailComponents) { if ((bool)sailComponent) { customSailsArea += sailComponent.GetSailArea(); } } if (hasDebug) Logger.LogDebug($"CustomSailsArea {customSailsArea}"); var multiplier = hasConfigOverride ? ValheimRaftPlugin.Instance.SailCustomAreaTier1Multiplier.Value : SailAreaForce.CustomTier1AreaForceMultiplier; totalSailArea += (customSailsArea * Math.Max(0.1f, multiplier)); } /* * Clamps everything by the maxSailSpeed */ if (totalSailArea != 0 && !ValheimRaftPlugin.Instance.HasShipWeightCalculations.Value) { totalSailArea = Math.Min(ValheimRaftPlugin.Instance.MaxSailSpeed.Value, totalSailArea); } return totalSailArea; } public float GetSailingForce() { var area = GetTotalSailArea(); if (!ValheimRaftPlugin.Instance.HasShipWeightCalculations.Value) return area; var mpFactor = ValheimRaftPlugin.Instance.MassPercentageFactor.Value; var speedCapMultiplier = ValheimRaftPlugin.Instance.SpeedCapMultiplier.Value; var sailForce = speedCapMultiplier * area / (TotalMass / mpFactor); if (ValheimRaftPlugin.Instance.HasDebugSails.Value) { Logger.LogDebug( $"GetSailingForce() = speedCapMultiplier * area /(totalMass / mpFactor); {speedCapMultiplier} * ({area}/({TotalMass}/{mpFactor})) = {sailForce}"); } var maxSailForce = Math.Min(ValheimRaftPlugin.Instance.MaxSailSpeed.Value, sailForce); var maxPropulsion = Math.Min(ValheimRaftPlugin.Instance.MaxPropulsionSpeed.Value, maxSailForce); return maxPropulsion; } public static void AddDynamicParent(ZNetView source, GameObject target, Vector3 offset) { var mbroot = target.GetComponentInParent(); if ((bool)mbroot) { source.m_zdo.Set(MBCharacterParentHash, mbroot.m_id); source.m_zdo.Set(MBCharacterOffsetHash, offset); } } public static void InitZDO(ZDO zdo) { // does not init the MBRaft as it should never exist as a piece within the raft items if (zdo.m_prefab == "MBRaft".GetStableHashCode()) { return; } var id = GetParentID(zdo); if (id != 0) { if (!m_allPieces.TryGetValue(id, out var list)) { list = new List(); m_allPieces.Add(id, list); } list.Add(zdo); } var cid = zdo.GetInt(MBCharacterParentHash); if (cid != 0) { if (!m_dynamicObjects.TryGetValue(cid, out var objectList)) { objectList = new List(); m_dynamicObjects.Add(cid, objectList); } objectList.Add(zdo.m_uid); } } public static void RemoveZDO(ZDO zdo) { var id = GetParentID(zdo); if (id != 0 && m_allPieces.TryGetValue(id, out var list)) { list.FastRemove(zdo); itemsRemovedDuringWait = true; } } private static int GetParentID(ZDO zdo) { var id = zdo.GetInt(MBParentIdHash); if (id == 0) { var zdoid = zdo.GetZDOID(MBParentHash); if (zdoid != ZDOID.None) { var zdoparent = ZDOMan.instance.GetZDO(zdoid); id = zdoparent == null ? ZDOPersistentID.ZDOIDToId(zdoid) : ZDOPersistentID.Instance.GetOrCreatePersistentID(zdoparent); zdo.Set(MBParentIdHash, id); zdo.Set(MBRotationVecHash, zdo.GetQuaternion(MBRotationHash, Quaternion.identity).eulerAngles); zdo.RemoveZDOID(MBParentHash); ZDOExtraData.s_quats.Remove(zdoid, MBRotationHash); } } return id; } public static void InitPiece(ZNetView netview) { var rb = netview.GetComponentInChildren(); if ((bool)rb && !rb.isKinematic) return; var id = GetParentID(netview.m_zdo); if (id == 0) return; var parentObj = ZDOPersistentID.Instance.GetGameObject(id); if ((bool)parentObj) { var mb = parentObj.GetComponent(); if ((bool)mb && (bool)mb.m_baseRoot) { mb.m_baseRoot.ActivatePiece(netview); } } else { AddInactivePiece(id, netview); } } public void ActivatePiece(ZNetView netview) { if ((bool)netview) { netview.transform.SetParent(transform); netview.transform.localPosition = netview.m_zdo.GetVec3(MBPositionHash, Vector3.zero); netview.transform.localRotation = Quaternion.Euler(netview.m_zdo.GetVec3(MBRotationVecHash, Vector3.zero)); var wnt = netview.GetComponent(); if ((bool)wnt) wnt.enabled = true; AddPiece(netview); } } public void AddTemporaryPiece(Piece piece) { piece.transform.SetParent(transform); } public void AddNewPiece(Piece piece) { if ((bool)piece && (bool)piece.m_nview) { if (hasDebug) Logger.LogDebug("Added new piece is valid"); AddNewPiece(piece.m_nview); } } public void AddNewPiece(ZNetView netView) { netView.transform.SetParent(transform); if (netView.m_zdo != null) { netView.m_zdo.Set(MBParentIdHash, ZDOPersistentID.Instance.GetOrCreatePersistentID(m_nview.m_zdo)); netView.m_zdo.Set(MBRotationVecHash, netView.transform.localRotation.eulerAngles); netView.m_zdo.Set(MBPositionHash, netView.transform.localPosition); } AddPiece(netView); InitZDO(netView.m_zdo); } public void AddPiece(ZNetView netView) { totalSailArea = 0; m_pieces.Add(netView); UpdatePieceCount(); EncapsulateBounds(netView); var wnt = netView.GetComponent(); if ((bool)wnt && ValheimRaftPlugin.Instance.MakeAllPiecesWaterProof.Value) wnt.m_noRoofWear = false; var cultivatable = netView.GetComponent(); if ((bool)cultivatable) cultivatable.UpdateMaterial(); var mast = netView.GetComponent(); if ((bool)mast) { m_mastPieces.Add(mast); } var sail = netView.GetComponent(); if ((bool)sail) { m_sailPiece.Add(sail); } var ramp = netView.GetComponent(); if ((bool)ramp) { ramp.ForceRampUpdate(); m_boardingRamps.Add(ramp); } var wheel = netView.GetComponent(); if ((bool)wheel) { wheel.DEPRECATED_InitializeControls(netView); if (!wheel.deprecatedShipControls) wheel.deprecatedShipControls = netView.GetComponentInChildren(); wheel.deprecatedShipControls.m_ship = m_ship; m_wheelPieces.Add(wheel); } var portal = netView.GetComponent(); if ((bool)portal) m_portals.Add(netView); var ladder = netView.GetComponent(); if ((bool)ladder) { m_ladders.Add(ladder); ladder.m_mbroot = this; } var meshes = netView.GetComponentsInChildren(true); foreach (var meshRenderer in meshes) if ((bool)meshRenderer.sharedMaterial) { var sharedMaterials = meshRenderer.sharedMaterials; for (var j = 0; j < sharedMaterials.Length; j++) { var material = new Material(sharedMaterials[j]); material.SetFloat("_RippleDistance", 0f); material.SetFloat("_ValueNoise", 0f); sharedMaterials[j] = material; } meshRenderer.sharedMaterials = sharedMaterials; } UpdateMass(netView); /* * @todo investigate why this is called. Determine if it is needed * * - most likely this is to prevent other rigidbody nodes from interacting with unity world physics within the ship */ var rbs = netView.GetComponentsInChildren(); for (var i = 0; i < rbs.Length; i++) if (rbs[i].isKinematic) Destroy(rbs[i]); } private void UpdatePieceCount() { if ((bool)m_nview && m_nview.m_zdo != null) m_nview.m_zdo.Set("MBPieceCount", m_pieces.Count); } public void EncapsulateBounds(ZNetView netview) { var piece = netview.GetComponent(); var colliders = piece ? piece.GetAllColliders() : [..netview.GetComponentsInChildren()]; var door = netview.GetComponentInChildren(); var ladder = netview.GetComponent(); var rope = netview.GetComponent(); if (!door && !ladder && !rope) m_bounds.Encapsulate(netview.transform.localPosition); for (var i = 0; i < colliders.Count; i++) { Physics.IgnoreCollision(colliders[i], m_blockingcollider, true); Physics.IgnoreCollision(colliders[i], m_floatcollider, true); Physics.IgnoreCollision(colliders[i], m_onboardcollider, true); } m_blockingcollider.size = new Vector3(m_bounds.size.x, 1.5f, m_bounds.size.z); m_blockingcollider.center = new Vector3(m_bounds.center.x, 0.2f, m_bounds.center.z); m_floatcollider.size = new Vector3(m_bounds.size.x, 3f, m_bounds.size.z); m_floatcollider.center = new Vector3(m_bounds.center.x, -0.2f, m_bounds.center.z); m_onboardcollider.size = m_bounds.size; m_onboardcollider.center = m_bounds.center; } internal int GetPieceCount() { if (!m_nview || m_nview.m_zdo == null) return m_pieces.Count; return m_nview.m_zdo.GetInt("MBPieceCount", m_pieces.Count); } } \ No newline at end of file diff --git a/src/ValheimRAFT/ValheimRAFT/SailAreaForce.cs b/src/ValheimRAFT/ValheimRAFT/SailAreaForce.cs deleted file mode 100644 index 7ebce429..00000000 --- a/src/ValheimRAFT/ValheimRAFT/SailAreaForce.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace ValheimRAFT; - -/* - * base values only for Config - * - * All of these values can be overriden by user config. - */ -static class SailAreaForce -{ - public static readonly float Tier1 = 5f; - public static readonly float Tier2 = 10f; - public static readonly float Tier3 = 20f; - public static readonly float Tier4 = 40f; - public static readonly float CustomTier1AreaForceMultiplier = 0.5f; - public static readonly bool HasPropulsionConfigOverride = false; -} \ No newline at end of file diff --git a/src/ValheimRAFT/ValheimRAFT/ValheimRaftPlugin.cs b/src/ValheimRAFT/ValheimRAFT/ValheimRaftPlugin.cs index 253dcb93..1e09ea5e 100644 --- a/src/ValheimRAFT/ValheimRAFT/ValheimRaftPlugin.cs +++ b/src/ValheimRAFT/ValheimRAFT/ValheimRaftPlugin.cs @@ -17,6 +17,7 @@ using ValheimRAFT.Util; using ValheimVehicles.ConsoleCommands; using ValheimVehicles.Prefabs; +using ValheimVehicles.Propulsion.Sail; using ValheimVehicles.Vehicles; using Logger = Jotunn.Logger; @@ -83,6 +84,7 @@ public class ValheimRaftPlugin : BaseUnityPlugin public ConfigEntry SailTier1Area { get; set; } public ConfigEntry SailTier2Area { get; set; } public ConfigEntry SailTier3Area { get; set; } + public ConfigEntry SailTier4Area { get; set; } public ConfigEntry SailCustomAreaTier1Multiplier { get; set; } public ConfigEntry BoatDragCoefficient { get; set; } public ConfigEntry MastShearForceThreshold { get; set; } @@ -245,6 +247,10 @@ private void CreatePropulsionConfig() SailTier3Area = Config.Bind("Propulsion", "SailTier3Area", SailAreaForce.Tier3, CreateConfigDescription("Manual sets the sail wind area of the tier 3 sail.", true, true)); + + SailTier4Area = Config.Bind("Propulsion", + "SailTier4Area", SailAreaForce.Tier4, + CreateConfigDescription("Manual sets the sail wind area of the tier 4 sail.", true, true)); } private void CreateServerConfig() diff --git a/src/ValheimVehicles/ValheimVehicles.Prefabs/LoadValheimAssets.cs b/src/ValheimVehicles/ValheimVehicles.Prefabs/LoadValheimAssets.cs index 315a0556..d5da6aff 100644 --- a/src/ValheimVehicles/ValheimVehicles.Prefabs/LoadValheimAssets.cs +++ b/src/ValheimVehicles/ValheimVehicles.Prefabs/LoadValheimAssets.cs @@ -9,6 +9,7 @@ public class LoadValheimAssets public static GameObject vanillaRaftPrefab; public static GameObject vikingShipPrefab; + public static GameObject drakkarPrefab; public static Transform waterMask; public static Piece woodFloorPiece; @@ -24,6 +25,7 @@ public void Init(PrefabManager prefabManager) { vanillaRaftPrefab = prefabManager.GetPrefab("Raft"); vikingShipPrefab = prefabManager.GetPrefab("VikingShip"); + drakkarPrefab = prefabManager.GetPrefab("VikingShip_Ashlands"); shipWaterEffects = vanillaRaftPrefab.transform.Find("WaterEffects").gameObject; waterMask = vikingShipPrefab.transform.Find("ship/visual/watermask"); diff --git a/src/ValheimVehicles/ValheimVehicles.Prefabs/PrefabNames.cs b/src/ValheimVehicles/ValheimVehicles.Prefabs/PrefabNames.cs index 83939ebb..852fdd17 100644 --- a/src/ValheimVehicles/ValheimVehicles.Prefabs/PrefabNames.cs +++ b/src/ValheimVehicles/ValheimVehicles.Prefabs/PrefabNames.cs @@ -16,6 +16,7 @@ public enum PrefabSizeVariant public const string Tier1RaftMastName = "MBRaftMast"; public const string Tier2RaftMastName = "MBKarveMast"; public const string Tier3RaftMastName = "MBVikingShipMast"; + public const string Tier4RaftMastName = $"{ValheimVehiclesPrefix}_DrakkalMast"; public const string Tier1CustomSailName = "MBSail"; public const string BoardingRamp = "MBBoardingRamp"; public const string BoardingRampWide = "MBBoardingRamp_Wide"; diff --git a/src/ValheimVehicles/ValheimVehicles.Prefabs/PrefabRegistryController.cs b/src/ValheimVehicles/ValheimVehicles.Prefabs/PrefabRegistryController.cs index 55bd2d8c..542c3a01 100644 --- a/src/ValheimVehicles/ValheimVehicles.Prefabs/PrefabRegistryController.cs +++ b/src/ValheimVehicles/ValheimVehicles.Prefabs/PrefabRegistryController.cs @@ -108,6 +108,9 @@ private static void UpdateRaftSailDescriptions() var tier3 = pieceManager.GetPiece(PrefabNames.Tier3RaftMastName); tier3.Piece.m_description = SailPrefabs.GetTieredSailAreaText(3); + + var tier4 = pieceManager.GetPiece(PrefabNames.Tier4RaftMastName); + tier4.Piece.m_description = SailPrefabs.GetTieredSailAreaText(4); } /** diff --git a/src/ValheimVehicles/ValheimVehicles.Prefabs/Registry/SailPrefabs.cs b/src/ValheimVehicles/ValheimVehicles.Prefabs/Registry/SailPrefabs.cs index 6cb0243e..2c0ca1b5 100644 --- a/src/ValheimVehicles/ValheimVehicles.Prefabs/Registry/SailPrefabs.cs +++ b/src/ValheimVehicles/ValheimVehicles.Prefabs/Registry/SailPrefabs.cs @@ -1,3 +1,5 @@ +using System.Linq; +using Jotunn; using Jotunn.Configs; using Jotunn.Entities; using Jotunn.Managers; @@ -20,6 +22,7 @@ public void Register(PrefabManager prefabManager, PieceManager pieceManager) RegisterRaftMast(prefabManager, pieceManager); RegisterKarveMast(prefabManager, pieceManager); RegisterVikingMast(prefabManager, pieceManager); + RegisterDrakkalMast(prefabManager, pieceManager); RegisterCustomSail(prefabManager, pieceManager); RegisterCustomSailCreator(prefabManager, pieceManager, 3); RegisterCustomSailCreator(prefabManager, pieceManager, 4); @@ -39,7 +42,7 @@ private void RegisterVikingMast(PrefabManager prefabManager, PieceManager pieceM vikingShipMastPrefabPiece.transform.localScale = new Vector3(2f, 2f, 2f); vikingShipMastPrefab.transform.localPosition = new Vector3(0, -1, 0); vikingShipMastPrefabPiece.m_name = "$mb_vikingship_mast"; - vikingShipMastPrefabPiece.m_description = "$mb_vikingship_mast_desc"; + vikingShipMastPrefabPiece.m_description = GetTieredSailAreaText(3); vikingShipMastPrefabPiece.m_placeEffect = LoadValheimAssets.woodFloorPiece.m_placeEffect; PrefabRegistryController.AddToRaftPrefabPieces(vikingShipMastPrefabPiece); @@ -48,9 +51,6 @@ private void RegisterVikingMast(PrefabManager prefabManager, PieceManager pieceM var vikingShipMastComponent = vikingShipMastPrefab.AddComponent(); vikingShipMastComponent.m_sailObject = vikingShipMastPrefab.transform.Find("Sail").gameObject; - // PrefabRegistryHelpers.AddBoundsToAllChildren(vikingShipMastPrefab, vikingShipMastPrefab); - - vikingShipMastComponent.m_sailCloth = vikingShipMastComponent.m_sailObject.GetComponentInChildren(); vikingShipMastComponent.m_allowSailRotation = true; @@ -68,31 +68,93 @@ private void RegisterVikingMast(PrefabManager prefabManager, PieceManager pieceM pieceManager.AddPiece(new CustomPiece(vikingShipMastPrefab, false, new PieceConfig { PieceTable = "Hammer", - Description = GetTieredSailAreaText(3), - Icon = LoadValheimVehicleAssets.VehicleSprites.GetSprite("vikingmast"), + Icon = LoadValheimVehicleAssets.VehicleSprites.GetSprite(SpriteNames.VikingMast), Category = PrefabNames.ValheimRaftMenuName, Enabled = true, - Requirements = new RequirementConfig[3] - { - new() + Requirements = + [ + new RequirementConfig { Amount = 10, Item = "FineWood", Recover = true }, - new() + new RequirementConfig { Amount = 2, Item = "RoundLog", Recover = true }, - new() + new RequirementConfig { Amount = 6, Item = "WolfPelt", Recover = true } - } + ] + })); + } + + private void RegisterDrakkalMast(PrefabManager prefabManager, PieceManager pieceManager) + { + var drakkalMast = LoadValheimAssets.drakkarPrefab.transform.Find("ship/visual/Mast") + .gameObject; + + var prefab = + prefabManager.CreateClonedPrefab(PrefabNames.Tier4RaftMastName, drakkalMast); + var vikingShipMastPrefabPiece = prefab.AddComponent(); + + vikingShipMastPrefabPiece.transform.localScale = new Vector3(1, 1, 1); + vikingShipMastPrefabPiece.m_name = "$valheim_vehicles_drakkalship_mast"; + vikingShipMastPrefabPiece.m_description = GetTieredSailAreaText(4); + vikingShipMastPrefabPiece.m_placeEffect = LoadValheimAssets.woodFloorPiece.m_placeEffect; + PrefabRegistryController.AddToRaftPrefabPieces(vikingShipMastPrefabPiece); + PrefabRegistryHelpers.AddNetViewWithPersistence(prefab); + + var mastComponent = prefab.AddComponent(); + var clothObj = prefab.GetComponentsInChildren()[0]; + mastComponent.m_sailObject = clothObj.transform.parent.gameObject; + + mastComponent.m_sailCloth = clothObj; + mastComponent.m_allowSailRotation = true; + mastComponent.m_allowSailShrinking = true; + + // shipCollider + PrefabRegistryHelpers.AddSnapPoint("$hud_snappoint_bottom", prefab); + + // Set wear and tear can be abstracted + PrefabRegistryHelpers.SetWearNTear(prefab, 3); + + PrefabRegistryHelpers.FixRopes(prefab); + PrefabRegistryHelpers.FixCollisionLayers(prefab); + + pieceManager.AddPiece(new CustomPiece(prefab, false, new PieceConfig + { + PieceTable = "Hammer", + Icon = LoadValheimVehicleAssets.VehicleSprites.GetSprite(SpriteNames.VikingMast), + Category = PrefabNames.ValheimRaftMenuName, + Enabled = true, + Requirements = + [ + new RequirementConfig + { + Amount = 10, + Item = "FineWood", + Recover = true + }, + new RequirementConfig + { + Amount = 10, + Item = "YggdrasilWood", + Recover = true + }, + new RequirementConfig + { + Amount = 20, + Item = "LinenThread", + Recover = true + } + ] })); } @@ -187,6 +249,8 @@ public static string GetTieredSailAreaText(int tier) $"$mb_karve_mast_desc\n$mb_raft_mast_generic_wind_desc [{ValheimRaftPlugin.Instance.SailTier2Area.Value}]", 3 => $"$mb_vikingship_mast_desc\n$mb_raft_mast_generic_wind_desc [{ValheimRaftPlugin.Instance.SailTier3Area.Value}]", + 4 => + $"$valheim_vehicles_drakkalship_mast_desc\n$mb_raft_mast_generic_wind_desc [{ValheimRaftPlugin.Instance.SailTier4Area.Value}]", _ => "" }; @@ -228,21 +292,21 @@ private void RegisterRaftMast(PrefabManager prefabManager, PieceManager pieceMan Icon = LoadValheimVehicleAssets.VehicleSprites.GetSprite("raftmast"), Category = PrefabNames.ValheimRaftMenuName, Enabled = true, - Requirements = new RequirementConfig[2] - { - new() + Requirements = + [ + new RequirementConfig { Amount = 10, Item = "Wood", Recover = true }, - new() + new RequirementConfig { Amount = 6, Item = "DeerHide", Recover = true } - } + ] })); } diff --git a/src/ValheimVehicles/ValheimVehicles.Prefabs/SpriteNames.cs b/src/ValheimVehicles/ValheimVehicles.Prefabs/SpriteNames.cs index e148aff8..96d39cdd 100644 --- a/src/ValheimVehicles/ValheimVehicles.Prefabs/SpriteNames.cs +++ b/src/ValheimVehicles/ValheimVehicles.Prefabs/SpriteNames.cs @@ -2,6 +2,7 @@ namespace ValheimVehicles.Prefabs; public abstract class SpriteNames { + public const string VikingMast = "vikingmast"; public const string ShipSteeringWheel = "steering_wheel"; public const string DirtFloor = "dirtfloor_icon"; public const string BoardingRamp = "boarding_ramp"; diff --git a/src/ValheimVehicles/ValheimVehicles.Propulsion/Sail/SailAreaForce.cs b/src/ValheimVehicles/ValheimVehicles.Propulsion/Sail/SailAreaForce.cs index 7d593ff9..f5a588f4 100644 --- a/src/ValheimVehicles/ValheimVehicles.Propulsion/Sail/SailAreaForce.cs +++ b/src/ValheimVehicles/ValheimVehicles.Propulsion/Sail/SailAreaForce.cs @@ -9,8 +9,9 @@ namespace ValheimVehicles.Propulsion.Sail; static class SailAreaForce { public static readonly float Tier1 = 5f; - public static readonly float Tier2 = 7.5f; - public static readonly float Tier3 = 10f; + public static readonly float Tier2 = 10f; + public static readonly float Tier3 = 20f; + public static readonly float Tier4 = 40f; public static readonly float CustomTier1AreaForceMultiplier = 0.5f; public static readonly bool HasPropulsionConfigOverride = false; } \ No newline at end of file diff --git a/src/ValheimVehicles/ValheimVehicles.Vehicles/BaseVehicleController.cs b/src/ValheimVehicles/ValheimVehicles.Vehicles/BaseVehicleController.cs index 5632b800..daa1ee54 100644 --- a/src/ValheimVehicles/ValheimVehicles.Vehicles/BaseVehicleController.cs +++ b/src/ValheimVehicles/ValheimVehicles.Vehicles/BaseVehicleController.cs @@ -126,6 +126,7 @@ internal InitializationState BaseVehicleInitState public int numberOfTier1Sails = 0; public int numberOfTier2Sails = 0; public int numberOfTier3Sails = 0; + public int numberOfTier4Sails = 0; public float customSailsArea = 0f; public float totalSailArea = 0f; @@ -1084,6 +1085,7 @@ public float GetTotalSailArea() numberOfTier1Sails = 0; numberOfTier2Sails = 0; numberOfTier3Sails = 0; + numberOfTier4Sails = 0; var hasConfigOverride = ValheimRaftPlugin.Instance.EnableCustomPropulsionConfig.Value; @@ -1096,7 +1098,7 @@ public float GetTotalSailArea() continue; } - if (mMastPiece.name.Contains("MBRaftMast")) + if (mMastPiece.name.StartsWith(PrefabNames.Tier1RaftMastName)) { ++numberOfTier1Sails; var multiplier = hasConfigOverride @@ -1104,7 +1106,7 @@ public float GetTotalSailArea() : Tier1; totalSailArea += numberOfTier1Sails * multiplier; } - else if (mMastPiece.name.Contains("MBKarveMast")) + else if (mMastPiece.name.StartsWith(PrefabNames.Tier2RaftMastName)) { ++numberOfTier2Sails; var multiplier = hasConfigOverride @@ -1112,7 +1114,7 @@ public float GetTotalSailArea() : Tier2; totalSailArea += numberOfTier2Sails * multiplier; } - else if (mMastPiece.name.Contains("MBVikingShipMast")) + else if (mMastPiece.name.StartsWith(PrefabNames.Tier3RaftMastName)) { ++numberOfTier3Sails; var multiplier = hasConfigOverride @@ -1121,6 +1123,15 @@ public float GetTotalSailArea() totalSailArea += numberOfTier3Sails * multiplier; ; } + else if (mMastPiece.name.StartsWith(PrefabNames.Tier4RaftMastName)) + { + ++numberOfTier4Sails; + var multiplier = hasConfigOverride + ? ValheimRaftPlugin.Instance.SailTier4Area.Value + : Tier4; + totalSailArea += numberOfTier4Sails * multiplier; + ; + } } var sailComponents = GetComponentsInChildren(); @@ -1811,7 +1822,8 @@ public void EncapsulateBounds(GameObject go) if (!door && !ladder && !rope && !go.name.StartsWith(PrefabNames.Tier1RaftMastName) && !go.name.StartsWith(PrefabNames.Tier1CustomSailName) && !go.name.StartsWith(PrefabNames.Tier2RaftMastName) && - !go.name.StartsWith(PrefabNames.Tier3RaftMastName)) + !go.name.StartsWith(PrefabNames.Tier3RaftMastName) && + !go.name.StartsWith(PrefabNames.Tier4RaftMastName)) { if (ValheimRaftPlugin.Instance.EnableExactVehicleBounds.Value || ShipHulls.IsHull(go)) { diff --git a/src/ValheimVehicles/ValheimVehicles.Vehicles/VehicleShip.cs b/src/ValheimVehicles/ValheimVehicles.Vehicles/VehicleShip.cs index 4541b809..04b08285 100644 --- a/src/ValheimVehicles/ValheimVehicles.Vehicles/VehicleShip.cs +++ b/src/ValheimVehicles/ValheimVehicles.Vehicles/VehicleShip.cs @@ -1222,7 +1222,7 @@ private float GetSailForceEnergy(float sailSize, float windAngleFactor) private Vector3 GetSailForce(float sailSize, float dt, bool isFlying) { - var windDir = EnvMan.instance.GetWindDir(); + var windDir = IsWindControllActive() ? Vector3.zero : EnvMan.instance.GetWindDir(); var windAngleFactorInterpolated = GetInterpolatedWindAngleFactor(); Vector3 target;