/// <summary> /// Forcefully adds parsed content elements to this container. /// </summary> /// <param name="tile">The tile to add content to.</param> /// <param name="contentElements">The content elements to add.</param> private void AddContent(ITile tile, IEnumerable <IParsedElement> contentElements) { contentElements.ThrowIfNull(nameof(contentElements)); // load and add tile flags and contents. foreach (var e in contentElements) { foreach (var attribute in e.Attributes) { if (attribute.Name.Equals("Content")) { if (attribute.Value is IEnumerable <IParsedElement> elements) { var thingStack = new Stack <IThing>(); foreach (var element in elements) { IItem item = this.ItemFactory.CreateItem(ItemCreationArguments.WithTypeId((ushort)element.Id)); if (item == null) { this.Logger.LogWarning($"Item with id {element.Id} not found in the catalog, skipping."); continue; } this.SetItemAttributes(item, element.Attributes); thingStack.Push(item); } // Add them in reversed order. while (thingStack.Count > 0) { var thing = thingStack.Pop(); tile.AddContent(this.ItemFactory, thing); if (thing is IContainedThing containedThing) { containedThing.ParentContainer = tile; } } } } else { // it's a flag if (Enum.TryParse(attribute.Name, out TileFlag flagMatch)) { tile.SetFlag(flagMatch); } else { this.Logger.LogWarning($"Unknown flag [{attribute.Name}] found on tile at location {tile.Location}."); } } } } }
/// <summary> /// Attempts to load all tiles within a 3 dimensional coordinates window. /// </summary> /// <param name="fromX">The start X coordinate for the load window.</param> /// <param name="toX">The end X coordinate for the load window.</param> /// <param name="fromY">The start Y coordinate for the load window.</param> /// <param name="toY">The end Y coordinate for the load window.</param> /// <param name="fromZ">The start Z coordinate for the load window.</param> /// <param name="toZ">The end Z coordinate for the load window.</param> /// <returns>A collection of ordered pairs containing the <see cref="Location"/> and its corresponding <see cref="ITile"/>.</returns> public IEnumerable <(Location Location, ITile Tile)> Load(int fromX, int toX, int fromY, int toY, sbyte fromZ, sbyte toZ) { if (fromZ != 7) { return(Enumerable.Empty <(Location, ITile)>()); } var tuplesAdded = new List <(Location loc, ITile tile)>(); for (int x = fromX; x <= toX; x++) { for (int y = fromY; y <= toY; y++) { var groundItem = this.ItemFactory.CreateItem(ItemCreationArguments.WithTypeId(GrassTypeId)); var location = new Location() { X = x, Y = y, Z = fromZ }; var newTuple = (location, this.TileFactory.CreateTile(location, groundItem)); tuplesAdded.Add(newTuple); } } foreach (var(loc, tile) in tuplesAdded) { this.tilesAndLocations.TryAdd(loc, tile); } return(tuplesAdded); }
/// <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> /// Attempts to load all tiles within a map window. /// </summary> /// <param name="window">The parameters to for the window to load.</param> /// <returns>A collection of <see cref="ITile"/>s loaded.</returns> public IEnumerable <ITile> Load(IMapWindowDimensions window) { window.ThrowIfNull(nameof(window)); if (window.FromZ != 7) { return(Enumerable.Empty <ITile>()); } var tiles = new List <ITile>(); for (int x = window.FromX; x <= window.ToX; x++) { for (int y = window.FromY; y <= window.ToY; y++) { var groundItem = this.itemFactory.CreateItem(ItemCreationArguments.WithTypeId(GrassTypeId)); var location = new Location() { X = x, Y = y, Z = window.FromZ }; var newTile = this.tileFactory.CreateTile(location, groundItem); tiles.Add(newTile); } } return(tiles); }
private void SetItemAttributes(IItem item, IList <IParsedAttribute> attributes) { if (attributes == null) { return; } foreach (var attribute in attributes) { if ("Content".Equals(attribute.Name) && item is IContainerItem containerItem) { if (!(attribute.Value is IEnumerable <IParsedElement> contentElements) || !contentElements.Any()) { continue; } foreach (var element in contentElements) { IItem contentItem = this.ItemFactory.CreateItem(ItemCreationArguments.WithTypeId((ushort)element.Id)); if (contentItem == null) { this.Logger.LogWarning($"Item with id {element.Id} not found in the catalog, skipping."); continue; } this.SetItemAttributes(contentItem, element.Attributes); // TODO: we should be able to go over capacity here. containerItem.AddContent(this.ItemFactory, contentItem, 0xFF); } continue; } // These are safe to add as Attributes of the item. if (!Enum.TryParse(attribute.Name, out CipItemAttribute cipAttr) || !(cipAttr.ToItemAttribute() is ItemAttribute itemAttribute)) { this.Logger.LogWarning($"Unsupported attribute {attribute.Name} on {item.Type.Name}, ignoring."); continue; } try { item.Attributes[itemAttribute] = attribute.Value as IConvertible; } catch { this.Logger.LogWarning($"Unexpected attribute {attribute.Name} with illegal value {attribute.Value} on item {item.Type.Name}, ignoring."); } } }
/// <summary> /// Executes the operation's logic. /// </summary> /// <param name="context">A reference to the operation context.</param> protected override void Execute(IOperationContext 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, }; context.GameApi.RemoveCreatureFromGame(this.Creature); if (context.ItemFactory.Create(corpseCreationArguments) is IThing corpseCreated && context.GameApi.AddContentToContainerOrFallback(creatureTile, ref corpseCreated)) { context.GameApi.CreateItemAtLocation(creatureTile.Location, context.PredefinedItemSet.FindPoolForBloodType(this.Creature.BloodType)); } } }
/// <summary> /// Executes the operation's logic. /// </summary> /// <param name="context">A reference to the operation context.</param> protected override void Execute(IOperationContext context) { if (this.Creature is IPlayer player) { this.SendNotification(context, new PlayerDeathNotification(player)); } // 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.CreatureManager.FindCreatureById(combatantId) is ICombatant combatantGainingExp) { combatantGainingExp.AddExperience(expToGive); } } } // Remove the creature... if (context.Map.HasTileAt(this.Creature.Location, out ITile creatureTile)) { // Add the corpse. var corpseCreationArguments = new ItemCreationArguments() { TypeId = this.Creature.CorpseTypeId, }; context.GameApi.RemoveCreatureFromGame(this.Creature); if (context.ItemFactory.CreateItem(corpseCreationArguments) is IItem corpseCreated && creatureTile.AddItemToContainerRecursively(context.ItemFactory, ref corpseCreated)) { context.GameApi.CreateItemAtLocationAsync(creatureTile.Location, context.PredefinedItemSet.FindPoolForBloodType(this.Creature.BloodType)); } } }
/// <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 condition's logic. /// </summary> /// <param name="context">The execution context for this condition.</param> protected override void Execute(IConditionContext context) { var inThingContainer = this.Item.ParentContainer; if (!(this.Item is IThing existingThing) || !this.Item.HasExpiration || inThingContainer == null) { // Silent fail. return; } if (this.Item.ExpirationTarget == 0) { // We will delete this item. context.Scheduler.ScheduleEvent(new DeleteItemOperation(requestorId: 0, this.Item)); return; } var creationArguments = ItemCreationArguments.WithTypeId(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) { 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); } } } }