diff --git a/Maple2.File.Ingest/Mapper/ItemMapper.cs b/Maple2.File.Ingest/Mapper/ItemMapper.cs index 2e65dd260..dd4ee4882 100644 --- a/Maple2.File.Ingest/Mapper/ItemMapper.cs +++ b/Maple2.File.Ingest/Mapper/ItemMapper.cs @@ -19,7 +19,6 @@ public ItemMapper(M2dReader xmlReader) { } protected override IEnumerable Map() { - IEnumerable<(int Id, ItemExtraction Extraction)> itemExtractionData = tableParser.ParseItemExtraction(); Dictionary itemExtractionTryCount = tableParser.ParseItemExtraction() .ToDictionary(entry => entry.Id, entry => entry.Extraction.TryCount); diff --git a/Maple2.File.Ingest/Mapper/MapDataMapper.cs b/Maple2.File.Ingest/Mapper/MapDataMapper.cs index f6f40a460..a94959015 100644 --- a/Maple2.File.Ingest/Mapper/MapDataMapper.cs +++ b/Maple2.File.Ingest/Mapper/MapDataMapper.cs @@ -141,6 +141,8 @@ private FieldAccelerationStructure ParseMapEntities(IEnumerable enti Rotation: placeable.Rotation, Scale: placeable.Scale, Bounds: entityBounds, + BreakDefense: vibrate.brokenDefence, + BreakTick: vibrate.brokenTick, VibrateIndex: vibrateObjectId++); break; diff --git a/Maple2.Model/Enum/Skill.cs b/Maple2.Model/Enum/Skill.cs index 3fefc40f7..9c09b15f8 100644 --- a/Maple2.Model/Enum/Skill.cs +++ b/Maple2.Model/Enum/Skill.cs @@ -95,7 +95,7 @@ public enum ApplyTargetType { Friendly = 2, Player1 = 3, // Unknown, Player2 = 5, // Unknown, - Player3 = 6, // Unknown, Recovery + RegionBuff = 6, Player4 = 7, // Unknown, Debuff (Archeon's ice bombs) HungryMobs = 8 } diff --git a/Maple2.Model/Game/Field/FieldAccelerationStructure.cs b/Maple2.Model/Game/Field/FieldAccelerationStructure.cs index a8accec6e..ad641203f 100644 --- a/Maple2.Model/Game/Field/FieldAccelerationStructure.cs +++ b/Maple2.Model/Game/Field/FieldAccelerationStructure.cs @@ -273,6 +273,10 @@ public List QueryVibrateObjectsCenterList(Vector3 center, Ve return vibrateObjects; } + + public FieldVibrateEntity? GetVibrateEntity(string entityId) { + return vibrateEntities.FirstOrDefault(vibrateEntity => vibrateEntity.Id.Id == entityId); + } #endregion #region Initialization @@ -570,6 +574,8 @@ public void AddEntities(Dictionary> gridAlignedEntit Rotation: new Vector3(0, 0, 0), Scale: 1, Bounds: new BoundingBox3(), + BreakDefense: 0, + BreakTick: 0, VibrateIndex: i)); } @@ -759,6 +765,8 @@ public void WriteTo(FieldEntity entity, IByteWriter writer) { switch (entity) { case FieldVibrateEntity vibrateEntity: writer.Write(vibrateEntity.VibrateIndex); + writer.WriteInt(vibrateEntity.BreakDefense); + writer.WriteInt(vibrateEntity.BreakTick); break; case FieldSpawnTile spawnTile: break; @@ -811,6 +819,8 @@ public void ReadFrom(IByteReader reader) { Rotation: new Vector3(0, 0, 0), Scale: 1, Bounds: new BoundingBox3(), + BreakDefense: 0, + BreakTick: 0, VibrateIndex: i)); } @@ -904,7 +914,9 @@ public FieldEntity ReadEntity(IByteReader reader) { Rotation: rotation, Scale: scale, Bounds: bounds, - VibrateIndex: reader.ReadInt()); + VibrateIndex: reader.ReadInt(), + BreakDefense: reader.ReadInt(), + BreakTick: reader.ReadInt()); case FieldEntityType.SpawnTile: return new FieldSpawnTile( Id: id, diff --git a/Maple2.Model/Metadata/FieldEntity/FieldEntity.cs b/Maple2.Model/Metadata/FieldEntity/FieldEntity.cs index d68bc0fcc..0176b9841 100644 --- a/Maple2.Model/Metadata/FieldEntity/FieldEntity.cs +++ b/Maple2.Model/Metadata/FieldEntity/FieldEntity.cs @@ -43,6 +43,8 @@ public record FieldVibrateEntity( Vector3 Rotation, float Scale, BoundingBox3 Bounds, + int BreakDefense, + int BreakTick, int VibrateIndex) : FieldEntity(Id, Position, Rotation, Scale, Bounds); public record FieldSpawnTile( diff --git a/Maple2.Server.Game/Manager/Config/BuffManager.cs b/Maple2.Server.Game/Manager/Config/BuffManager.cs index 238ca02f9..fb04ffdd3 100644 --- a/Maple2.Server.Game/Manager/Config/BuffManager.cs +++ b/Maple2.Server.Game/Manager/Config/BuffManager.cs @@ -353,6 +353,10 @@ private void ModifyBuffStackCount(Buff buff) { } foreach (AdditionalEffectMetadataModifyOverlapCount modifyOverlapCount in buff.Metadata.ModifyOverlapCount) { foreach (Buff buffResult in EnumerateBuffs(modifyOverlapCount.Id).Where(b => b.Stack(modifyOverlapCount.OffsetCount))) { + if (buffResult.Stacks <= 0) { + Remove(buffResult.Id, buffResult.Caster.ObjectId); + continue; + } Actor.Field.Broadcast(BuffPacket.Update(buffResult)); } } diff --git a/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.Factory.cs b/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.Factory.cs index ef3b34a05..13982c8aa 100644 --- a/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.Factory.cs +++ b/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.Factory.cs @@ -349,16 +349,13 @@ private SemaphoreSlim GetMapLock(int mapId) { private FieldManager? GetInternal(int mapId, long ownerId = 0, int roomId = 0) { if (roomId != 0) { // Specified room - FieldManager? foundField = FindRoom(mapId, roomId); + FieldManager? foundField = FindRoom(); if (foundField != null) { return foundField; } } if (ServerTableMetadata.InstanceFieldTable.Entries.TryGetValue(mapId, out InstanceFieldMetadata? metadata)) { - if (ownerId == 0 && roomId == 0) { - return Create(mapId); - } switch (metadata.Type) { case InstanceType.ugcMap: // this is both homes and UGD. Currently just doing homes. if (roomId != 0) { // User wants to go into decorplanner or blueprint designer @@ -370,6 +367,16 @@ private SemaphoreSlim GetMapLock(int mapId) { return homeField; case InstanceType.DungeonLobby: return Create(mapId, ownerId: ownerId, roomId: roomId); // TODO: this should never happen. + case InstanceType.channelScale: + if (fields.TryGetValue(mapId, out ConcurrentDictionary? scalingFields) && !scalingFields.IsEmpty) { + return scalingFields.Values.FirstOrDefault(f => !f.Disposed); + } + break; + default: + if (ownerId == 0 && roomId == 0) { + return Create(mapId); + } + break; } } @@ -377,7 +384,7 @@ private SemaphoreSlim GetMapLock(int mapId) { return Create(mapId, ownerId: ownerId, roomId: roomId); } - if (roomId == 0 && mapFields.Count > 0) { + if (roomId == 0 && !mapFields.IsEmpty) { FieldManager? firstField = mapFields.Values.FirstOrDefault(); if (firstField != null) { return firstField; @@ -387,7 +394,7 @@ private SemaphoreSlim GetMapLock(int mapId) { return mapFields.TryGetValue(roomId, out FieldManager? field) ? field : Create(mapId, ownerId: ownerId, roomId: roomId); - FieldManager? FindRoom(int mapId, int roomId) { + FieldManager? FindRoom() { if (fields.TryGetValue(mapId, out ConcurrentDictionary? roomFields) && roomFields.TryGetValue(roomId, out FieldManager? field)) { return field; } diff --git a/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.State.cs b/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.State.cs index 8e321ddcf..ca02f9e5a 100644 --- a/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.State.cs +++ b/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.State.cs @@ -495,6 +495,8 @@ public IEnumerable GetTargets(IActor caster, Prism[] prisms, ApplyTarget return []; case ApplyTargetType.HungryMobs: return prisms.Filter(Pets.Values.Where(pet => pet.OwnerId == 0), limit, ignore); + case ApplyTargetType.RegionBuff: + return prisms.Filter(Players.Values, limit, ignore); default: Log.Debug("Unhandled SkillEntity:{Entity}", targetType); return []; diff --git a/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.cs b/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.cs index 92736bfa0..fb1862832 100644 --- a/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.cs +++ b/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.cs @@ -90,7 +90,7 @@ public FieldManager(MapMetadata metadata, UgcMapMetadata ugcMetadata, MapEntityM TriggerObjects = new TriggerCollection(entities); Scheduler = new EventQueue(); - FieldActor = new FieldActor(this, npcMetadata); // pulls from argument because member NpcMetadata is null here + FieldActor = new FieldActor(this, NextLocalId(), metadata, npcMetadata); // pulls from argument because member NpcMetadata is null here cancel = new CancellationTokenSource(); thread = new Thread(UpdateLoop); Ai = new AiManager(this); @@ -116,6 +116,7 @@ public virtual void Init() { } initialized = true; + FieldTick = Environment.TickCount64; if (MapData.TryGet(Metadata.XBlock, out FieldAccelerationStructure? accelerationStructure)) { AccelerationStructure = accelerationStructure; @@ -570,14 +571,22 @@ public void MovePlayerAlongPath(string pathName) { } } - public void VibrateObjects(SkillRecord record, Vector3 position) { - float rangeDistance = record.Attack.Range.Distance; + public void VibrateObjects(DamageRecord record, Vector3 position) { + if (record.AttackMetadata.BrokenOffence == 0) { + // No items are going to vibrate/break. + return; + } + + float rangeDistance = record.AttackMetadata.Range.Distance; if (AccelerationStructure is null) { return; } List vibrateObjects = AccelerationStructure.QueryVibrateObjectsCenterList(position, 2 * new Vector3(rangeDistance, rangeDistance, rangeDistance)); foreach (FieldVibrateEntity vibrate in vibrateObjects) { + if (vibrate.BreakDefense < record.AttackMetadata.BrokenOffence) { + // TODO Keep a record of when the vibrate object was broken. Don't send if it's currently broken and respawning. + } Broadcast(VibratePacket.Attack(vibrate.Id.Id, record)); } } diff --git a/Maple2.Server.Game/Model/Field/Actor/FieldActor.cs b/Maple2.Server.Game/Model/Field/Actor/FieldActor.cs index 1f993f222..345527f37 100644 --- a/Maple2.Server.Game/Model/Field/Actor/FieldActor.cs +++ b/Maple2.Server.Game/Model/Field/Actor/FieldActor.cs @@ -5,6 +5,7 @@ using Maple2.Tools.VectorMath; using Maple2.Tools.Collision; using Maple2.Database.Storage; +using Maple2.Model.Metadata; using Maple2.Server.Game.Manager; using Maple2.Server.Game.Model.ActorStateComponent; using Maple2.Server.Game.Model.Skill; @@ -12,13 +13,12 @@ namespace Maple2.Server.Game.Model; // Dummy Actor for field to own entities. -internal sealed class FieldActor : IActor { +public class FieldActor : Actor { public FieldManager Field { get; } public NpcMetadataStorage NpcMetadata { get; init; } - public int ObjectId => 0; public bool IsDead => false; - public IPrism Shape => new PointPrism(Position); + public override IPrism Shape => new PointPrism(Position); public ActorState State => ActorState.None; public ActorSubState SubState => ActorSubState.None; public Vector3 Position { get => Transform.Position; set => Transform.Position = value; } @@ -30,7 +30,7 @@ internal sealed class FieldActor : IActor { public SkillState SkillState { get; init; } public SkillQueue ActiveSkills { get; init; } - public FieldActor(FieldManager field, NpcMetadataStorage npcMetadata) { + public FieldActor(FieldManager field, int objectId, MapMetadata mapMetadata, NpcMetadataStorage npcMetadata) : base(field, objectId, mapMetadata, npcMetadata) { Field = field; Stats = new StatsManager(this); Buffs = new BuffManager(this); diff --git a/Maple2.Server.Game/Model/Field/Entity/FieldSkill.cs b/Maple2.Server.Game/Model/Field/Entity/FieldSkill.cs index 61fabecf1..557997e37 100644 --- a/Maple2.Server.Game/Model/Field/Entity/FieldSkill.cs +++ b/Maple2.Server.Game/Model/Field/Entity/FieldSkill.cs @@ -173,8 +173,7 @@ public override void Update(long tickCount) { // } // } - Field.VibrateObjects(record, Position); - + Field.VibrateObjects(damage, Position); foreach (IActor target in targets) { target.ApplyDamage(Caster, damage, attack); // TODO: Set PrevUid? @@ -188,6 +187,7 @@ public override void Update(long tickCount) { Field.Broadcast(SkillDamagePacket.Target(record, targetRecords)); Field.Broadcast(SkillDamagePacket.Region(damage)); } + Caster.ApplyEffects(attack.SkillsOnDamage, Caster, damage, targets: targets); Caster.ApplyEffects(attack.Skills, Caster, Caster, skillId: Value.Id, targets: targets); } diff --git a/Maple2.Server.Game/PacketHandlers/RequestCubeHandler.cs b/Maple2.Server.Game/PacketHandlers/RequestCubeHandler.cs index 30930a636..95f26b4aa 100644 --- a/Maple2.Server.Game/PacketHandlers/RequestCubeHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/RequestCubeHandler.cs @@ -303,6 +303,7 @@ private void HandleLiftupObject(GameSession session, IByteReader packet) { session.HeldLiftup = weapon; session.Field.Broadcast(CubePacket.LiftupObject(session.Player, session.HeldLiftup)); + session.Player.InBattle = true; } private void HandleLiftupDrop(GameSession session) { diff --git a/Maple2.Server.Game/PacketHandlers/VibrateHandler.cs b/Maple2.Server.Game/PacketHandlers/VibrateHandler.cs index 7309ce4d9..2911a85ac 100644 --- a/Maple2.Server.Game/PacketHandlers/VibrateHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/VibrateHandler.cs @@ -1,6 +1,7 @@ using System.Numerics; using Maple2.Database.Storage; using Maple2.Model.Metadata; +using Maple2.Model.Metadata.FieldEntity; using Maple2.PacketLib.Tools; using Maple2.Server.Core.Constants; using Maple2.Server.Core.PacketHandlers; @@ -24,26 +25,36 @@ public override void Handle(GameSession session, IByteReader packet) { long skillUid = packet.ReadLong(); int skillId = packet.ReadInt(); short level = packet.ReadShort(); - if (!SkillMetadata.TryGet(skillId, level, out SkillMetadata? metadata)) { - Logger.Error("Invalid skill use: {SkillId},{Level}", skillId, level); - return; - } - - var record = new SkillRecord(metadata, skillUid, session.Player); byte motionPoint = packet.ReadByte(); - if (!record.TrySetMotionPoint(motionPoint)) { - Logger.Error("Invalid MotionPoint({MotionPoint}) for {Record}", motionPoint, record); - return; - } byte attackPoint = packet.ReadByte(); - if (!record.TrySetAttackPoint(attackPoint)) { - Logger.Error("Invalid AttackPoint({AttackPoint}) for {Record}", attackPoint, record); + + SkillRecord? record = session.Player.ActiveSkills.Get(skillUid); + if (record == null) { + Logger.Warning("Invalid Skill {SkillUid}", skillUid); return; } + DamageRecord damage = new(record.Metadata, record.Attack) { + CasterId = session.Player.ObjectId, + TargetUid = record.TargetUid, + OwnerId = session.Player.ObjectId, + SkillId = record.SkillId, + Level = record.Level, + MotionPoint = record.MotionPoint, + AttackPoint = record.AttackPoint, + Position = record.Position, + Direction = record.Direction, + }; + record.ServerTick = packet.ReadInt(); record.Position = packet.Read(); - session.Field?.Broadcast(VibratePacket.Attack(entityId, record)); + FieldVibrateEntity? vibrate = session.Field?.AccelerationStructure?.GetVibrateEntity(entityId); + if (vibrate != null && vibrate.BreakDefense < record.Attack.BrokenOffence) { + //TODO: Keep a record of when the vibrate was broken. + } + + // Packet gets sent regardless + session.Field?.Broadcast(VibratePacket.Attack(entityId, damage)); } } diff --git a/Maple2.Server.Game/Packets/VibratePacket.cs b/Maple2.Server.Game/Packets/VibratePacket.cs index 42198d78a..51de0c8c7 100644 --- a/Maple2.Server.Game/Packets/VibratePacket.cs +++ b/Maple2.Server.Game/Packets/VibratePacket.cs @@ -12,16 +12,16 @@ private enum Command : byte { Invoke = 2, } - public static ByteWriter Attack(string entityId, SkillRecord skill) { + public static ByteWriter Attack(string entityId, DamageRecord damage) { var pWriter = Packet.Of(SendOp.Vibrate); pWriter.Write(Command.Attack); pWriter.WriteString(entityId); - pWriter.WriteLong(skill.CastUid); - pWriter.WriteInt(skill.SkillId); - pWriter.WriteShort(skill.Level); - pWriter.WriteByte(skill.MotionPoint); - pWriter.WriteByte(skill.AttackPoint); - pWriter.Write(skill.Position); + pWriter.WriteLong(damage.SkillUid); + pWriter.WriteInt(damage.SkillId); + pWriter.WriteShort(damage.Level); + pWriter.WriteByte(damage.MotionPoint); + pWriter.WriteByte(damage.AttackPoint); + pWriter.Write(damage.Position); pWriter.WriteInt(Environment.TickCount); pWriter.WriteString(); pWriter.WriteByte();