/// <summary> /// Broadcasts PropDisappears in prop's region. /// </summary> /// <param name="prop"></param> public static void PropDisappears(Prop prop) { var packet = new Packet(Op.PropDisappears, MabiId.Broadcast); packet.PutLong(prop.EntityId); prop.Region.Broadcast(packet, prop, false); }
/// <summary> /// Broadcasts prop update in its region. /// </summary> /// <param name="prop"></param> public static void PropUpdate(Prop prop) { var packet = new Packet(Op.PropUpdate, prop.EntityId); packet.AddPropUpdateInfo(prop); prop.Region.Broadcast(packet); }
/// <summary> /// Sends BlacksmithingMiniGame to creature's client, which starts /// the Blacksmithing mini-game. /// </summary> /// <remarks> /// The position of the dots is relative to the upper left of the /// field. They land exactly on those spots after "wavering" for a /// moment. This wavering is randomized on the client side and /// doesn't affect anything. /// /// The time bar is always the same, but the time it takes to fill /// up changes based on the "time displacement". The lower the value, /// the longer it takes to fill up. Using values that are too high /// or too low mess up the calculations and cause confusing results. /// The official range seems to be between ~0.81 and ~0.98. /// </remarks> /// <param name="creature"></param> /// <param name="prop"></param> /// <param name="item"></param> /// <param name="dots"></param> /// <param name="deviation"></param> public static void BlacksmithingMiniGame(Creature creature, Prop prop, Item item, List<BlacksmithDot> dots, int deviation) { if (dots == null || dots.Count != 5) throw new ArgumentException("5 dots required."); var packet = new Packet(Op.BlacksmithingMiniGame, creature.EntityId); // Untested if this is actually the deviation/cursor size, // but Tailoring does something very similar. Just like with // Tailoring, wrong values cause failed games. packet.PutShort((short)deviation); foreach (var dot in dots) { packet.PutShort((short)dot.X); packet.PutShort((short)dot.Y); packet.PutFloat(dot.TimeDisplacement); packet.PutShort((short)dot.Deviation); } packet.PutLong(prop.EntityId); packet.PutInt(0); packet.PutLong(item.EntityId); packet.PutInt(0); creature.Client.Send(packet); }
protected virtual void DefaultBehavior(Creature creature, Prop prop) { if (this.State == "open") return; this.SetState("open"); this.DropItems(creature); }
/// <summary> /// Door's behavior. /// </summary> /// <param name="creature"></param> /// <param name="prop"></param> private void DefaultBehavior(Creature creature, Prop prop) { // Open doors can't be interacted with if (this.State == "open") return; // If it's unlocked, warp inside if (!this.IsLocked) { this.WarpInside(creature, prop); return; } // Check if it's switch room door if (_isSwitchDoor) return; // Check dungeon doors for boss room doors // TODO: Mixing normal and boss doors like this seems wrong. if (this.DoorType == DungeonBlockType.BossDoor) { var dungeonRegion = this.Region as DungeonRegion; if (dungeonRegion != null) { // Check if all rooms have been cleared if (!dungeonRegion.Dungeon.CheckDoors()) { Send.Notice(creature, Localization.Get("Unable to enter the boss room. There must be a closed door somewhere in the dungeon.")); return; } } } // Check if character has the key if (!this.RemoveKey(creature)) { Send.Notice(creature, NoticeType.MiddleSystem, Localization.Get("There is no matching key.")); return; } // Unlock the door, but don't open it if it's supposed to block the bosses if (this.BlockBoss) { if (this.State != "unlocked") { this.SetState("unlocked"); this.IsLocked = false; _closedFrom = new Position(_closedFrom.X, _closedFrom.Y + 1); // Fix closed from to be inside boss room. this.AddConfirmation(); } this.WarpInside(creature, prop); } else this.Open(); Send.Notice(creature, NoticeType.MiddleSystem, Localization.Get("You have opened the door with the key.")); }
/// <summary> /// Broadcasts new prop extension. /// </summary> /// <param name="prop"></param> /// <param name="ext"></param> public static void AddPropExtension(Prop prop, PropExtension ext) { var packet = new Packet(Op.AddPropExtension, prop.EntityId); packet.PutInt((int)ext.SignalType); packet.PutInt((int)ext.EventType); packet.PutString(ext.Name); packet.PutByte(ext.Mode); packet.PutString(ext.Value.ToString()); prop.Region.Broadcast(packet); }
public static Packet AddPropInfo(this Packet packet, Prop prop) { packet.PutLong(prop.EntityId); packet.PutInt(prop.Info.Id); // Client side props (A0 range, instead of A1) // look a bit different. if (prop.ServerSide) { packet.PutString(prop.Ident); packet.PutString(prop.Title); packet.PutBin(prop.Info); packet.PutString(prop.State); packet.PutLong(0); packet.PutByte(prop.HasXml); if (prop.HasXml) packet.PutString(prop.Xml.ToString()); packet.PutInt(prop.Extensions.Count); foreach (var ext in prop.Extensions) { packet.PutInt((int)ext.SignalType); packet.PutInt((int)ext.EventType); packet.PutString(ext.Name); packet.PutByte(ext.Mode); packet.PutString(ext.Value.ToString()); } packet.PutShort(0); } else { packet.PutString(prop.State); packet.PutLong(DateTime.Now); if (prop.HasXml) { packet.PutByte(true); packet.PutString(prop.Xml.ToString()); } else { packet.PutByte(false); } packet.PutFloat(prop.Info.Direction); } return packet; }
/// <summary> /// Sends positive PersonalShopSetUpR to creature's client. /// </summary> /// <param name="creature"></param> /// <param name="shopProp"></param> public static void PersonalShopSetUpR(Creature creature, Prop shopProp) { var location = shopProp.GetLocation(); var packet = new Packet(Op.PersonalShopSetUpR, creature.EntityId); packet.PutByte(true); packet.PutLong(shopProp.EntityId); packet.PutByte(1); // no location if 0? packet.PutInt(location.RegionId); packet.PutInt(location.X); packet.PutInt(location.Y); creature.Client.Send(packet); }
public static Packet AddPropUpdateInfo(this Packet packet, Prop prop) { packet.PutString(prop.State); packet.PutLong(DateTime.Now); packet.PutByte(prop.HasXml); if (prop.HasXml) packet.PutString(prop.Xml.ToString()); packet.PutFloat(prop.Info.Direction); packet.PutShort(0); return packet; }
/// <summary> /// Fountain's default behavior, checking state, who and how many times /// they used the fountain, and calling Touch. /// </summary> /// <param name="creature"></param> /// <param name="prop"></param> protected virtual void DefaultBehavior(Creature creature, Prop prop) { if (!this.IsOn) return; lock (_touchedBy) { if (_touchedBy.Contains(creature.EntityId)) return; _touchedBy.Add(creature.EntityId); } this.Touch(creature); Interlocked.Increment(ref count); if (count >= MaxTouch) this.TurnOff(); }
/// <summary> /// Door's behavior. /// </summary> /// <param name="creature"></param> /// <param name="prop"></param> private void DefaultBehavior(Creature creature, Prop prop) { // Open doors can't be interacted with if (this.State == "open") return; // If it's unlocked, warp inside if (!this.IsLocked) { this.WarpInside(creature, prop); return; } // Check if it's switch room door if (_isSwitchDoor) return; // Check if character has the key if (!this.RemoveKey(creature)) { Send.Notice(creature, NoticeType.MiddleSystem, Localization.Get("There is no matching key.")); return; } // Unlock the door, but don't open it if it's supposed to block the bosses if (this.BlockBoss) { if (this.State != "unlocked") { this.SetState("unlocked"); this.IsLocked = false; _closedFrom = new Position(_closedFrom.X, _closedFrom.Y + 1); // Fix closed from to be inside boss room. this.AddConfirmation(); } this.WarpInside(creature, prop); } else this.Open(); Send.Notice(creature, NoticeType.MiddleSystem, Localization.Get("You have opened the door with the key.")); }
/// <summary> /// Uses LightningRod /// </summary> /// <param name="attacker"></param> /// <param name="skill"></param> /// <param name="packet"></param> public void Use(Creature attacker, Skill skill, Packet packet) { // Set full charge variable attacker.Temp.LightningRodFullCharge = (DateTime.Now >= attacker.Temp.LightningRodPrepareTime.AddMilliseconds(skill.RankData.Var3)); // Get direction for target Area var direction = Mabi.MabiMath.ByteToRadian(attacker.Direction); var attackerPos = attacker.GetPosition(); // Calculate polygon points var r = MabiMath.ByteToRadian(attacker.Direction); var poe = attackerPos.GetRelative(r, 800); var pivot = new Point(poe.X, poe.Y); var p1 = new Point(pivot.X - SkillLength / 2, pivot.Y - SkillWidth / 2); var p2 = new Point(pivot.X - SkillLength / 2, pivot.Y + SkillWidth / 2); var p3 = new Point(pivot.X + SkillLength / 2, pivot.Y + SkillWidth / 2); var p4 = new Point(pivot.X + SkillLength / 2, pivot.Y - SkillWidth / 2); p1 = this.RotatePoint(p1, pivot, r); p2 = this.RotatePoint(p2, pivot, r); p3 = this.RotatePoint(p3, pivot, r); p4 = this.RotatePoint(p4, pivot, r); // TargetProp var lProp = new Prop(280, attacker.RegionId, poe.X, poe.Y, MabiMath.ByteToRadian(attacker.Direction), 1f, 0f, "single"); attacker.Region.AddProp(lProp); // Prepare Combat Actions var cap = new CombatActionPack(attacker, skill.Info.Id); var targetAreaId = new Location(attacker.RegionId, poe).ToLocationId(); var aAction = new AttackerAction(CombatActionType.SpecialHit, attacker, targetAreaId); aAction.Set(AttackerOptions.KnockBackHit1 | AttackerOptions.UseEffect); aAction.PropId = lProp.EntityId; cap.Add(aAction); // Get targets in Polygon - includes collission check var targets = attacker.Region.GetCreaturesInPolygon(p1, p2, p3, p4).Where(x => attacker.CanTarget(x) && !attacker.Region.Collisions.Any(attacker.GetPosition(), x.GetPosition())).ToList(); var rnd = RandomProvider.Get(); // Check crit var crit = false; var critSkill = attacker.Skills.Get(SkillId.CriticalHit); if (critSkill != null && critSkill.Info.Rank > SkillRank.Novice) { var critChance = Math2.Clamp(0, 30, attacker.GetTotalCritChance(0)); if (rnd.NextDouble() * 100 < critChance) crit = true; } foreach (var target in targets) { var tAction = new TargetAction(CombatActionType.TakeHit, target, attacker, SkillId.CombatMastery); tAction.Set(TargetOptions.None); tAction.AttackerSkillId = skill.Info.Id; cap.Add(tAction); var damage = attacker.GetRndMagicDamage(skill, skill.RankData.Var1, skill.RankData.Var2); // Add damage if the skill is fully charged var dmgMultiplier = skill.RankData.Var4 / 100f; if (attacker.Temp.LightningRodFullCharge) { damage += (damage * dmgMultiplier); } // Critical Hit if (crit) { var bonus = critSkill.RankData.Var1 / 100f; damage = damage + (damage * bonus); tAction.Set(TargetOptions.Critical); } // MDef and MProt SkillHelper.HandleMagicDefenseProtection(target, ref damage); // Conditions SkillHelper.HandleConditions(attacker, target, ref damage); // Mana Deflector var delayReduction = ManaDeflector.Handle(attacker, target, ref damage, tAction); // Mana Shield ManaShield.Handle(target, ref damage, tAction); // Apply Damage target.TakeDamage(tAction.Damage = damage, attacker); // Stun Time tAction.Stun = TargetStun; // Death or Knockback if (target.IsDead) { tAction.Set(TargetOptions.FinishingKnockDown); attacker.Shove(target, KnockbackDistance); } else { // Always knock down if (target.Is(RaceStands.KnockDownable)) { tAction.Set(TargetOptions.KnockDown); attacker.Shove(target, KnockbackDistance); } } } // Update current weapon SkillHelper.UpdateWeapon(attacker, targets.FirstOrDefault(), ProficiencyGainType.Melee, attacker.RightHand); cap.Handle(); Send.Effect(attacker, Effect.LightningRod, (int)LightningRodEffect.Attack, poe.X, poe.Y); Send.SkillUse(attacker, skill.Info.Id, targetAreaId, 0, 1); skill.Train(1); // Use the Skill attacker.Region.RemoveProp(lProp); }
/// <summary> /// Door's behavior when it's not locked. /// </summary> /// <param name="creature"></param> /// <param name="prop"></param> private void WarpInside(Creature creature, Prop prop) { var creaturePos = creature.GetPosition(); var cCoord = new Position(creaturePos.X / Dungeon.TileSize, creaturePos.Y / Dungeon.TileSize); if (cCoord == _closedFrom) { Send.Notice(creature, NoticeType.MiddleSystem, Localization.Get("There is a monster still standing.\nYou must defeat all the monsters for the door to open.")); return; } var x = _closedFrom.X * Dungeon.TileSize + Dungeon.TileSize / 2; var y = _closedFrom.Y * Dungeon.TileSize + Dungeon.TileSize / 2; if (cCoord.X < _closedFrom.X) x -= 1000; else if (cCoord.X > _closedFrom.X) x += 1000; else if (cCoord.Y < _closedFrom.Y) y -= 1000; else if (cCoord.Y > _closedFrom.Y) y += 1000; creature.SetPosition(x, y); Send.SetLocation(creature, x, y); }
public virtual void OnPropEvent(Puzzle puzzle, Prop prop) { }
/// <summary> /// Adds all props found in the client for this region. /// </summary> private void LoadClientProps() { if (_regionData == null || _regionData.Areas == null) return; foreach (var area in _regionData.Areas.Values) { foreach (var prop in area.Props.Values) { var add = new Prop(prop.EntityId, "", "", "", prop.Id, this.Id, (int)prop.X, (int)prop.Y, prop.Direction, prop.Scale, 0); // Add drop behaviour if drop type exists var dropType = prop.GetDropType(); if (dropType != -1) add.Behavior = Prop.GetDropBehavior(dropType); this.AddProp(add); } } }
/// <summary> /// Despawns prop, sends EntityDisappears. /// </summary> public void RemoveProp(Prop prop) { if (!prop.ServerSide) { Log.Error("RemoveProp: Client side props can't be removed."); prop.DisappearTime = DateTime.MinValue; return; } _propsRWLS.EnterWriteLock(); try { _props.Remove(prop.EntityId); } finally { _propsRWLS.ExitWriteLock(); } Send.PropDisappears(prop); prop.Region = null; }
/// <summary> /// Spawns prop, sends EntityAppears. /// </summary> public void AddProp(Prop prop) { _propsRWLS.EnterWriteLock(); try { _props.Add(prop.EntityId, prop); } finally { _propsRWLS.ExitWriteLock(); } prop.Region = this; Send.EntityAppears(prop); }
/// <summary> /// Adds all props found in the client for this region. /// </summary> protected void LoadProps() { foreach (var area in this.RegionInfoData.Areas) { foreach (var prop in area.Props.Values) { var add = new Prop(prop.EntityId, prop.Id, this.Id, (int)prop.X, (int)prop.Y, prop.Direction, prop.Scale, 0, "", "", ""); // Add copy of extensions foreach (var para in prop.Parameters) add.Extensions.AddSilent(new PropExtension(para.SignalType, para.EventType, para.Name, 0)); // Add drop behaviour if drop type exists var dropType = prop.GetDropType(); if (dropType != -1) add.Behavior = Prop.GetDropBehavior(dropType); // Replace default shapes with the ones loaded from region. add.Shapes.Clear(); add.Shapes.AddRange(prop.Shapes.Select(a => a.GetPoints(0, 0, 0))); this.AddProp(add); } } }
/// <summary> /// Initiates floor, creating puzzles and props. /// </summary> /// <param name="iRegion"></param> public void InitFloorRegion(int iRegion) { this.CreatePuzzles(iRegion); var region = this.Regions[iRegion]; var floor = this.Generator.Floors[iRegion - 1]; var gen = floor.MazeGenerator; var floorData = this.Data.Floors[iRegion - 1]; var iPrevRegion = iRegion - 1; var iNextRegion = iRegion + 1; var startTile = gen.StartPos; var startPos = new Generation.Position(startTile.X * Dungeon.TileSize + Dungeon.TileSize / 2, startTile.Y * Dungeon.TileSize + Dungeon.TileSize / 2); var startRoomTrait = floor.GetRoom(startTile); var startRoomIncomingDirection = startRoomTrait.GetIncomingDirection(); var endTile = gen.EndPos; var endPos = new Generation.Position(endTile.X * Dungeon.TileSize + Dungeon.TileSize / 2, endTile.Y * Dungeon.TileSize + Dungeon.TileSize / 2); var endRoomTrait = floor.GetRoom(endTile); var endRoomDirection = 0; for (int dir = 0; dir < 4; ++dir) { if (endRoomTrait.Links[dir] == LinkType.To) { endRoomDirection = dir; break; } } // Create upstairs prop var stairsBlock = this.Data.Style.Get(DungeonBlockType.StairsUp, startRoomIncomingDirection); var stairs = new Prop(stairsBlock.PropId, region.Id, startPos.X, startPos.Y, MabiMath.DegreeToRadian(stairsBlock.Rotation), 1, 0, "single"); stairs.Info.Color1 = floorData.Color1; stairs.Info.Color2 = floorData.Color1; stairs.Info.Color3 = floorData.Color3; region.AddProp(stairs); // Create portal prop leading to prev floor var portalBlock = this.Data.Style.Get(DungeonBlockType.PortalUp, startRoomIncomingDirection); var portal = new Prop(portalBlock.PropId, region.Id, startPos.X, startPos.Y, MabiMath.DegreeToRadian(portalBlock.Rotation), 1, 0, "single", "_upstairs", Localization.Get("<mini>TO</mini> Upstairs")); portal.Info.Color1 = floorData.Color1; portal.Info.Color2 = floorData.Color1; portal.Info.Color3 = floorData.Color3; portal.Behavior = (cr, pr) => { // Indoor_RDungeon_EB marks the end position on the prev floor. var clientEvent = this.Regions[iPrevRegion].GetClientEvent("Indoor_RDungeon_EB"); if (clientEvent == null) { Log.Error("Event 'Indoor_RDungeon_EB' not found while trying to warp to '{0}'.", this.Regions[iPrevRegion].Name); return; } // Warp to prev region var regionId = this.Regions[iPrevRegion].Id; var x = (int)clientEvent.Data.X; var y = (int)clientEvent.Data.Y; cr.Warp(regionId, x, y); }; region.AddProp(portal); // Create save statue var saveStatue = new Prop(this.Data.SaveStatuePropId, region.Id, startPos.X, startPos.Y, MabiMath.DegreeToRadian(stairsBlock.Rotation + 180), 1, 0, "single"); saveStatue.Info.Color1 = floorData.Color1; saveStatue.Info.Color2 = floorData.Color1; saveStatue.Info.Color3 = floorData.Color3; saveStatue.Behavior = (cr, pr) => { cr.DungeonSaveLocation = cr.GetLocation(); Send.Notice(cr, Localization.Get("You have memorized this location.")); // Scroll message var msg = string.Format("You're currently on Floor {0} of {1}. ", iRegion, this.Data.EngName); Send.Notice(cr, NoticeType.Top, ScrollMessageDuration, msg + this.GetPlayerListScrollMessage()); }; region.AddProp(saveStatue); // Spawn boss or downstair props // TODO: There is one dungeon that has two boss rooms. if (floor.IsLastFloor) { // Create door to treasure room _bossExitDoor = new Prop(this.Data.BossExitDoorId, region.Id, endPos.X, endPos.Y + Dungeon.TileSize / 2, Rotation(Direction.Up), 1, 0, "closed"); _bossExitDoor.Info.Color1 = floorData.Color1; _bossExitDoor.Info.Color2 = floorData.Color1; _bossExitDoor.Info.Color3 = floorData.Color3; region.AddProp(_bossExitDoor); // Get or create boss door if (endRoomTrait.PuzzleDoors[Direction.Down] == null) { Log.Warning("Dungeon.InitFloorRegion: No locked place in last section of '{0}'.", this.Name); _bossDoor = new Door(this.Data.BossDoorId, region.Id, endPos.X, endPos.Y - Dungeon.TileSize, Direction.Up, DungeonBlockType.BossDoor, "", "closed"); _bossDoor.Info.Color1 = floorData.Color1; _bossDoor.Info.Color2 = floorData.Color1; _bossDoor.Info.Color3 = floorData.Color3; _bossDoor.Behavior = (cr, pr) => { _bossDoor.Open(); }; _bossDoor.Behavior += this.BossDoorBehavior; _bossDoor.UpdateShapes(); endRoomTrait.PuzzleDoors[Direction.Down] = _bossDoor; // making sure another open dummy door won't be added here region.AddProp(_bossDoor); } else { _bossDoor = endRoomTrait.PuzzleDoors[Direction.Down]; } // Create exit statue var exitStatue = new Prop(this.Data.LastStatuePropId, region.Id, endPos.X, endPos.Y + Dungeon.TileSize * 2, Rotation(Direction.Up), 1, 0, "single"); exitStatue.Info.Color1 = floorData.Color1; exitStatue.Info.Color2 = floorData.Color1; exitStatue.Info.Color3 = floorData.Color3; exitStatue.Extensions.AddSilent(new ConfirmationPropExtension("GotoLobby", "_LT[code.standard.msg.dungeon_exit_notice_msg]", "_LT[code.standard.msg.dungeon_exit_notice_title]", "haskey(chest)")); exitStatue.Behavior = (cr, pr) => { cr.Warp(this.Data.Exit); }; region.AddProp(exitStatue); } else { // Create downstairs prop var stairsDownBlock = this.Data.Style.Get(DungeonBlockType.StairsDown, endRoomDirection); var stairsDown = new Prop(stairsDownBlock.PropId, region.Id, endPos.X, endPos.Y, MabiMath.DegreeToRadian(stairsDownBlock.Rotation), 1, 0, "single"); stairsDown.Info.Color1 = floorData.Color1; stairsDown.Info.Color2 = floorData.Color1; stairsDown.Info.Color3 = floorData.Color3; region.AddProp(stairsDown); // Create portal leading to the next floor var portalDownBlock = this.Data.Style.Get(DungeonBlockType.PortalDown, endRoomDirection); var portalDown = new Prop(portalDownBlock.PropId, region.Id, endPos.X, endPos.Y, MabiMath.DegreeToRadian(portalDownBlock.Rotation), 1, 0, "single", "_downstairs", Localization.Get("<mini>TO</mini> Downstairs")); portalDown.Info.Color1 = floorData.Color1; portalDown.Info.Color2 = floorData.Color1; portalDown.Info.Color3 = floorData.Color3; portalDown.Behavior = (cr, pr) => { // Indoor_RDungeon_SB marks the start position on the next floor. var clientEvent = this.Regions[iNextRegion].GetClientEvent("Indoor_RDungeon_SB"); if (clientEvent == null) { Log.Error("Event 'Indoor_RDungeon_SB' not found while trying to warp to '{0}'.", this.Regions[iNextRegion].Name); return; } // Warp to next floor var regionId = this.Regions[iNextRegion].Id; var x = (int)clientEvent.Data.X; var y = (int)clientEvent.Data.Y; cr.Warp(regionId, x, y); }; region.AddProp(portalDown); } // Place dummy doors for (int x = 0; x < floor.MazeGenerator.Width; ++x) { for (int y = 0; y < floor.MazeGenerator.Height; ++y) { var room = floor.MazeGenerator.GetRoom(x, y); var roomTrait = floor.GetRoom(x, y); var isRoom = (roomTrait.RoomType >= RoomType.Start); if (!room.Visited || !isRoom) continue; for (var dir = 0; dir < 4; ++dir) { // Skip stairs if (roomTrait.RoomType == RoomType.Start && dir == startRoomIncomingDirection) continue; if (roomTrait.RoomType == RoomType.End && dir == endRoomDirection) continue; if (roomTrait.Links[dir] == LinkType.None) continue; if (roomTrait.PuzzleDoors[dir] == null) { var doorX = x * Dungeon.TileSize + Dungeon.TileSize / 2; var doorY = y * Dungeon.TileSize + Dungeon.TileSize / 2; var doorBlock = this.Data.Style.Get(DungeonBlockType.Door, dir); var doorProp = new Prop(doorBlock.PropId, region.Id, doorX, doorY, MabiMath.DegreeToRadian(doorBlock.Rotation), state: "open"); doorProp.Info.Color1 = floorData.Color1; doorProp.Info.Color2 = floorData.Color2; doorProp.Info.Color3 = floorData.Color3; region.AddProp(doorProp); } else if (roomTrait.PuzzleDoors[dir].EntityId == 0) { // Add doors from failed puzzles roomTrait.PuzzleDoors[dir].Info.Region = region.Id; region.AddProp(roomTrait.PuzzleDoors[dir]); } } } } }
/// <summary> /// Broadcasts prop extension remove. /// </summary> /// <param name="prop"></param> /// <param name="ext"></param> public static void RemovePropExtension(Prop prop, PropExtension ext) { var packet = new Packet(Op.RemovePropExtension, prop.EntityId); packet.PutString(ext.Name); prop.Region.Broadcast(packet); }
protected override void DefaultBehavior(Creature creature, Prop prop) { // Make sure the chest was still closed when it was clicked. // No security violation because it could be caused by lag. if (prop.State == "open") return; // Check key var key = creature.Inventory.GetItem(a => a.Info.Id == 70028 && a.MetaData1.GetString("prop_to_unlock") == this.LockName); if (key == null) { Send.Notice(creature, Localization.Get("There is no matching key.")); return; } // Remove key creature.Inventory.Remove(key); // Open and drop prop.SetState("open"); this.DropItems(creature); }
/// <summary> /// Generates areas, incl (client) props and events. /// </summary> private void GenerateAreas() { this.Data = new RegionInfoData(); var areaId = 2; var floor = this.Floor; for (int x = 0; x < floor.MazeGenerator.Width; ++x) { for (int y = 0; y < floor.MazeGenerator.Height; ++y) { var room = floor.MazeGenerator.GetRoom(x, y); var roomTrait = floor.GetRoom(x, y); if (!room.Visited) continue; var isStart = (roomTrait.RoomType == RoomType.Start); var isEnd = (roomTrait.RoomType == RoomType.End); var isRoom = (roomTrait.RoomType >= RoomType.Start); var isBossRoom = (floor.HasBossRoom && isEnd); var eventId = 0L; if (!isBossRoom) { var areaData = new AreaData(); areaData.Id = areaId++; areaData.Name = "Tile" + areaData.Id; areaData.X1 = x * Dungeon.TileSize; areaData.Y1 = y * Dungeon.TileSize; areaData.X2 = x * Dungeon.TileSize + Dungeon.TileSize; areaData.Y2 = y * Dungeon.TileSize + Dungeon.TileSize; this.Data.Areas.Add(areaData); var type = (isRoom ? DungeonBlockType.Room : DungeonBlockType.Alley); var propEntityId = MabiId.ClientProps | ((long)this.Id << 32) | ((long)areaData.Id << 16) | 1; var block = this.Dungeon.Data.Style.Get(type, room.Directions); var tileCenter = new Point(x * Dungeon.TileSize + Dungeon.TileSize / 2, y * Dungeon.TileSize + Dungeon.TileSize / 2); var prop = new Prop(propEntityId, block.PropId, this.Id, tileCenter.X, tileCenter.Y, MabiMath.DegreeToRadian(block.Rotation), 1, 0, "", "", ""); this.AddProp(prop); // Debug //foreach (var points in prop.Shapes) //{ // foreach (var point in points) // { // var pole = new Prop(30, this.Id, point.X, point.Y, 0, 1, 0, "", "", ""); // pole.Shapes.Clear(); // this.AddProp(pole); // } //} // TODO: This region/data stuff is a mess... create // proper classes, put them in the regions and be // done with it. if (isStart || isEnd) { var xp = tileCenter.X; var yp = tileCenter.Y; if (roomTrait.DoorType[Direction.Up] >= 3000) yp += 400; else if (roomTrait.DoorType[Direction.Right] >= 3000) xp += 400; else if (roomTrait.DoorType[Direction.Down] >= 3000) yp -= 400; else if (roomTrait.DoorType[Direction.Left] >= 3000) xp -= 400; var eventData = new EventData(); eventData.Id = MabiId.AreaEvents | ((long)this.Id << 32) | ((long)areaData.Id << 16) | eventId++; eventData.Name = (isStart ? "Indoor_RDungeon_SB" : "Indoor_RDungeon_EB"); eventData.X = xp; eventData.Y = yp; var shape = new ShapeData(); shape.DirX1 = 1; shape.DirY2 = 1; shape.LenX = 100; shape.LenY = 100; shape.PosX = xp; shape.PosY = yp; eventData.Shapes.Add(shape); areaData.Events.Add(eventData.Id, eventData); _clientEvents.Add(eventData.Id, new ClientEvent(eventData, this.Data.Name, areaData.Name)); } } else { // Big main room var areaData = new AreaData(); areaData.Id = areaId++; areaData.Name = "Tile" + areaData.Id; areaData.X1 = x * Dungeon.TileSize - Dungeon.TileSize; areaData.Y1 = y * Dungeon.TileSize; areaData.X2 = x * Dungeon.TileSize + Dungeon.TileSize * 2; areaData.Y2 = y * Dungeon.TileSize + Dungeon.TileSize * 2; this.Data.Areas.Add(areaData); var block = this.Dungeon.Data.Style.Get(DungeonBlockType.BossRoom); var propEntityId = MabiId.ClientProps | ((long)this.Id << 32) | ((long)areaData.Id << 16) | 1; var tileCenter = new Point(x * Dungeon.TileSize + Dungeon.TileSize / 2, y * Dungeon.TileSize + Dungeon.TileSize); var prop = new Prop(propEntityId, block.PropId, this.Id, tileCenter.X, tileCenter.Y, MabiMath.DegreeToRadian(block.Rotation), 1, 0, "", "", ""); this.AddProp(prop); // Debug //foreach (var points in prop.Shapes) //{ // foreach (var point in points) // { // var pole = new Prop(30, this.Id, point.X, point.Y, 0, 1, 0, "", "", ""); // pole.Shapes.Clear(); // this.AddProp(pole); // } //} // Treasure room areaData = new AreaData(); areaData.Id = areaId++; areaData.Name = "Tile" + areaData.Id; areaData.X1 = x * Dungeon.TileSize; areaData.Y1 = y * Dungeon.TileSize + Dungeon.TileSize * 2; areaData.X2 = x * Dungeon.TileSize + Dungeon.TileSize; areaData.Y2 = y * Dungeon.TileSize + Dungeon.TileSize * 2 + Dungeon.TileSize; this.Data.Areas.Add(areaData); } } } }
protected void ShootFirework(Location location, FireworkType type, string message) { var region = ChannelServer.Instance.World.GetRegion(location.RegionId); if (region == null) { Log.Warning(this.GetType().Name + ".ShootFirework: Unknown region."); return; } if (message == null) message = ""; var delay = 500; var rnd = RandomProvider.Get(); var height = rnd.Between(750, 2000); var heightf = height / 100f; var prop = new Prop(208, location.RegionId, location.X, location.Y, 0); prop.DisappearTime = DateTime.Now.AddMilliseconds(20000 + delay); region.AddProp(prop); Task.Delay(delay).ContinueWith(__ => { prop.Xml.SetAttributeValue("height", height); prop.Xml.SetAttributeValue("message", message + " (" + heightf.ToString("0.##") + "m)"); prop.Xml.SetAttributeValue("type", (int)type); prop.Xml.SetAttributeValue("seed", Interlocked.Increment(ref _fireworkSeed)); Send.PropUpdate(prop); }); }
/// <summary> /// Creates sitting prop, fails silently if item or chair /// data doesn't exist. /// </summary> /// <param name="creature"></param> /// <param name="chairItemEntityId"></param> private void SetUpChair(Creature creature, long chairItemEntityId) { if (chairItemEntityId == 0) return; // Check item var item = creature.Inventory.GetItem(chairItemEntityId); if (item == null || item.Data.Type != ItemType.Misc) return; // Get chair data var chairData = AuraData.ChairDb.Find(item.Info.Id); if (chairData == null) return; var pos = creature.GetPosition(); // Effect if (chairData.Effect != 0) Send.Effect(creature, chairData.Effect, true); // Chair prop var sittingProp = new Prop((!creature.IsGiant ? chairData.PropId : chairData.GiantPropId), creature.RegionId, pos.X, pos.Y, MabiMath.ByteToRadian(creature.Direction)); sittingProp.Info.Color1 = item.Info.Color1; sittingProp.Info.Color2 = item.Info.Color2; sittingProp.Info.Color3 = item.Info.Color3; sittingProp.State = "stand"; creature.Region.AddProp(sittingProp); // Move char Send.AssignSittingProp(creature, sittingProp.EntityId, 1); // Update chair sittingProp.XML = string.Format("<xml OWNER='{0}' SITCHAR='{0}'/>", creature.EntityId); Send.PropUpdate(sittingProp); creature.Temp.CurrentChairData = chairData; creature.Temp.SittingProp = sittingProp; }
/// <summary> /// Generates entity id for prop. /// </summary> /// <param name="prop"></param> /// <returns></returns> private long GetNewPropEntityId(Prop prop) { var regionId = this.Id; var areaId = this.GetAreaId((int)prop.Info.X, (int)prop.Info.Y); var propId = 0; lock (_propIds) { if (!_propIds.ContainsKey(areaId)) _propIds[areaId] = 1; propId = _propIds[areaId]++; if (propId >= ushort.MaxValue) throw new Exception("Max prop id reached in region '" + regionId + "', area '" + areaId + "'."); } var result = MabiId.ServerProps; result |= (long)regionId << 32; result |= (long)areaId << 16; result |= (ushort)propId; return result; }
/// <summary> /// Handles item burning, retruns whether it was successful. /// </summary> /// <param name="creature"></param> /// <param name="item"></param> /// <param name="campfire"></param> /// <param name="enchantersBurn"></param> public bool Burn(Creature creature, Item item, Prop campfire, bool enchantersBurn) { var skill = creature.Skills.Get(SkillId.Enchant); var enchantBurnSuccess = false; var powderBurnSuccess = false; var exp = 0; // Enchanter's Burn if (enchantersBurn) { var rnd = RandomProvider.Get(); var isEquip = item.HasTag("/equip/"); var hasEnchantBurnItems = (creature.Inventory.Has(51102) && creature.Inventory.Has(63016)); // Mana Herb + Holy Water // Enchant burning if (!isEquip || !hasEnchantBurnItems) { // Unofficial Send.SystemMessage(creature, Localization.Get("You don't the necessary items.")); return false; } // Get chances // All unofficial var rank = (skill == null ? 16 : (int)skill.Info.Rank); var enchantChance = (skill == null ? 0 : skill.RankData.Var3); // Campfire r8+ bonus if (enchantChance > 0 && campfire.Temp.CampfireSkillRank.Rank >= SkillRank.R8) enchantChance += (16 - rank); // Powder = double enchant chance, based on the Wiki saying // r1 doesn't guarantee getting the enchants, but it does // guarantee getting powder. var powderChance = enchantChance * 2; // Debug if (creature.Titles.SelectedTitle == TitleId.devCAT) Send.ServerMessage(creature, "Debug: Chance for enchant: {0:0}, chance for powder: {1:0}", enchantChance, powderChance); // Try prefix if (item.OptionInfo.Prefix != 0 && rnd.Next(100) < enchantChance) { var enchant = Item.CreateEnchant(item.OptionInfo.Prefix); creature.AcquireItem(enchant); enchantBurnSuccess = true; } // Try suffix if (item.OptionInfo.Suffix != 0 && rnd.Next(100) < enchantChance) { var enchant = Item.CreateEnchant(item.OptionInfo.Suffix); creature.AcquireItem(enchant); enchantBurnSuccess = true; } // Try suffix if (item.OptionInfo.Prefix + item.OptionInfo.Suffix != 0 && rnd.Next(100) < powderChance) { var powder = new Item(62003); // Blessed Magic Powder creature.AcquireItem(powder); powderBurnSuccess = true; } // Reduce items creature.Inventory.Remove(51102, 1); // Mana Herb creature.Inventory.Remove(63016, 1); // Holy Water // Training this.BurnTraining(skill, enchantBurnSuccess, powderBurnSuccess); // Success/Fail motion Send.UseMotion(creature, 14, enchantBurnSuccess ? 0 : 3); } // Add exp based on item buying price (random+unofficial) if (item.OptionInfo.Price > 0) { exp = 40 + (int)(item.OptionInfo.Price / (float)item.Data.StackMax / 100f * item.Info.Amount); creature.GiveExp(exp); } // Remove item from cursor creature.Inventory.Remove(item); // Effect Send.Effect(MabiId.Broadcast, creature, Effect.BurnItem, campfire.EntityId, enchantBurnSuccess); Send.Notice(creature, NoticeType.MiddleSystem, Localization.Get("Burning EXP {0}"), exp); return true; }
/// <summary> /// Completes skill, placing the campfire. /// </summary> /// <param name="creature"></param> /// <param name="skill"></param> /// <param name="packet"></param> public void Complete(Creature creature, Skill skill, Packet packet) { var positionId = packet.GetLong(); var unkInt1 = packet.GetInt(); var unkInt2 = packet.GetInt(); var propId = PropId; // Handle items if (skill.Info.Id == SkillId.Campfire) { // Check Firewood, the client should stop the player long before Complete. if (creature.Inventory.Count(creature.Temp.FirewoodItemId) < FirewoodCost) throw new ModerateViolation("Used Campfire without Firewood."); // Remove Firewood creature.Inventory.Remove(creature.Temp.FirewoodItemId, FirewoodCost); } else { // Check kit var item = creature.Inventory.GetItem(creature.Temp.CampfireKitItemEntityId); if (item == null) throw new ModerateViolation("Used CampfireKit with invalid kit."); propId = this.GetPropId(item); // Change the prop ID based on what campfire kit was used // Reduce kit creature.Inventory.Decrement(item); } // Set up Campfire var pos = new Position(positionId); var effect = (skill.Info.Rank < SkillRank.RB ? "campfire_01" : "campfire_02"); var prop = new Prop(propId, creature.RegionId, pos.X, pos.Y, MabiMath.ByteToRadian(creature.Direction), 1); // Logs prop.State = "single"; if (prop.Data.Id != HalloweenPropId) prop.Xml.SetAttributeValue("EFFECT", effect); // Fire effect prop.DisappearTime = DateTime.Now.AddMinutes(this.GetDuration(skill.Info.Rank, creature.RegionId)); // Disappear after x minutes // Temp data for Rest prop.Temp.CampfireSkillRank = skill.RankData; if (skill.Info.Id == SkillId.Campfire) prop.Temp.CampfireFirewood = AuraData.ItemDb.Find(creature.Temp.FirewoodItemId); creature.Region.AddProp(prop); // Training if (skill.Info.Rank == SkillRank.Novice) skill.Train(1); // Use Campfire. // Complete Send.SkillComplete(creature, skill.Info.Id, positionId, unkInt1, unkInt2); }
/// <summary> /// Adds all props found in the client for this region. /// </summary> protected void LoadProps() { foreach (var areaData in this.Data.Areas) { foreach (var propData in areaData.Props.Values) { var prop = new Prop(propData, this.Id, this.Data.Name, areaData.Name); this.AddProp(prop); } } }
/// <summary> /// Behavior for this dungeon's boss door. /// </summary> /// <param name="_"></param> /// <param name="prop"></param> public void BossDoorBehavior(Creature _, Prop prop) { // Get door var door = prop as Door; if (door == null) { Log.Error("Dungeon.BossDoorBehavior: Boss door... is not a door!?"); return; } // Make sure it got unlocked if (door.IsLocked) return; // Check if bosses were already spawned if (_bossSpawned) return; _bossSpawned = true; // Remove all monsters this.Regions.ForEach(a => a.RemoveAllMonsters()); // Call OnBoss if (this.Script != null) this.Script.OnBoss(this); // Open boss and exit door if no bosses were spawned if (_bossesRemaining == 0) { _bossDoor.SetState("open"); _bossExitDoor.SetState("open"); } }
/// <summary> /// Spawns prop, sends EntityAppears. /// </summary> public void AddProp(Prop prop) { // Generate prop id if it doesn't have one yet. if (prop.EntityId == 0) prop.EntityId = this.GetNewPropEntityId(prop); _propsRWLS.EnterWriteLock(); try { if (_props.ContainsKey(prop.EntityId)) throw new ArgumentException("A prop with id '" + prop.EntityId.ToString("X16") + "' already exists."); _props.Add(prop.EntityId, prop); } finally { _propsRWLS.ExitWriteLock(); } prop.Region = this; // Add collisions this.Collisions.Add(prop); //Send.EntityAppears(prop); }