public override IEnumerable <CooperativeTaskStatus> DoWork() { IConversationPartner partner = ConversationRequest.GetPartner(Context.LocalKnowledge); if (partner != null && DialogMachine != null) { DialogMachine.Name = Context.Name; ConversationRequest.SetPartner(DialogMachine.LocalKnowledge, partner); partner.Begin(); DialogMachine.Restart(); DialogMachine.IsEnabled = true; while (!DialogMachine.IsInEndState) { yield return(Wait()); } DialogMachine.IsEnabled = false; ConversationRequest.UnsetPartner(Context.LocalKnowledge); ConversationRequest.UnsetPartner(DialogMachine.LocalKnowledge); partner.End(); yield return(Finished(true)); } yield return(Finished(false)); }
public static void ExecuteEvents(this IConversationPartner conversationPartner, Game game, EventTrigger trigger) { var @event = conversationPartner.EventList.FirstOrDefault(e => e is ConversationEvent ce && ce.Interaction == ConversationEvent.InteractionType.Talk); if (@event != null) { uint x = (uint)game.RenderPlayer.Position.X; uint y = (uint)game.RenderPlayer.Position.Y; bool lastEventStatus = false; @event.ExecuteEvent(game.Map, game, ref trigger, x, y, game.CurrentTicks, ref lastEventStatus, out bool _, out var _, conversationPartner); } }
public override IEnumerable <CooperativeTaskStatus> DoWork() { IConversationPartner partner = ConversationRequest.GetPartner(Context.LocalKnowledge); if (partner != null) { IEnumerable <string> responses = ((StateMachine)Context).CurrentState.Transitions.OfType <ConversationTransition>().Select(t => t.Response); partner.Say(Context.Name, Text, responses); yield return(Finished(true)); } else { yield return(Finished(false)); } }
public static Event ExecuteEvent(this Event @event, Map map, Game game, ref EventTrigger trigger, uint x, uint y, uint ticks, ref bool lastEventStatus, out bool aborted, out EventProvider eventProvider, IConversationPartner conversationPartner = null) { eventProvider = null; // Note: Aborted means that an event is not even executed. It does not mean that a decision // box is answered with No for example. It is used when: // - A condition of a condition event is not met and there is no event that is triggered in that case. // - A text popup does not accept the given trigger. // This is important in 3D when there might be an event on the current block and on the next one. // For example buttons use 2 events (one for Eye interaction and one for Hand interaction). aborted = false; var events = conversationPartner == null ? map.Events : conversationPartner.Events; switch (@event.Type) { case EventType.Teleport: { if (trigger != EventTrigger.Move && trigger != EventTrigger.Always) { aborted = true; return(null); } if (!(@event is TeleportEvent teleportEvent)) { throw new AmbermoonException(ExceptionScope.Data, "Invalid teleport event."); } game.Teleport(teleportEvent); // Note: The teleporter from Mine 1 to 2 has a teleport event which has another // one as its next event. We have to avoid further event execution by just return // null here. I guess teleport events are not allowed to chain anything and this // might be a data bug. return(null); } case EventType.Door: { if (!(@event is DoorEvent doorEvent)) { throw new AmbermoonException(ExceptionScope.Data, "Invalid door event."); } if (!game.ShowDoor(doorEvent, false, false, map, x, y)) { // already unlocked lastEventStatus = true; return(doorEvent.Next); } return(null); } case EventType.Chest: { if (trigger == EventTrigger.Mouth) { aborted = true; return(null); } if (!(@event is ChestEvent chestEvent)) { throw new AmbermoonException(ExceptionScope.Data, "Invalid chest event."); } if (chestEvent.Flags.HasFlag(ChestEvent.ChestFlags.SearchSkillCheck) && game.RandomInt(0, 99) >= game.CurrentPartyMember.Abilities[Ability.Searching].TotalCurrentValue) { aborted = true; return(null); } game.ShowChest(chestEvent, false, false, map, new Position((int)x, (int)y)); return(null); } case EventType.PopupText: { if (!(@event is PopupTextEvent popupTextEvent)) { throw new AmbermoonException(ExceptionScope.Data, "Invalid text popup event."); } switch (trigger) { case EventTrigger.Move: if (!popupTextEvent.CanTriggerByMoving) { aborted = true; return(null); } break; case EventTrigger.Eye: if (!popupTextEvent.CanTriggerByCursor) { aborted = true; return(null); } break; case EventTrigger.Always: break; default: aborted = true; return(null); } bool eventStatus = lastEventStatus; EventProvider provider = null; if (conversationPartner != null) { provider = eventProvider = new EventProvider(); } game.ShowTextPopup(map, popupTextEvent, _ => { if (@event.Next != null) { if (conversationPartner == null) { map.TriggerEventChain(game, EventTrigger.Always, x, y, game.CurrentTicks, @event.Next, eventStatus); } else { provider?.Provide(popupTextEvent.Next); } } else { game.ResetMapCharacterInteraction(map); } }); return(null); // next event is only executed after popup response } case EventType.Spinner: { if (trigger == EventTrigger.Eye) { game.ShowMessagePopup(game.DataNameProvider.SeeRoundDiskInFloor); aborted = true; return(null); } if (trigger != EventTrigger.Move && trigger != EventTrigger.Always) { return(null); } if (!(@event is SpinnerEvent spinnerEvent)) { throw new AmbermoonException(ExceptionScope.Data, "Invalid spinner event."); } game.Spin(spinnerEvent.Direction, spinnerEvent.Next); break; } case EventType.Trap: { if (!(@event is TrapEvent trapEvent)) { throw new AmbermoonException(ExceptionScope.Data, "Invalid trap event."); } if (trigger == EventTrigger.Eye) { // Note: Eye will only detect the trap, not trigger it! game.ShowMessagePopup(game.DataNameProvider.YouNoticeATrap); aborted = true; return(null); } else if (trigger != EventTrigger.Move && trigger != EventTrigger.Always) { aborted = true; return(null); } game.TriggerTrap(trapEvent); return(null); // next event is only executed after trap effect } case EventType.RemoveBuffs: { if (!(@event is RemoveBuffsEvent removeBuffsEvent)) { throw new AmbermoonException(ExceptionScope.Data, "Invalid remove buffs event."); } if (removeBuffsEvent.AffectedBuff == null) // all { for (int i = 0; i < 6; ++i) { game.CurrentSavegame.ActiveSpells[i] = null; } game.UpdateLight(); } else { int index = (int)removeBuffsEvent.AffectedBuff; if (index < 6) { game.CurrentSavegame.ActiveSpells[index] = null; if (index == (int)Data.Enumerations.ActiveSpellType.Light) { game.UpdateLight(); } } } break; } case EventType.Riddlemouth: { if (trigger != EventTrigger.Always && trigger != EventTrigger.Eye && trigger != EventTrigger.Hand && trigger != EventTrigger.Mouth) { return(null); } if (!(@event is RiddlemouthEvent riddleMouthEvent)) { throw new AmbermoonException(ExceptionScope.Data, "Invalid riddle mouth event."); } game.ShowRiddlemouth(map, riddleMouthEvent, () => { map.TriggerEventChain(game, EventTrigger.Always, x, y, game.CurrentTicks, @event.Next, true); }); return(null); // next event is only executed after popup response } case EventType.Award: { if (!(@event is AwardEvent awardEvent)) { throw new AmbermoonException(ExceptionScope.Data, "Invalid award event."); } EventProvider provider = null; if (conversationPartner != null) { provider = eventProvider = new EventProvider(); } void Award(PartyMember partyMember, Action followAction) => game.AwardPlayer(partyMember, awardEvent, followAction); void Done() { if (awardEvent.Next != null) { if (conversationPartner == null) { TriggerEventChain(map, game, EventTrigger.Always, x, y, game.CurrentTicks, awardEvent.Next, true); } else { provider?.Provide(awardEvent.Next); } } } switch (awardEvent.Target) { case AwardEvent.AwardTarget.ActivePlayer: Award(game.CurrentPartyMember, Done); break; case AwardEvent.AwardTarget.All: if (awardEvent.TypeOfAward == AwardEvent.AwardType.HitPoints && (awardEvent.Operation == AwardEvent.AwardOperation.Decrease || awardEvent.Operation == AwardEvent.AwardOperation.DecreasePercentage)) { Func <PartyMember, uint> damageProvider = awardEvent.Operation == AwardEvent.AwardOperation.Decrease ? (Func <PartyMember, uint>)(_ => awardEvent.Value) : p => awardEvent.Value * p.HitPoints.TotalMaxValue / 100; // Note: Awards damage silently. game.DamageAllPartyMembers(damageProvider, p => p.Alive, null, Done, Ailment.None, false); } else { game.ForeachPartyMember(Award, p => p.Alive, Done); } break; default: return(awardEvent.Next); } return(null); } case EventType.ChangeTile: { if (!(@event is ChangeTileEvent changeTileEvent)) { throw new AmbermoonException(ExceptionScope.Data, "Invalid chest event."); } // Note: Savegame stores the front tile index for 2D and wall/object index for 3D. // Note: If map index is 0 (same map) we have to replace it with the real map index // for savegames. Otherwise it will be interpreted as "end of tile changes marker". if (changeTileEvent.MapIndex == 0) { changeTileEvent.MapIndex = map.Index; } if (changeTileEvent.X == 0) { changeTileEvent.X = x + 1; } if (changeTileEvent.Y == 0) { changeTileEvent.Y = y + 1; } game.UpdateMapTile(changeTileEvent, x, y); // Change tile events that are triggered directly should be disabled afterwards. // But only if they don't chain some important events. int eventIndex = map.EventList.IndexOf(changeTileEvent); if (eventIndex != -1) { bool remove = true; while (true) { var next = changeTileEvent.Next; if (next == null) { break; } if (next.Type == EventType.Teleport || next.Type == EventType.Chest || next.Type == EventType.Door || next.Type == EventType.EnterPlace || next.Type == EventType.Riddlemouth || next.Type == EventType.StartBattle) { remove = false; } break; } if (remove) { game.CurrentSavegame.ActivateEvent(map.Index, (uint)eventIndex, false); } } break; } case EventType.StartBattle: { if (trigger != EventTrigger.Move && trigger != EventTrigger.Always) { aborted = true; return(null); } if (!(@event is StartBattleEvent battleEvent)) { throw new AmbermoonException(ExceptionScope.Data, "Invalid battle event."); } game.StartBattle(battleEvent, battleEvent.Next, game.GetCombatBackgroundIndex(map, x, y)); return(null); } case EventType.EnterPlace: { if (trigger != EventTrigger.Move && trigger != EventTrigger.Always) { aborted = true; return(null); } if (!(@event is EnterPlaceEvent enterPlaceEvent)) { throw new AmbermoonException(ExceptionScope.Data, "Invalid place event."); } if (!game.EnterPlace(map, enterPlaceEvent)) { aborted = true; } return(null); } case EventType.Condition: { if (!(@event is ConditionEvent conditionEvent)) { throw new AmbermoonException(ExceptionScope.Data, "Invalid condition event."); } var mapEventIfFalse = conditionEvent.ContinueIfFalseWithMapEventIndex == 0xffff ? null : events[(int)conditionEvent.ContinueIfFalseWithMapEventIndex]; switch (conditionEvent.TypeOfCondition) { case ConditionEvent.ConditionType.GlobalVariable: if (game.CurrentSavegame.GetGlobalVariable(conditionEvent.ObjectIndex) != (conditionEvent.Value != 0)) { aborted = mapEventIfFalse == null; lastEventStatus = false; return(mapEventIfFalse); } break; case ConditionEvent.ConditionType.EventBit: if (game.CurrentSavegame.GetEventBit(1 + (conditionEvent.ObjectIndex >> 6), conditionEvent.ObjectIndex & 0x3f) != (conditionEvent.Value != 0)) { aborted = mapEventIfFalse == null; lastEventStatus = false; return(mapEventIfFalse); } break; case ConditionEvent.ConditionType.DoorOpen: if (game.CurrentSavegame.IsDoorLocked(conditionEvent.ObjectIndex) != (conditionEvent.Value == 0)) { aborted = mapEventIfFalse == null; lastEventStatus = false; return(mapEventIfFalse); } break; case ConditionEvent.ConditionType.ChestOpen: if (game.CurrentSavegame.IsChestLocked(conditionEvent.ObjectIndex) != (conditionEvent.Value == 0)) { aborted = mapEventIfFalse == null; lastEventStatus = false; return(mapEventIfFalse); } break; case ConditionEvent.ConditionType.CharacterBit: if (game.CurrentSavegame.GetCharacterBit(1 + (conditionEvent.ObjectIndex >> 5), conditionEvent.ObjectIndex & 0x1f) != (conditionEvent.Value != 0)) { aborted = mapEventIfFalse == null; lastEventStatus = false; return(mapEventIfFalse); } break; case ConditionEvent.ConditionType.PartyMember: { if (game.PartyMembers.Any(m => m.Index == conditionEvent.ObjectIndex) != (conditionEvent.Value != 0)) { aborted = mapEventIfFalse == null; lastEventStatus = false; return(mapEventIfFalse); } break; } case ConditionEvent.ConditionType.ItemOwned: { int totalCount = 0; foreach (var partyMember in game.PartyMembers) { foreach (var slot in partyMember.Inventory.Slots) { if (slot.ItemIndex == conditionEvent.ObjectIndex) { totalCount += slot.Amount; } } foreach (var slot in partyMember.Equipment.Slots) { if (slot.Value.ItemIndex == conditionEvent.ObjectIndex) { totalCount += slot.Value.Amount; } } } if (conditionEvent.Value == 0 && totalCount != 0) { aborted = mapEventIfFalse == null; lastEventStatus = false; return(mapEventIfFalse); } else if (conditionEvent.Value == 1 && (totalCount == 0 || totalCount < conditionEvent.Count)) { aborted = mapEventIfFalse == null; lastEventStatus = false; return(mapEventIfFalse); } break; } case ConditionEvent.ConditionType.UseItem: { if (trigger < EventTrigger.Item0) { // no item used aborted = mapEventIfFalse == null; lastEventStatus = false; return(mapEventIfFalse); } uint itemIndex = (uint)(trigger - EventTrigger.Item0); if (itemIndex != conditionEvent.ObjectIndex) { // wrong item used aborted = mapEventIfFalse == null; lastEventStatus = false; return(mapEventIfFalse); } break; } case ConditionEvent.ConditionType.KnowsKeyword: if (game.CurrentSavegame.IsDictionaryWordKnown(conditionEvent.ObjectIndex) != (conditionEvent.Value != 0)) { aborted = mapEventIfFalse == null; lastEventStatus = false; return(mapEventIfFalse); } break; case ConditionEvent.ConditionType.LastEventResult: if (lastEventStatus != (conditionEvent.Value != 0)) { aborted = mapEventIfFalse == null; lastEventStatus = false; return(mapEventIfFalse); } break; case ConditionEvent.ConditionType.GameOptionSet: if (game.CurrentSavegame.IsGameOptionActive((Data.Enumerations.Option)(1 << (int)conditionEvent.ObjectIndex)) != (conditionEvent.Value != 0)) { aborted = mapEventIfFalse == null; lastEventStatus = false; return(mapEventIfFalse); } break; case ConditionEvent.ConditionType.CanSee: { bool canSee = game.CanSee(); if (canSee != (conditionEvent.Value != 0)) { aborted = mapEventIfFalse == null; lastEventStatus = false; return(mapEventIfFalse); } break; } case ConditionEvent.ConditionType.Direction: { if ((game.PlayerDirection == (CharacterDirection)conditionEvent.ObjectIndex) != (conditionEvent.Value != 0)) { aborted = mapEventIfFalse == null; lastEventStatus = false; return(mapEventIfFalse); } break; } case ConditionEvent.ConditionType.HasAilment: if (game.CurrentPartyMember.Ailments.HasFlag((Ailment)(1 << (int)conditionEvent.ObjectIndex)) != (conditionEvent.Value != 0)) { aborted = mapEventIfFalse == null; lastEventStatus = false; return(mapEventIfFalse); } break; case ConditionEvent.ConditionType.Hand: if (trigger != EventTrigger.Hand) { aborted = mapEventIfFalse == null; lastEventStatus = false; return(mapEventIfFalse); } break; case ConditionEvent.ConditionType.SayWord: { if (trigger != EventTrigger.Mouth) { aborted = true; return(null); } game.SayWord(map, x, y, events, conditionEvent); return(null); } case ConditionEvent.ConditionType.EnterNumber: { game.EnterNumber(map, x, y, events, conditionEvent); return(null); } case ConditionEvent.ConditionType.Levitating: if (trigger != EventTrigger.Levitating) { aborted = mapEventIfFalse == null; lastEventStatus = false; return(mapEventIfFalse); } break; case ConditionEvent.ConditionType.HasGold: if ((game.CurrentPartyMember.Gold >= conditionEvent.ObjectIndex) != (conditionEvent.Value != 0)) { aborted = mapEventIfFalse == null; lastEventStatus = false; return(mapEventIfFalse); } break; case ConditionEvent.ConditionType.HasFood: if ((game.CurrentPartyMember.Food >= conditionEvent.ObjectIndex) != (conditionEvent.Value != 0)) { aborted = mapEventIfFalse == null; lastEventStatus = false; return(mapEventIfFalse); } break; case ConditionEvent.ConditionType.Eye: if (trigger != EventTrigger.Eye) { aborted = mapEventIfFalse == null; lastEventStatus = false; return(mapEventIfFalse); } break; } // For some follow-up events we won't proceed by using Eye, Hand or Mouth. if (conversationPartner == null && conditionEvent.Next != null && trigger != EventTrigger.Move && trigger != EventTrigger.Always && conditionEvent.TypeOfCondition != ConditionEvent.ConditionType.Hand && conditionEvent.TypeOfCondition != ConditionEvent.ConditionType.Eye && conditionEvent.TypeOfCondition != ConditionEvent.ConditionType.Hand && conditionEvent.TypeOfCondition != ConditionEvent.ConditionType.UseItem && conditionEvent.TypeOfCondition != ConditionEvent.ConditionType.Levitating && conditionEvent.TypeOfCondition != ConditionEvent.ConditionType.EnterNumber && conditionEvent.TypeOfCondition != ConditionEvent.ConditionType.SayWord && conditionEvent.TypeOfCondition != ConditionEvent.ConditionType.LastEventResult) { var next = conditionEvent.Next; while (next != null && (next.Type == EventType.Condition || next.Type == EventType.Action || next.Type == EventType.Award || next.Type == EventType.ChangeMusic || next.Type == EventType.Dice100Roll)) { next = next.Next; } if (next != null) { switch (next.Type) { case EventType.Teleport: case EventType.StartBattle: case EventType.EnterPlace: case EventType.Riddlemouth: aborted = true; return(null); } } } trigger = EventTrigger.Always; // following events should not dependent on the trigger anymore break; } case EventType.Action: { if (!(@event is ActionEvent actionEvent)) { throw new AmbermoonException(ExceptionScope.Data, "Invalid action event."); } bool ClearSetToggle(Func <bool> currentValueRetriever) { switch (actionEvent.Value) { case 0: // Clear return(false); case 1: // Set return(true); case 2: // Toggle return(!currentValueRetriever()); default: // Leave as it is return(currentValueRetriever()); } } switch (actionEvent.TypeOfAction) { case ActionEvent.ActionType.SetGlobalVariable: game.CurrentSavegame.SetGlobalVariable(actionEvent.ObjectIndex, ClearSetToggle(() => game.CurrentSavegame.GetGlobalVariable(actionEvent.ObjectIndex))); break; case ActionEvent.ActionType.SetEventBit: { var mapIndex = 1 + (actionEvent.ObjectIndex >> 6); var eventIndex = actionEvent.ObjectIndex & 0x3f; game.SetMapEventBit(mapIndex, eventIndex, ClearSetToggle(() => game.CurrentSavegame.GetEventBit(mapIndex, eventIndex))); break; } case ActionEvent.ActionType.LockDoor: if (!ClearSetToggle(() => !game.CurrentSavegame.IsDoorLocked(actionEvent.ObjectIndex))) { game.CurrentSavegame.UnlockDoor(actionEvent.ObjectIndex); } else { game.CurrentSavegame.LockDoor(actionEvent.ObjectIndex); } break; case ActionEvent.ActionType.LockChest: if (!ClearSetToggle(() => !game.CurrentSavegame.IsChestLocked(actionEvent.ObjectIndex))) { game.CurrentSavegame.UnlockChest(actionEvent.ObjectIndex); } else { game.CurrentSavegame.LockChest(actionEvent.ObjectIndex); } break; case ActionEvent.ActionType.SetCharacterBit: { var mapIndex = 1 + (actionEvent.ObjectIndex >> 5); var eventIndex = actionEvent.ObjectIndex & 0x1f; game.SetMapCharacterBit(mapIndex, eventIndex, ClearSetToggle(() => game.CurrentSavegame.GetCharacterBit(mapIndex, eventIndex))); break; } case ActionEvent.ActionType.AddItem: { var itemIndex = actionEvent.ObjectIndex; if (itemIndex > 0) { if (actionEvent.Value == 1) // Add { int numberToAdd = Math.Max(1, (int)actionEvent.Count); foreach (var partyMember in game.PartyMembers) { numberToAdd = game.DropItem(partyMember, itemIndex, numberToAdd); if (numberToAdd == 0) { break; } } // Ignore the rest as we couldn't do anything about it. } else if (actionEvent.Value == 0) // Remove { int numberToRemove = (int)Math.Max(1, actionEvent.Count); // Prefer inventory foreach (var partyMember in game.PartyMembers) { foreach (var slot in partyMember.Inventory.Slots) { if (slot?.ItemIndex == itemIndex) { int slotCount = slot.Amount; slot.Remove(Math.Min(numberToRemove, slotCount)); int numRemoved = slotCount - slot.Amount; game.InventoryItemRemoved(itemIndex, numRemoved, partyMember); numberToRemove -= numRemoved; if (numberToRemove == 0) { break; } } } if (numberToRemove == 0) { break; } } foreach (var partyMember in game.PartyMembers) { foreach (var slot in partyMember.Equipment.Slots.Values) { if (slot?.ItemIndex == itemIndex) { int slotCount = slot.Amount; bool cursed = slot.Flags.HasFlag(ItemSlotFlags.Cursed); slot.Remove(Math.Min(numberToRemove, slotCount)); int numRemoved = slotCount - slot.Amount; game.EquipmentRemoved(partyMember, itemIndex, numRemoved, cursed); numberToRemove -= numRemoved; if (numberToRemove == 0) { break; } } } if (numberToRemove == 0) { break; } } } } break; } case ActionEvent.ActionType.AddKeyword: // Note: This may also remove a keyword but this is no real use case. // We will only add keywords here and ignore the action value. // The original code seems to do the same. game.CurrentSavegame.AddDictionaryWord(actionEvent.ObjectIndex); break; case ActionEvent.ActionType.SetGameOption: { var option = (Data.Enumerations.Option)(1 << (int)actionEvent.ObjectIndex); game.CurrentSavegame.SetGameOption(option, ClearSetToggle(() => game.CurrentSavegame.IsGameOptionActive(option))); break; } case ActionEvent.ActionType.SetDirection: { game.SetPlayerDirection((CharacterDirection)(actionEvent.ObjectIndex % 5)); break; } case ActionEvent.ActionType.AddAilment: { var ailment = (Ailment)(1 << (int)actionEvent.ObjectIndex); if (ClearSetToggle(() => game.CurrentPartyMember.Ailments.HasFlag(ailment))) { game.AddAilment(ailment); } else { game.RemoveAilment(ailment, game.CurrentPartyMember); } break; } case ActionEvent.ActionType.AddGold: // Note: The original code always removes the gold. But as this is not used // at all I think controlling the behavior with the value bit is better. if (actionEvent.Value == 1) // Add { game.DistributeGold(actionEvent.ObjectIndex, true); } else if (actionEvent.Value == 0) // Remove { var partyMembers = game.PartyMembers.ToArray(); int totalGoldToRemove = (int)Math.Min(actionEvent.ObjectIndex, partyMembers.Sum(p => p.Gold)); int goldToRemovePerPlayer = totalGoldToRemove / partyMembers.Length; int singleGoldMemberCount = totalGoldToRemove % partyMembers.Length; foreach (var partyMember in partyMembers) { int goldToRemove = goldToRemovePerPlayer; if (singleGoldMemberCount != 0) { ++goldToRemove; } int removeAmount = Math.Min(goldToRemove, partyMember.Gold); if (removeAmount == goldToRemove) { --singleGoldMemberCount; } partyMember.Gold = (ushort)Math.Max(0, partyMember.Gold - removeAmount); partyMember.TotalWeight -= (uint)removeAmount * Character.GoldWeight; totalGoldToRemove -= removeAmount; } if (totalGoldToRemove != 0) { foreach (var partyMember in partyMembers) { if (partyMember.Gold != 0) { int removeAmount = Math.Min(totalGoldToRemove, partyMember.Gold); partyMember.Gold = (ushort)Math.Max(0, partyMember.Gold - removeAmount); partyMember.TotalWeight -= (uint)removeAmount * Character.GoldWeight; totalGoldToRemove -= removeAmount; } } } } break; case ActionEvent.ActionType.AddFood: // Note: The original code always removes the food. But as this is not used // at all I think controlling the behavior with the value bit is better. if (actionEvent.Value == 1) // Add { game.DistributeFood(actionEvent.ObjectIndex, true); } else if (actionEvent.Value == 0) // Remove { var partyMembers = game.PartyMembers.ToArray(); int totalFoodToRemove = (int)Math.Min(actionEvent.ObjectIndex, partyMembers.Sum(p => p.Food)); int foodToRemovePerPlayer = totalFoodToRemove / partyMembers.Length; int singleFoodMemberCount = totalFoodToRemove % partyMembers.Length; foreach (var partyMember in partyMembers) { int foodToRemove = foodToRemovePerPlayer; if (singleFoodMemberCount != 0) { ++foodToRemove; } int removeAmount = Math.Min(foodToRemove, partyMember.Food); if (removeAmount == foodToRemove) { --singleFoodMemberCount; } partyMember.Food = (ushort)Math.Max(0, partyMember.Food - removeAmount); partyMember.TotalWeight -= (uint)removeAmount * Character.FoodWeight; totalFoodToRemove -= removeAmount; } if (totalFoodToRemove != 0) { foreach (var partyMember in partyMembers) { if (partyMember.Food != 0) { int removeAmount = Math.Min(totalFoodToRemove, partyMember.Food); partyMember.Food = (ushort)Math.Max(0, partyMember.Food - removeAmount); partyMember.TotalWeight -= (uint)removeAmount * Character.FoodWeight; totalFoodToRemove -= removeAmount; } } } } break; } break; } case EventType.Dice100Roll: { if (!(@event is Dice100RollEvent diceEvent)) { throw new AmbermoonException(ExceptionScope.Data, "Invalid dice 100 event."); } var mapEventIfFalse = diceEvent.ContinueIfFalseWithMapEventIndex == 0xffff ? null : events[(int)diceEvent.ContinueIfFalseWithMapEventIndex]; lastEventStatus = game.RollDice100() < diceEvent.Chance; return(lastEventStatus ? diceEvent.Next : mapEventIfFalse); } case EventType.Conversation: { if (!(@event is ConversationEvent conversationEvent)) { throw new AmbermoonException(ExceptionScope.Data, "Invalid conversation event."); } switch (conversationEvent.Interaction) { case ConversationEvent.InteractionType.Talk: if (trigger != EventTrigger.Mouth) { aborted = true; return(null); } game.ShowConversation(conversationPartner, conversationEvent, new Game.ConversationItems()); return(null); default: // Note: this is handled by the conversation window. // It should never appear inside a running event chain. aborted = true; return(null); } } case EventType.PrintText: case EventType.Create: case EventType.Exit: case EventType.Interact: { // Note: These are only used by conversations and are handled in // game.ShowConversation. So we don't need to do anything here. throw new AmbermoonException(ExceptionScope.Data, "Conversation events must not be called outside of conversations."); } case EventType.Decision: { if (!(@event is DecisionEvent decisionEvent)) { throw new AmbermoonException(ExceptionScope.Data, "Invalid decision event."); } game.ShowDecisionPopup(map, decisionEvent, response => { if (response == PopupTextEvent.Response.Yes) { map.TriggerEventChain(game, EventTrigger.Always, x, y, game.CurrentTicks, @event.Next, true); } else // Close and No have the same meaning here { if (decisionEvent.NoEventIndex != 0xffff) { map.TriggerEventChain(game, EventTrigger.Always, x, y, game.CurrentTicks, events[(int)decisionEvent.NoEventIndex], false); } } }); return(null); // next event is only executed after popup response } case EventType.ChangeMusic: if (!(@event is ChangeMusicEvent changeMusicEvent)) { throw new AmbermoonException(ExceptionScope.Data, "Invalid change music event."); } game.PlayMusic((Data.Enumerations.Song)changeMusicEvent.MusicIndex); break; case EventType.Spawn: if (!(@event is SpawnEvent spawnEvent)) { throw new AmbermoonException(ExceptionScope.Data, "Invalid spawn event."); } game.SpawnTransport(spawnEvent.MapIndex == 0 ? map.Index : spawnEvent.MapIndex, spawnEvent.X, spawnEvent.Y, spawnEvent.TravelType); break; case EventType.Unknown: // TODO break; default: Console.WriteLine($"Unknown event type found: {@event.Type}"); return(@event.Next); } lastEventStatus = true; return(@event.Next); }
public static void SetPartner(Blackboard knowledge, IConversationPartner partner) { knowledge.Set(PartnerKey, partner); }
public static void Initiate(Blackboard knowledge, IConversationPartner partner) { SetPartner(knowledge, partner); }