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}");
+ }
}
}