A game where you get to play as a slime, made with Godot.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

220 lines
6.9 KiB

[Tool]
public partial class Terrain
{
bool _unhandledMouseMotion = false; // Used to detect when mouse moves off the terrain.
TilePos? _tileHover = null; // Position of currently hovered tile.
bool _isSelecting = false; // Whether left mouse is held down to select tiles.
(TilePos, TilePos)? _selection = null;
public override void _Input(InputEvent ev)
{
if (!IsEditing()) return;
if (_isSelecting && ev is InputEventMouseButton { ButtonIndex: MouseButton.Left, Pressed: false }) {
_isSelecting = false;
GetViewport().SetInputAsHandled();
}
if ((ev is InputEventMouseButton { ButtonIndex: var wheel, Pressed: var pressed, ShiftPressed: true })
&& (wheel is MouseButton.WheelUp or MouseButton.WheelDown) && (_selection != null))
{
// NOTE: Potential bug in the Godot editor?
// Does it zoom both when mouse wheel is "pressed" and "released"?
// Because just cancelling one of them still causes zooming to occur.
GetViewport().SetInputAsHandled();
if (!pressed) return;
const float AdjustHeight = 0.5f;
var value = (wheel == MouseButton.WheelUp)
? AdjustHeight : -AdjustHeight;
var selection = TileRegion.From(_selection.Value);
// Raise connected corners.
foreach (var innerCorner in Enum.GetValues<Corner>()) {
var outerCorner = innerCorner.GetOpposite();
var innerPos = selection.GetTileFor(innerCorner);
var outerPos = innerPos.GetNeighbor(innerCorner);
var innerHeight = GetCornerHeights(innerPos)[innerCorner];
var outerHeight = GetCornerHeights(outerPos)[outerCorner];
if (IsEqualApprox(outerHeight, innerHeight))
SetCornerHeight(outerPos, outerCorner, innerHeight + value);
}
// Raise connected sides.
foreach (var side in Enum.GetValues<Side>()) {
foreach (var innerPos in selection.GetTilesFor(side)) {
var outerPos = innerPos.GetNeighbor(side);
var innerHeights = GetCornerHeights(innerPos);
var outerHeights = GetCornerHeights(outerPos);
var (innerCorner1, innerCorner2) = side.GetCorners();
var (outerCorner1, outerCorner2) = side.GetOpposite().GetCorners();
var current = outerHeights;
var changed = false;
foreach (var (innerCorner, outerCorner) in new[]{ (innerCorner1, outerCorner1), (innerCorner2, outerCorner2) }) {
var innerHeight = innerHeights[innerCorner];
var outerHeight = outerHeights[outerCorner];
if (IsEqualApprox(outerHeight, innerHeight)) {
current = current.With(outerCorner, innerHeight + value);
changed = true;
}
}
if (changed) SetCornerHeights(outerPos, current);
}
}
// Raise selected tiles themselves.
foreach (var tile in selection.GetAllTiles())
AdjustTileHeight(tile, value);
UpdateMeshAndShape();
NotifyPropertyListChanged();
}
if (ev is InputEventMouseMotion)
_unhandledMouseMotion = true;
}
public override void _InputEvent(Camera3D camera, InputEvent ev, Vector3 position, Vector3 normal, int shapeIdx)
{
if (!IsEditing()) return;
var localPos = ToLocal(position);
var tilePos = ToTilePos(localPos);
if (ev is InputEventMouseButton { ButtonIndex: MouseButton.Left, Pressed: true }) {
_selection = new(tilePos, tilePos);
_isSelecting = true;
GetViewport().SetInputAsHandled();
}
if (ev is InputEventMouseMotion) {
_unhandledMouseMotion = false;
_tileHover = tilePos;
if (_isSelecting) _selection = _selection.Value with { Item2 = tilePos };
}
}
public override void _Process(double delta)
{
if (!IsEditing()) {
_tileHover = null;
_selection = null;
_isSelecting = false;
}
if (_unhandledMouseMotion)
_tileHover = null;
if ((_tileHover != null) || (_selection != null)) {
var mesh = GetOrCreateMesh("EditToolMesh");
mesh.ClearSurfaces();
mesh.SurfaceBegin(Mesh.PrimitiveType.Lines);
void AddLine(Vector3 start, Vector3 end) {
mesh.SurfaceAddVertex(start);
mesh.SurfaceAddVertex(end);
}
void AddQuad(Vector3 topLeft, Vector3 topRight,
Vector3 bottomLeft, Vector3 bottomRight) {
AddLine(topLeft , topRight );
AddLine(topRight , bottomRight);
AddLine(bottomRight, bottomLeft );
AddLine(bottomLeft , topLeft );
}
if (_tileHover is TilePos hover) {
var corners = GetCornersPosition(hover);
var margin = 0.1f;
mesh.SurfaceSetColor(Colors.Black);
AddQuad(
corners.TopLeft + new Vector3(-margin, 0, -margin),
corners.TopRight + new Vector3(+margin, 0, -margin),
corners.BottomLeft + new Vector3(-margin, 0, +margin),
corners.BottomRight + new Vector3(+margin, 0, +margin)
);
}
mesh.SurfaceSetColor(Colors.Blue);
if (_selection is (TilePos, TilePos) selection)
foreach (var pos in TileRegion.From(selection).GetAllTiles()) {
var corners = GetCornersPosition(pos);
AddQuad(corners.TopLeft, corners.TopRight, corners.BottomLeft, corners.BottomRight);
}
mesh.SurfaceEnd();
mesh.SurfaceSetMaterial(0, _editToolMaterial);
} else {
var meshInstance = (MeshInstance3D)GetNodeOrNull("EditToolMesh");
var mesh = (ImmediateMesh)meshInstance?.Mesh;
mesh?.ClearSurfaces();
}
}
bool IsEditing()
{
if (Engine.IsEditorHint()) {
var selection = EditorInterface.Singleton.GetSelection();
return selection.GetSelectedNodes().Contains(this);
} else {
return false;
}
}
readonly record struct TileRegion(int Left, int Top, int Right, int Bottom)
{
public TilePos TopLeft => new(Left , Top);
public TilePos TopRight => new(Right, Top);
public TilePos BottomRight => new(Right, Bottom);
public TilePos BottomLeft => new(Left , Bottom);
public int Width => Right - Left + 1;
public int Height => Bottom - Top + 1;
public static TileRegion From((TilePos, TilePos) selection)
=> From(selection.Item1, selection.Item2);
public static TileRegion From(TilePos a, TilePos b)
=> new(Min(a.X, b.X), Min(a.Y, b.Y), Max(a.X, b.X), Max(a.Y, b.Y));
public TilePos GetTileFor(Corner corner)
=> corner switch {
Corner.TopLeft => TopLeft,
Corner.TopRight => TopRight,
Corner.BottomRight => BottomRight,
Corner.BottomLeft => BottomLeft,
_ => throw new ArgumentException($"Invalid Corner value '{corner}'", nameof(corner)),
};
public IEnumerable<TilePos> GetTilesFor(Side side)
{
var (left, top, right, bottom) = this;
return side switch {
Side.Left => Enumerable.Range(Top, Height).Select(y => new TilePos(left, y)),
Side.Top => Enumerable.Range(Left, Width).Select(x => new TilePos(x, top)),
Side.Right => Enumerable.Range(Top, Height).Select(y => new TilePos(right, y)),
Side.Bottom => Enumerable.Range(Left, Width).Select(x => new TilePos(x, bottom)),
_ => throw new ArgumentException($"Invalid Side value '{side}'", nameof(side)),
};
}
public IEnumerable<TilePos> GetAllTiles()
{
for (var x = Left; x <= Right; x++)
for (var y = Top; y <= Bottom; y++)
yield return new(x, y);
}
}
}