Add woodland atmosphere, interaction cues and scene restoration
This commit is contained in:
1 parent
88ab26dcec
commit
e153dc9b15
12 files changed
+256
-9
No files matched your search
@@ -1,2 +1,7 @@
|
|||||||
mod_rpg_name = RPG World
|
mod_rpg_name = RPG World
|
||||||
mod_rpg_desc = Top-down tile world in the spirit of RPG Maker: three tile layers and a collision layer, grid walking with WASD, events on the action key E/F, random encounters and battles that return to the map.
|
mod_rpg_desc = Top-down tile world in the spirit of RPG Maker: three tile layers and a collision layer, grid walking with WASD, events on the action key E/F, random encounters and battles that return to the map.
|
||||||
|
|
||||||
|
rpg/ui/location = EXPLORING
|
||||||
|
rpg/ui/interact = E / F · Interact
|
||||||
|
rpg/ui/walk = WASD / arrows · Walk
|
||||||
|
rpg/ui/hint = Shift · Run | The journal keeps your next steps.
|
||||||
@@ -1,2 +1,7 @@
|
|||||||
mod_rpg_name = RPG-мир
|
mod_rpg_name = RPG-мир
|
||||||
mod_rpg_desc = Мир из тайлов с видом сверху в духе RPG Maker: три слоя тайлов и слой коллизий, ходьба по клеткам на WASD, события по клавише действия E/F, случайные встречи и бои с возвратом на карту.
|
mod_rpg_desc = Мир из тайлов с видом сверху в духе RPG Maker: три слоя тайлов и слой коллизий, ходьба по клеткам на WASD, события по клавише действия E/F, случайные встречи и бои с возвратом на карту.
|
||||||
|
|
||||||
|
rpg/ui/location = В ПУТИ
|
||||||
|
rpg/ui/interact = E / F · Взаимодействовать
|
||||||
|
rpg/ui/walk = WASD / стрелки · Идти
|
||||||
|
rpg/ui/hint = Shift · Бежать | Следующие шаги — в журнале.
|
||||||
@@ -21,3 +21,64 @@ grow_horizontal = 2
|
|||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
color = Color(0.06, 0.07, 0.09, 1)
|
color = Color(0.06, 0.07, 0.09, 1)
|
||||||
|
|
||||||
|
[node name="Hud" type="Control" parent="."]
|
||||||
|
layout_mode = 1
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Location" type="VBoxContainer" parent="Hud"]
|
||||||
|
layout_mode = 0
|
||||||
|
offset_left = 32.0
|
||||||
|
offset_top = 84.0
|
||||||
|
offset_right = 530.0
|
||||||
|
offset_bottom = 145.0
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Eyebrow" type="Label" parent="Hud/Location"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(0.82, 0.72, 0.47, 1)
|
||||||
|
theme_override_colors/font_shadow_color = Color(0.03, 0.06, 0.07, 0.95)
|
||||||
|
theme_override_constants/shadow_offset_x = 1
|
||||||
|
theme_override_constants/shadow_offset_y = 2
|
||||||
|
theme_override_font_sizes/font_size = 12
|
||||||
|
text = "rpg/ui/location"
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Name" type="Label" parent="Hud/Location"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(0.94, 0.94, 0.84, 1)
|
||||||
|
theme_override_colors/font_shadow_color = Color(0.03, 0.06, 0.07, 0.95)
|
||||||
|
theme_override_constants/shadow_offset_x = 1
|
||||||
|
theme_override_constants/shadow_offset_y = 2
|
||||||
|
theme_override_font_sizes/font_size = 24
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Controls" type="VBoxContainer" parent="Hud"]
|
||||||
|
layout_mode = 1
|
||||||
|
anchor_top = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
offset_left = 32.0
|
||||||
|
offset_top = -76.0
|
||||||
|
offset_right = 570.0
|
||||||
|
offset_bottom = -22.0
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Prompt" type="Label" parent="Hud/Controls"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(0.96, 0.88, 0.65, 1)
|
||||||
|
theme_override_colors/font_shadow_color = Color(0.02, 0.04, 0.05, 1)
|
||||||
|
theme_override_constants/shadow_offset_y = 2
|
||||||
|
theme_override_font_sizes/font_size = 18
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="Hint" type="Label" parent="Hud/Controls"]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_colors/font_color = Color(0.75, 0.83, 0.78, 1)
|
||||||
|
theme_override_colors/font_shadow_color = Color(0.02, 0.04, 0.05, 1)
|
||||||
|
theme_override_constants/shadow_offset_y = 2
|
||||||
|
theme_override_font_sizes/font_size = 13
|
||||||
|
text = "rpg/ui/hint"
|
||||||
|
mouse_filter = 2
|
||||||
@@ -9,7 +9,7 @@ shader_type canvas_item;
|
|||||||
uniform vec4 day_color : source_color = vec4(1.0, 1.0, 1.0, 1.0);
|
uniform vec4 day_color : source_color = vec4(1.0, 1.0, 1.0, 1.0);
|
||||||
uniform vec4 morning_color : source_color = vec4(0.86, 0.72, 0.84, 1.0);
|
uniform vec4 morning_color : source_color = vec4(0.86, 0.72, 0.84, 1.0);
|
||||||
uniform vec4 evening_color : source_color = vec4(0.84, 0.72, 0.92, 1.0);
|
uniform vec4 evening_color : source_color = vec4(0.84, 0.72, 0.92, 1.0);
|
||||||
uniform vec4 night_color : source_color = vec4(0.2, 0.26, 0.42, 1.0);
|
uniform vec4 night_color : source_color = vec4(0.52, 0.62, 0.76, 1.0);
|
||||||
uniform vec4 time_weights = vec4(0.0, 1.0, 0.0, 0.0);
|
uniform vec4 time_weights = vec4(0.0, 1.0, 0.0, 0.0);
|
||||||
|
|
||||||
// A map that sets its own ambient (indoors) uses it instead of the time of day.
|
// A map that sets its own ambient (indoors) uses it instead of the time of day.
|
||||||
|
|||||||
@@ -61,5 +61,8 @@ namespace Cthangover.Mods.Rpg
|
|||||||
/// lights the map (<see cref="RpgLighting"/>).
|
/// lights the map (<see cref="RpgLighting"/>).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Ambient { get; set; }
|
public string Ambient { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Optional woodland leaves, fireflies and water ripples; empty keeps interiors still.</summary>
|
||||||
|
public string Atmosphere { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+20
-2
@@ -30,6 +30,10 @@ namespace Cthangover.Mods.Rpg
|
|||||||
private Vector2 to;
|
private Vector2 to;
|
||||||
private float progress = 1f;
|
private float progress = 1f;
|
||||||
private float animationTime;
|
private float animationTime;
|
||||||
|
private bool hasShadow;
|
||||||
|
|
||||||
|
/// <summary>Current speed in tiles/s; the player can dash without changing NPC routes.</summary>
|
||||||
|
public float Speed { get; set; } = TilesPerSecond;
|
||||||
|
|
||||||
/// <summary>Raised when a step reaches its cell.</summary>
|
/// <summary>Raised when a step reaches its cell.</summary>
|
||||||
public event Action<RpgActor> StepFinished;
|
public event Action<RpgActor> StepFinished;
|
||||||
@@ -70,6 +74,8 @@ namespace Cthangover.Mods.Rpg
|
|||||||
{
|
{
|
||||||
if (sprite == null)
|
if (sprite == null)
|
||||||
return;
|
return;
|
||||||
|
hasShadow = false;
|
||||||
|
QueueRedraw();
|
||||||
sprite.QueueFree();
|
sprite.QueueFree();
|
||||||
sprite = null;
|
sprite = null;
|
||||||
}
|
}
|
||||||
@@ -79,6 +85,8 @@ namespace Cthangover.Mods.Rpg
|
|||||||
{
|
{
|
||||||
if (texture == null)
|
if (texture == null)
|
||||||
return;
|
return;
|
||||||
|
hasShadow = true;
|
||||||
|
QueueRedraw();
|
||||||
sprite = new Sprite2D { Texture = texture, Hframes = 3, Vframes = 4, Centered = false, UseParentMaterial = true };
|
sprite = new Sprite2D { Texture = texture, Hframes = 3, Vframes = 4, Centered = false, UseParentMaterial = true };
|
||||||
var frame = new Vector2(texture.GetWidth() / 3f, texture.GetHeight() / 4f);
|
var frame = new Vector2(texture.GetWidth() / 3f, texture.GetHeight() / 4f);
|
||||||
sprite.Position = new Vector2((tileSize - frame.X) / 2f, tileSize - frame.Y);
|
sprite.Position = new Vector2((tileSize - frame.X) / 2f, tileSize - frame.Y);
|
||||||
@@ -155,14 +163,24 @@ namespace Cthangover.Mods.Rpg
|
|||||||
if (!IsMoving)
|
if (!IsMoving)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
progress = Mathf.Min(1f, progress + (float)delta * TilesPerSecond);
|
progress = Mathf.Min(1f, progress + (float)delta * Math.Clamp(Speed, 1f, 12f));
|
||||||
animationTime += (float)delta * TilesPerSecond * 2f;
|
animationTime += (float)delta * Math.Clamp(Speed, 1f, 12f) * 2f;
|
||||||
Position = from.Lerp(to, progress);
|
Position = from.Lerp(to, progress);
|
||||||
UpdateFrame();
|
UpdateFrame();
|
||||||
if (!IsMoving)
|
if (!IsMoving)
|
||||||
StepFinished?.Invoke(this);
|
StepFinished?.Invoke(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>A soft contact shadow anchors charsets to the ground without changing their art.</summary>
|
||||||
|
public override void _Draw()
|
||||||
|
{
|
||||||
|
if (!hasShadow)
|
||||||
|
return;
|
||||||
|
DrawSetTransform(new Vector2(tileSize / 2f, tileSize - 3), 0, new Vector2(1, 0.32f));
|
||||||
|
DrawCircle(Vector2.Zero, tileSize * 0.28f, new Color(0.04f, 0.09f, 0.1f, 0.28f));
|
||||||
|
DrawSetTransform(Vector2.Zero);
|
||||||
|
}
|
||||||
|
|
||||||
private Vector2 CellPosition(Vector2I cell) => new(cell.X * tileSize, cell.Y * tileSize);
|
private Vector2 CellPosition(Vector2I cell) => new(cell.X * tileSize, cell.Y * tileSize);
|
||||||
|
|
||||||
private void UpdateFrame()
|
private void UpdateFrame()
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
namespace Cthangover.Mods.Rpg
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opt-in woodland motion in map coordinates: drifting leaves, fireflies and small
|
||||||
|
/// water ripples. Positions are deterministic; nothing enters the save or collision
|
||||||
|
/// model. The small fixed draw budget is independent of map dimensions.
|
||||||
|
/// </summary>
|
||||||
|
public partial class RpgAtmosphere : Node2D
|
||||||
|
{
|
||||||
|
private readonly Vector2 extent;
|
||||||
|
private readonly List<Vector2> water = new();
|
||||||
|
private readonly int tileSize;
|
||||||
|
private double elapsed;
|
||||||
|
private double redraw;
|
||||||
|
|
||||||
|
/// <summary>Collects water cells once; an empty Atmosphere leaves this node unmounted.</summary>
|
||||||
|
public RpgAtmosphere(RpgMap map, RpgMapGrid grid, int tileSize, RpgTileset tileset)
|
||||||
|
{
|
||||||
|
Name = "Atmosphere";
|
||||||
|
this.tileSize = tileSize;
|
||||||
|
extent = new Vector2(grid.Width, grid.Height) * tileSize;
|
||||||
|
var waterTiles = new HashSet<int>();
|
||||||
|
foreach (var tile in tileset?.Tiles ?? new List<RpgTileInfo>())
|
||||||
|
if (string.Equals(tile.Tag, "water", StringComparison.OrdinalIgnoreCase))
|
||||||
|
waterTiles.Add(tile.Index);
|
||||||
|
for (var y = 0; y < grid.Height; y++)
|
||||||
|
for (var x = 0; x < grid.Width; x++)
|
||||||
|
if (waterTiles.Contains(grid.GetTile(RpgLayer.Back, x, y)))
|
||||||
|
water.Add(new Vector2(x, y) * tileSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Updates at 20 Hz, retaining crisp pixel positions and avoiding a particle system per tile.</summary>
|
||||||
|
public override void _Process(double delta)
|
||||||
|
{
|
||||||
|
elapsed += delta;
|
||||||
|
redraw += delta;
|
||||||
|
if (redraw < 0.05)
|
||||||
|
return;
|
||||||
|
redraw = 0;
|
||||||
|
QueueRedraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Draws small translucent accents; the ground and actor sprites retain their own palette.</summary>
|
||||||
|
public override void _Draw()
|
||||||
|
{
|
||||||
|
var t = (float)elapsed;
|
||||||
|
for (var i = 0; i < 36; i++)
|
||||||
|
{
|
||||||
|
var x = Mathf.PosMod(i * 137.3f + t * (2 + i % 3), extent.X);
|
||||||
|
var y = Mathf.PosMod(i * 83.7f + t * 1.4f + Mathf.Sin(t * 0.4f + i) * 7, extent.Y);
|
||||||
|
var p = new Vector2(Mathf.Round(x), Mathf.Round(y));
|
||||||
|
if (i % 3 == 0)
|
||||||
|
{
|
||||||
|
var alpha = 0.15f + 0.45f * Mathf.Pow(Mathf.Max(0, Mathf.Sin(t * 1.2f + i)), 2);
|
||||||
|
DrawCircle(p, 3, new Color(0.72f, 0.91f, 0.5f, alpha * 0.12f));
|
||||||
|
DrawRect(new Rect2(p, Vector2.One), new Color(0.95f, 1f, 0.65f, alpha));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
DrawLine(p, p + new Vector2(2, i % 2 == 0 ? 1 : -1), new Color(0.77f, 0.68f, 0.34f, 0.32f));
|
||||||
|
}
|
||||||
|
for (var i = 0; i < water.Count; i += 2)
|
||||||
|
{
|
||||||
|
var phase = t * 0.6f + i * 1.7f;
|
||||||
|
var p = water[i] + new Vector2(8 + Mathf.Sin(phase) * 4, 10 + i % 13);
|
||||||
|
var alpha = 0.08f + 0.12f * Mathf.Max(0, Mathf.Sin(phase));
|
||||||
|
DrawLine(p.Round(), (p + new Vector2(tileSize * 0.25f, 0)).Round(), new Color(0.63f, 0.87f, 0.84f, alpha));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,7 +50,7 @@ namespace Cthangover.Mods.Rpg
|
|||||||
if (dialogBox == null || dialogBox.IsRunning)
|
if (dialogBox == null || dialogBox.IsRunning)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var dialog = ScenarioParser.Parse(text.ToString());
|
var dialog = new ScenarioConverter().Convert(text.ToString());
|
||||||
if (dialog?.Queue == null || dialog.Queue.Count == 0)
|
if (dialog?.Queue == null || dialog.Queue.Count == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
namespace Cthangover.Mods.Rpg
|
||||||
|
{
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
/// <summary>Highlights exactly the event the action key will run. Uses the view's target selection, so hints cannot disagree with input.</summary>
|
||||||
|
public partial class RpgInteractionCue : Node2D
|
||||||
|
{
|
||||||
|
private readonly RpgWorldView view;
|
||||||
|
private float elapsed;
|
||||||
|
|
||||||
|
/// <summary>Attaches to one map; no subscriptions or state survive unmount.</summary>
|
||||||
|
public RpgInteractionCue(RpgWorldView view)
|
||||||
|
{
|
||||||
|
this.view = view;
|
||||||
|
Name = "InteractionCue";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Redraws the small corner marker while an action event is in reach.</summary>
|
||||||
|
public override void _Process(double delta)
|
||||||
|
{
|
||||||
|
elapsed += (float)delta;
|
||||||
|
QueueRedraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Draws four corners around the target cell, hidden whenever another UI owns input.</summary>
|
||||||
|
public override void _Draw()
|
||||||
|
{
|
||||||
|
if (RpgInput.IsWalkBlocked(this))
|
||||||
|
return;
|
||||||
|
var target = view.InteractionTarget();
|
||||||
|
if (target == null)
|
||||||
|
return;
|
||||||
|
var rect = new Rect2(target.Position + new Vector2(2, 2), new Vector2(view.TileSize - 4, view.TileSize - 4));
|
||||||
|
var color = new Color(1f, 0.83f, 0.48f, 0.65f + 0.2f * Mathf.Sin(elapsed * 3));
|
||||||
|
foreach (var corner in new[] { rect.Position, new Vector2(rect.End.X, rect.Position.Y), rect.End, new Vector2(rect.Position.X, rect.End.Y) })
|
||||||
|
{
|
||||||
|
var dx = corner.X == rect.Position.X ? 5 : -5;
|
||||||
|
var dy = corner.Y == rect.Position.Y ? 5 : -5;
|
||||||
|
DrawLine(corner, corner + new Vector2(dx, 0), color);
|
||||||
|
DrawLine(corner, corner + new Vector2(0, dy), color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,9 @@ namespace Cthangover.Mods.Rpg
|
|||||||
public partial class RpgWorldHost : Control
|
public partial class RpgWorldHost : Control
|
||||||
{
|
{
|
||||||
private Control backdrop;
|
private Control backdrop;
|
||||||
|
private Control hud;
|
||||||
|
private Label mapName;
|
||||||
|
private Label prompt;
|
||||||
|
|
||||||
/// <summary>The host in the running shell, or null.</summary>
|
/// <summary>The host in the running shell, or null.</summary>
|
||||||
public static RpgWorldHost Current { get; private set; }
|
public static RpgWorldHost Current { get; private set; }
|
||||||
@@ -43,9 +46,22 @@ namespace Cthangover.Mods.Rpg
|
|||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
backdrop = GetNodeOrNull<Control>("Backdrop");
|
backdrop = GetNodeOrNull<Control>("Backdrop");
|
||||||
|
hud = GetNodeOrNull<Control>("Hud");
|
||||||
|
mapName = GetNodeOrNull<Label>("Hud/Location/Name");
|
||||||
|
prompt = GetNodeOrNull<Label>("Hud/Controls/Prompt");
|
||||||
Visible = false;
|
Visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Updates the action hint from the same target selection as the world.</summary>
|
||||||
|
public override void _Process(double delta)
|
||||||
|
{
|
||||||
|
if (View == null || hud == null)
|
||||||
|
return;
|
||||||
|
hud.Visible = !RpgInput.IsWalkBlocked(this);
|
||||||
|
if (prompt != null)
|
||||||
|
prompt.Text = Tr(View.InteractionTarget() != null ? "rpg/ui/interact" : "rpg/ui/walk");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Unsubscribes; the view goes with the node.</summary>
|
/// <summary>Unsubscribes; the view goes with the node.</summary>
|
||||||
public override void _ExitTree()
|
public override void _ExitTree()
|
||||||
{
|
{
|
||||||
@@ -97,7 +113,10 @@ namespace Cthangover.Mods.Rpg
|
|||||||
|
|
||||||
View = new RpgWorldView();
|
View = new RpgWorldView();
|
||||||
AddChild(View);
|
AddChild(View);
|
||||||
|
MoveChild(View, 1);
|
||||||
View.Build(map);
|
View.Build(map);
|
||||||
|
if (mapName != null)
|
||||||
|
mapName.Text = Tr(map.Name ?? map.ID);
|
||||||
|
|
||||||
var state = RpgState.Instance.Data;
|
var state = RpgState.Instance.Data;
|
||||||
if (state.Map == map.ID && View.Contains(state.X, state.Y))
|
if (state.Map == map.ID && View.Contains(state.X, state.Y))
|
||||||
|
|||||||
@@ -75,6 +75,9 @@ namespace Cthangover.Mods.Rpg
|
|||||||
foreach (var mapEvent in RpgMapEvents.Of(map.ID))
|
foreach (var mapEvent in RpgMapEvents.Of(map.ID))
|
||||||
AddEventActor(mapEvent);
|
AddEventActor(mapEvent);
|
||||||
RefreshEvents();
|
RefreshEvents();
|
||||||
|
if (string.Equals(map.Atmosphere, "woodland", StringComparison.OrdinalIgnoreCase))
|
||||||
|
AddChild(new RpgAtmosphere(map, grid, TileSize, Tileset));
|
||||||
|
AddChild(new RpgInteractionCue(this));
|
||||||
|
|
||||||
Player = new RpgActor(TileSize) { Name = "Player", Material = lighting.Material };
|
Player = new RpgActor(TileSize) { Name = "Player", Material = lighting.Material };
|
||||||
Player.UseCharset(textures.Charset(RpgPlayerLook.Charset()));
|
Player.UseCharset(textures.Charset(RpgPlayerLook.Charset()));
|
||||||
@@ -155,8 +158,7 @@ namespace Cthangover.Mods.Rpg
|
|||||||
if (Player == null || Player.IsMoving)
|
if (Player == null || Player.IsMoving)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var target = EventAt(Player.Cell + RpgDirections.Delta(Player.Facing), RpgEventTrigger.Action)
|
var target = InteractionTarget();
|
||||||
?? EventAt(Player.Cell, RpgEventTrigger.Action);
|
|
||||||
if (target == null)
|
if (target == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -165,6 +167,15 @@ namespace Cthangover.Mods.Rpg
|
|||||||
return RpgEventRunner.Run(target.Current, Map.ID);
|
return RpgEventRunner.Run(target.Current, Map.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>The action event in reach, shared by input and its visual cue; hidden while moving.</summary>
|
||||||
|
public RpgActor InteractionTarget()
|
||||||
|
{
|
||||||
|
if (Player == null || Player.IsMoving)
|
||||||
|
return null;
|
||||||
|
return EventAt(Player.Cell + RpgDirections.Delta(Player.Facing), RpgEventTrigger.Action)
|
||||||
|
?? EventAt(Player.Cell, RpgEventTrigger.Action);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Walking: one decision per frame while the player stands still.</summary>
|
/// <summary>Walking: one decision per frame while the player stands still.</summary>
|
||||||
public override void _Process(double delta)
|
public override void _Process(double delta)
|
||||||
{
|
{
|
||||||
@@ -178,12 +189,15 @@ namespace Cthangover.Mods.Rpg
|
|||||||
{
|
{
|
||||||
var next = Player.Cell + RpgDirections.Delta(held.Value);
|
var next = Player.Cell + RpgDirections.Delta(held.Value);
|
||||||
if (CanEnter(next))
|
if (CanEnter(next))
|
||||||
|
{
|
||||||
|
Player.Speed = Input.IsPhysicalKeyPressed(Key.Shift) ? 7f : RpgActor.TilesPerSecond;
|
||||||
Player.StepTo(held.Value);
|
Player.StepTo(held.Value);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
Player.Face(held.Value);
|
Player.Face(held.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UpdateCamera();
|
UpdateCamera(delta);
|
||||||
lighting.Update(this, eventActors.Values);
|
lighting.Update(this, eventActors.Values);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,7 +318,7 @@ namespace Cthangover.Mods.Rpg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateCamera()
|
private void UpdateCamera(double delta = 0)
|
||||||
{
|
{
|
||||||
if (Player == null || GetParent() is not Control host)
|
if (Player == null || GetParent() is not Control host)
|
||||||
return;
|
return;
|
||||||
@@ -313,7 +327,8 @@ namespace Cthangover.Mods.Rpg
|
|||||||
var mapSize = new Vector2(grid.Width, grid.Height) * TileSize * zoom;
|
var mapSize = new Vector2(grid.Width, grid.Height) * TileSize * zoom;
|
||||||
var screen = host.Size;
|
var screen = host.Size;
|
||||||
var focus = (Player.Position + new Vector2(TileSize, TileSize) / 2f) * zoom;
|
var focus = (Player.Position + new Vector2(TileSize, TileSize) / 2f) * zoom;
|
||||||
Position = new Vector2(Axis(screen.X, mapSize.X, focus.X), Axis(screen.Y, mapSize.Y, focus.Y));
|
var target = new Vector2(Axis(screen.X, mapSize.X, focus.X), Axis(screen.Y, mapSize.Y, focus.Y));
|
||||||
|
Position = delta <= 0 ? target : Position.Lerp(target, 1f - Mathf.Exp(-(float)delta * 14f)).Round();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static float Axis(float screen, float map, float focus)
|
private static float Axis(float screen, float map, float focus)
|
||||||
|
|||||||
@@ -57,12 +57,15 @@ namespace Cthangover.Mods.Rpg.Tests
|
|||||||
{
|
{
|
||||||
var json = @"{""Id"":""m"",""Tileset"":""village"",""Width"":2,""Height"":1,
|
var json = @"{""Id"":""m"",""Tileset"":""village"",""Width"":2,""Height"":1,
|
||||||
""Spawn"":{""X"":1,""Y"":0,""Dir"":""left""},
|
""Spawn"":{""X"":1,""Y"":0,""Dir"":""left""},
|
||||||
|
""Atmosphere"":""woodland"",""Ambient"":""#c5d6b0"",
|
||||||
""Encounters"":{""Steps"":20,""Groups"":[""slime""]}}";
|
""Encounters"":{""Steps"":20,""Groups"":[""slime""]}}";
|
||||||
|
|
||||||
var map = JsonSerializer.Deserialize<RpgMap>(json, Cthangover.Core.Utils.JsonOptionsProvider.ReadOptions);
|
var map = JsonSerializer.Deserialize<RpgMap>(json, Cthangover.Core.Utils.JsonOptionsProvider.ReadOptions);
|
||||||
var again = JsonSerializer.Deserialize<RpgMap>(JsonSerializer.Serialize(map), Cthangover.Core.Utils.JsonOptionsProvider.ReadOptions);
|
var again = JsonSerializer.Deserialize<RpgMap>(JsonSerializer.Serialize(map), Cthangover.Core.Utils.JsonOptionsProvider.ReadOptions);
|
||||||
|
|
||||||
ModAssert.Equal("m", again.ID, "id");
|
ModAssert.Equal("m", again.ID, "id");
|
||||||
|
ModAssert.Equal("woodland", again.Atmosphere, "atmosphere survives saves and editor snapshots");
|
||||||
|
ModAssert.Equal("#c5d6b0", again.Ambient, "fixed ambient retained");
|
||||||
ModAssert.Equal("Left", again.Spawn.Dir.ToString(), "spawn facing, read in any case");
|
ModAssert.Equal("Left", again.Spawn.Dir.ToString(), "spawn facing, read in any case");
|
||||||
ModAssert.Equal(20, again.Encounters.Steps, "encounter steps");
|
ModAssert.Equal(20, again.Encounters.Steps, "encounter steps");
|
||||||
ModAssert.Equal("slime", again.Encounters.Groups[0], "encounter group");
|
ModAssert.Equal("slime", again.Encounters.Groups[0], "encounter group");
|
||||||
|
|||||||
Reference in new issue
Block a user