diff --git a/Maple2.File.Ingest/Mapper/ItemMapper.cs b/Maple2.File.Ingest/Mapper/ItemMapper.cs index 53a817993..b33caf37e 100644 --- a/Maple2.File.Ingest/Mapper/ItemMapper.cs +++ b/Maple2.File.Ingest/Mapper/ItemMapper.cs @@ -156,6 +156,7 @@ protected override IEnumerable Map() { IsSolidCube: data.install.cubeProp == 1, FunctionId: data.install.funcCode, ObjectCubeId: data.install.objCode, + IndoorPortal: data.install.indoor != 0, MapAttribute: Enum.TryParse(data.install.mapAttribute, true, out MapAttribute mapAttribute) ? mapAttribute : MapAttribute.none); var itemMetadata = new ItemMetadata( diff --git a/Maple2.Model/Metadata/ItemMetadata.cs b/Maple2.Model/Metadata/ItemMetadata.cs index 8d56a90cd..51334a577 100644 --- a/Maple2.Model/Metadata/ItemMetadata.cs +++ b/Maple2.Model/Metadata/ItemMetadata.cs @@ -121,8 +121,8 @@ public record ItemMetadataInstall( bool IsSolidCube, int FunctionId, int ObjectCubeId, - MapAttribute MapAttribute -); + bool IndoorPortal, + MapAttribute MapAttribute); public record DefaultHairMetadata( Vector3 BackPosition = default, diff --git a/Maple2.Server.Game/Commands/CoordCommand.cs b/Maple2.Server.Game/Commands/CoordCommand.cs index 5f72b6031..7d0c5a08a 100644 --- a/Maple2.Server.Game/Commands/CoordCommand.cs +++ b/Maple2.Server.Game/Commands/CoordCommand.cs @@ -67,7 +67,12 @@ float ParseCoord(string? input, float current, bool asBlock) { ctx.Console.Error.WriteLine($"Invalid relative coordinate: {input}. Use ~[value] or ~+[value] or ~-[value]."); return current; } - if (float.TryParse(input, out float abs)) return abs; + if (float.TryParse(input, out float abs)) { + if (asBlock) { + abs *= Constant.BlockSize; + } + return abs; + } ctx.Console.Error.WriteLine($"Invalid coordinate: {input}. Use a number or ~[value] for relative coordinates."); return current; } diff --git a/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.State.cs b/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.State.cs index fe7a2cbc2..e9df08bac 100644 --- a/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.State.cs +++ b/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.State.cs @@ -216,6 +216,27 @@ public FieldPortal SpawnPortal(QuestSummonPortal metadata, FieldNpc npc, FieldPl return fieldPortal; } + public FieldPortal? SpawnFieldToHomePortal(PlotCube plotCube, long targetHomeAccountId) { + if (plotCube.Metadata.Install is { IndoorPortal: false }) { + return null; + } + + var transform = new Transform { + Position = plotCube.Position, + RotationAnglesDegrees = new Vector3(0, 0, plotCube.Rotation), + }; + + Vector3 newPosition = plotCube.Position; + newPosition -= transform.FrontAxis * 75; + newPosition.Z -= 75; + + var portal = new Portal(NextLocalId(), Constant.DefaultHomeMapId, -1, PortalType.FieldToHome, PortalActionType.Interact, newPosition, new Vector3(0, 0, plotCube.Rotation), new Vector3(75, 75, 75), 0, 0, Visible: true, MinimapVisible: false, Enable: true); + FieldPortal fieldPortal = SpawnPortal(portal); + fieldPortal.HomeId = targetHomeAccountId; + + return fieldPortal; + } + public FieldItem SpawnItem(IActor owner, Item item) { lock (item) { using GameStorage.Request db = GameStorage.Context(); diff --git a/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.cs b/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.cs index ed2e2c3ef..c64412626 100644 --- a/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.cs +++ b/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.cs @@ -137,13 +137,17 @@ public virtual void Init() { using GameStorage.Request db = GameStorage.Context(); foreach (Plot plot in db.LoadPlotsForMap(MapId)) { Plots[plot.Number] = plot; - } - Plots.Values - .SelectMany(plot => plot.Cubes.Values) - .Where(plotCube => plotCube.Interact != null) - .ToList() - .ForEach(plotCube => AddFieldFunctionInteract(plotCube)); + plot.Cubes.Values + .Where(plotCube => plotCube.Interact != null) + .ToList() + .ForEach(plotCube => AddFieldFunctionInteract(plotCube)); + + plot.Cubes.Values + .Where(plotCube => plotCube.Metadata.Install is { IndoorPortal: true }) + .ToList() + .ForEach(plotCube => SpawnFieldToHomePortal(plotCube, plot.OwnerId)); + } } // Create default to place liftable cubes diff --git a/Maple2.Server.Game/Manager/Field/FieldManager/HomeFieldManager.cs b/Maple2.Server.Game/Manager/Field/FieldManager/HomeFieldManager.cs index c295ddc55..721769eb5 100644 --- a/Maple2.Server.Game/Manager/Field/FieldManager/HomeFieldManager.cs +++ b/Maple2.Server.Game/Manager/Field/FieldManager/HomeFieldManager.cs @@ -28,19 +28,17 @@ public override void Init() { plot.Cubes.Clear(); } Plots[plot.Number] = plot; - } - Plots.Values - .SelectMany(c => c.Cubes.Values) - .Where(p => p.Interact?.PortalSettings is not null) - .ToList() - .ForEach(cubePortal => SpawnCubePortal(cubePortal)); - - Plots.Values - .SelectMany(plot => plot.Cubes.Values) - .Where(plotCube => plotCube.Interact != null) - .ToList() - .ForEach(plotCube => AddFieldFunctionInteract(plotCube)); + plot.Cubes.Values + .Where(p => p.Interact?.PortalSettings is not null) + .ToList() + .ForEach(cubePortal => SpawnCubePortal(cubePortal)); + + plot.Cubes.Values + .Where(plotCube => plotCube.Interact != null) + .ToList() + .ForEach(plotCube => AddFieldFunctionInteract(plotCube)); + } } public void SetHomeSurvey(HomeSurvey survey) { diff --git a/Maple2.Server.Game/Manager/HousingManager.cs b/Maple2.Server.Game/Manager/HousingManager.cs index 6614665bf..f22306f0e 100644 --- a/Maple2.Server.Game/Manager/HousingManager.cs +++ b/Maple2.Server.Game/Manager/HousingManager.cs @@ -529,7 +529,7 @@ private void DeleteCube(PlotCube cube) { public bool TryPlaceCube(HeldCube cube, Plot plot, ItemMetadata itemMetadata, in Vector3B position, float rotation, [NotNullWhen(true)] out PlotCube? result, bool isReplace = false) { result = null; - if (session.Field is null) return false; + if (session.Field is null || itemMetadata.Install is null) return false; tableMetadata.FurnishingShopTable.Entries.TryGetValue(cube.ItemId, out FurnishingShopTable.Entry? shopEntry); @@ -549,7 +549,8 @@ public bool TryPlaceCube(HeldCube cube, Plot plot, ItemMetadata itemMetadata, in } float groundHeight = 0; - if (plot.MapId is not Constant.DefaultHomeMapId) { + bool outdoor = plot.MapId is not Constant.DefaultHomeMapId; + if (outdoor) { FieldSellableTile? groundTile = session.Field.AccelerationStructure?.FirstSellableTile(position, (x) => x.SellableGroup == plot.Number); if (groundTile is null) { session.Send(CubePacket.Error(UgcMapError.s_ugcmap_cant_create_on_place)); @@ -559,6 +560,17 @@ public bool TryPlaceCube(HeldCube cube, Plot plot, ItemMetadata itemMetadata, in groundHeight = groundTile.Position.Z / Constant.BlockSize; } + if (!outdoor && itemMetadata.Install.IndoorPortal) { + session.Send(CubePacket.Error(UgcMapError.s_ugcmap_cant_be_created)); + return false; + } + + // can only place one indoor portal per plot + if (outdoor && plot.Cubes.Any(x => x.Value.Metadata.Install is { IndoorPortal: true })) { + session.Send(CubePacket.Error(UgcMapError.s_ugcmap_cant_create_on_place)); + return false; + } + bool isSolidCube = itemMetadata.Install!.IsSolidCube; bool isOnGround = Math.Abs(position.Z - groundHeight) < 0.1; bool allowWaterOnGround = itemMetadata.Install.MapAttribute is MapAttribute.water && Constant.AllowWaterOnGround; @@ -585,7 +597,7 @@ public bool TryPlaceCube(HeldCube cube, Plot plot, ItemMetadata itemMetadata, in } // Only check height on outside plots since we already check if it's inside the area when getting the ground height - if (plot.MapId is not Constant.DefaultHomeMapId && position.Z - groundHeight > plot.Metadata.Limit.Height) { + if (outdoor && position.Z - groundHeight > plot.Metadata.Limit.Height) { session.Send(CubePacket.Error(UgcMapError.s_ugcmap_area_limit)); return false; } @@ -656,6 +668,16 @@ public bool TryPlaceCube(HeldCube cube, Plot plot, ItemMetadata itemMetadata, in session.Field.AddFieldFunctionInteract(result); } + if (outdoor && itemMetadata.Install.IndoorPortal) { + FieldPortal? fieldPortal = session.Field.SpawnFieldToHomePortal(result, plot.OwnerId); + if (fieldPortal is null) { + DeleteCube(result); + session.Send(CubePacket.Error(UgcMapError.s_ugcmap_not_owned_item)); + return false; + } + session.Field.Broadcast(PortalPacket.Add(fieldPortal)); + } + plot.Cubes.Add(position, result); return true; } @@ -674,6 +696,13 @@ public bool TryRemoveCube(Plot plot, in Vector3B position, [NotNullWhen(true)] o session.Field?.RemoveFieldFunctionInteract(cube.Interact.Id); } + if (plot.MapId is not Constant.DefaultHomeMapId && cube.Metadata.Install is { IndoorPortal: true }) { + FieldPortal? portal = session.Field?.GetPortals().FirstOrDefault(x => x.HomeId == plot.OwnerId); + if (portal is not null) { + session.Field?.RemovePortal(portal.ObjectId); + } + } + if (plot.IsPlanner) { return true; }