/// <summary> /// Executes the operation's logic. /// </summary> /// <param name="context">A reference to the operation context.</param> protected override void Execute(IElevatedOperationContext context) { var requestor = this.GetRequestor(context.CreatureFinder); var inThingContainer = this.AtLocation.DecodeContainer(context.Map, context.ContainerManager, out byte index, requestor); // Adjust index if this a map location. var existingThing = (this.AtLocation.Type == LocationType.Map && (inThingContainer is ITile fromTile)) ? fromTile.FindItemWithTypeId(this.TypeId) : inThingContainer?.FindThingAtIndex(index); if (inThingContainer == null || !(existingThing is IItem existingItem)) { return; } // At this point, we have an item to remove, let's proceed. (bool removeSuccessful, IThing remainder) = inThingContainer.RemoveContent(context.ItemFactory, ref existingThing, index, amount: existingItem.Amount); if (!removeSuccessful) { // Failing to remove the item from the original cylinder stops the entire operation. return; } if (inThingContainer is ITile atTile) { this.SendNotification( context, new TileUpdatedNotification( () => context.Map.PlayersThatCanSee(atTile.Location), atTile.Location, context.MapDescriptor.DescribeTile)); } // Evaluate if the new item triggers a separation. // context.EventRulesApi.EvaluateRules(this, EventRuleType.Separation, new SeparationEventRuleArguments(atCylinder.Location, item, this.GetRequestor(context.CreatureFinder))); }
/// <summary> /// Executes the operation's logic. /// </summary> /// <param name="context">A reference to the operation context.</param> protected override void Execute(IElevatedOperationContext context) { if (this.Creature is IPlayer player) { this.SendNotification(context, new TextMessageNotification(() => player.YieldSingleItem(), MessageType.EventAdvance, "You are dead.")); this.SendNotification(context, new GenericNotification(() => player.YieldSingleItem(), new PlayerCancelWalkPacket(player.Direction), new PlayerDeathPacket())); } // Remove the creature... if (context.Map.GetTileAt(this.Creature.Location) is ITile creatureTile) { // Add the corpse. var corpseCreationArguments = new ItemCreationArguments() { TypeId = this.Creature.CorpseTypeId, }; if (context.ItemFactory.Create(corpseCreationArguments) is IThing corpseCreated && this.AddContentToContainerOrFallback(context, creatureTile, ref corpseCreated)) { context.GameApi.CreateItemAtLocation(creatureTile.Location, context.PredefinedItemSet.FindPoolForBloodType(this.Creature.BloodType)); } this.RemoveCreature(context, this.Creature); } }
/// <summary> /// Executes the operation's logic. /// </summary> /// <param name="context">A reference to the operation context.</param> protected override void Execute(IElevatedOperationContext context) { var requestor = this.GetRequestor(context.CreatureFinder); var inThingContainer = this.Item.ParentContainer; if (inThingContainer == null || !(this.Item is IThing existingThing)) { return; } // At this point, we have an item to remove, let's proceed. (bool removeSuccessful, IThing remainder) = inThingContainer.RemoveContent(context.ItemFactory, ref existingThing, amount: this.Item.Amount); if (!removeSuccessful) { // Failing to remove the item from the original cylinder stops the entire operation. return; } if (inThingContainer is ITile atTile) { this.SendNotification( context, new TileUpdatedNotification( () => context.Map.PlayersThatCanSee(atTile.Location), atTile.Location, context.MapDescriptor.DescribeTile)); } // Evaluate if the new item triggers a separation. // context.EventRulesApi.EvaluateRules(this, EventRuleType.Separation, new SeparationEventRuleArguments(atCylinder.Location, item, this.GetRequestor(context.CreatureFinder))); }
/// <summary> /// Executes the operation's logic. /// </summary> /// <param name="context">A reference to the operation context.</param> protected override void Execute(IElevatedOperationContext context) { bool successfulRemoval = this.RemoveCreature(context, this.Creature); if (!successfulRemoval) { // handles check for isPlayer. // this.NotifyOfFailure(); return; } }
/// <summary> /// Executes the operation's logic. /// </summary> /// <param name="context">A reference to the operation context.</param> protected override void Execute(IElevatedOperationContext context) { bool successfulPlacement = this.PlaceCreature(context, this.AtTile, this.Creature); if (!successfulPlacement) { context.Logger.Warning($"Failed to place creature {this.Creature.Name} at {this.AtTile.Location}"); return; } }
/// <summary> /// Executes the operation's logic. /// </summary> /// <param name="context">A reference to the operation context.</param> protected override void Execute(IElevatedOperationContext context) { if (this.Creature is IPlayer player) { this.SendNotification(context, new TextMessageNotification(() => player.YieldSingleItem(), MessageType.EventAdvance, "You are dead.")); this.SendNotification(context, new GenericNotification(() => player.YieldSingleItem(), new PlayerCancelWalkPacket(player.Direction), new PlayerDeathPacket())); } // Give out the experience if this is a monster if (this.Creature is IMonster monster && monster.ExperienceToYield > 0 && this.Creature is ICombatant monsterCombatant) { ulong totalDamageDealt = (ulong)monsterCombatant.DamageTakenInSession.Sum(t => t.Damage); foreach (var(combatantId, damage) in monsterCombatant.DamageTakenInSession) { if (damage == 0) { continue; } var expPercentage = Convert.ToDecimal(damage) / totalDamageDealt; var expToGive = (long)Math.Round(monster.ExperienceToYield * expPercentage, 0, MidpointRounding.ToEven); if (expToGive == 0) { continue; } if (context.CreatureFinder.FindCreatureById(combatantId) is ICombatant combatantGainingExp) { combatantGainingExp.AddExperience(expToGive); } } } // Remove the creature... if (context.Map.GetTileAt(this.Creature.Location) is ITile creatureTile) { // Add the corpse. var corpseCreationArguments = new ItemCreationArguments() { TypeId = this.Creature.CorpseTypeId, }; if (context.ItemFactory.Create(corpseCreationArguments) is IThing corpseCreated && this.AddContentToContainerOrFallback(context, creatureTile, ref corpseCreated)) { context.GameApi.CreateItemAtLocation(creatureTile.Location, context.PredefinedItemSet.FindPoolForBloodType(this.Creature.BloodType)); } this.RemoveCreature(context, this.Creature); } }
/// <summary> /// Executes the operation's logic. /// </summary> /// <param name="context">A reference to the operation context.</param> protected override void Execute(IElevatedOperationContext context) { if (!this.Player.IsDead && this.Player.HasCondition(ConditionType.InFight)) { this.SendNotification( context, new TextMessageNotification( () => this.Player.YieldSingleItem(), MessageType.StatusSmall, "You may not logout during or immediately after a fight.")); return; } if (!context.Map.GetTileAt(this.Player.Location, out ITile tile)) { return; } // TODO: more validations missing using var uow = context.ApplicationContext.CreateNewUnitOfWork(); if (!(uow.Characters.FindByName(this.Player.Name) is CharacterEntity characterEntity)) { throw new InvalidOperationException($"Unable to find entity for player {this.Player.Name}."); } // At this point, we're allowed to log this player out, so go for it. var playerLocation = this.Player.Location; var saveAtLocation = this.Player.IsDead ? characterEntity.StartingLocation : playerLocation; var removedFromMap = context.GameApi.RemoveCreatureFromGame(this.Player); if (removedFromMap || this.Player.IsDead) { if (!this.Player.IsDead) { this.SendNotification(context, new GenericNotification(() => context.Map.FindPlayersThatCanSee(playerLocation), new MagicEffectPacket(playerLocation, AnimatedEffect.Puff))); } if (this.Player.Client.Connection != null && !this.Player.Client.Connection.IsOrphaned) { this.Player.Client.Connection.Close(); } context.CreatureManager.UnregisterCreature(this.Player); characterEntity.LastLocation = saveAtLocation; uow.Complete(); } }
/// <summary> /// Executes the operation's logic. /// </summary> /// <param name="context">A reference to the operation context.</param> protected override void Execute(IElevatedOperationContext context) { const byte FallbackIndex = 0xFF; var inThingContainer = this.FromLocation.DecodeContainer(context.Map, context.ContainerManager, out byte index, this.FromCreature); // Adjust index if this a map location. var existingThing = (this.FromLocation.Type == LocationType.Map && (inThingContainer is ITile fromTile)) ? fromTile.FindItemWithTypeId(this.FromTypeId) : inThingContainer?.FindThingAtIndex(index); if (existingThing == null || !(existingThing is IItem existingItem)) { // Silent fail. return; } IThing thingCreated = context.ItemFactory.Create(ItemCreationArguments.WithTypeId(this.ToTypeId)); if (thingCreated == null) { return; } // At this point, we have an item to change, and we were able to generate the new one, let's proceed. (bool replaceSuccessful, IThing replaceRemainder) = inThingContainer.ReplaceContent(context.ItemFactory, existingThing, thingCreated, index, existingItem.Amount); if (!replaceSuccessful || replaceRemainder != null) { this.AddContentToContainerOrFallback(context, inThingContainer, ref replaceRemainder, FallbackIndex, includeTileAsFallback: true, this.GetRequestor(context.CreatureFinder)); } if (replaceSuccessful) { if (inThingContainer is ITile atTile) { this.SendNotification( context, new TileUpdatedNotification( () => context.Map.PlayersThatCanSee(atTile.Location), atTile.Location, context.MapDescriptor.DescribeTile)); // Evaluate if the new item triggers a collision. // context.EventRulesApi.EvaluateRules(this, EventRuleType.Collision, new CollisionEventRuleArguments(fromCylinder.Location, existingThing, this.GetRequestor(context.CreatureFinder))); } } }
/// <summary> /// Executes the operation's logic. /// </summary> /// <param name="context">A reference to the operation context.</param> protected override void Execute(IElevatedOperationContext context) { var inThingContainer = this.Item.ParentContainer; if (!(this.Item is IThing existingThing) || !this.Item.HasExpiration) { // Silent fail. return; } var creationArguments = new ItemCreationArguments() { TypeId = this.Item.ExpirationTarget }; if (this.Item.IsLiquidPool) { creationArguments.Attributes = new[]
/// <summary> /// Executes the operation's logic. /// </summary> /// <param name="context">A reference to the operation context.</param> protected override void Execute(IElevatedOperationContext context) { context.Scheduler.CancelAllFor(this.Creature.Id, this.TypeToCancel); if (this.Creature is IPlayer player) { if (this.TypeToCancel == typeof(IOperation) && player is ICombatant playerAsCombatant) { playerAsCombatant.SetAttackTarget(null); } this.SendNotification( context, new GenericNotification( () => player.YieldSingleItem(), new PlayerCancelAttackPacket(), new PlayerCancelWalkPacket(this.Creature.Direction.GetClientSafeDirection()))); } }
/// <summary> /// Executes the operation's logic. /// </summary> /// <param name="context">A reference to the operation context.</param> protected override void Execute(IElevatedOperationContext context) { if (!this.Player.IsDead && this.Player.HasCondition(ConditionType.InFight)) { this.SendNotification( context, new TextMessageNotification( () => this.Player.YieldSingleItem(), MessageType.StatusSmall, "You may not logout during or immediately after a fight.")); return; } if (!context.Map.GetTileAt(this.Player.Location, out ITile tile)) { return; } // TODO: more validations missing // At this point, we're allowed to log this player out, so go for it. var playerLocation = this.Player.Location; var removedFromMap = this.RemoveCreature(context, this.Player); if (removedFromMap || this.Player.IsDead) { if (this.Player.Client.Connection != null) { this.Player.Client.Connection.Close(); } if (!this.Player.IsDead) { this.SendNotification(context, new GenericNotification(() => context.Map.PlayersThatCanSee(playerLocation), new MagicEffectPacket(playerLocation, AnimatedEffect.Puff))); } context.CreatureManager.UnregisterCreature(this.Player); } }
/// <summary> /// Executes the operation's logic. /// </summary> /// <param name="context">A reference to the operation context.</param> protected override void Execute(IElevatedOperationContext context) { var rng = new Random(); using var uow = context.ApplicationContext.CreateNewUnitOfWork(); for (int i = 0; i < this.Spawn.Count; i++) { var r = this.Spawn.Radius / 4; var monsterType = uow.MonsterTypes.GetById(this.Spawn.MonsterRaceId.ToString()); if (!(monsterType is MonsterTypeEntity monsterTypeEntity)) { context.Logger.Warning($"Unable to place monster. Could not find a monster with the id {this.Spawn.MonsterRaceId} in the repository. ({nameof(SpawnMonstersOperation)})"); return; } var newMonster = context.CreatureFactory.Create( new CreatureCreationArguments() { Type = CreatureType.Monster, Metadata = monsterTypeEntity, }) as IMonster; var randomLoc = this.Spawn.Location + new Location { X = (int)Math.Round(r * Math.Cos(rng.Next(360))), Y = (int)Math.Round(r * Math.Sin(rng.Next(360))), Z = 0 }; // Need to actually pathfind to avoid placing a monster in unreachable places. var(_, foundLocation, _) = context.PathFinder.FindBetween(this.Spawn.Location, randomLoc, newMonster, (i + 1) * 10); // TODO: some property of newMonster here to figure out what actually blocks path finding. if (context.Map.GetTileAt(foundLocation, out ITile targetTile) && !targetTile.IsPathBlocking()) { context.Scheduler.ScheduleEvent(new PlaceCreatureOperation(requestorId: 0, targetTile, newMonster)); } } }
/// <summary> /// Executes the operation's logic. /// </summary> /// <param name="context">A reference to the operation context.</param> protected override void Execute(IElevatedOperationContext context) { // This will eventually come from the character, or fall back. var targetLoginLocation = MapConstants.ThaisTempleMark; var creationArguments = new PlayerCreationArguments() { Client = this.Client, Type = CreatureType.Player, Metadata = this.PlayerMetadata, }; if (!(context.CreatureFactory.CreateCreature(creationArguments) is IPlayer player)) { context.Logger.Warning($"Unable to create player instance for {this.PlayerMetadata.Name}, aborting log in."); return; } if (!context.Map.GetTileAt(targetLoginLocation, out ITile targetTile) || !this.PlaceCreature(context, targetTile, player)) { // Unable to place the player in the map. context.Scheduler.ScheduleEvent( new GenericNotification( () => player.YieldSingleItem(), new GameServerDisconnectPacket("Your character could not be placed on the map.\nPlease try again, or contact an administrator if the issue persists."))); return; } var(descriptionMetadata, descriptionBytes) = context.MapDescriptor.DescribeAt(player, player.Location); // TODO: In addition, we need to send the player's inventory, the first time login message + outfit window here if applicable. // And any VIP records here. var notification = new GenericNotification( () => player.YieldSingleItem(), new PlayerLoginPacket(player.Id, player), new MapDescriptionPacket(player.Location, descriptionBytes), new MagicEffectPacket(player.Location, AnimatedEffect.BubbleBlue), new PlayerStatsPacket(player), new PlayerSkillsPacket(player), new WorldLightPacket(this.CurrentWorldLightLevel, this.CurrentWorldLightColor), new CreatureLightPacket(player), new TextMessagePacket(MessageType.StatusDefault, "This is a test message"), new PlayerConditionsPacket(player)); if (descriptionMetadata.TryGetValue(IMapDescriptor.CreatureIdsToLearnMetadataKeyName, out object creatureIdsToLearnBoxed) && descriptionMetadata.TryGetValue(IMapDescriptor.CreatureIdsToForgetMetadataKeyName, out object creatureIdsToForgetBoxed) && creatureIdsToLearnBoxed is IEnumerable <uint> creatureIdsToLearn && creatureIdsToForgetBoxed is IEnumerable <uint> creatureIdsToForget) { notification.Sent += (client) => { foreach (var creatureId in creatureIdsToLearn) { client.AddKnownCreature(creatureId); } foreach (var creatureId in creatureIdsToForget) { client.RemoveKnownCreature(creatureId); } }; } notification.Send(new NotificationContext(context.Logger, context.MapDescriptor, context.CreatureFinder)); }
///// <summary> ///// Gets the type of exhaustion that this operation produces. ///// </summary> // public override ExhaustionType ExhaustionType => ExhaustionType.None; /// <summary> /// Attempts to place a creature on the map. /// </summary> /// <param name="context">A reference to the operation context.</param> /// <param name="targetTile">The tile to place the creature at.</param> /// <param name="creature">The creature to place.</param> /// <returns>True if the creature is successfully added to the map, false otherwise.</returns> protected bool PlaceCreature(IElevatedOperationContext context, ITile targetTile, ICreature creature) { targetTile.ThrowIfNull(nameof(targetTile)); creature.ThrowIfNull(nameof(creature)); var(addSuccessful, _) = targetTile.AddContent(context.ItemFactory, creature); if (addSuccessful) { context.CreatureManager.RegisterCreature(creature); if (creature is ICombatant combatant) { combatant.StatChanged += context.GameApi.CreatureStatChanged; combatant.Death += context.CombatApi.CombatantDeath; combatant.AttackTargetChanged += context.CombatApi.CombatantAttackTargetChanged; combatant.FollowTargetChanged += context.CombatApi.CreatureFollowTargetChanged; combatant.LocationChanged += context.GameApi.AfterThingLocationChanged; // As a creature that can sense. combatant.CreatureSeen += context.CombatApi.CreatureHasSeenCreature; combatant.CreatureLost += context.CombatApi.CreatureHasLostCreature; // As a skilled creature combatant.SkillChanged += context.GameApi.SkilledCreatureSkillChanged; // Find now-spectators of this creature to start tracking that guy. foreach (var spectator in context.Map.CreaturesThatCanSee(creature.Location)) { if (spectator is ICombatant combatantSpectator) { combatant.StartSensingCreature(combatantSpectator); combatantSpectator.StartSensingCreature(combatant); } } } /* * if (creature is IPlayer player) * { * player.Inventory.SlotChanged += context.GameApi.OnPlayerInventoryChanged; * } */ context.Logger.Debug($"Placed {creature.Name} at {targetTile.Location}."); var placedAtStackPos = targetTile.GetStackOrderOfThing(creature); this.SendNotification( context, new CreatureMovedNotification( () => { if (creature is IPlayer player) { return(context.Map.PlayersThatCanSee(creature.Location).Except(player.YieldSingleItem())); } return(context.Map.PlayersThatCanSee(creature.Location)); }, creature.Id,
/// <summary> /// Executes the operation's logic. /// </summary> /// <param name="context">The execution context for this operation.</param> protected abstract void Execute(IElevatedOperationContext context);
/// <summary> /// Executes the operation's logic. /// </summary> /// <param name="context">A reference to the operation context.</param> protected override void Execute(IElevatedOperationContext context) { const byte FallbackIndex = 0xFF; var inThingContainer = this.FromLocation.DecodeContainer(context.Map, context.ContainerManager, out byte index, this.FromCreature); // Adjust index if this a map location. var existingThing = (this.FromLocation.Type == LocationType.Map && (inThingContainer is ITile fromTile)) ? fromTile.FindItemWithTypeId(this.FromTypeId) : inThingContainer?.FindThingAtIndex(index); if (existingThing == null || !(existingThing is IItem existingItem)) { // Silent fail. return; } var creationArguments = new ItemCreationArguments() { TypeId = this.ToTypeId }; IThing thingCreated = context.ItemFactory.Create(creationArguments); if (thingCreated == null) { return; } // At this point, we have an item to change, and we were able to generate the new one, let's proceed. (bool replaceSuccessful, IThing replaceRemainder) = inThingContainer.ReplaceContent(context.ItemFactory, existingThing, thingCreated, index, existingItem.Amount); if (!replaceSuccessful || replaceRemainder != null) { this.AddContentToContainerOrFallback(context, inThingContainer, ref replaceRemainder, FallbackIndex, includeTileAsFallback: true, this.GetRequestor(context.CreatureFinder)); } if (replaceSuccessful) { if (inThingContainer is ITile atTile) { this.SendNotification( context, new TileUpdatedNotification( () => context.Map.PlayersThatCanSee(atTile.Location), atTile.Location, context.MapDescriptor.DescribeTile)); // Evaluate if the new item triggers a collision. // context.EventRulesApi.EvaluateRules(this, EventRuleType.Collision, new CollisionEventRuleArguments(fromCylinder.Location, existingThing, this.GetRequestor(context.CreatureFinder))); } if (thingCreated is IItem itemCreated) { // Start decay for items that need it. if (itemCreated.HasExpiration) { // TODO: the item location will change and this will break. var expirationOp = itemCreated.ExpirationTarget == 0 ? new DeleteItemOperation(requestorId: 0, thingCreated.TypeId, thingCreated.Location) : new ExpireItemOperation(requestorId: 0, itemCreated) as IOperation; context.Scheduler.ScheduleEvent(expirationOp, itemCreated.ExpirationTimeLeft); } } } }