From b5f877ab4a79342908382a3b76bbbe1cb224ded2 Mon Sep 17 00:00:00 2001 From: Fro Zen <38363715+Frozenreflex@users.noreply.github.com> Date: Fri, 1 Oct 2021 16:39:31 -0500 Subject: [PATCH] Improve the behavior of death planes (#257) * Add ObjectScript for death planes * Fix size of death barrier objects * Switch death plane to use SmashAsync * Add initial water death plane script * Switch smasher for death planes to death plane * Remove comment in DeathPlane * Merge and improve object script loading for LuaScriptComponent and custom scripts. * Add player fall death script. * Move death plane scripts. * Add death (teleport) plane for properties. * Correct player sphere physics to be at center instead of bottom. (Fix Portabello waters being too high) * Disable check for hasCustomClientScript * Disable fallback, replace FV death plane objectscript with nativescript * Reformatting * Add CapsuleBody (for cylinders) Fixes NT big assembly teleporters * Capsule-sphere collision test * Clean up some code * Replace client script names with server ones * Run native DeathPlane script in multiple worlds * Move PrimitiveModel cube origins from center to bottom * Fix scripts linked to client script names * Correctly split PATH env variable on *nix systems * Apply binoculars script to all binoculars * Always give all rewards for achievements * Fix for ghost players after dying to death plane * Fix Instantiate when item is not in Objects table * Fix not dying after falling near Tortoise Terrace * Add LOT-specific scripts. Co-authored-by: TheNexusAvenger <13441476+TheNexusAvenger@users.noreply.github.com> Co-authored-by: enteryournamehere <11255568+enteryournamehere@users.noreply.github.com> --- Uchu.Master/ServerInstance.cs | 2 +- Uchu.Physics.Test/CollisionTest.cs | 34 +++++++++ Uchu.Physics/Objects/CapsuleBody.cs | 51 ++++++++++++++ Uchu.Physics/PhysicsSimulation.cs | 70 ++++++++++++++++--- .../AvantGardens/MaelstromSample.cs | 6 +- .../AvantGardens/MaelstromVacuum.cs | 6 +- .../AvantGardens/TargetFilter.cs | 6 +- Uchu.StandardScripts/General/Binoculars.cs | 11 ++- .../General/DeathPlane/DeathPlane.cs | 31 ++++++++ .../General/DeathPlane/PlayerFallDeath.cs | 32 +++++++++ .../General/DeathPlane/PropertyDeathPlane.cs | 28 ++++++++ .../General/DeathPlane/SharkDeathPlane.cs | 39 +++++++++++ .../General/DeathPlane/WorldDeathPlane.cs | 50 +++++++++++++ .../General/NexusForcePlaque.cs | 6 +- Uchu.StandardScripts/NexusTower/Vault.cs | 5 +- .../VentureExplorer/MissionConsole.cs | 6 +- .../ReplicaComponents/LuaScriptComponent.cs | 28 ++++---- .../Components/Server/PhysicsComponent.cs | 2 +- .../Server/PrimitiveModelComponent.cs | 4 +- Uchu.World/Objects/GameObjects/GameObject.cs | 20 ++---- Uchu.World/Objects/GameObjects/Player.cs | 4 +- Uchu.World/Objects/Zone.cs | 60 ++++++++++++++++ .../Native/Attributes/LotSpecificAttribute.cs | 22 ++++++ .../Scripting/Native/NativeScriptPack.cs | 19 +++-- Uchu.World/Scripting/ScriptManager.cs | 26 +++++-- 25 files changed, 493 insertions(+), 75 deletions(-) create mode 100644 Uchu.Physics/Objects/CapsuleBody.cs create mode 100644 Uchu.StandardScripts/General/DeathPlane/DeathPlane.cs create mode 100644 Uchu.StandardScripts/General/DeathPlane/PlayerFallDeath.cs create mode 100644 Uchu.StandardScripts/General/DeathPlane/PropertyDeathPlane.cs create mode 100644 Uchu.StandardScripts/General/DeathPlane/SharkDeathPlane.cs create mode 100644 Uchu.StandardScripts/General/DeathPlane/WorldDeathPlane.cs create mode 100644 Uchu.World/Scripting/Native/Attributes/LotSpecificAttribute.cs diff --git a/Uchu.Master/ServerInstance.cs b/Uchu.Master/ServerInstance.cs index 545e69b7..6adcd36f 100644 --- a/Uchu.Master/ServerInstance.cs +++ b/Uchu.Master/ServerInstance.cs @@ -40,7 +40,7 @@ public void Start(string location, string dotnet) var useDotNet = !string.IsNullOrWhiteSpace(dotnet); if (useDotNet && dotnet?.ToLower() == "dotnet" && !File.Exists(dotnet)) { - var pathDirectories = (Environment.GetEnvironmentVariable("PATH") ?? "").Split(";"); + var pathDirectories = (Environment.GetEnvironmentVariable("PATH") ?? "").Split(new[] { ';', ':' }); var dotNetInPath = pathDirectories.Any(pathDirectory => File.Exists(Path.Combine(pathDirectory, dotnet))); if (!dotNetInPath) { diff --git a/Uchu.Physics.Test/CollisionTest.cs b/Uchu.Physics.Test/CollisionTest.cs index 299c1bdf..4091087b 100644 --- a/Uchu.Physics.Test/CollisionTest.cs +++ b/Uchu.Physics.Test/CollisionTest.cs @@ -129,5 +129,39 @@ public void BoxBoxCollision() Assert.IsFalse(PhysicsSimulation.BoxBoxCollision(smallBox5, box7)); Assert.IsTrue(PhysicsSimulation.BoxBoxCollision(box9, box8Rotated)); } + + [Test] + public void CapsuleSphereCollision() + { + // r=1.5 at (0,8,0) + var sphere = SphereBody.Create(_simulation, + new Vector3(0, 8, 0), + 1.5f); + + // pointing straight up, should hit sphere + var capsule1 = CapsuleBody.Create(this._simulation, + new Vector3(0, 0, 0), + Quaternion.Identity, + 1f, + 6f); + + // rotated 45deg, should miss sphere + var capsule2 = CapsuleBody.Create(this._simulation, + new Vector3(0, 0, 0), + Quaternion.CreateFromAxisAngle(Vector3.UnitX, (float) (0.125 * Math.Tau)), + 1f, + 6f); + + // floating, rotated 90deg, should hit sphere + var capsule3 = CapsuleBody.Create(this._simulation, + new Vector3(8, 8, 0), + Quaternion.CreateFromAxisAngle(Vector3.UnitZ, (float) (0.25 * Math.Tau)), + 1f, + 6f); + + Assert.IsTrue(PhysicsSimulation.CapsuleSphereCollision(capsule1, sphere)); + Assert.IsFalse(PhysicsSimulation.CapsuleSphereCollision(capsule2, sphere)); + Assert.IsTrue(PhysicsSimulation.CapsuleSphereCollision(capsule3, sphere)); + } } } diff --git a/Uchu.Physics/Objects/CapsuleBody.cs b/Uchu.Physics/Objects/CapsuleBody.cs new file mode 100644 index 00000000..f1f462a8 --- /dev/null +++ b/Uchu.Physics/Objects/CapsuleBody.cs @@ -0,0 +1,51 @@ +using System.Numerics; + +namespace Uchu.Physics +{ + public class CapsuleBody : PhysicsObject + { + /// + /// Value used to determine the order in which objects are passed to the collision detection functions. + /// + public override int CollisionPrecedence { get; } = 2; + + /// + /// Creates a capsule body. + /// + /// The simulation to use. + public CapsuleBody(PhysicsSimulation simulation) : base(simulation) + { + } + + /// + /// Creates a capsule body. + /// + /// The simulation to use. + /// The position of the body. + /// The rotation of the body. + /// The radius of the capsule. + /// The height of the cylinder part of the capsule. + /// The capsule body that was created. + public static CapsuleBody Create(PhysicsSimulation simulation, Vector3 position, Quaternion rotation, + float radius, float height) + { + var obj = new CapsuleBody(simulation); + obj.Position = position; + obj.Rotation = rotation; + obj.Radius = radius; + obj.Height = height; + simulation.Register(obj); + return obj; + } + + /// + /// Radius of the capsule. + /// + public float Radius; + + /// + /// Height of the cylinder part of the capsule. + /// + public float Height; + } +} diff --git a/Uchu.Physics/PhysicsSimulation.cs b/Uchu.Physics/PhysicsSimulation.cs index c73fe582..8aae587f 100644 --- a/Uchu.Physics/PhysicsSimulation.cs +++ b/Uchu.Physics/PhysicsSimulation.cs @@ -72,14 +72,20 @@ public static bool Collides(PhysicsObject firstObject, PhysicsObject secondObjec } // First precedence value >= second precedence value - // Cube = 1, sphere = 0 - - if (first is BoxBody box && second is SphereBody sphere) - return BoxSphereCollision(box, sphere); - if (first is SphereBody sphere1 && second is SphereBody sphere2) - return SphereSphereCollision(sphere1, sphere2); - if (first is BoxBody box1 && second is BoxBody box2) - return BoxBoxCollision(box1, box2); + // Capsule = 2, cube = 1, sphere = 0 + + if (first is BoxBody boxSphereFirst && second is SphereBody boxSphereSecond) + return BoxSphereCollision(boxSphereFirst, boxSphereSecond); + if (first is SphereBody sphereSphereFirst && second is SphereBody sphereSphereSecond) + return SphereSphereCollision(sphereSphereFirst, sphereSphereSecond); + if (first is BoxBody boxBoxFirst && second is BoxBody boxBoxSecond) + return BoxBoxCollision(boxBoxFirst, boxBoxSecond); + if (first is CapsuleBody capsuleSphereFirst && second is SphereBody capsuleSphereSecond) + return CapsuleSphereCollision(capsuleSphereFirst, capsuleSphereSecond); + if (first is CapsuleBody capsuleBoxFirst && second is BoxBody capsuleBoxSecond) + return CapsuleBoxCollision(capsuleBoxFirst, capsuleBoxSecond); + if (first is CapsuleBody capsuleCapsuleFirst && second is CapsuleBody capsuleCapsuleSecond) + return CapsuleCapsuleCollision(capsuleCapsuleFirst, capsuleCapsuleSecond); throw new NotSupportedException(); } @@ -144,6 +150,54 @@ public static bool SphereSphereCollision(SphereBody firstSphere, SphereBody seco return distanceSquared < maxDistanceSquared; } + /// + /// Determine whether a capsule and a sphere intersect. + /// + /// Capsule physics object + /// Sphere physics object + public static bool CapsuleSphereCollision(CapsuleBody capsule, SphereBody sphere) + { + // reference frame: capsule at (0, 0, 0), pointing in positive y direction + var sphereCoordsRelative = sphere.Position - capsule.Position; + + // to transform coordinate system to have capsule be axis-aligned, we apply + // the reverse of the rotation to the sphere coordinates + var inverse = new Quaternion(capsule.Rotation.X, capsule.Rotation.Y, capsule.Rotation.Z, -capsule.Rotation.W); + var sphereCoordsTransformed = Vector3.Transform(sphereCoordsRelative, inverse); + + // coordinates of the line in the centre of the cylinder part + const int capsuleMinY = 0; + var capsuleMaxY = capsule.Height; + + const int closestPointToSphereX = 0; + var closestPointToSphereY = Math.Clamp(sphereCoordsTransformed.Y, capsuleMinY, capsuleMaxY); + const int closestPointToSphereZ = 0; + + return Vector3.DistanceSquared(sphereCoordsTransformed, + new Vector3(closestPointToSphereX, closestPointToSphereY, closestPointToSphereZ)) + < Math.Pow(capsule.Radius + sphere.Radius, 2); + } + + /// + /// Determine whether two capsules intersect. + /// + /// The first capsule + /// The second capsule + public static bool CapsuleCapsuleCollision(CapsuleBody firstCapsule, CapsuleBody secondCapsule) + { + throw new NotImplementedException("Capsule-capsule collision checks are not implemented."); + } + + /// + /// Determine whether a capsule and a box intersect. + /// + /// The capsule + /// The box + public static bool CapsuleBoxCollision(CapsuleBody capsule, BoxBody box) + { + throw new NotImplementedException("Capsule-box collision checks are not implemented."); + } + /// /// Registers a physics object. /// diff --git a/Uchu.StandardScripts/AvantGardens/MaelstromSample.cs b/Uchu.StandardScripts/AvantGardens/MaelstromSample.cs index 56261625..ed71c826 100644 --- a/Uchu.StandardScripts/AvantGardens/MaelstromSample.cs +++ b/Uchu.StandardScripts/AvantGardens/MaelstromSample.cs @@ -5,9 +5,9 @@ namespace Uchu.StandardScripts.AvantGardens { /// - /// Native implementation of scripts/02_client/map/ag/l_ag_maelstrom_sample.lua + /// Script to show/hide maelstrom samples based on whether the player has the relevant mission /// - [ScriptName("l_ag_maelstrom_sample.lua")] + [LotSpecific(14718)] public class MaelstromSample : ObjectScript { /// @@ -21,4 +21,4 @@ public MaelstromSample(GameObject gameObject) : base(gameObject) missionFilter.AddMissionIdToFilter(MissionId.SampleforScience); } } -} \ No newline at end of file +} diff --git a/Uchu.StandardScripts/AvantGardens/MaelstromVacuum.cs b/Uchu.StandardScripts/AvantGardens/MaelstromVacuum.cs index 3e1998fb..59d3cfd4 100644 --- a/Uchu.StandardScripts/AvantGardens/MaelstromVacuum.cs +++ b/Uchu.StandardScripts/AvantGardens/MaelstromVacuum.cs @@ -7,9 +7,9 @@ namespace Uchu.StandardScripts.AvantGardens { /// - /// Native implementation of scripts/02_client/Equipment/l_maelstrom_extracticator_client.lua + /// Script for spawned maelstrom vacuum (https://lu.lcdruniverse.org/explorer/objects/14596) /// - [ScriptName("l_maelstrom_extracticator_client.lua")] + [ScriptName("ScriptComponent_1582_script_name__removed")] public class MaelstromVacuum : ObjectScript { /// @@ -57,4 +57,4 @@ public MaelstromVacuum(GameObject gameObject) : base(gameObject) }); } } -} \ No newline at end of file +} diff --git a/Uchu.StandardScripts/AvantGardens/TargetFilter.cs b/Uchu.StandardScripts/AvantGardens/TargetFilter.cs index e31d9432..90d3eb67 100644 --- a/Uchu.StandardScripts/AvantGardens/TargetFilter.cs +++ b/Uchu.StandardScripts/AvantGardens/TargetFilter.cs @@ -5,9 +5,9 @@ namespace Uchu.StandardScripts.AvantGardens { /// - /// Native implementation of scripts/02_client/map/ag/l_ag_plunger_target.lua + /// Script to show/hide targets based on whether the player has the relevant mission /// - [ScriptName("l_ag_plunger_target.lua")] + [LotSpecific(14380)] public class TargetFilter : ObjectScript { /// @@ -20,4 +20,4 @@ public TargetFilter(GameObject gameObject) : base(gameObject) missionFilter.AddMissionIdToFilter(MissionId.SixShooter); } } -} \ No newline at end of file +} diff --git a/Uchu.StandardScripts/General/Binoculars.cs b/Uchu.StandardScripts/General/Binoculars.cs index c357a3fc..b9b69ec1 100644 --- a/Uchu.StandardScripts/General/Binoculars.cs +++ b/Uchu.StandardScripts/General/Binoculars.cs @@ -4,9 +4,14 @@ namespace Uchu.StandardScripts.General { /// - /// Native implementation of scripts/02_client/map/general/l_binoculars_client.lua + /// Script to set flags for binoculars (e.g. https://lu.lcdruniverse.org/explorer/objects/6700) the player uses /// - [ScriptName("l_binoculars_client.lua")] + [ScriptName("ScriptComponent_1002_script_name__removed")] // Binoculars, id 6700 + [ScriptName("ScriptComponent_952_script_name__removed")] // AG - Spaceship Binoculars, id 6842 + [ScriptName("ScriptComponent_975_script_name__removed")] // GF - Binoculars, id 6958 + [ScriptName("ScriptComponent_1020_script_name__removed")] // PR - Binoculars, id 7607 + [ScriptName("ScriptComponent_1021_script_name__removed")] // NS - Binoculars, id 7608 + [ScriptName("ScriptComponent_1335_script_name__removed")] // FB - Binoculars, id 12306 public class Binoculars : ObjectScript { /// @@ -40,4 +45,4 @@ public Binoculars(GameObject gameObject) : base(gameObject) }); } } -} \ No newline at end of file +} diff --git a/Uchu.StandardScripts/General/DeathPlane/DeathPlane.cs b/Uchu.StandardScripts/General/DeathPlane/DeathPlane.cs new file mode 100644 index 00000000..7e6e3be0 --- /dev/null +++ b/Uchu.StandardScripts/General/DeathPlane/DeathPlane.cs @@ -0,0 +1,31 @@ +using System.Threading.Tasks; +using Uchu.World; +using Uchu.World.Scripting.Native; + +namespace Uchu.StandardScripts.General.DeathPlane +{ + /// + /// Native implementation of scripts/ai/act/l_act_player_death_trigger.lua + /// + [ScriptName("l_act_player_death_trigger.lua")] + public class DeathPlane : ObjectScript + { + /// + /// Creates the object script. + /// + /// Game object to control with the script. + public DeathPlane(GameObject gameObject) : base(gameObject) + { + var physics = gameObject.GetComponent(); + if (physics == default) return; + Listen(physics.OnEnter, other => + { + if (!(other.GameObject is Player player)) return; + Task.Run(async () => + { + await player.GetComponent().SmashAsync(gameObject); + }); + }); + } + } +} \ No newline at end of file diff --git a/Uchu.StandardScripts/General/DeathPlane/PlayerFallDeath.cs b/Uchu.StandardScripts/General/DeathPlane/PlayerFallDeath.cs new file mode 100644 index 00000000..6b720afe --- /dev/null +++ b/Uchu.StandardScripts/General/DeathPlane/PlayerFallDeath.cs @@ -0,0 +1,32 @@ +using System.Threading.Tasks; +using Uchu.World; +using Uchu.World.Scripting.Native; + +namespace Uchu.StandardScripts.General.DeathPlane +{ + /// + /// Native implementation of scripts/ai/gf/l_player_fall_death.lua + /// + [ScriptName("l_player_fall_death.lua")] + public class PlayerFallDeath : ObjectScript + { + /// + /// Creates the object script. + /// + /// Game object to control with the script. + public PlayerFallDeath(GameObject gameObject) : base(gameObject) + { + var physics = gameObject.GetComponent(); + if (physics == default) return; + Listen(physics.OnEnter, other => + { + if (!(other.GameObject is Player player)) return; + Task.Run(async () => + { + await Task.Delay(2000); + await player.GetComponent().SmashAsync(gameObject); + }); + }); + } + } +} \ No newline at end of file diff --git a/Uchu.StandardScripts/General/DeathPlane/PropertyDeathPlane.cs b/Uchu.StandardScripts/General/DeathPlane/PropertyDeathPlane.cs new file mode 100644 index 00000000..9fee430f --- /dev/null +++ b/Uchu.StandardScripts/General/DeathPlane/PropertyDeathPlane.cs @@ -0,0 +1,28 @@ +using Uchu.World; +using Uchu.World.Scripting.Native; + +namespace Uchu.StandardScripts.General.DeathPlane +{ + /// + /// Native implementation of scripts/ai/ns/ns_pp_01/l_ns_pp_01_teleport.lua + /// + [ScriptName("l_ns_pp_01_teleport.lua")] + public class PropertyDeathPlane : ObjectScript + { + /// + /// Creates the object script. + /// + /// Game object to control with the script. + public PropertyDeathPlane(GameObject gameObject) : base(gameObject) + { + var physics = gameObject.GetComponent(); + if (physics == default) return; + Listen(physics.OnEnter, other => + { + if (!(other.GameObject is Player player)) return; + var teleportObject = this.GetGroup("Teleport")[0]; + player.Teleport(teleportObject.Transform.Position, ignore: false); + }); + } + } +} \ No newline at end of file diff --git a/Uchu.StandardScripts/General/DeathPlane/SharkDeathPlane.cs b/Uchu.StandardScripts/General/DeathPlane/SharkDeathPlane.cs new file mode 100644 index 00000000..b9badff9 --- /dev/null +++ b/Uchu.StandardScripts/General/DeathPlane/SharkDeathPlane.cs @@ -0,0 +1,39 @@ +using System.Numerics; +using System.Threading.Tasks; +using Uchu.Physics; +using Uchu.World; +using Uchu.World.Scripting.Native; + +namespace Uchu.StandardScripts.General.DeathPlane +{ + /// + /// Native implementation of scripts/ai/act/l_act_shark_player_death_trigger.lua + /// + [ScriptName("l_act_shark_player_death_trigger.lua")] + public class SharkDeathPlane : ObjectScript + { + /// + /// Creates the object script. + /// + /// Game object to control with the script. + public SharkDeathPlane(GameObject gameObject) : base(gameObject) + { + var physics = gameObject.GetComponent(); + if (physics == default) return; + Listen(physics.OnEnter, other => + { + if (!(other.GameObject is Player player)) return; + var character = player.GetComponent(); + var missionInventoryComponent = player.GetComponent(); + Task.Run(async () => + { + //achievements require this order + await missionInventoryComponent.ScriptAsync(665, gameObject.Lot); + await missionInventoryComponent.ScriptAsync(664, gameObject.Lot); + await missionInventoryComponent.ScriptAsync(663, gameObject.Lot); + }); + player.GetComponent().SmashAsync(gameObject, animation: "big-shark-death"); + }); + } + } +} \ No newline at end of file diff --git a/Uchu.StandardScripts/General/DeathPlane/WorldDeathPlane.cs b/Uchu.StandardScripts/General/DeathPlane/WorldDeathPlane.cs new file mode 100644 index 00000000..6d0c1337 --- /dev/null +++ b/Uchu.StandardScripts/General/DeathPlane/WorldDeathPlane.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Uchu.Core; +using Uchu.World; +using Uchu.World.Scripting.Native; + +namespace Uchu.StandardScripts.General.DeathPlane +{ + public class WorldDeathPlane: NativeScript + { + public override Task LoadAsync() + { + List dead = new List(); + Dictionary heights = new Dictionary(){ + {1300, 80}, + {1400, -200}, + {1800, -100}, + //if we decide that -100 is fine for every world, switch this to a + //list containing just the zoneids, replace the if check below with + //heights.contains, and replace height in the listen statements + //with -100 + }; + if (!heights.ContainsKey(Zone.ZoneId)) + { + return Task.CompletedTask; + } + var height = heights[Zone.ZoneId]; + Listen(Zone.OnPlayerLoad, player => + { + var destructibleComponent = player.GetComponent(); + Listen(player.OnPositionUpdate, (position, rotation) => + { + if (position.Y < height && destructibleComponent.Alive && !dead.Contains(player)) + { + dead.Add(player); + destructibleComponent.SmashAsync(player); + } + }); + Listen(destructibleComponent.OnResurrect, async () => + { + await Task.Delay(1000); + dead.Remove(player); + }); + }); + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/Uchu.StandardScripts/General/NexusForcePlaque.cs b/Uchu.StandardScripts/General/NexusForcePlaque.cs index 3d8a034f..72a30837 100644 --- a/Uchu.StandardScripts/General/NexusForcePlaque.cs +++ b/Uchu.StandardScripts/General/NexusForcePlaque.cs @@ -4,9 +4,9 @@ namespace Uchu.StandardScripts.General { /// - /// Native implementation of scripts/02_client/map/general/l_story_box_interact_client.lua + /// Script to set flags for story plaques (https://lu.lcdruniverse.org/explorer/objects/8139) the player uses /// - [ScriptName("l_story_box_interact_client.lua")] + [ScriptName("ScriptComponent_1054_script_name__removed")] public class NexusForcePlaque : ObjectScript { /// @@ -41,4 +41,4 @@ public NexusForcePlaque(GameObject gameObject) : base(gameObject) }); } } -} \ No newline at end of file +} diff --git a/Uchu.StandardScripts/NexusTower/Vault.cs b/Uchu.StandardScripts/NexusTower/Vault.cs index 77fbf4ce..b3539a0f 100644 --- a/Uchu.StandardScripts/NexusTower/Vault.cs +++ b/Uchu.StandardScripts/NexusTower/Vault.cs @@ -5,9 +5,10 @@ namespace Uchu.StandardScripts.NexusTower { /// - /// Native implementation of scripts/02_client/map/general/l_bank_interact_client.lua + /// Script to handle interactions with the Vault /// - [ScriptName("l_bank_interact_client.lua")] + [ScriptName("ScriptComponent_1480_script_name__removed")] + [ScriptName("ScriptComponent_1481_script_name__removed")] public class Vault : ObjectScript { /// diff --git a/Uchu.StandardScripts/VentureExplorer/MissionConsole.cs b/Uchu.StandardScripts/VentureExplorer/MissionConsole.cs index 68b1fe92..3d19c827 100644 --- a/Uchu.StandardScripts/VentureExplorer/MissionConsole.cs +++ b/Uchu.StandardScripts/VentureExplorer/MissionConsole.cs @@ -7,9 +7,9 @@ namespace Uchu.StandardScripts.VentureExplorer { /// - /// Native implementation of scripts/02_client/map/ve/l_mission_console_client.lua + /// Script for VE console (https://lu.lcdruniverse.org/explorer/objects/12551) /// - [ScriptName("l_mission_console_client.lua")] + [ScriptName("ScriptComponent_1372_script_name__removed")] public class MissionConsole : ObjectScript { /// @@ -85,4 +85,4 @@ public override void CompleteOnce() }); } } -} \ No newline at end of file +} diff --git a/Uchu.World/Objects/Components/ReplicaComponents/LuaScriptComponent.cs b/Uchu.World/Objects/Components/ReplicaComponents/LuaScriptComponent.cs index c08342f8..270ee5c6 100644 --- a/Uchu.World/Objects/Components/ReplicaComponents/LuaScriptComponent.cs +++ b/Uchu.World/Objects/Components/ReplicaComponents/LuaScriptComponent.cs @@ -31,26 +31,22 @@ protected LuaScriptComponent() return; } - if (GameObject.Settings.TryGetValue("custom_script_server", out var scriptOverride) && (string) scriptOverride != "") - { - ScriptName = (string) scriptOverride; - } + // Set the server script name. + if (GameObject.Settings.TryGetValue("custom_script_server", out var serverScriptOverride) && (string) serverScriptOverride != "") + this.ScriptName = (string) serverScriptOverride; else - { - ScriptName = script.Scriptname; - } - - // Set the script name. - this.ClientScriptName = script.Clientscriptname; + this.ScriptName = script.Scriptname; + + // Set the client script name. + if (GameObject.Settings.TryGetValue("custom_script_client", out var clientScriptOverride) && (string) clientScriptOverride != "") + this.ClientScriptName = (string) clientScriptOverride; + else + this.ClientScriptName = script.Scriptname; + Logger.Debug($"{GameObject} -> {this.ScriptName}, {this.ClientScriptName}"); // Start the object script. - var newObjectScriptName = Zone.ScriptManager.ObjectScriptTypes.Keys.FirstOrDefault( - objectScriptName => (this.ScriptName ?? "").ToLower().EndsWith(objectScriptName)) - ?? Zone.ScriptManager.ObjectScriptTypes.Keys.FirstOrDefault( - objectScriptName => (this.ClientScriptName ?? "").ToLower().EndsWith(objectScriptName)); - if (newObjectScriptName == null) return; - this.Zone.LoadObjectScript(this.GameObject, Zone.ScriptManager.ObjectScriptTypes[newObjectScriptName]); + this.Zone.LoadScriptForObject(this.GameObject); }); } diff --git a/Uchu.World/Objects/Components/Server/PhysicsComponent.cs b/Uchu.World/Objects/Components/Server/PhysicsComponent.cs index 555b3f77..bd237108 100644 --- a/Uchu.World/Objects/Components/Server/PhysicsComponent.cs +++ b/Uchu.World/Objects/Components/Server/PhysicsComponent.cs @@ -124,7 +124,7 @@ public void SetPhysicsByPath(string path) // We can't read HKX so this is basica // 640 x 1 x 640. used for https://lu.lcdruniverse.org/explorer/objects/5633 else if (path.Contains("misc_phys_640x640")) { - size = new Vector3(640, 640, 12.5f)* Math.Abs(GameObject.Transform.Scale); + size = new Vector3(640, 12.5f, 640)* Math.Abs(GameObject.Transform.Scale); finalObject = BoxBody.Create(Zone.Simulation, Transform.Position, Transform.Rotation, size ); } // 20 x 50 x 1. used for https://lu.lcdruniverse.org/explorer/objects/8575 diff --git a/Uchu.World/Objects/Components/Server/PrimitiveModelComponent.cs b/Uchu.World/Objects/Components/Server/PrimitiveModelComponent.cs index 0076f74e..4fa53fa6 100644 --- a/Uchu.World/Objects/Components/Server/PrimitiveModelComponent.cs +++ b/Uchu.World/Objects/Components/Server/PrimitiveModelComponent.cs @@ -42,7 +42,7 @@ private PhysicsObject LoadPrimitiveModel() Z = (float) GameObject.Settings["primitiveModelValueZ"] }; - return BoxBody.Create(Zone.Simulation, Transform.Position, Transform.Rotation, cuboidSize); + return BoxBody.Create(Zone.Simulation, Transform.Position + new Vector3(0, cuboidSize.Y / 2, 0), Transform.Rotation, cuboidSize); case PrimitiveModelType.Cone: break; case PrimitiveModelType.Cylinder: @@ -52,7 +52,7 @@ private PhysicsObject LoadPrimitiveModel() Y = (float) GameObject.Settings["primitiveModelValueY"] }; - return SphereBody.Create(Zone.Simulation, Transform.Position, cylinderSize.X); + return CapsuleBody.Create(Zone.Simulation, Transform.Position, Transform.Rotation, cylinderSize.X, cylinderSize.Y); case PrimitiveModelType.Sphere: break; case PrimitiveModelType.Invalid: diff --git a/Uchu.World/Objects/GameObjects/GameObject.cs b/Uchu.World/Objects/GameObjects/GameObject.cs index 69df22de..21932518 100644 --- a/Uchu.World/Objects/GameObjects/GameObject.cs +++ b/Uchu.World/Objects/GameObjects/GameObject.cs @@ -152,22 +152,10 @@ protected GameObject() // Load the script for the object. // Special case for when custom_script_server but there is no script component. - if (!this.Settings.TryGetValue("custom_script_server", out var scriptNameValue) || - this.GetComponent() != default || this.GetComponent() != default) return; - var scriptName = ((string) scriptNameValue).ToLower(); - Logger.Debug($"{this} -> {scriptNameValue}"); - var scriptLoaded = false; - foreach (var (objectScriptName, objectScriptType) in Zone.ScriptManager.ObjectScriptTypes) - { - if (!scriptName.EndsWith(objectScriptName)) continue; - this.Zone.LoadObjectScript(this, objectScriptType); - scriptLoaded = true; - break; - } - //if it attempted to load a script that isn't here yet, log it - if (!scriptLoaded && scriptName != ""){ - Logger.Debug($"Did not load script: {scriptName} Object LOT: {Lot.Id}"); - } + var hasCustomServerScript = this.Settings.TryGetValue("custom_script_server", out _); + var hasLotScript = this.Zone.ScriptManager.LotObjectScriptTypes.ContainsKey(this.Lot); + if ((hasCustomServerScript || hasLotScript) && this.GetComponent() == default && this.GetComponent() == default) + this.Zone.LoadScriptForObject(this); }); Listen(OnDestroyed, () => diff --git a/Uchu.World/Objects/GameObjects/Player.cs b/Uchu.World/Objects/GameObjects/Player.cs index 0d89a75f..634d5b38 100644 --- a/Uchu.World/Objects/GameObjects/Player.cs +++ b/Uchu.World/Objects/GameObjects/Player.cs @@ -363,7 +363,9 @@ private void UpdatePhysics(Vector3 position, Quaternion rotation) physicsComponent.Physics is { } physics)) return; - physics.Position = Transform.Position; + // The Transform.Position of the player refers to the bottom of the player, not the center. Since the player + // is treated as a sphere of radius 1, moving the sphere up by 1 corrects this. + physics.Position = Transform.Position + new Vector3(0, 1, 0); physics.Rotation = Transform.Rotation; } diff --git a/Uchu.World/Objects/Zone.cs b/Uchu.World/Objects/Zone.cs index f49dc1cc..39a81780 100644 --- a/Uchu.World/Objects/Zone.cs +++ b/Uchu.World/Objects/Zone.cs @@ -147,6 +147,66 @@ public async Task InitializeAsync() Loaded = true; } + /// + /// Sets up the object script for an object. + /// + /// Game object to load. + public void LoadScriptForObject(GameObject gameObject) + { + // Get the script names to try to load. + var scriptNames = new List(); + var scriptComponent = gameObject.GetComponent(); + + if (gameObject.Settings.TryGetValue("custom_script_server", out var serverScriptOverride) && + (string) serverScriptOverride != "") + { + scriptNames.Add(((string) serverScriptOverride).ToLower()); + } + + if (!string.IsNullOrWhiteSpace(scriptComponent?.ScriptName) && + !scriptNames.Contains(scriptComponent.ScriptName.ToLower())) + { + scriptNames.Add(scriptComponent.ScriptName.ToLower()); + } + + // Log the script names. + var hasLotScript = this.ScriptManager.LotObjectScriptTypes.ContainsKey(gameObject.Lot); + if (scriptNames.Count == 0 && !hasLotScript) return; + string scriptNamesOutput = default; + if (scriptNames.Count > 0) + { + scriptNamesOutput = scriptNames.Aggregate((i, j) => i + ", " + j); + Logger.Debug($"Loading script for {gameObject} (LOT {gameObject.Lot}): {scriptNamesOutput}"); + } + else if (hasLotScript) + { + Logger.Debug($"Loading LOT script for {gameObject} (LOT {gameObject.Lot})"); + } + + // Attempt to load a script. + var scriptLoaded = false; + foreach (var scriptName in scriptNames) + { + if (scriptLoaded) break; + foreach (var (objectScriptName, objectScriptType) in this.ScriptManager.NameObjectScriptTypes) + { + if (!scriptName.EndsWith(objectScriptName)) continue; + this.LoadObjectScript(gameObject, objectScriptType); + scriptLoaded = true; + break; + } + } + if (!scriptLoaded && hasLotScript) + { + this.LoadObjectScript(gameObject, this.ScriptManager.LotObjectScriptTypes[gameObject.Lot]); + scriptLoaded = true; + } + + // Output if no script was loaded. + if (scriptLoaded || scriptNamesOutput == default) return; + Logger.Debug($"No script found for {gameObject} (LOT {gameObject.Lot}): {scriptNamesOutput}"); + } + /// /// Loads an object script. Either happens immediately or is queued /// for when all the objects load depending on if the zone has loaded. diff --git a/Uchu.World/Scripting/Native/Attributes/LotSpecificAttribute.cs b/Uchu.World/Scripting/Native/Attributes/LotSpecificAttribute.cs new file mode 100644 index 00000000..b1be7d22 --- /dev/null +++ b/Uchu.World/Scripting/Native/Attributes/LotSpecificAttribute.cs @@ -0,0 +1,22 @@ +using System; + +namespace Uchu.World.Scripting.Native +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class LotSpecific : Attribute + { + /// + /// LOT of the script that the class applies to. + /// + public Lot Lot { get; } + + /// + /// Creates the attribute. + /// + /// LOT of the script that the class applies to. + public LotSpecific(int lot) + { + this.Lot = lot; + } + } +} \ No newline at end of file diff --git a/Uchu.World/Scripting/Native/NativeScriptPack.cs b/Uchu.World/Scripting/Native/NativeScriptPack.cs index 44ba42bc..9b1629a5 100644 --- a/Uchu.World/Scripting/Native/NativeScriptPack.cs +++ b/Uchu.World/Scripting/Native/NativeScriptPack.cs @@ -11,10 +11,15 @@ namespace Uchu.World.Scripting.Native internal class NativeScriptPack : ScriptPack { /// - /// Object script types in the script pack. + /// Object script types in the script pack referenced by name. /// - public Dictionary ObjectScriptTypes { get; } = new Dictionary(); + public Dictionary NameObjectScriptTypes { get; } = new Dictionary(); + /// + /// Object script types in the script pack referenced by LOT. + /// + public Dictionary LotObjectScriptTypes { get; } = new Dictionary(); + private List _scripts; private Assembly _assembly; @@ -59,11 +64,17 @@ internal void ReadAssembly() { // Ignore non-object scripts. if (!type.IsAssignableTo(typeof(ObjectScript))) continue; + + // Add the object scripts. var scriptNames = type.GetCustomAttributes(); foreach (var scriptName in scriptNames) { - // Add the object script. - this.ObjectScriptTypes.Add(scriptName.Name.ToLower(), type); + this.NameObjectScriptTypes.Add(scriptName.Name.ToLower(), type); + } + var scriptLots = type.GetCustomAttributes(); + foreach (var scriptLot in scriptLots) + { + this.LotObjectScriptTypes.Add(scriptLot.Lot, type); } } } diff --git a/Uchu.World/Scripting/ScriptManager.cs b/Uchu.World/Scripting/ScriptManager.cs index 97596f27..33e7cdac 100644 --- a/Uchu.World/Scripting/ScriptManager.cs +++ b/Uchu.World/Scripting/ScriptManager.cs @@ -13,9 +13,14 @@ namespace Uchu.World.Scripting public class ScriptManager { /// - /// Object scripts types in the script manager. + /// Object scripts types in the script manager referenced by name. /// - public Dictionary ObjectScriptTypes { get; } = new Dictionary(); + public Dictionary NameObjectScriptTypes { get; } = new Dictionary(); + + /// + /// Object scripts types in the script manager referenced by LOT. + /// + public Dictionary LotObjectScriptTypes { get; } = new Dictionary(); private Zone Zone { get; } @@ -44,15 +49,24 @@ internal async Task LoadDefaultScriptsAsync() foreach (var pack in ScriptPacks) { if (!(pack is NativeScriptPack nativePack)) continue; - foreach (var (scriptName, objectScriptType) in nativePack.ObjectScriptTypes) + foreach (var (scriptName, objectScriptType) in nativePack.NameObjectScriptTypes) { - if (ObjectScriptTypes.ContainsKey(scriptName)) + if (NameObjectScriptTypes.ContainsKey(scriptName)) { - Logger.Warning($"Object script time registered multiple times: {scriptName}"); + Logger.Warning($"Name-based object script time registered multiple times: {scriptName}"); } - ObjectScriptTypes[scriptName] = objectScriptType; + NameObjectScriptTypes[scriptName] = objectScriptType; Logger.Information($"Registered object script {scriptName}"); } + foreach (var (scriptLot, objectScriptType) in nativePack.LotObjectScriptTypes) + { + if (LotObjectScriptTypes.ContainsKey(scriptLot)) + { + Logger.Warning($"LOT-based object script time registered multiple times: {scriptLot}"); + } + LotObjectScriptTypes[scriptLot] = objectScriptType; + Logger.Information($"Registered object script {objectScriptType.Name} for LOT {scriptLot}"); + } } }