Skip to content

Commit

Permalink
Improved NPC interaction, characters are only visible when they shoul…
Browse files Browse the repository at this point in the history
…d be
  • Loading branch information
Pyrdacor committed Oct 5, 2020
1 parent 0c97129 commit b8acb9f
Show file tree
Hide file tree
Showing 12 changed files with 144 additions and 41 deletions.
21 changes: 13 additions & 8 deletions Ambermoon.Core/EventExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -241,21 +241,26 @@ public static Event ExecuteEvent(this Event @event, Map map, Game game,

switch (conversationEvent.Interaction)
{
case ConversationEvent.InteractionType.Keyword:
// TODO: this has to be handled by the conversation window
aborted = true;
return null;
case ConversationEvent.InteractionType.ShowItem:
// TODO: this has to be handled by the conversation window
aborted = true;
return null;
case ConversationEvent.InteractionType.GiveItem:
// TODO: this has to be handled by the conversation window
aborted = true;
return null;
case ConversationEvent.InteractionType.Talk:
if (trigger != EventTrigger.Mouth)
{
aborted = true;
return null;
}
break;
case ConversationEvent.InteractionType.Look:
if (trigger != EventTrigger.Eye)
{
aborted = true;
return null;
}
break;
case ConversationEvent.InteractionType.Keyword:
case ConversationEvent.InteractionType.Leave:
// TODO: this has to be handled by the conversation window
aborted = true;
return null;
Expand Down
34 changes: 32 additions & 2 deletions Ambermoon.Core/Game.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,7 @@ public void OnMouseDown(Position position, MouseButtons buttons)
return;

relativePosition.Offset(-mapViewArea.Left, -mapViewArea.Top);
var previousCursor = cursor.Type;

if (cursor.Type == CursorType.Eye)
TriggerMapEvents(EventTrigger.Eye, relativePosition);
Expand All @@ -1106,7 +1107,10 @@ public void OnMouseDown(Position position, MouseButtons buttons)
}

if (cursor.Type > CursorType.Wait)
CursorType = CursorType.Sword;
{
if (cursor.Type != CursorType.Click || previousCursor == CursorType.Click)
CursorType = CursorType.Sword;
}
return;
}
else
Expand Down Expand Up @@ -1262,7 +1266,7 @@ void UpdateCursor(Position cursorPosition, MouseButtons buttons)

public void OnMouseMove(Position position, MouseButtons buttons)
{
if (!InputEnable)
if (!InputEnable && !layout.PopupActive)
UntrapMouse();

if (trapped)
Expand Down Expand Up @@ -2206,6 +2210,19 @@ internal void SetMapEventBit(uint mapIndex, uint eventListIndex, bool bit)
internal void SetMapCharacterBit(uint mapIndex, uint characterIndex, bool bit)
{
CurrentSavegame.SetCharacterBit(mapIndex, characterIndex, bit);

// TODO: what if we change an adjacent world map which is visible instead? is there even a use case?
if (Map.Index == mapIndex)
{
if (is3D)
{
renderMap3D.UpdateCharacterVisibility(characterIndex);
}
else
{
renderMap2D.UpdateCharacterVisibility(characterIndex);
}
}
}

internal void ResetStorageItem(int slotIndex, ItemSlot item)
Expand Down Expand Up @@ -2462,6 +2479,19 @@ internal void OpenDictionary(Action<string> choiceHandler)
popup.Closed += UntrapMouse;
}

internal void ShowMessagePopup(string text)
{
InputEnable = false;
// Simple text popup
var popup = layout.OpenTextPopup(ProcessText(text), () =>
{
InputEnable = true;
ResetCursor();
}, true, true, false, TextAlign.Center);
CursorType = CursorType.Click;
TrapMouse(popup.ContentArea);
}

internal void ShowTextPopup(IText text, Action<PopupTextEvent.Response> responseHandler)
{
InputEnable = false;
Expand Down
21 changes: 19 additions & 2 deletions Ambermoon.Core/Render/Character2D.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public enum State
readonly Func<Character2DAnimationInfo> animationInfoProvider;
readonly Func<uint> paletteIndexProvider;
readonly Func<Position> drawOffsetProvider;
bool active = true;
bool visible = true;
Character2DAnimationInfo CurrentAnimationInfo => animationInfoProvider();
public uint CurrentBaseFrameIndex { get; private set; }
public uint CurrentFrameIndex { get; private set; }
Expand All @@ -48,8 +50,23 @@ public int BaselineOffset
public Rect DisplayArea => new Rect(sprite.X, sprite.Y, sprite.Width, sprite.Height);
public bool Visible
{
get => sprite.Visible;
set => sprite.Visible = value;
get => visible;
set
{
visible = value;

sprite.Visible = visible && active;
}
}
public bool Active
{
get => active;
set
{
active = value;

sprite.Visible = visible && active;
}
}
public State CurrentState { get; private set; }
public uint NumFrames => Math.Max(1, CurrentState switch
Expand Down
2 changes: 1 addition & 1 deletion Ambermoon.Core/Render/IMapCharacter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
internal interface IMapCharacter
{
void Move(int x, int y, uint ticks);
bool Interact(EventTrigger trigger);
bool Interact(EventTrigger trigger, bool bed);
Position Position { get; }
}
}
46 changes: 37 additions & 9 deletions Ambermoon.Core/Render/MapCharacter2D.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ public void Move(int x, int y, uint ticks)

public override void Update(uint ticks, Time gameTime)
{
if (!Active)
return;

Position newPosition = Position;

void MoveRandom()
Expand Down Expand Up @@ -181,7 +184,7 @@ void MoveRandom()
base.Update(ticks, gameTime);
}

public bool Interact(EventTrigger trigger)
public bool Interact(EventTrigger trigger, bool bed)
{
switch (trigger)
{
Expand All @@ -199,29 +202,54 @@ public bool Interact(EventTrigger trigger)
return false;
}

if (trigger == EventTrigger.Mouth && bed &&
!characterReference.CharacterFlags.HasFlag(Flags.UseTileset))
{
game.ShowMessagePopup(game.DataNameProvider.PersonAsleepMessage);
return true;
}

if (characterReference.CharacterFlags.HasFlag(Flags.TextPopup))
{
if (trigger == EventTrigger.Eye)
{
// TODO
// Popup NPCs can't be looked at but only talked to.
return false;
}
else if (trigger == EventTrigger.Mouth)
{
ShowPopup(map.Texts[(int)characterReference.Index]);
return true;
}
}

bool HandleConversation(IConversationPartner conversationPartner)
{
if (trigger == EventTrigger.Eye)
{
game.ShowTextPopup(game.ProcessText(conversationPartner.Texts[0]), null);
return true;
}
else if (trigger == EventTrigger.Mouth)
{
if (conversationPartner == null)
throw new AmbermoonException(ExceptionScope.Data, "Invalid NPC or party member index.");

conversationPartner.ExecuteEvents(game, trigger);
return true;
}
else
{
return false;
}
}

switch (characterReference.Type)
{
case CharacterType.PartyMember:
// TODO
break;
return HandleConversation(game.CurrentSavegame.PartyMembers[(int)characterReference.Index]);
case CharacterType.NPC:
var npc = game.CharacterManager.GetNPC(characterReference.Index);
if (npc == null)
throw new AmbermoonException(ExceptionScope.Data, "Invalid NPC index.");
npc.ExecuteEvents(game, trigger);
break;
return HandleConversation(game.CharacterManager.GetNPC(characterReference.Index));
case CharacterType.Monster:
// TODO
break;
Expand Down
36 changes: 24 additions & 12 deletions Ambermoon.Core/Render/RenderMap2D.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ internal class RenderMap2D
uint ticksPerFrame = 0;
bool worldMap = false;
uint lastFrame = 0;
readonly List<MapCharacter2D> mapCharacters = new List<MapCharacter2D>();
readonly Dictionary<uint, MapCharacter2D> mapCharacters = new Dictionary<uint, MapCharacter2D>();

public event Action<Map[]> MapChanged;

Expand Down Expand Up @@ -109,11 +109,14 @@ public void Update(uint ticks, Time gameTime)
}

foreach (var mapCharacter in mapCharacters)
mapCharacter.Update(ticks, gameTime);
mapCharacter.Value.Update(ticks, gameTime);
}

bool TestCharacterInteraction(MapCharacter2D mapCharacter, bool cursor, Position position)
{
if (!mapCharacter.Visible || !mapCharacter.Active)
return false;

if (position == mapCharacter.Position)
return true;

Expand All @@ -127,10 +130,11 @@ public bool TriggerEvents(IRenderPlayer player, EventTrigger trigger,
{
// First check character interaction
var position = new Position((int)x, trigger == EventTrigger.Move && !Map.IsWorldMap ? (int)y - 1 : (int)y);
foreach (var mapCharacter in mapCharacters)
foreach (var mapCharacter in mapCharacters.ToList())
{
if (TestCharacterInteraction(mapCharacter, trigger != EventTrigger.Move, position) &&
mapCharacter.Interact(trigger))
if (TestCharacterInteraction(mapCharacter.Value, trigger != EventTrigger.Move, position) &&
mapCharacter.Value.Interact(trigger, this[(uint)mapCharacter.Value.Position.X,
(uint)mapCharacter.Value.Position.Y + (mapCharacter.Value.IsNPC ? 1u : 0u)].Type == Map.TileType.Bed))
return true;
}
}
Expand Down Expand Up @@ -239,7 +243,7 @@ public void Destroy()

void ClearCharacters()
{
mapCharacters.ForEach(character => character.Destroy());
mapCharacters.Values.ToList().ForEach(character => character.Destroy());
mapCharacters.Clear();
}

Expand Down Expand Up @@ -503,16 +507,16 @@ public void SetMap(Map map, uint initialScrollX = 0, uint initialScrollY = 0)

ClearCharacters();

uint characterIndex = 0;

foreach (var characterReference in map.CharacterReferences)
for (uint characterIndex = 0; characterIndex < map.CharacterReferences.Length; ++characterIndex)
{
if (characterReference == null || game.CurrentSavegame.GetCharacterBit(map.Index, characterIndex++))
var characterReference = map.CharacterReferences[characterIndex];

if (characterReference == null)
break;

var mapCharacter = MapCharacter2D.Create(game, renderView, mapManager, this, characterReference);
mapCharacter.Visible = true;
mapCharacters.Add(mapCharacter);
mapCharacter.Active = !game.CurrentSavegame.GetCharacterBit(map.Index, characterIndex);
mapCharacters.Add(characterIndex, mapCharacter);
}

ScrollTo(initialScrollX, initialScrollY, true); // also updates tiles etc
Expand All @@ -523,6 +527,14 @@ public void SetMap(Map map, uint initialScrollX = 0, uint initialScrollY = 0)
InvokeMapChangedHandler(map);
}

public void UpdateCharacterVisibility(uint characterIndex)
{
if (Map.CharacterReferences[characterIndex] == null)
throw new AmbermoonException(ExceptionScope.Application, "Null map character");

mapCharacters[characterIndex].Active = !game.CurrentSavegame.GetCharacterBit(Map.Index, characterIndex);
}

void InvokeMapChangedHandler(params Map[] maps)
{
MapChanged?.Invoke(maps);
Expand Down
9 changes: 8 additions & 1 deletion Ambermoon.Core/Render/RenderMap3D.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
using Ambermoon.Geometry;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

namespace Ambermoon.Render
Expand Down Expand Up @@ -443,6 +442,14 @@ public void Update(uint ticks, Time gameTime)
mapObject.Value.ForEach(obj => obj.Update(ticks));
}

public void UpdateCharacterVisibility(uint characterIndex)
{
if (Map.CharacterReferences[characterIndex] == null)
throw new AmbermoonException(ExceptionScope.Application, "Null map character");

// TODO
}

public CollisionDetectionInfo3D GetCollisionDetectionInfo(Position position)
{
var info = new CollisionDetectionInfo3D();
Expand Down
8 changes: 4 additions & 4 deletions Ambermoon.Core/UI/Layout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@ internal Popup OpenPopup(Position position, int columns, int rows,

internal Popup OpenTextPopup(IText text, Position position, int maxWidth, int maxTextHeight,
bool disableButtons = true, bool closeOnClick = true, bool transparent = false,
TextColor textColor = TextColor.Gray, Action closeAction = null)
TextColor textColor = TextColor.Gray, Action closeAction = null, TextAlign textAlign = TextAlign.Left)
{
ClosePopup(false);
var processedText = RenderView.TextProcessor.WrapText(text,
Expand All @@ -550,19 +550,19 @@ internal Popup OpenTextPopup(IText text, Position position, int maxWidth, int ma
CloseOnClick = closeOnClick
};
bool scrolling = textBounds.Height / Global.GlyphLineHeight < processedText.LineCount;
activePopup.AddText(textBounds, text, textColor, TextAlign.Left, true, 1, scrolling);
activePopup.AddText(textBounds, text, textColor, textAlign, true, 1, scrolling);
if (closeAction != null)
activePopup.Closed += closeAction;
return activePopup;
}

internal Popup OpenTextPopup(IText text, Action closeAction, bool disableButtons = false,
bool closeOnClick = true, bool transparent = false)
bool closeOnClick = true, bool transparent = false, TextAlign textAlign = TextAlign.Left)
{
const int maxTextWidth = 256;
const int maxTextHeight = 112;
var popup = OpenTextPopup(text, new Position(16, 53), maxTextWidth, maxTextHeight, disableButtons,
closeOnClick, transparent, TextColor.Gray, closeAction);
closeOnClick, transparent, TextColor.Gray, closeAction, textAlign);
return popup;
}

Expand Down
3 changes: 3 additions & 0 deletions Ambermoon.Core/UI/Popup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ internal class Popup
readonly List<Button> buttons = new List<Button>();
readonly List<TextInput> inputs = new List<TextInput>();
ListBox listBox = null;
public Rect ContentArea { get; }

public Popup(Game game, IRenderView renderView, Position position, int columns, int rows, bool transparent,
byte displayLayerOffset = 0)
Expand Down Expand Up @@ -75,6 +76,8 @@ void AddBorder(PopupFrame frame, int column, int row)
fill.X = position.X + 16;
fill.Y = position.Y + 16;
fill.Visible = true;

ContentArea = new Rect(fill.X, fill.Y, fill.Width, fill.Height);
}
}

Expand Down
3 changes: 1 addition & 2 deletions Ambermoon.Data.Common/Event.cs
Original file line number Diff line number Diff line change
Expand Up @@ -465,8 +465,7 @@ public enum InteractionType
GiveItem = 2,
// TODO: ask to join, ask to leave, give gold, give food
Talk = 7,
Look = 8
// TODO: are there others like Touch?
Leave = 8
}

public InteractionType Interaction { get; set; }
Expand Down
Loading

0 comments on commit b8acb9f

Please sign in to comment.