ported from perforce
This commit is contained in:
4
DonkeysAndDroids.Godot/.editorconfig
Normal file
4
DonkeysAndDroids.Godot/.editorconfig
Normal file
@@ -0,0 +1,4 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
2
DonkeysAndDroids.Godot/.gitattributes
vendored
Normal file
2
DonkeysAndDroids.Godot/.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Normalize EOL for all files that Git considers text files.
|
||||
* text=auto eol=lf
|
||||
3
DonkeysAndDroids.Godot/.gitignore
vendored
Normal file
3
DonkeysAndDroids.Godot/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
/android/
|
||||
29
DonkeysAndDroids.Godot/Background.cs
Normal file
29
DonkeysAndDroids.Godot/Background.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Godot;
|
||||
|
||||
public partial class Background : Control
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
var rect = GetNode<ColorRect>("SwirlRect");
|
||||
m_SwirlMaterial = rect.Material as ShaderMaterial;
|
||||
|
||||
m_Particles = GetNode<GpuParticles2D>("DustParticles");
|
||||
m_ParticlesProcess = m_Particles.ProcessMaterial as ParticleProcessMaterial;
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
m_Particles.Position = Size / 2;
|
||||
((ParticleProcessMaterial)m_Particles.ProcessMaterial).EmissionBoxExtents = new(Size.X / 2, Size.Y / 2, 1);
|
||||
Refresh();
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
}
|
||||
|
||||
private GpuParticles2D m_Particles;
|
||||
private ParticleProcessMaterial m_ParticlesProcess;
|
||||
|
||||
private ShaderMaterial m_SwirlMaterial;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/Background.cs.uid
Normal file
1
DonkeysAndDroids.Godot/Background.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bqii5k4sdtlnq
|
||||
258
DonkeysAndDroids.Godot/BoardCameraController.cs
Normal file
258
DonkeysAndDroids.Godot/BoardCameraController.cs
Normal file
@@ -0,0 +1,258 @@
|
||||
using Godot;
|
||||
|
||||
public partial class BoardCameraController : Camera3D
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
InitBoardBounds();
|
||||
InitOrbit();
|
||||
}
|
||||
|
||||
private void InitBoardBounds()
|
||||
{
|
||||
if (HexBoard != null && HexBoard.BoardBounds.Size != Vector2.Zero)
|
||||
{
|
||||
m_BoardRect = HexBoard.BoardBounds;
|
||||
var center2D = m_BoardRect.Position + m_BoardRect.Size * 0.5f;
|
||||
m_TargetPosition = new(center2D.X - 1, 0f, center2D.Y + 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_BoardRect = new(-10, -10, 20, 20);
|
||||
m_TargetPosition = new(-1, 0f, 3);
|
||||
}
|
||||
|
||||
var radius = Mathf.Max(m_BoardRect.Size.X, m_BoardRect.Size.Y) * 0.5f;
|
||||
m_MinDistance = MinZoomDistance;
|
||||
m_MaxDistance = Mathf.Max(m_MinDistance * 2f, radius * MaxZoomOutFactor);
|
||||
}
|
||||
|
||||
private void InitOrbit()
|
||||
{
|
||||
var startYawRad = GlobalTransform.Basis.GetEuler().Y;
|
||||
var startYawDeg = Mathf.RadToDeg(startYawRad);
|
||||
|
||||
m_RotationIndex = Mathf.PosMod(Mathf.RoundToInt(startYawDeg / 60f), 6);
|
||||
m_YawDegrees = m_RotationIndex * 60f;
|
||||
|
||||
m_Distance = (m_MaxDistance - m_MinDistance) * 0.75f + m_MinDistance;
|
||||
UpdateTransformFromOrbit();
|
||||
|
||||
if (HexBoard != null)
|
||||
HexBoard.SetCameraRotationIndex(m_RotationIndex);
|
||||
}
|
||||
|
||||
private void UpdateTransformFromOrbit()
|
||||
{
|
||||
var pitchRad = Mathf.DegToRad(PitchDegrees);
|
||||
var yawRad = Mathf.DegToRad(m_YawDegrees);
|
||||
|
||||
var horizontal = m_Distance * Mathf.Cos(pitchRad);
|
||||
var height = m_Distance * Mathf.Sin(pitchRad);
|
||||
|
||||
var offset = new Vector3(Mathf.Sin(yawRad) * horizontal, height, Mathf.Cos(yawRad) * horizontal);
|
||||
|
||||
GlobalPosition = m_TargetPosition + offset;
|
||||
LookAt(m_TargetPosition, Vector3.Up);
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
var dt = (float)delta;
|
||||
|
||||
HandleRotationInput(dt);
|
||||
HandleMovementInput(dt);
|
||||
HandleZoomInput(dt);
|
||||
}
|
||||
|
||||
public override void _UnhandledInput(InputEvent @event)
|
||||
{
|
||||
if (@event is InputEventMouseButton mb && mb.Pressed)
|
||||
{
|
||||
if (mb.ButtonIndex == MouseButton.WheelUp)
|
||||
m_PendingWheelSteps -= 1f;
|
||||
else if (mb.ButtonIndex == MouseButton.WheelDown)
|
||||
m_PendingWheelSteps += 1f;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleMovementInput(float delta)
|
||||
{
|
||||
var input = Vector2.Zero;
|
||||
|
||||
input.Y -= Input.GetActionStrength("camera_move_forward");
|
||||
input.Y += Input.GetActionStrength("camera_move_back");
|
||||
input.X -= Input.GetActionStrength("camera_move_left");
|
||||
input.X += Input.GetActionStrength("camera_move_right");
|
||||
|
||||
if (input.LengthSquared() < 0.0001f)
|
||||
return;
|
||||
|
||||
var yawRad = Mathf.DegToRad(m_YawDegrees);
|
||||
var forward = new Vector2(Mathf.Sin(yawRad), Mathf.Cos(yawRad));
|
||||
var right = new Vector2(forward.Y, -forward.X);
|
||||
|
||||
var worldDir = right * input.X + forward * input.Y;
|
||||
var deltaPos = worldDir * MoveSpeed * delta;
|
||||
|
||||
m_TargetPosition.X += deltaPos.X;
|
||||
m_TargetPosition.Z += deltaPos.Y;
|
||||
|
||||
ClampTargetToBoard();
|
||||
UpdateTransformFromOrbit();
|
||||
}
|
||||
|
||||
private void HandleZoomInput(float delta)
|
||||
{
|
||||
var zoomDelta = 0f;
|
||||
|
||||
var axis = Input.GetActionStrength("camera_zoom_out") - Input.GetActionStrength("camera_zoom_in");
|
||||
if (Mathf.Abs(axis) > 0.001f)
|
||||
zoomDelta += axis * PadZoomSpeed * delta;
|
||||
|
||||
if (Mathf.Abs(m_PendingWheelSteps) > 0.001f)
|
||||
{
|
||||
zoomDelta += m_PendingWheelSteps * WheelZoomStep;
|
||||
m_PendingWheelSteps = 0f;
|
||||
}
|
||||
|
||||
if (Mathf.Abs(zoomDelta) < 0.0001f)
|
||||
return;
|
||||
|
||||
m_Distance = Mathf.Clamp(m_Distance + zoomDelta, m_MinDistance, m_MaxDistance);
|
||||
UpdateTransformFromOrbit();
|
||||
}
|
||||
|
||||
private void HandleRotationInput(float delta)
|
||||
{
|
||||
if (Input.IsActionJustPressed("camera_rotate_left"))
|
||||
QueueRotate(-1);
|
||||
if (Input.IsActionJustPressed("camera_rotate_right"))
|
||||
QueueRotate(+1);
|
||||
|
||||
var axis = Input.GetActionStrength("camera_rotate_right") - Input.GetActionStrength("camera_rotate_left");
|
||||
var axisDir = 0;
|
||||
if (axis > GamepadDeadZone)
|
||||
axisDir = +1;
|
||||
else if (axis < -GamepadDeadZone)
|
||||
axisDir = -1;
|
||||
|
||||
if (axisDir != 0)
|
||||
{
|
||||
if (m_RotateAxisDir != axisDir)
|
||||
{
|
||||
m_RotateAxisDir = axisDir;
|
||||
m_RotateAxisTimer = 0f;
|
||||
QueueRotate(axisDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_RotateAxisTimer += delta;
|
||||
if (m_RotateAxisTimer >= GamepadRotateRepeatDelay)
|
||||
{
|
||||
m_RotateAxisTimer = 0f;
|
||||
QueueRotate(axisDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_RotateAxisDir = 0;
|
||||
m_RotateAxisTimer = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
private void QueueRotate(int steps)
|
||||
{
|
||||
if (steps == 0 || m_RotationTween != null)
|
||||
return;
|
||||
|
||||
m_RotationIndex = Mathf.PosMod(m_RotationIndex + steps, 6);
|
||||
var targetYaw = m_RotationIndex * 60f;
|
||||
|
||||
var current = m_YawDegrees;
|
||||
var deltaYaw = Mathf.Wrap(targetYaw - current, -180f, 180f);
|
||||
targetYaw = current + deltaYaw;
|
||||
|
||||
HexBoard?.SetCameraRotationIndex(m_RotationIndex);
|
||||
|
||||
m_RotationTween = CreateTween();
|
||||
m_RotationTween.SetTrans(Tween.TransitionType.Back);
|
||||
m_RotationTween.SetEase(Tween.EaseType.Out);
|
||||
m_RotationTween.TweenProperty(this, nameof(YawDegrees), targetYaw, RotationDuration);
|
||||
m_RotationTween.TweenCallback(Callable.From(() =>
|
||||
{
|
||||
YawDegrees = m_RotationIndex * 60f;
|
||||
m_RotationTween = null;
|
||||
}));
|
||||
}
|
||||
|
||||
private void ClampTargetToBoard()
|
||||
{
|
||||
if (m_BoardRect.Size == Vector2.Zero)
|
||||
return;
|
||||
|
||||
var minX = m_BoardRect.Position.X;
|
||||
var maxX = m_BoardRect.Position.X + m_BoardRect.Size.X;
|
||||
var minZ = m_BoardRect.Position.Y;
|
||||
var maxZ = m_BoardRect.Position.Y + m_BoardRect.Size.Y;
|
||||
|
||||
m_TargetPosition.X = Mathf.Clamp(m_TargetPosition.X, minX, maxX);
|
||||
m_TargetPosition.Z = Mathf.Clamp(m_TargetPosition.Z, minZ, maxZ);
|
||||
}
|
||||
|
||||
public float YawDegrees
|
||||
{
|
||||
get => m_YawDegrees;
|
||||
set
|
||||
{
|
||||
m_YawDegrees = value;
|
||||
UpdateTransformFromOrbit();
|
||||
}
|
||||
}
|
||||
|
||||
[Export]
|
||||
public HexBoard3D HexBoard { get; set; }
|
||||
|
||||
[Export]
|
||||
public float GamepadDeadZone = 0.4f;
|
||||
|
||||
[Export]
|
||||
public float GamepadRotateRepeatDelay = 1.0f;
|
||||
|
||||
[Export]
|
||||
public float MaxZoomOutFactor = 2.2f;
|
||||
|
||||
[ExportCategory("Zoom")]
|
||||
[Export]
|
||||
public float MinZoomDistance = 8f;
|
||||
|
||||
[ExportCategory("Movement")]
|
||||
[Export]
|
||||
public float MoveSpeed = 10f;
|
||||
|
||||
[Export]
|
||||
public float PadZoomSpeed = 40f;
|
||||
|
||||
[ExportCategory("Rotation")]
|
||||
[Export]
|
||||
public float PitchDegrees = 60f;
|
||||
|
||||
[Export]
|
||||
public float RotationDuration = 0.35f;
|
||||
|
||||
[Export]
|
||||
public float WheelZoomStep = 3f;
|
||||
|
||||
private Rect2 m_BoardRect;
|
||||
private float m_Distance;
|
||||
private float m_MaxDistance;
|
||||
private float m_MinDistance;
|
||||
private float m_PendingWheelSteps;
|
||||
private int m_RotateAxisDir;
|
||||
private float m_RotateAxisTimer;
|
||||
private int m_RotationIndex;
|
||||
private Tween m_RotationTween;
|
||||
private Vector3 m_TargetPosition = new(-1, 0f, 3);
|
||||
private float m_YawDegrees;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/BoardCameraController.cs.uid
Normal file
1
DonkeysAndDroids.Godot/BoardCameraController.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cs6idwp164gov
|
||||
50
DonkeysAndDroids.Godot/BoardNode.cs
Normal file
50
DonkeysAndDroids.Godot/BoardNode.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using Godot;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
using RobotAndDonkey.Game.Utils;
|
||||
using System;
|
||||
|
||||
public partial class BoardNode : Node3D
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
m_HexBoard3D = GetNode<HexBoard3D>("HexBoard3D");
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
}
|
||||
|
||||
public void Configure(Board board)
|
||||
{
|
||||
m_HexBoard3D.Configure(board);
|
||||
}
|
||||
|
||||
public void Configure(Result result, Tween tween)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case CellTypeResult cellResult:
|
||||
{
|
||||
var cell = cellResult.Board.Cells[cellResult.Board.FindCellIndex(cellResult.Hex)];
|
||||
tween.TweenCallback(Callable.From(() => m_HexBoard3D.Configure(cell)));
|
||||
break;
|
||||
}
|
||||
case ModifyCellResult cellResult:
|
||||
{
|
||||
var cell = cellResult.Board.Cells[cellResult.Board.FindCellIndex(cellResult.Hex)];
|
||||
m_HexBoard3D.ModifyCell(cell, cellResult.Modifier, tween);
|
||||
break;
|
||||
}
|
||||
case PoiResult cellResult:
|
||||
{
|
||||
var cell = cellResult.Board.Cells[cellResult.Board.FindCellIndex(cellResult.Hex)];
|
||||
m_HexBoard3D.ModifyPoi(cell, cellResult.Poi, tween);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private HexBoard3D m_HexBoard3D;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/BoardNode.cs.uid
Normal file
1
DonkeysAndDroids.Godot/BoardNode.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b5ehrdnu2ovli
|
||||
91
DonkeysAndDroids.Godot/BufferOverflow.cs
Normal file
91
DonkeysAndDroids.Godot/BufferOverflow.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using DonkeysAndDroids;
|
||||
using Godot;
|
||||
using RobotAndDonkey.Game.Execution.Commands;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public partial class BufferOverflow : Control, IScreen
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
m_Hand = GetNode<CardRow>("%Hand");
|
||||
m_Remove = GetNode<Button>("%Remove");
|
||||
m_Remove.Pressed += OnRemovePressed;
|
||||
m_Skip = GetNode<Button>("%Skip");
|
||||
m_Skip.Pressed += OnSkipPressed;
|
||||
m_Hand.Connect(CardRow.SignalName.SelectionChanged, new(this, nameof(OnSelectionChanged)));
|
||||
}
|
||||
|
||||
private void OnSelectionChanged(CardControl[] selection)
|
||||
{
|
||||
if (selection.Length != 1)
|
||||
{
|
||||
m_DestroyCardCommand = null;
|
||||
return;
|
||||
}
|
||||
|
||||
m_DestroyCardCommand = new(Main.Instance.CurrentRequest.RequestId, m_Hand.OrderedCards.IndexOf(selection[0].Card));
|
||||
}
|
||||
|
||||
private void OnRemovePressed()
|
||||
{
|
||||
Main.Instance.Execute(m_DestroyCardCommand);
|
||||
}
|
||||
|
||||
private void OnSkipPressed()
|
||||
{
|
||||
Main.Instance.Execute(m_StopBufferOverflowCommand);
|
||||
}
|
||||
|
||||
public void Deactivate()
|
||||
{
|
||||
}
|
||||
|
||||
public void EnableInputs()
|
||||
{
|
||||
m_Remove.Disabled = false;
|
||||
m_Skip.Disabled = false;
|
||||
}
|
||||
|
||||
public void DisableInputs()
|
||||
{
|
||||
m_Remove.Disabled = true;
|
||||
m_Skip.Disabled = true;
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
}
|
||||
|
||||
public bool HandleResult(Result result, Tween tween)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case HandResult handResult:
|
||||
{
|
||||
m_Hand.Configure(handResult.Hand, tween, true, false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
m_Skip.Disabled = m_StopBufferOverflowCommand == null || !m_StopBufferOverflowCommand.IsValid(Main.Instance.CoreLoop, out _);
|
||||
m_Remove.Disabled = m_DestroyCardCommand == null || !m_DestroyCardCommand.IsValid(Main.Instance.CoreLoop, out _);
|
||||
}
|
||||
|
||||
public void Configure(Tween tween)
|
||||
{
|
||||
m_Hand.Configure(Main.Instance.CoreLoop.Hand, tween, false, false);
|
||||
m_StopBufferOverflowCommand = new(Main.Instance.CurrentRequest.RequestId);
|
||||
}
|
||||
|
||||
private DestroyCardCommand m_DestroyCardCommand;
|
||||
|
||||
private CardRow m_Hand;
|
||||
private Button m_Remove;
|
||||
private Button m_Skip;
|
||||
private StopBufferOverflowCommand m_StopBufferOverflowCommand;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/BufferOverflow.cs.uid
Normal file
1
DonkeysAndDroids.Godot/BufferOverflow.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b05rg3st8wwtp
|
||||
597
DonkeysAndDroids.Godot/CardControl.cs
Normal file
597
DonkeysAndDroids.Godot/CardControl.cs
Normal file
@@ -0,0 +1,597 @@
|
||||
using Godot;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
using RobotAndDonkey.Game.Cards.Patches;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RobotAndDonkey.Game;
|
||||
|
||||
public partial class CardControl : Control
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
m_ArtTextureRect = GetNode<TextureRect>("%ArtTextureRect");
|
||||
m_ModifiersContainer = GetNode<HBoxContainer>("%ModifiersContainer");
|
||||
m_BorderPanel = GetNode<Panel>("%Frame");
|
||||
m_Background = GetNode<Panel>("%Background");
|
||||
m_Label = GetNode<Label>("%Label");
|
||||
|
||||
MouseEntered += OnMouseEntered;
|
||||
MouseExited += OnMouseExited;
|
||||
|
||||
PivotOffset = Size / 2;
|
||||
Scale = new(1, 1);
|
||||
Modulate = new(1, 1, 1);
|
||||
m_DesiredPosition = Position;
|
||||
UpdateState();
|
||||
SnapToDesired();
|
||||
|
||||
if (ModifierIconScene == null)
|
||||
GD.PushWarning($"{nameof(CardControl)} on node '{Name}' has no ModifierIconScene assigned. No modifier icons will be shown.");
|
||||
|
||||
if (m_Card == null)
|
||||
{
|
||||
m_Card = new DetoxiumPrime();
|
||||
m_Card.AddModifier(new UnreliableCardModifier(EModifierDuration.Permanent), []);
|
||||
m_Card.AddModifier(new EffectiveModifier(EModifierDuration.Permanent), []);
|
||||
m_Card.AddModifier(new RaceConditionModifier(EModifierDuration.Permanent), []);
|
||||
m_Card.AddModifier(new OptimizedModifier(EModifierDuration.Permanent), []);
|
||||
}
|
||||
Refresh();
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
UpdateAnimatedState(delta);
|
||||
}
|
||||
|
||||
private void OnMouseEntered()
|
||||
{
|
||||
if (m_Dragging || GetViewport().GuiIsDragging())
|
||||
return;
|
||||
|
||||
if (!CanDrag && !CanSelect || Disabled)
|
||||
return;
|
||||
|
||||
if (ShouldHoverSelect())
|
||||
Selected = true;
|
||||
|
||||
m_Hover = true;
|
||||
ZIndex = 1;
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
private bool ShouldHoverSelect()
|
||||
{
|
||||
if (!CanSelect || Disabled || m_CardRow == null)
|
||||
return false;
|
||||
|
||||
if (!m_CardRow.SupportsMultiSelect)
|
||||
return false;
|
||||
|
||||
if (!Input.IsMouseButtonPressed(MouseButton.Left))
|
||||
return false;
|
||||
|
||||
if (CardRow.IsCardPressActive)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnMouseExited()
|
||||
{
|
||||
m_PressedLeftDown = false;
|
||||
if (m_Dragging)
|
||||
return;
|
||||
|
||||
if (!CanDrag && !CanSelect || Disabled)
|
||||
return;
|
||||
|
||||
m_DropBefore = null;
|
||||
m_Hover = false;
|
||||
ZIndex = 0;
|
||||
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
private void UpdateState()
|
||||
{
|
||||
Vector2 newPivot = Size / 2;
|
||||
Vector2 newScale = new Vector2(1, 1);
|
||||
Color newModulate = new Color(1, 1, 1, 1);
|
||||
if (Selected)
|
||||
{
|
||||
newPivot.Y = Size.Y;
|
||||
newScale = new(1.25f, 1.25f);
|
||||
}
|
||||
|
||||
if (m_Hover)
|
||||
{
|
||||
newScale = new(1.25f, 1.25f);
|
||||
newModulate = new(1.25f, 1.25f, 1.25f);
|
||||
}
|
||||
|
||||
if (m_DropBefore == false)
|
||||
{
|
||||
newPivot.X = Size.X;
|
||||
newScale = new(1.25f, 1.25f);
|
||||
newModulate.A = 0.5f;
|
||||
}
|
||||
else if (m_DropBefore == true)
|
||||
{
|
||||
newPivot.X = 0;
|
||||
newScale = new(1.25f, 1.25f);
|
||||
newModulate.A = 0.5f;
|
||||
}
|
||||
if (m_Dragging)
|
||||
{
|
||||
newModulate.A = 0.25f;
|
||||
}
|
||||
|
||||
if (m_DesiredModulate == newModulate && m_DesiredScale == newScale && m_DesiredPivotOffset == newPivot)
|
||||
return;
|
||||
|
||||
m_DesiredPivotOffset = newPivot;
|
||||
m_DesiredScale = newScale;
|
||||
m_DesiredModulate = newModulate;
|
||||
EmitSignal(SignalName.CardStateChanged, this);
|
||||
}
|
||||
|
||||
public void Configure(Card card, CardRow cardRow)
|
||||
{
|
||||
m_Card = card;
|
||||
m_CardRow = cardRow;
|
||||
Refresh();
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
RefreshRarity();
|
||||
RefreshArtTexture();
|
||||
SetDescription(m_Card.Name, m_Card.ToolTip);
|
||||
SetModifiers(m_Card.Modifiers);
|
||||
}
|
||||
|
||||
private void RefreshArtTexture()
|
||||
{
|
||||
SetArtTexture(GD.Load<Texture2D>(m_Card.Id switch
|
||||
{
|
||||
ECard.Bitflip => "res://images/bitflip.png",
|
||||
ECard.ShortCircuit => "res://images/short-circuit.png",
|
||||
ECard.Slipstream => "res://images/slipstream.png",
|
||||
ECard.Latency => "res://images/latency.png",
|
||||
ECard.Rain => "res://images/rain.png",
|
||||
ECard.Drought => "res://images/drought.png",
|
||||
ECard.Pest => "res://images/pest.png",
|
||||
ECard.Gravity => "res://images/gravity.png",
|
||||
ECard.HeatWave => "res://images/heat-wave.png",
|
||||
ECard.SolarFlare => "res://images/solar-flare.png",
|
||||
ECard.LightningStorm => "res://images/lightning-storm.png",
|
||||
ECard.MeteorStorm => "res://images/meteor-storm.png",
|
||||
ECard.WindStorm => "res://images/wind-storm.png",
|
||||
ECard.Move => "res://images/move.png",
|
||||
ECard.TurnLeft => "res://images/turn-left.png",
|
||||
ECard.TurnRight => "res://images/turn-right.png",
|
||||
ECard.Interact => "res://images/interact.png",
|
||||
ECard.NoOp => "res://images/no-op.png",
|
||||
ECard.Potentiate => "res://images/potentiate.png",
|
||||
ECard.Optimize => "res://images/optimize.png",
|
||||
ECard.Streamline => "res://images/streamline.png",
|
||||
ECard.Persist => "res://images/persist.png",
|
||||
ECard.Remember => "res://images/remember.png",
|
||||
ECard.Reason => "res://images/reason.png",
|
||||
ECard.DetoxiumPrime => "res://images/detoxium-prime.png",
|
||||
ECard.FlyingDisk => "res://images/flying-disk.png",
|
||||
ECard.AluminumHat => "res://images/aluminum-hat.png",
|
||||
ECard.EMField => "res://images/em-field.png",
|
||||
ECard.AtomicClock => "res://images/atomic-clock.png",
|
||||
ECard.Jump => "res://images/jump.png",
|
||||
ECard.Repeat => "res://images/repeat.png",
|
||||
ECard.Rest => "res://images/rest.png",
|
||||
ECard.Stabilize => "res://images/stabilize.png",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
}));
|
||||
}
|
||||
|
||||
private void RefreshRarity()
|
||||
{
|
||||
var color = m_Card.Rarity switch
|
||||
{
|
||||
ERarity.Common => new(1, 1, 1),
|
||||
ERarity.Magic => new(0.3f, 1, 0.3f),
|
||||
ERarity.Uncommon => new(0.4f, 0.6f, 1.5f),
|
||||
ERarity.Rare => new(0.8f, 0.3f, 1),
|
||||
ERarity.Legendary => new Color(1, 0.5f, 0.2f),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
SetRarity(color);
|
||||
}
|
||||
|
||||
public void SetRarity(Color color)
|
||||
{
|
||||
if (m_BorderPanel == null || m_Background == null)
|
||||
return;
|
||||
|
||||
m_BorderPanel.Modulate = color;
|
||||
m_Background.Modulate = color;
|
||||
}
|
||||
|
||||
public void SetArtTexture(Texture2D texture)
|
||||
{
|
||||
if (m_ArtTextureRect == null)
|
||||
return;
|
||||
|
||||
m_ArtTextureRect.Texture = texture;
|
||||
}
|
||||
|
||||
public void SetDescription(string displayName, string description)
|
||||
{
|
||||
if (m_Label != null)
|
||||
m_Label.Text = displayName;
|
||||
|
||||
var header = string.IsNullOrEmpty(displayName) ? string.Empty : displayName.Trim();
|
||||
var body = string.IsNullOrEmpty(description) ? string.Empty : description.Trim();
|
||||
|
||||
if (!string.IsNullOrEmpty(header) && !string.IsNullOrEmpty(body))
|
||||
TooltipText = $"{header}\n{body}";
|
||||
else if (!string.IsNullOrEmpty(header))
|
||||
TooltipText = header;
|
||||
else
|
||||
TooltipText = body;
|
||||
}
|
||||
|
||||
public void SetModifiers(IReadOnlyList<Modifier> modifiers)
|
||||
{
|
||||
if (m_ModifiersContainer == null)
|
||||
return;
|
||||
|
||||
foreach (var child in m_ModifiersContainer.GetChildren())
|
||||
{
|
||||
child.QueueFree();
|
||||
}
|
||||
|
||||
foreach (var modifier in modifiers)
|
||||
{
|
||||
if (modifier.DebuffSources.Count > 0)
|
||||
continue;
|
||||
|
||||
var icon = ModifierIconScene.Instantiate<ModifierIcon>();
|
||||
icon.Configure(modifier);
|
||||
m_ModifiersContainer.AddChild(icon);
|
||||
}
|
||||
}
|
||||
|
||||
public void AnimateCard(Tween tween)
|
||||
{
|
||||
}
|
||||
|
||||
public void SetDesiredPosition(Vector2 desiredPosition)
|
||||
{
|
||||
m_DesiredPosition = desiredPosition;
|
||||
}
|
||||
|
||||
public void SnapToDesired()
|
||||
{
|
||||
Position = m_DesiredPosition;
|
||||
PivotOffset = m_DesiredPivotOffset;
|
||||
Scale = m_DesiredScale;
|
||||
Modulate = m_DesiredModulate;
|
||||
}
|
||||
|
||||
private void UpdateAnimatedState(double delta)
|
||||
{
|
||||
var weight = GetBlendWeight(delta);
|
||||
if (weight >= 1f)
|
||||
{
|
||||
SnapToDesired();
|
||||
return;
|
||||
}
|
||||
|
||||
Position = Position.Lerp(m_DesiredPosition, weight);
|
||||
PivotOffset = PivotOffset.Lerp(m_DesiredPivotOffset, weight);
|
||||
Scale = Scale.Lerp(m_DesiredScale, weight);
|
||||
Modulate = Modulate.Lerp(m_DesiredModulate, weight);
|
||||
}
|
||||
|
||||
private float GetBlendWeight(double delta)
|
||||
{
|
||||
var duration = GetAnimationDuration() * 0.05f;
|
||||
if (duration <= 0f)
|
||||
return 1f;
|
||||
|
||||
var weight = 1f - Mathf.Exp(-(float)delta / duration);
|
||||
return Mathf.Clamp(weight, 0f, 1f);
|
||||
}
|
||||
|
||||
private float GetAnimationDuration()
|
||||
{
|
||||
if (m_CardRow != null)
|
||||
return m_CardRow.AnimationDuration;
|
||||
|
||||
return 0.15f;
|
||||
}
|
||||
|
||||
public override void _GuiInput(InputEvent @event)
|
||||
{
|
||||
if (@event is not InputEventMouseButton mouseButton || mouseButton.ButtonIndex != MouseButton.Left)
|
||||
return;
|
||||
|
||||
AcceptEvent();
|
||||
if (Disabled)
|
||||
return;
|
||||
|
||||
if (mouseButton.Pressed)
|
||||
{
|
||||
m_PressedLeftDown = true;
|
||||
CardRow.NotifyCardPress(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_PressedLeftDown && CanSelect)
|
||||
Selected = !Selected;
|
||||
|
||||
m_PressedLeftDown = false;
|
||||
CardRow.NotifyCardPress(false);
|
||||
}
|
||||
}
|
||||
|
||||
public override Variant _GetDragData(Vector2 atPosition)
|
||||
{
|
||||
if (!CanDrag || Disabled)
|
||||
return default;
|
||||
|
||||
m_Dragging = true;
|
||||
UpdateState();
|
||||
UpdateDragPreviewForDropTarget(null);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void _Notification(int what)
|
||||
{
|
||||
switch ((long)what)
|
||||
{
|
||||
case NotificationDragBegin:
|
||||
m_DropBefore = null;
|
||||
UpdateState();
|
||||
break;
|
||||
case NotificationDragEnd:
|
||||
m_Dragging = false;
|
||||
m_Hover = false;
|
||||
m_DropBefore = null;
|
||||
m_DragPreviewRoot = null;
|
||||
m_DragPreviewCards = null;
|
||||
UpdateState();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool _CanDropData(Vector2 atPosition, Variant data)
|
||||
{
|
||||
if (!CanDrag || Disabled)
|
||||
return false;
|
||||
|
||||
var dataObject = data.AsGodotObject();
|
||||
if (dataObject is not CardControl sourceControl || sourceControl == this)
|
||||
return false;
|
||||
|
||||
if (GetParent<CardRow>() is { } cardRow)
|
||||
{
|
||||
if (!cardRow._CanDropData(atPosition, data))
|
||||
return false;
|
||||
}
|
||||
|
||||
sourceControl.UpdateDragPreviewForDropTarget(this);
|
||||
var size = Size.X / 2;
|
||||
var before = atPosition.X < size;
|
||||
if (before != m_DropBefore)
|
||||
{
|
||||
GD.Print($"Can drop before={before} {m_Card.Id}");
|
||||
m_DropBefore = before;
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void UpdateDragPreviewForDropTarget(CardControl targetControl)
|
||||
{
|
||||
var draggedCards = GetDraggedCardsForPreview();
|
||||
if (ShouldForceSinglePreview(targetControl, draggedCards))
|
||||
SetDragPreviewCards([this]);
|
||||
else
|
||||
SetDragPreviewCards(draggedCards);
|
||||
}
|
||||
|
||||
private IReadOnlyList<CardControl> GetDraggedCardsForPreview()
|
||||
{
|
||||
if (m_CardRow == null || !m_CardRow.SupportsMultiDrag)
|
||||
return [this];
|
||||
|
||||
var selectedCards = m_CardRow.SelectedCards;
|
||||
if (selectedCards.Count <= 1)
|
||||
return [this];
|
||||
|
||||
foreach (var selectedCard in selectedCards)
|
||||
{
|
||||
if (selectedCard.CardId == Card.CardId)
|
||||
return selectedCards.Select(c => m_CardRow.CardToControl[c.CardId]).ToList();
|
||||
}
|
||||
|
||||
return [this];
|
||||
}
|
||||
|
||||
private bool ShouldForceSinglePreview(CardControl targetControl, IReadOnlyList<CardControl> draggedCards)
|
||||
{
|
||||
if (targetControl == null)
|
||||
return false;
|
||||
|
||||
if (draggedCards.Count <= 1)
|
||||
return false;
|
||||
|
||||
if (!targetControl.Selected)
|
||||
return false;
|
||||
|
||||
foreach (var draggedCard in draggedCards)
|
||||
{
|
||||
if (draggedCard == targetControl)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetDragPreviewCards(IReadOnlyList<CardControl> cards)
|
||||
{
|
||||
if (m_DragPreviewCards != null && m_DragPreviewCards.SequenceEqual(cards))
|
||||
return;
|
||||
|
||||
m_DragPreviewCards = cards.ToList();
|
||||
|
||||
foreach (var card in m_CardRow.CardToControl.Values)
|
||||
{
|
||||
card.m_Dragging = false;
|
||||
card.UpdateState();
|
||||
}
|
||||
|
||||
if (m_DragPreviewRoot != null)
|
||||
{
|
||||
foreach (var child in m_DragPreviewRoot.GetChildren())
|
||||
child.QueueFree();
|
||||
}
|
||||
|
||||
if (cards.Count == 0)
|
||||
return;
|
||||
|
||||
if (cards.Count == 1)
|
||||
{
|
||||
m_DragPreviewRoot?.QueueFree();
|
||||
m_DragPreviewRoot = null;
|
||||
var cardControl = cards[0];
|
||||
var previewCard = (CardControl)cardControl.Duplicate();
|
||||
previewCard.SetAnchorsPreset(LayoutPreset.TopLeft);
|
||||
previewCard.Configure(cardControl.Card, null);
|
||||
previewCard.Selected = cardControl.Selected;
|
||||
previewCard.UpdateState();
|
||||
previewCard.SetDesiredPosition(previewCard.Position);
|
||||
previewCard.SnapToDesired();
|
||||
SetDragPreview(previewCard);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_DragPreviewRoot == null)
|
||||
{
|
||||
m_DragPreviewRoot = new();
|
||||
m_DragPreviewRoot.MouseFilter = MouseFilterEnum.Ignore;
|
||||
}
|
||||
|
||||
var spacing = m_CardRow?.CardSpacing ?? 0f;
|
||||
var offset = Mathf.Max(6f, spacing * 2f);
|
||||
var offsetVector = new Vector2(offset, offset);
|
||||
|
||||
for (var index = 0; index < cards.Count; index++)
|
||||
{
|
||||
var cardControl = cards[index];
|
||||
cardControl.m_Dragging = true;
|
||||
cardControl.UpdateState();
|
||||
var previewCard = (CardControl)cardControl.Duplicate();
|
||||
previewCard.SetAnchorsPreset(LayoutPreset.TopLeft);
|
||||
previewCard.Configure(cardControl.Card, null);
|
||||
previewCard.Position = new(offsetVector.X * index, offsetVector.Y * index);
|
||||
previewCard.Size = Size;
|
||||
previewCard.UpdateState();
|
||||
previewCard.SetDesiredPosition(previewCard.Position);
|
||||
previewCard.SnapToDesired();
|
||||
previewCard.ZIndex = index + 1;
|
||||
m_DragPreviewRoot.AddChild(previewCard);
|
||||
}
|
||||
|
||||
m_DragPreviewRoot.Size = Size + offsetVector * Math.Max(0, cards.Count - 1);
|
||||
SetDragPreview(m_DragPreviewRoot);
|
||||
}
|
||||
|
||||
public override void _DropData(Vector2 atPosition, Variant data)
|
||||
{
|
||||
if (!CanDrag)
|
||||
return;
|
||||
|
||||
var obj = data.AsGodotObject();
|
||||
if (obj is CardControl source)
|
||||
{
|
||||
var size = Size.X / 2;
|
||||
var before = atPosition.X < size;
|
||||
EmitSignal(SignalName.CardDroppedOn, source, this, before);
|
||||
}
|
||||
}
|
||||
|
||||
[Export]
|
||||
public bool CanDrag { get; set; }
|
||||
|
||||
[Export]
|
||||
public bool CanSelect { get; set; }
|
||||
|
||||
[Export]
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
[Export]
|
||||
public bool Selected
|
||||
{
|
||||
get => m_Selected;
|
||||
set
|
||||
{
|
||||
if (m_Selected == value)
|
||||
return;
|
||||
|
||||
m_Selected = value;
|
||||
EmitSignal(SignalName.CardSelectionChanged, this, m_Selected);
|
||||
UpdateState();
|
||||
|
||||
if (m_Selected && m_CardRow != null)
|
||||
{
|
||||
Tooltip.Instance.Describe(Card);
|
||||
CardRow.SelectionContext = m_CardRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CardRow CardRow => m_CardRow;
|
||||
|
||||
public Card Card => m_Card;
|
||||
|
||||
public Vector2 DesiredScale => m_DesiredScale;
|
||||
|
||||
public Vector2 DesiredPivotOffset => m_DesiredPivotOffset;
|
||||
|
||||
[Signal]
|
||||
public delegate void CardDroppedOnEventHandler(CardControl source, CardControl target, bool before);
|
||||
|
||||
[Signal]
|
||||
public delegate void CardSelectionChangedEventHandler(CardControl card, bool selected);
|
||||
|
||||
[Signal]
|
||||
public delegate void CardStateChangedEventHandler(CardControl card);
|
||||
|
||||
[Export]
|
||||
public PackedScene ModifierIconScene { get; set; }
|
||||
|
||||
private TextureRect m_ArtTextureRect;
|
||||
private Panel m_Background;
|
||||
private Panel m_BorderPanel;
|
||||
private HBoxContainer m_ModifiersContainer;
|
||||
private Card m_Card;
|
||||
private bool m_PressedLeftDown;
|
||||
private bool m_Dragging;
|
||||
private bool m_Selected;
|
||||
private bool m_Hover;
|
||||
private bool? m_DropBefore;
|
||||
private CardRow m_CardRow;
|
||||
private Label m_Label;
|
||||
private HBoxContainer m_DragPreviewRoot;
|
||||
private IReadOnlyList<CardControl> m_DragPreviewCards;
|
||||
private Vector2 m_DesiredPosition;
|
||||
private Vector2 m_DesiredPivotOffset;
|
||||
private Vector2 m_DesiredScale;
|
||||
private Color m_DesiredModulate;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/CardControl.cs.uid
Normal file
1
DonkeysAndDroids.Godot/CardControl.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://oiles788elk5
|
||||
756
DonkeysAndDroids.Godot/CardRow.cs
Normal file
756
DonkeysAndDroids.Godot/CardRow.cs
Normal file
@@ -0,0 +1,756 @@
|
||||
using Godot;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using RobotAndDonkey.Game;
|
||||
|
||||
public partial class CardRow : Control
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
CacheBackgroundPanels();
|
||||
UpdateBackgroundVisibility();
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (s_CardPressActive && !Input.IsMouseButtonPressed(MouseButton.Left))
|
||||
s_CardPressActive = false;
|
||||
|
||||
if (OrderedCards.Count == 0)
|
||||
{
|
||||
m_LayoutDirty = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var needsLayout = m_LayoutDirty || GlobalPosition != m_LastPosition || Size != m_LastSize;
|
||||
if (!needsLayout)
|
||||
return;
|
||||
|
||||
m_LayoutDirty = false;
|
||||
LayoutCards();
|
||||
if (GetParent() is not Container)
|
||||
SetAnchorsPreset(LayoutPreset.Center);
|
||||
}
|
||||
|
||||
public override void _GuiInput(InputEvent @event)
|
||||
{
|
||||
if (@event is InputEventMouseButton mouseButton && mouseButton.ButtonIndex == MouseButton.Left && mouseButton.Pressed)
|
||||
ClearSelection();
|
||||
}
|
||||
|
||||
public void Configure(IReadOnlyList<Card> cards, Tween tween, bool celebrateModifiers, bool drag)
|
||||
{
|
||||
Debug.WriteLine($"Configure {cards.Count} cards, celebrate={celebrateModifiers}, drag={drag}");
|
||||
if (CardScene == null)
|
||||
{
|
||||
GD.PushWarning($"{nameof(CardRow)} has no CardScene assigned. No cards will be shown.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cards.SequenceEqual(OrderedCards))
|
||||
return;
|
||||
|
||||
var newSet = new HashSet<Guid>(cards.Select(c => c.CardId));
|
||||
var toRemove = OrderedCards.Where(c => !newSet.Contains(c.CardId)).ToList();
|
||||
|
||||
foreach (var card in toRemove)
|
||||
{
|
||||
if (!CardToControl.Remove(card.CardId, out var control))
|
||||
continue;
|
||||
|
||||
control.QueueFree();
|
||||
}
|
||||
|
||||
OrderedCards.Clear();
|
||||
OrderedCards.AddRange(cards);
|
||||
|
||||
var newControls = new List<CardControl>();
|
||||
for (var index = 0; index < cards.Count; index++)
|
||||
{
|
||||
var card = cards[index];
|
||||
if (!CardToControl.TryGetValue(card.CardId, out var control))
|
||||
{
|
||||
Debug.WriteLine($"Add new card {card.Id}");
|
||||
control = CreateCardControl(card);
|
||||
CardToControl.Add(card.CardId, control);
|
||||
AddChild(control);
|
||||
newControls.Add(control);
|
||||
}
|
||||
else
|
||||
{
|
||||
control.Configure(card, this);
|
||||
}
|
||||
}
|
||||
|
||||
LayoutCards();
|
||||
foreach (var control in newControls)
|
||||
control.SnapToDesired();
|
||||
|
||||
if (celebrateModifiers)
|
||||
{
|
||||
foreach (var card in cards)
|
||||
{
|
||||
foreach (var modifier in card.Modifiers)
|
||||
{
|
||||
ModifyCard(card, modifier.Id, tween);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GetLayoutMetrics(Card card, out float visualWidth, out float leftOffset, out float topOffset, out float bottomOffset)
|
||||
{
|
||||
var size = CardSize;
|
||||
var desiredScale = Vector2.One;
|
||||
var desiredPivot = size / 2f;
|
||||
|
||||
if (CardToControl.TryGetValue(card.CardId, out var control))
|
||||
{
|
||||
size = control.Size;
|
||||
desiredScale = control.DesiredScale;
|
||||
desiredPivot = control.DesiredPivotOffset;
|
||||
}
|
||||
|
||||
visualWidth = size.X * desiredScale.X;
|
||||
leftOffset = (1f - desiredScale.X) * desiredPivot.X;
|
||||
topOffset = (1f - desiredScale.Y) * desiredPivot.Y;
|
||||
bottomOffset = topOffset + size.Y * desiredScale.Y;
|
||||
}
|
||||
|
||||
private void CalculateCardPositions(IReadOnlyList<Card> cards)
|
||||
{
|
||||
m_CardPositions.Clear();
|
||||
var count = cards.Count;
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
var cardHeight = CardSize.Y;
|
||||
|
||||
var cardIds = new Guid[count];
|
||||
var visualWidths = new float[count];
|
||||
var leftOffsets = new float[count];
|
||||
var topOffsets = new float[count];
|
||||
var bottomOffsets = new float[count];
|
||||
var totalVisualWidth = 0f;
|
||||
|
||||
for (var index = 0; index < count; index++)
|
||||
{
|
||||
var card = cards[index];
|
||||
cardIds[index] = card.CardId;
|
||||
|
||||
GetLayoutMetrics(card, out var visualWidth, out var leftOffset, out var topOffset, out var bottomOffset);
|
||||
|
||||
visualWidths[index] = visualWidth;
|
||||
leftOffsets[index] = leftOffset;
|
||||
topOffsets[index] = topOffset;
|
||||
bottomOffsets[index] = bottomOffset;
|
||||
totalVisualWidth += visualWidth;
|
||||
}
|
||||
|
||||
var availableWidth = Size.X > 0 ? Size.X : CardSize.X;
|
||||
var availableHeight = Size.Y > 0 ? Size.Y : CardSize.Y;
|
||||
|
||||
if (availableWidth <= 0)
|
||||
availableWidth = totalVisualWidth + (count - 1) * CardSpacing;
|
||||
|
||||
if (availableHeight <= 0)
|
||||
availableHeight = cardHeight;
|
||||
|
||||
if (WrapCards)
|
||||
{
|
||||
var rowSpacing = CardSpacing;
|
||||
var rowTopOffsets = new List<float>();
|
||||
var rowBottomOffsets = new List<float>();
|
||||
var rowIndices = new int[count];
|
||||
|
||||
var rowWidth = 0f;
|
||||
var rowIndex = 0;
|
||||
var rowHasCards = false;
|
||||
var rowTopOffset = 0f;
|
||||
var rowBottomOffset = 0f;
|
||||
|
||||
for (var index = 0; index < count; index++)
|
||||
{
|
||||
var cardWidthForLayout = visualWidths[index];
|
||||
var spacing = rowWidth > 0f ? CardSpacing : 0f;
|
||||
|
||||
if (rowWidth > 0f && rowWidth + spacing + cardWidthForLayout > availableWidth)
|
||||
{
|
||||
rowTopOffsets.Add(rowTopOffset);
|
||||
rowBottomOffsets.Add(rowBottomOffset);
|
||||
rowIndex += 1;
|
||||
rowWidth = 0f;
|
||||
rowHasCards = false;
|
||||
spacing = 0f;
|
||||
}
|
||||
|
||||
if (!rowHasCards)
|
||||
{
|
||||
rowTopOffset = topOffsets[index];
|
||||
rowBottomOffset = bottomOffsets[index];
|
||||
rowHasCards = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
rowTopOffset = Mathf.Min(rowTopOffset, topOffsets[index]);
|
||||
rowBottomOffset = Mathf.Max(rowBottomOffset, bottomOffsets[index]);
|
||||
}
|
||||
|
||||
rowIndices[index] = rowIndex;
|
||||
rowWidth += spacing + cardWidthForLayout;
|
||||
}
|
||||
|
||||
if (rowHasCards)
|
||||
{
|
||||
rowTopOffsets.Add(rowTopOffset);
|
||||
rowBottomOffsets.Add(rowBottomOffset);
|
||||
}
|
||||
|
||||
var rowTops = new float[rowTopOffsets.Count];
|
||||
if (rowTops.Length > 0)
|
||||
{
|
||||
rowTops[0] = 0f;
|
||||
for (var index = 1; index < rowTopOffsets.Count; index++)
|
||||
{
|
||||
var previousBottom = rowTops[index - 1] + rowBottomOffsets[index - 1];
|
||||
rowTops[index] = previousBottom + rowSpacing - rowTopOffsets[index];
|
||||
}
|
||||
}
|
||||
|
||||
var currentRow = 0;
|
||||
var currentVisualLeft = 0f;
|
||||
|
||||
for (var index = 0; index < count; index++)
|
||||
{
|
||||
var cardRowIndex = rowIndices[index];
|
||||
if (cardRowIndex != currentRow)
|
||||
{
|
||||
currentRow = cardRowIndex;
|
||||
currentVisualLeft = 0f;
|
||||
}
|
||||
|
||||
if (currentVisualLeft > 0f)
|
||||
currentVisualLeft += CardSpacing;
|
||||
|
||||
var positionX = currentVisualLeft - leftOffsets[index];
|
||||
var positionY = rowTops[cardRowIndex];
|
||||
m_CardPositions[cardIds[index]] = new Vector2(positionX, positionY);
|
||||
|
||||
currentVisualLeft += visualWidths[index];
|
||||
}
|
||||
|
||||
if (rowTopOffsets.Count > 0)
|
||||
{
|
||||
var totalHeight = rowTops[^1] + rowBottomOffsets[^1];
|
||||
CustomMinimumSize = new(0, totalHeight);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var spacing = CardSpacing;
|
||||
var widthWithBaseSpacing = totalVisualWidth + (count - 1) * CardSpacing;
|
||||
|
||||
if (count > 1)
|
||||
{
|
||||
if (widthWithBaseSpacing > availableWidth)
|
||||
{
|
||||
spacing = (availableWidth - totalVisualWidth) / (count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
var startVisualLeft = (availableWidth - widthWithBaseSpacing) * 0.5f;
|
||||
var y = (availableHeight - cardHeight) * 0.5f;
|
||||
|
||||
var currentVisualLeft = startVisualLeft;
|
||||
|
||||
for (var index = 0; index < count; index++)
|
||||
{
|
||||
var positionX = currentVisualLeft - leftOffsets[index];
|
||||
m_CardPositions[cardIds[index]] = new Vector2(positionX, y);
|
||||
|
||||
currentVisualLeft += visualWidths[index] + spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RunCard(Card card, Tween tween)
|
||||
{
|
||||
if (!CardToControl.Remove(card.CardId, out var control))
|
||||
return;
|
||||
|
||||
Main.Instance.Music.Play(MusicManager.ESound.Card);
|
||||
control.QueueFree();
|
||||
OrderedCards.Remove(card);
|
||||
LayoutCards();
|
||||
}
|
||||
|
||||
public void ModifyCard(Card card, EModifierId modifier, Tween tween)
|
||||
{
|
||||
if (!CardToControl.TryGetValue(card.CardId, out var control))
|
||||
{
|
||||
Debug.WriteLine($"Failed to modify {card.Id}");
|
||||
return;
|
||||
}
|
||||
|
||||
var hasModifier = card.Modifiers.Any(m => m.Id == modifier && m.DebuffSources.Count == 0);
|
||||
Debug.WriteLine($"Modify {card.Id}");
|
||||
control.Configure(card, this);
|
||||
if (hasModifier)
|
||||
Main.Instance.Music.Play(modifier);
|
||||
}
|
||||
|
||||
private CardControl CreateCardControl(Card card)
|
||||
{
|
||||
var node = CardScene.Instantiate<CardControl>();
|
||||
node.Name = $"Card_{card.Id}";
|
||||
|
||||
node.SetAnchorsPreset(LayoutPreset.TopLeft);
|
||||
node.Size = CardSize;
|
||||
node.PivotOffset = CardSize / 2f;
|
||||
node.CanDrag = CardsCanDrag;
|
||||
node.CanSelect = CardsCanSelect;
|
||||
node.Configure(card, this);
|
||||
|
||||
node.Connect(CardControl.SignalName.CardDroppedOn, new(this, nameof(OnCardDroppedOn)));
|
||||
node.Connect(CardControl.SignalName.CardSelectionChanged, new(this, nameof(OnCardSelectionChanged)));
|
||||
node.Connect(CardControl.SignalName.CardStateChanged, new(this, nameof(OnCardStateChanged)));
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private void OnCardStateChanged(CardControl card)
|
||||
{
|
||||
m_LayoutDirty = true;
|
||||
}
|
||||
|
||||
private void OnCardSelectionChanged(CardControl card, bool selected)
|
||||
{
|
||||
if (!m_SuppressSelectionValidation && CanChangeSelection != null && !CanChangeSelection(card, selected))
|
||||
{
|
||||
m_SuppressSelectionValidation = true;
|
||||
card.Selected = !selected;
|
||||
m_SuppressSelectionValidation = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_SuppressSelectionSignals)
|
||||
return;
|
||||
|
||||
if (selected)
|
||||
SelectionContext = this;
|
||||
|
||||
if (selected && !SupportsMultiSelect)
|
||||
{
|
||||
m_SuppressSelectionValidation = true;
|
||||
foreach (var other in CardToControl.Values)
|
||||
{
|
||||
if (other != card && other.Selected)
|
||||
other.Selected = false;
|
||||
}
|
||||
m_SuppressSelectionValidation = false;
|
||||
}
|
||||
|
||||
EmitSignal(SignalName.SelectionChanged, CardToControl.Values.Where(c => c.Selected).ToArray());
|
||||
}
|
||||
|
||||
private void OnCardDroppedOn(CardControl source, CardControl target, bool before)
|
||||
{
|
||||
if (source == target)
|
||||
return;
|
||||
|
||||
var sourceRow = source.CardRow;
|
||||
var targetRow = target.CardRow;
|
||||
|
||||
var fromCardId = sourceRow.CardToControl.FirstOrDefault(kv => kv.Value == source).Key;
|
||||
var toCardId = targetRow.CardToControl.FirstOrDefault(kv => kv.Value == target).Key;
|
||||
|
||||
if (fromCardId == Guid.Empty || toCardId == Guid.Empty)
|
||||
return;
|
||||
|
||||
var fromCard = sourceRow.OrderedCards.Single(c => c.CardId == fromCardId);
|
||||
var toCard = OrderedCards.Single(c => c.CardId == toCardId);
|
||||
var draggedCards = GetDraggedCards(sourceRow, fromCard);
|
||||
|
||||
if (draggedCards.Contains(toCard))
|
||||
{
|
||||
if (SelectedCards.Contains(toCard))
|
||||
{
|
||||
draggedCards = [fromCard];
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var targetCards = targetRow.OrderedCards;
|
||||
var toIndex = targetCards.IndexOf(toCard);
|
||||
if (toIndex == -1)
|
||||
return;
|
||||
|
||||
var insertIndex = before ? toIndex : toIndex + 1;
|
||||
|
||||
MoveCards(sourceRow, targetRow, draggedCards, insertIndex);
|
||||
}
|
||||
|
||||
public override bool _CanDropData(Vector2 atPosition, Variant data)
|
||||
{
|
||||
if (Disabled || !CardsCanDrag)
|
||||
return false;
|
||||
|
||||
if (data.Obj is not CardControl sourceControl)
|
||||
return false;
|
||||
|
||||
var sourceRow = sourceControl.CardRow;
|
||||
var fromCardId = sourceRow.CardToControl.FirstOrDefault(kv => kv.Value == sourceControl).Key;
|
||||
if (fromCardId == Guid.Empty)
|
||||
return false;
|
||||
|
||||
var fromCard = sourceRow.OrderedCards.Single(c => c.CardId == fromCardId);
|
||||
var draggedCards = GetDraggedCards(sourceRow, fromCard);
|
||||
if (draggedCards.Count == 0)
|
||||
return false;
|
||||
|
||||
if (sourceRow != this && OccupiedSpace + draggedCards.Select(c => c.OccupiedSpace).Sum() > MaxCards)
|
||||
return false;
|
||||
|
||||
sourceControl.UpdateDragPreviewForDropTarget(null);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void _DropData(Vector2 atPosition, Variant data)
|
||||
{
|
||||
if (Disabled || !CardsCanDrag)
|
||||
return;
|
||||
|
||||
if (data.Obj is not CardControl sourceControl)
|
||||
return;
|
||||
|
||||
var sourceRow = sourceControl.CardRow;
|
||||
var fromCardId = sourceRow?.CardToControl.FirstOrDefault(kv => kv.Value == sourceControl).Key;
|
||||
if (fromCardId == Guid.Empty)
|
||||
return;
|
||||
|
||||
var fromCard = sourceRow.OrderedCards.Single(c => c.CardId == fromCardId);
|
||||
var draggedCards = GetDraggedCards(sourceRow, fromCard);
|
||||
if (draggedCards.Count == 0)
|
||||
return;
|
||||
|
||||
if (OccupiedSpace + draggedCards.Select(c => c.OccupiedSpace).Sum() > MaxCards)
|
||||
return;
|
||||
|
||||
var insertIndex = GetDropIndex(atPosition);
|
||||
MoveCards(sourceRow, this, draggedCards, insertIndex);
|
||||
}
|
||||
|
||||
private List<Card> GetDraggedCards(CardRow sourceRow, Card fromCard)
|
||||
{
|
||||
if (fromCard == null)
|
||||
return [];
|
||||
|
||||
if (!sourceRow.SupportsMultiDrag)
|
||||
return [fromCard];
|
||||
|
||||
var selected = sourceRow.SelectedCardIndices.Select(i => sourceRow.OrderedCards[i]).ToList();
|
||||
if (selected.Count > 1 && selected.Contains(fromCard))
|
||||
{
|
||||
var originalOrder = sourceRow.OrderedCards;
|
||||
return selected.OrderBy(c => originalOrder.IndexOf(c)).ToList();
|
||||
}
|
||||
|
||||
return [fromCard];
|
||||
}
|
||||
|
||||
private void MoveCards(CardRow sourceRow, CardRow targetRow, List<Card> draggedCards, int rawInsertIndex)
|
||||
{
|
||||
if (draggedCards == null || draggedCards.Count == 0)
|
||||
return;
|
||||
|
||||
var draggedSet = new HashSet<Card>(draggedCards);
|
||||
|
||||
if (sourceRow == targetRow)
|
||||
{
|
||||
var original = sourceRow.OrderedCards.ToList();
|
||||
if (original.Count == 0)
|
||||
return;
|
||||
|
||||
var insertIndex = Mathf.Clamp(rawInsertIndex, 0, original.Count);
|
||||
|
||||
var removedBefore = 0;
|
||||
foreach (var card in draggedCards)
|
||||
{
|
||||
var idx = original.IndexOf(card);
|
||||
if (idx >= 0 && idx < insertIndex)
|
||||
removedBefore++;
|
||||
}
|
||||
|
||||
var adjustedIndex = insertIndex - removedBefore;
|
||||
|
||||
var newOrder = original.Where(c => !draggedSet.Contains(c)).ToList();
|
||||
adjustedIndex = Mathf.Clamp(adjustedIndex, 0, newOrder.Count);
|
||||
newOrder.InsertRange(adjustedIndex, draggedCards);
|
||||
|
||||
sourceRow.Configure(newOrder, null, false, true);
|
||||
sourceRow.EmitSignal(SignalName.OrderChanged);
|
||||
}
|
||||
else
|
||||
{
|
||||
var sourceList = sourceRow.OrderedCards.ToList();
|
||||
var targetList = targetRow.OrderedCards.ToList();
|
||||
|
||||
foreach (var card in draggedCards)
|
||||
{
|
||||
sourceList.Remove(card);
|
||||
targetList.Remove(card);
|
||||
}
|
||||
|
||||
var insertIndex = Mathf.Clamp(rawInsertIndex, 0, targetList.Count);
|
||||
targetList.InsertRange(insertIndex, draggedCards);
|
||||
|
||||
sourceRow.Configure(sourceList, null, false, true);
|
||||
targetRow.Configure(targetList, null, false, true);
|
||||
targetRow.EmitSignal(SignalName.OrderChanged);
|
||||
}
|
||||
}
|
||||
|
||||
private int GetDropIndex(Vector2 localPosition)
|
||||
{
|
||||
var count = OrderedCards.Count;
|
||||
if (count == 0)
|
||||
return 0;
|
||||
|
||||
var orderedControls = OrderedCards.Select(card => CardToControl.GetValueOrDefault(card.CardId)).Where(control => control != null).ToList();
|
||||
|
||||
if (orderedControls.Count == 0)
|
||||
return 0;
|
||||
|
||||
var mouseX = localPosition.X;
|
||||
|
||||
if (mouseX <= orderedControls[0].Position.X)
|
||||
return 0;
|
||||
|
||||
var last = orderedControls[^1];
|
||||
if (mouseX >= last.Position.X + CardSize.X)
|
||||
return orderedControls.Count;
|
||||
|
||||
for (var i = 0; i < orderedControls.Count; i++)
|
||||
{
|
||||
var control = orderedControls[i];
|
||||
if (mouseX < control.Position.X)
|
||||
return i;
|
||||
}
|
||||
|
||||
return orderedControls.Count;
|
||||
}
|
||||
|
||||
private void LayoutCards()
|
||||
{
|
||||
Debug.WriteLine("Layout");
|
||||
var count = OrderedCards.Count;
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
m_LastPosition = GlobalPosition;
|
||||
m_LastSize = Size;
|
||||
|
||||
CalculateCardPositions(OrderedCards);
|
||||
|
||||
foreach (var card in OrderedCards)
|
||||
{
|
||||
if (!CardToControl.TryGetValue(card.CardId, out var control))
|
||||
continue;
|
||||
|
||||
if (m_CardPositions.TryGetValue(card.CardId, out var targetPos))
|
||||
control.SetDesiredPosition(targetPos);
|
||||
}
|
||||
}
|
||||
|
||||
private void CacheBackgroundPanels()
|
||||
{
|
||||
m_Backgrounds.Clear();
|
||||
|
||||
foreach (var child in GetChildren())
|
||||
{
|
||||
if (child is Control control && control.Name.ToString().StartsWith("Background"))
|
||||
{
|
||||
control.ZIndex = 0;
|
||||
control.MouseFilter = MouseFilterEnum.Ignore;
|
||||
m_Backgrounds.Add(control);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateBackgroundVisibility()
|
||||
{
|
||||
if (m_Backgrounds.Count == 0)
|
||||
return;
|
||||
|
||||
var clamped = Mathf.Clamp(BackgroundVariant, 0, m_Backgrounds.Count - 1);
|
||||
for (var i = 0; i < m_Backgrounds.Count; i++)
|
||||
m_Backgrounds[i].Visible = i == clamped;
|
||||
}
|
||||
|
||||
public void ApplySelection(IReadOnlyCollection<Guid> selectedCardIds, bool suppressSignals)
|
||||
{
|
||||
m_SuppressSelectionValidation = true;
|
||||
m_SuppressSelectionSignals = suppressSignals;
|
||||
foreach (var kvp in CardToControl)
|
||||
{
|
||||
kvp.Value.Selected = selectedCardIds.Contains(kvp.Key);
|
||||
}
|
||||
m_SuppressSelectionSignals = false;
|
||||
m_SuppressSelectionValidation = false;
|
||||
}
|
||||
|
||||
public void ClearSelection()
|
||||
{
|
||||
ApplySelection([], true);
|
||||
EmitSignal(SignalName.SelectionChanged, []);
|
||||
}
|
||||
|
||||
public static List<Card> GetSortedCards(IEnumerable<Card> cards)
|
||||
{
|
||||
return cards.Order(Comparer<Card>.Create((a, b) =>
|
||||
{
|
||||
var cmp = a.Rarity.CompareTo(b.Rarity);
|
||||
if (cmp == 0)
|
||||
cmp = a.Id.CompareTo(b.Id);
|
||||
if (cmp == 0)
|
||||
cmp = a.Modifiers.Count.CompareTo(b.Modifiers.Count);
|
||||
if (cmp == 0)
|
||||
cmp = a.CardId.CompareTo(b.CardId);
|
||||
return cmp;
|
||||
})).ToList();
|
||||
}
|
||||
|
||||
[Export]
|
||||
public PackedScene CardScene { get; set; }
|
||||
|
||||
[Export]
|
||||
public Vector2 CardSize { get; set; } = new(96, 96);
|
||||
|
||||
[Export]
|
||||
public float CardSpacing { get; set; } = 8f;
|
||||
|
||||
[Export]
|
||||
public float AnimationDuration { get; set; } = 0.15f;
|
||||
|
||||
[Export]
|
||||
public bool CardsCanDrag { get; set; } = true;
|
||||
|
||||
[Export]
|
||||
public bool CardsCanSelect { get; set; } = false;
|
||||
|
||||
[Export]
|
||||
public bool SupportsMultiSelect { get; set; } = false;
|
||||
|
||||
[Export]
|
||||
public bool SupportsMultiDrag { get; set; } = false;
|
||||
|
||||
[Export]
|
||||
public int BackgroundVariant
|
||||
{
|
||||
get => m_BackgroundVariant;
|
||||
set
|
||||
{
|
||||
if (m_BackgroundVariant == value)
|
||||
return;
|
||||
|
||||
m_BackgroundVariant = value;
|
||||
UpdateBackgroundVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
public Func<CardControl, bool, bool> CanChangeSelection { get; set; }
|
||||
|
||||
public static bool IsCardPressActive => s_CardPressActive;
|
||||
|
||||
public static void NotifyCardPress(bool pressed)
|
||||
{
|
||||
s_CardPressActive = pressed;
|
||||
}
|
||||
|
||||
|
||||
public static CardRow SelectionContext
|
||||
{
|
||||
get => s_SelectionContext;
|
||||
set
|
||||
{
|
||||
if (s_SelectionContext == value)
|
||||
return;
|
||||
|
||||
if (s_SelectionContext != null)
|
||||
{
|
||||
foreach (var card in s_SelectionContext.CardToControl)
|
||||
card.Value.Selected = false;
|
||||
}
|
||||
|
||||
s_SelectionContext = value;
|
||||
}
|
||||
}
|
||||
|
||||
[Export]
|
||||
public bool Disabled
|
||||
{
|
||||
get => m_Disabled;
|
||||
set
|
||||
{
|
||||
if (m_Disabled == value)
|
||||
return;
|
||||
|
||||
m_Disabled = value;
|
||||
foreach (var card in CardToControl)
|
||||
card.Value.Disabled = m_Disabled;
|
||||
}
|
||||
}
|
||||
|
||||
[Export]
|
||||
public int MaxCards { get; set; } = int.MaxValue;
|
||||
|
||||
[Export]
|
||||
public bool WrapCards { get; set; } = false;
|
||||
|
||||
public int OccupiedSpace => OrderedCards.Select(c => c.OccupiedSpace).Sum();
|
||||
|
||||
public IEnumerable<int> SelectedCardIndices =>
|
||||
OrderedCards.Select((c, i) => (Card: CardToControl[c.CardId], Index: i)).Where(pair => pair.Card.Selected).Select(pair => pair.Index);
|
||||
|
||||
public IReadOnlyList<Guid> SelectedCardIds =>
|
||||
OrderedCards.Where(card => CardToControl.TryGetValue(card.CardId, out var control) && control.Selected)
|
||||
.Select(card => card.CardId)
|
||||
.ToList();
|
||||
|
||||
public IReadOnlyList<Card> SelectedCards =>
|
||||
OrderedCards.Where(card => CardToControl.TryGetValue(card.CardId, out var control) && control.Selected)
|
||||
.ToList();
|
||||
|
||||
public List<Card> OrderedCards { get; } = [];
|
||||
|
||||
public Dictionary<Guid, CardControl> CardToControl => m_CardToControl;
|
||||
|
||||
[Signal]
|
||||
public delegate void OrderChangedEventHandler();
|
||||
|
||||
[Signal]
|
||||
public delegate void SelectionChangedEventHandler(CardControl[] selection);
|
||||
|
||||
private static CardRow s_SelectionContext;
|
||||
|
||||
private static bool s_CardPressActive;
|
||||
|
||||
private readonly List<Control> m_Backgrounds = [];
|
||||
private readonly Dictionary<Guid, CardControl> m_CardToControl = new();
|
||||
private readonly Dictionary<Guid, Vector2> m_CardPositions = [];
|
||||
|
||||
private int m_BackgroundVariant;
|
||||
private bool m_Disabled;
|
||||
private bool m_SuppressSelectionValidation;
|
||||
private bool m_SuppressSelectionSignals;
|
||||
private bool m_LayoutDirty;
|
||||
private Vector2 m_LastPosition;
|
||||
private Vector2 m_LastSize;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/CardRow.cs.uid
Normal file
1
DonkeysAndDroids.Godot/CardRow.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://breqe5ccn40sg
|
||||
213
DonkeysAndDroids.Godot/CoreLoopScreen.cs
Normal file
213
DonkeysAndDroids.Godot/CoreLoopScreen.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
using System;
|
||||
using DonkeysAndDroids;
|
||||
using Godot;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using System.Collections.Generic;
|
||||
using RobotAndDonkey.Game;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
public partial class CoreLoopScreen : Control, IScreen
|
||||
{
|
||||
// Called when the node enters the scene tree for the first time.
|
||||
public override void _Ready()
|
||||
{
|
||||
m_DrawGlitch = GetNode<DrawGlitch>("%Draw Glitch");
|
||||
m_Improve = GetNode<Improve>("%Improve");
|
||||
m_CosmicRays = GetNode<Gamble>("%Cosmic Rays");
|
||||
m_BufferOverflow = GetNode<BufferOverflow>("%Buffer Overflow");
|
||||
m_ProgramScreen = GetNode<ProgramScreen>("%Program");
|
||||
m_BoardNode = GetNode<BoardNode>("%BoardNode");
|
||||
m_CurrencyBar = GetNode<CurrencyBar>("%CurrencyBar");
|
||||
m_OptionsButton = GetNode<Button>("%OptionsButton");
|
||||
m_SeedButton = GetNode<Button>("%SeedButton");
|
||||
m_SeedButton.Pressed += OnSeedButtonPressed;
|
||||
m_OptionsButton.Pressed += OnOptionsButtonPressed;
|
||||
}
|
||||
|
||||
private void OnSeedButtonPressed()
|
||||
{
|
||||
DisplayServer.ClipboardSet(Main.Instance.StringSeed);
|
||||
//Main.Instance.StartCoreLoop(Random.Shared.Next(), EDifficulty.Easy, ERobotType.Vintage);
|
||||
}
|
||||
|
||||
public bool TransitionScreen<T>(T newScreen, Tween tween) where T : Control, IScreen
|
||||
{
|
||||
newScreen.EnableInputs();
|
||||
if (m_CurrentScreen == newScreen)
|
||||
return false;
|
||||
|
||||
var parent = newScreen.GetParent();
|
||||
parent.MoveChild(newScreen, 1);
|
||||
var oldControl = (Control)m_CurrentScreen;
|
||||
var oldScreen = m_CurrentScreen;
|
||||
m_CurrentScreen = newScreen;
|
||||
newScreen.Modulate = new(1, 1, 1);
|
||||
newScreen.Visible = true;
|
||||
newScreen.Activate();
|
||||
if (oldScreen == null)
|
||||
return true;
|
||||
|
||||
tween.TweenProperty(oldControl, "modulate", new Color(0, 0, 0, 0), 0.25);
|
||||
tween.TweenCallback(Callable.From(() =>
|
||||
{
|
||||
oldScreen.Deactivate();
|
||||
oldControl.Visible = false;
|
||||
oldControl.Modulate = new(1, 1, 1);
|
||||
}));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void EnableInputs()
|
||||
{
|
||||
m_SeedButton.Text = Main.Instance.StringSeed;
|
||||
m_CurrentScreen?.EnableInputs();
|
||||
m_BoardNode.Configure(Main.Instance.CoreLoop.Board);
|
||||
m_CurrencyBar.Configure(Main.Instance.CoreLoop, Main.Instance.CoreLoop.ProgramCount, Main.Instance.CoreLoop.Currency, null);
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
}
|
||||
|
||||
public void Deactivate()
|
||||
{
|
||||
if (m_CurrentScreen is Control control)
|
||||
{
|
||||
control.Visible = false;
|
||||
control.Modulate = new(1, 1, 1);
|
||||
m_CurrentScreen.Deactivate();
|
||||
m_CurrentScreen = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void DisableInputs()
|
||||
{
|
||||
m_CurrentScreen?.DisableInputs();
|
||||
}
|
||||
|
||||
// Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
}
|
||||
|
||||
public override void _UnhandledInput(InputEvent @event)
|
||||
{
|
||||
if (@event is InputEventMouse mouseEvent)
|
||||
{
|
||||
var container = GetNode<Control>("%BoardViewPortContainer");
|
||||
if (!container.GetGlobalRect().HasPoint(mouseEvent.Position))
|
||||
return;
|
||||
|
||||
var viewPort = GetNode<SubViewport>("%BoardViewPort");
|
||||
viewPort.PushInput(@event);
|
||||
|
||||
if (viewPort.IsInputHandled())
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOptionsButtonPressed()
|
||||
{
|
||||
Main.Instance.OptionsMenu.ShowMenu();
|
||||
}
|
||||
|
||||
public override void _UnhandledKeyInput(InputEvent @event)
|
||||
{
|
||||
if (@event.IsActionPressed("options") && m_CurrentScreen != null)
|
||||
{
|
||||
Main.Instance.OptionsMenu.ShowMenu();
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawGlitch(Card card, Tween tween)
|
||||
{
|
||||
TransitionScreen(m_DrawGlitch, tween);
|
||||
m_DrawGlitch.Configure(card, tween);
|
||||
}
|
||||
|
||||
public void Improve(Tween tween)
|
||||
{
|
||||
TransitionScreen(m_Improve, tween);
|
||||
m_Improve.Configure(Main.Instance.CoreLoop.Shop, Main.Instance.CoreLoop.PatchDeck, tween);
|
||||
}
|
||||
|
||||
public bool HandleResult(Result result, Tween tween)
|
||||
{
|
||||
if (m_CurrentScreen.HandleResult(result, tween))
|
||||
return true;
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case CellTypeResult cellResult:
|
||||
{
|
||||
m_BoardNode.Configure(cellResult, tween);
|
||||
return true;
|
||||
}
|
||||
case ModifyCellResult cellResult:
|
||||
{
|
||||
m_BoardNode.Configure(cellResult, tween);
|
||||
return true;
|
||||
}
|
||||
case PoiResult cellResult:
|
||||
{
|
||||
m_BoardNode.Configure(cellResult, tween);
|
||||
return true;
|
||||
}
|
||||
case CurrencyResult currencyResult:
|
||||
{
|
||||
m_CurrencyBar.Configure(Main.Instance.CoreLoop, Main.Instance.CoreLoop.ProgramCount, currencyResult.NewCurrency, tween);
|
||||
return true;
|
||||
}
|
||||
case ProgramResult programResult:
|
||||
{
|
||||
m_CurrencyBar.Configure(Main.Instance.CoreLoop, programResult.NewProgram, Main.Instance.CoreLoop.Currency, tween);
|
||||
return true;
|
||||
}
|
||||
case ModifyCardResult modifyCardResult:
|
||||
{
|
||||
m_Improve.Deck.ModifyCard(modifyCardResult.Card, modifyCardResult.Modifier, tween);
|
||||
return true;
|
||||
}
|
||||
case DeckResult deckResult:
|
||||
{
|
||||
m_Improve.Deck.Configure(deckResult.Deck, tween, false, false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
GD.Print($"Unhandled result {result.GetType().Name} in screen {m_CurrentScreen.GetType().Name}");
|
||||
return false;
|
||||
}
|
||||
|
||||
public void CosmicRays(List<Card> gambleCards, Tween tween)
|
||||
{
|
||||
TransitionScreen(m_CosmicRays, tween);
|
||||
m_CosmicRays.Configure(gambleCards, tween);
|
||||
}
|
||||
|
||||
public void BufferOverflow(Tween tween)
|
||||
{
|
||||
TransitionScreen(m_BufferOverflow, tween);
|
||||
m_BufferOverflow.Configure(tween);
|
||||
}
|
||||
|
||||
public void ExecuteProgram(Tween tween)
|
||||
{
|
||||
var becomeActive = TransitionScreen(m_ProgramScreen, tween);
|
||||
m_ProgramScreen.Configure(becomeActive ? tween : null);
|
||||
}
|
||||
|
||||
private ProgramScreen m_ProgramScreen;
|
||||
private DrawGlitch m_DrawGlitch;
|
||||
private Improve m_Improve;
|
||||
private IScreen m_CurrentScreen;
|
||||
private Gamble m_CosmicRays;
|
||||
private BufferOverflow m_BufferOverflow;
|
||||
private BoardNode m_BoardNode;
|
||||
private CurrencyBar m_CurrencyBar;
|
||||
private Button m_SeedButton;
|
||||
private Button m_OptionsButton;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/CoreLoopScreen.cs.uid
Normal file
1
DonkeysAndDroids.Godot/CoreLoopScreen.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b8aed65amla7p
|
||||
68
DonkeysAndDroids.Godot/CorruptionArea.cs
Normal file
68
DonkeysAndDroids.Godot/CorruptionArea.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using Godot;
|
||||
|
||||
public partial class CorruptionArea : Node3D
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
_disc = GetNode<MeshInstance3D>("Disc");
|
||||
if (_disc == null)
|
||||
{
|
||||
GD.PushWarning("CorruptionArea: Could not find child MeshInstance3D named 'Disc'.");
|
||||
return;
|
||||
}
|
||||
|
||||
_baseScale = _disc.Scale;
|
||||
ApplyRadius();
|
||||
|
||||
_shaderMaterial = (ShaderMaterial)_disc.GetActiveMaterial(0).Duplicate();
|
||||
_disc.MaterialOverride = _shaderMaterial;
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (_shaderMaterial == null)
|
||||
return;
|
||||
|
||||
var t = Time.GetTicksMsec() / 1000.0f;
|
||||
var pulse = 1.0f + Mathf.Sin(t * PulseSpeed) * PulseAmount;
|
||||
|
||||
_shaderMaterial.SetShaderParameter("u_pulse", pulse);
|
||||
_shaderMaterial.SetShaderParameter("base_color", Color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call this if you change Radius at runtime.
|
||||
/// </summary>
|
||||
public void ApplyRadius()
|
||||
{
|
||||
if (_disc == null)
|
||||
return;
|
||||
|
||||
var r = Mathf.Max(Radius, 0.01f);
|
||||
// PlaneMesh is 2x2 units, so scale XZ by radius to get the desired world radius.
|
||||
_disc.Scale = new(_baseScale.X * r, _baseScale.Y, _baseScale.Z * r);
|
||||
}
|
||||
|
||||
public void SetRadius(float radius)
|
||||
{
|
||||
Radius = radius;
|
||||
ApplyRadius();
|
||||
}
|
||||
|
||||
[Export]
|
||||
public float Radius { get; set; } = 1.0f; // World-space radius in XZ units.
|
||||
|
||||
[Export]
|
||||
public float PulseSpeed { get; set; } = 1.5f;
|
||||
|
||||
[Export]
|
||||
public float PulseAmount { get; set; } = 0.25f;
|
||||
|
||||
[Export]
|
||||
public Color Color { get; set; } = new(0.6f, 0.1f, 0.8f, 0.45f);
|
||||
|
||||
private Vector3 _baseScale = Vector3.One;
|
||||
|
||||
private MeshInstance3D _disc;
|
||||
private ShaderMaterial _shaderMaterial;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/CorruptionArea.cs.uid
Normal file
1
DonkeysAndDroids.Godot/CorruptionArea.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://gdp1jvwnkerk
|
||||
132
DonkeysAndDroids.Godot/CurrencyBar.cs
Normal file
132
DonkeysAndDroids.Godot/CurrencyBar.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using Godot;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
|
||||
public partial class CurrencyBar : Control
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
m_EnergyContainer = GetNode<Control>("%Energy");
|
||||
m_CarryContainer = GetNode<Control>("%Carry");
|
||||
m_DeliveryContainer = GetNode<Control>("%Delivery");
|
||||
m_TapeContainer = GetNode<Control>("%Tape");
|
||||
m_ProgramContainer = GetNode<Control>("%Program");
|
||||
m_HandContainer = GetNode<Control>("%Hand");
|
||||
|
||||
m_EnergyLabel = GetNode<Label>("%Energy/Label");
|
||||
m_CarryLabel = GetNode<Label>("%Carry/Label");
|
||||
m_DeliveryLabel = GetNode<Label>("%Delivery/Label");
|
||||
m_TapeLabel = GetNode<Label>("%Tape/Label");
|
||||
m_ProgramLabel = GetNode<Label>("%Program/Label");
|
||||
m_HandLabel = GetNode<Label>("%Hand/Label");
|
||||
|
||||
ResetVisuals();
|
||||
}
|
||||
|
||||
private void ResetVisuals()
|
||||
{
|
||||
m_EnergyContainer.Scale = Vector2.One;
|
||||
m_CarryContainer.Scale = Vector2.One;
|
||||
m_DeliveryContainer.Scale = Vector2.One;
|
||||
m_TapeContainer.Scale = Vector2.One;
|
||||
m_ProgramContainer.Scale = Vector2.One;
|
||||
m_HandContainer.Scale = Vector2.One;
|
||||
|
||||
m_EnergyLabel.Modulate = s_BaseColor;
|
||||
m_CarryLabel.Modulate = s_BaseColor;
|
||||
m_DeliveryLabel.Modulate = s_BaseColor;
|
||||
m_TapeLabel.Modulate = s_BaseColor;
|
||||
m_ProgramLabel.Modulate = s_BaseColor;
|
||||
m_HandLabel.Modulate = s_BaseColor;
|
||||
}
|
||||
|
||||
public void Configure(CoreLoop coreLoop, int programCount, Currency currency, Tween tween)
|
||||
{
|
||||
if (!m_HasCurrency)
|
||||
{
|
||||
m_CurrentCurrency = currency;
|
||||
m_CurrentProgramCount = programCount;
|
||||
m_HasCurrency = true;
|
||||
SetAllTexts(coreLoop, programCount, currency);
|
||||
ResetVisuals();
|
||||
return;
|
||||
}
|
||||
|
||||
AnimateStat(m_EnergyContainer, m_EnergyLabel, m_CurrentCurrency.Energy, currency.Energy, tween, currency.Energy > m_CurrentCurrency.Energy ? MusicManager.ESound.Rest : MusicManager.ESound.Energy, value => value.ToString());
|
||||
|
||||
var oldCarryCombined = m_CurrentCurrency.Carry + m_CurrentCurrency.MaxCarry * 10000;
|
||||
var newCarryCombined = currency.Carry + currency.MaxCarry * 10000;
|
||||
AnimateStat(m_CarryContainer, m_CarryLabel, oldCarryCombined, newCarryCombined, tween, MusicManager.ESound.Carry, _ => $"{currency.Carry}/{currency.MaxCarry}");
|
||||
|
||||
AnimateStat(m_DeliveryContainer, m_DeliveryLabel, m_CurrentCurrency.Delivery, currency.Delivery, tween, MusicManager.ESound.Delivery, value => $"{value}/{coreLoop.Board.TargetDeliveryAmount}");
|
||||
|
||||
AnimateStat(m_TapeContainer, m_TapeLabel, m_CurrentCurrency.TapeLength, currency.TapeLength, tween, MusicManager.ESound.TapeLength, value => value.ToString());
|
||||
|
||||
AnimateStat(m_HandContainer, m_HandLabel, m_CurrentCurrency.HandSize, currency.HandSize, tween, MusicManager.ESound.Hand, value => value.ToString());
|
||||
|
||||
AnimateStat(m_ProgramContainer, m_ProgramLabel, m_CurrentProgramCount, programCount, tween, MusicManager.ESound.Program, value => $"Program {(value == 0 ? 0 : 1 + coreLoop.Robot.ProgramCount - value)}/{coreLoop.Robot.ProgramCount}");
|
||||
|
||||
m_CurrentCurrency = currency;
|
||||
m_CurrentProgramCount = programCount;
|
||||
}
|
||||
|
||||
private void SetAllTexts(CoreLoop coreLoop, int programCount, Currency currency)
|
||||
{
|
||||
m_EnergyLabel.Text = currency.Energy.ToString();
|
||||
m_CarryLabel.Text = $"{currency.Carry}/{currency.MaxCarry}";
|
||||
m_DeliveryLabel.Text = $"{currency.Delivery}/{coreLoop.Board.TargetDeliveryAmount}";
|
||||
m_TapeLabel.Text = currency.TapeLength.ToString();
|
||||
m_HandLabel.Text = currency.HandSize.ToString();
|
||||
var programNumber = (programCount == 0 ? 0 : 1 + coreLoop.Robot.ProgramCount - programCount);
|
||||
m_ProgramLabel.Text = $"Program {programNumber}/{coreLoop.Robot.ProgramCount}";
|
||||
}
|
||||
|
||||
private void AnimateStat(Control container, Label label, int oldValue, int newValue, Tween tween, MusicManager.ESound sound, Func<int, string> formatter)
|
||||
{
|
||||
if (oldValue == newValue || formatter == null)
|
||||
{
|
||||
label.Text = formatter?.Invoke(newValue) ?? label.Text;
|
||||
return;
|
||||
}
|
||||
|
||||
if (tween != null)
|
||||
{
|
||||
var increased = newValue > oldValue;
|
||||
label.Modulate = increased ? s_GoodColor : s_BadColor;
|
||||
tween.SetParallel();
|
||||
tween.TweenCallback(Callable.From(() =>
|
||||
{
|
||||
container.Scale = new(2, 2);
|
||||
label.Text = formatter(newValue);
|
||||
Main.Instance.Music.Play(sound);
|
||||
}));
|
||||
tween.TweenProperty(container, "scale", Vector2.One, 0.25f).SetTrans(Tween.TransitionType.Back).SetEase(Tween.EaseType.Out);
|
||||
tween.TweenProperty(label, "modulate", s_BaseColor, 0.25f).SetTrans(Tween.TransitionType.Cubic).SetEase(Tween.EaseType.Out);
|
||||
tween.SetParallel(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
label.Text = formatter(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Color s_BadColor = new(0.94f, 0.60f, 0.60f);
|
||||
private static readonly Color s_BaseColor = new(1f, 1f, 1f);
|
||||
private static readonly Color s_GoodColor = new(0.65f, 0.84f, 0.65f);
|
||||
|
||||
private Control m_CarryContainer;
|
||||
private Label m_CarryLabel;
|
||||
private Currency m_CurrentCurrency;
|
||||
private int m_CurrentProgramCount;
|
||||
private Control m_DeliveryContainer;
|
||||
private Label m_DeliveryLabel;
|
||||
private Control m_EnergyContainer;
|
||||
private Label m_EnergyLabel;
|
||||
private bool m_HasCurrency;
|
||||
private Control m_ProgramContainer;
|
||||
private Label m_ProgramLabel;
|
||||
private Control m_TapeContainer;
|
||||
private Label m_TapeLabel;
|
||||
private Control m_HandContainer;
|
||||
private Label m_HandLabel;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/CurrencyBar.cs.uid
Normal file
1
DonkeysAndDroids.Godot/CurrencyBar.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://6cx86f16c8ml
|
||||
10
DonkeysAndDroids.Godot/DonkeysAndDroids.csproj
Normal file
10
DonkeysAndDroids.Godot/DonkeysAndDroids.csproj
Normal file
@@ -0,0 +1,10 @@
|
||||
<Project Sdk="Godot.NET.Sdk/4.5.1">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RobotAndDonkey.Game\RobotAndDonkey.Game.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
9
DonkeysAndDroids.Godot/DonkeysAndDroids.csproj.user
Normal file
9
DonkeysAndDroids.Godot/DonkeysAndDroids.csproj.user
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ActiveDebugProfile>Godot (debug)</ActiveDebugProfile>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
36
DonkeysAndDroids.Godot/DonkeysAndDroids.sln
Normal file
36
DonkeysAndDroids.Godot/DonkeysAndDroids.sln
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 18
|
||||
VisualStudioVersion = 18.0.11205.157 d18.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DonkeysAndDroids", "DonkeysAndDroids.csproj", "{0B4F8DC2-61FA-4BA2-A464-C79B25F82807}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RobotAndDonkey.Game", "..\RobotAndDonkey.Game\RobotAndDonkey.Game.csproj", "{C905453E-CBCA-6B8A-4CAD-932E551DBC72}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
ExportDebug|Any CPU = ExportDebug|Any CPU
|
||||
ExportRelease|Any CPU = ExportRelease|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{0B4F8DC2-61FA-4BA2-A464-C79B25F82807}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0B4F8DC2-61FA-4BA2-A464-C79B25F82807}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0B4F8DC2-61FA-4BA2-A464-C79B25F82807}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
|
||||
{0B4F8DC2-61FA-4BA2-A464-C79B25F82807}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
|
||||
{0B4F8DC2-61FA-4BA2-A464-C79B25F82807}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
|
||||
{0B4F8DC2-61FA-4BA2-A464-C79B25F82807}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
|
||||
{C905453E-CBCA-6B8A-4CAD-932E551DBC72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C905453E-CBCA-6B8A-4CAD-932E551DBC72}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C905453E-CBCA-6B8A-4CAD-932E551DBC72}.ExportDebug|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C905453E-CBCA-6B8A-4CAD-932E551DBC72}.ExportDebug|Any CPU.Build.0 = Release|Any CPU
|
||||
{C905453E-CBCA-6B8A-4CAD-932E551DBC72}.ExportRelease|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C905453E-CBCA-6B8A-4CAD-932E551DBC72}.ExportRelease|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {BE8B6832-9C61-4C03-AA94-1899E276011D}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
114
DonkeysAndDroids.Godot/DrawGlitch.cs
Normal file
114
DonkeysAndDroids.Godot/DrawGlitch.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using DonkeysAndDroids;
|
||||
using Godot;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
using RobotAndDonkey.Game.Execution.Commands;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public partial class DrawGlitch : Control, IScreen
|
||||
{
|
||||
// Called when the node enters the scene tree for the first time.
|
||||
public override void _Ready()
|
||||
{
|
||||
m_CardControl = GetNode<CardControl>("%Card");
|
||||
m_Background = GetNode<Control>("%Background");
|
||||
m_Deck = GetNode<CardRow>("%Deck");
|
||||
m_AcceptButton = GetNode<Button>("VBoxContainer/MarginContainer/HBoxContainer/AcceptButton");
|
||||
m_DeferButton = GetNode<Button>("VBoxContainer/MarginContainer/HBoxContainer/DeferButton");
|
||||
m_AcceptButton.Pressed += OnAcceptPressed;
|
||||
m_DeferButton.Pressed += OnDeferPressed;
|
||||
}
|
||||
|
||||
private void OnDeferPressed()
|
||||
{
|
||||
Main.Instance.Execute(new DeferCardCommand(Main.Instance.CurrentRequest.RequestId));
|
||||
}
|
||||
|
||||
private void OnAcceptPressed()
|
||||
{
|
||||
Main.Instance.Execute(new AcceptCardCommand(Main.Instance.CurrentRequest.RequestId));
|
||||
}
|
||||
|
||||
public void Deactivate()
|
||||
{
|
||||
}
|
||||
|
||||
public void EnableInputs()
|
||||
{
|
||||
m_Background.Modulate = new(1, 1, 1);
|
||||
m_CardControl.Modulate = new(1, 1, 1);
|
||||
m_CardControl.Disabled = false;
|
||||
m_AcceptButton.Disabled = false;
|
||||
m_DeferButton.Disabled = false;
|
||||
m_Deck.Disabled = false;
|
||||
}
|
||||
|
||||
public void DisableInputs()
|
||||
{
|
||||
m_CardControl.Disabled = true;
|
||||
m_AcceptButton.Disabled = true;
|
||||
m_DeferButton.Disabled = true;
|
||||
m_Deck.Disabled = true;
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
}
|
||||
|
||||
// Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
}
|
||||
|
||||
public void Configure(Card card, Tween tween)
|
||||
{
|
||||
m_CardControl.Configure(card, null);
|
||||
var energy = new DeferCardCommand(Main.Instance.CurrentRequest.RequestId).EstimateEnergyCost(Main.Instance.CoreLoop);
|
||||
m_DeferButton.Text = $"Defer ({energy} energy)";
|
||||
Deck.Configure([], null, false, false);
|
||||
Deck.Configure(CardRow.GetSortedCards(Main.Instance.CoreLoop.PatchDeck), tween, false, false);
|
||||
Tooltip.Instance.Describe(card);
|
||||
}
|
||||
|
||||
public bool HandleResult(Result result, Tween tween)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case RunCardResult:
|
||||
{
|
||||
tween.TweenCallback(Callable.From(() =>
|
||||
{
|
||||
m_CardControl.Disabled = true;
|
||||
m_AcceptButton.Disabled = true;
|
||||
m_DeferButton.Disabled = true;
|
||||
m_Deck.Disabled = true;
|
||||
}));
|
||||
tween.TweenProperty(m_Background, "modulate", new Color(1, 1, 1, 0), 0.5f);
|
||||
tween.TweenCallback(Callable.From(() => Main.Instance.Music.Play(MusicManager.ESound.Card)));
|
||||
m_CardControl.AnimateCard(tween);
|
||||
tween.TweenProperty(m_CardControl, "modulate", new Color(1, 1, 1, 0), 0.5f);
|
||||
return true;
|
||||
}
|
||||
case DeckResult deckResult:
|
||||
{
|
||||
Deck.Configure(CardRow.GetSortedCards(deckResult.Deck), tween, false, false);
|
||||
return true;
|
||||
}
|
||||
case ModifyCardResult modifyCardResult:
|
||||
{
|
||||
Deck.ModifyCard(modifyCardResult.Card, modifyCardResult.Modifier, tween);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public CardRow Deck => m_Deck;
|
||||
|
||||
private CardRow m_Deck;
|
||||
|
||||
private CardControl m_CardControl;
|
||||
private Button m_AcceptButton;
|
||||
private Button m_DeferButton;
|
||||
private Control m_Background;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/DrawGlitch.cs.uid
Normal file
1
DonkeysAndDroids.Godot/DrawGlitch.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bohqea762phhu
|
||||
574
DonkeysAndDroids.Godot/GD.cs
Normal file
574
DonkeysAndDroids.Godot/GD.cs
Normal file
@@ -0,0 +1,574 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using Godot;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Godot's global functions.
|
||||
/// </summary>
|
||||
public static class GD
|
||||
{
|
||||
/// <summary>
|
||||
/// Decodes a byte array back to a <see cref="Variant"/> value, without decoding objects.
|
||||
/// Note: If you need object deserialization, see <see cref="BytesToVarWithObjects"/>.
|
||||
/// </summary>
|
||||
/// <param name="bytes">Byte array that will be decoded to a <see cref="Variant"/>.</param>
|
||||
/// <returns>The decoded <see cref="Variant"/>.</returns>
|
||||
public static Variant BytesToVar(Span<byte> bytes) => Godot.GD.BytesToVar(bytes);
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a byte array back to a <see cref="Variant"/> value. Decoding objects is allowed.
|
||||
/// Warning: Deserialized object can contain code which gets executed. Do not use this
|
||||
/// option if the serialized object comes from untrusted sources to avoid potential security
|
||||
/// threats (remote code execution).
|
||||
/// </summary>
|
||||
/// <param name="bytes">Byte array that will be decoded to a <see cref="Variant"/>.</param>
|
||||
/// <returns>The decoded <see cref="Variant"/>.</returns>
|
||||
public static Variant BytesToVarWithObjects(Span<byte> bytes) => Godot.GD.BytesToVarWithObjects(bytes);
|
||||
|
||||
/// <summary>
|
||||
/// Converts <paramref name="what"/> to <paramref name="type"/> in the best way possible.
|
||||
/// The <paramref name="type"/> parameter uses the <see cref="Variant.Type"/> values.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// Variant a = new Godot.Collections.Array { 4, 2.5, 1.2 };
|
||||
/// GD.Print(a.VariantType == Variant.Type.Array); // Prints true
|
||||
///
|
||||
/// var b = GD.Convert(a, Variant.Type.PackedByteArray);
|
||||
/// GD.Print(b); // Prints [4, 2, 1]
|
||||
/// GD.Print(b.VariantType == Variant.Type.Array); // Prints false
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <returns>The <c>Variant</c> converted to the given <paramref name="type"/>.</returns>
|
||||
public static Variant Convert(Variant what, Variant.Type type) => Godot.GD.Convert(what, type);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the integer hash of the passed <paramref name="var"/>.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// GD.Print(GD.Hash("a")); // Prints 177670
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="var">Variable that will be hashed.</param>
|
||||
/// <returns>Hash of the variable passed.</returns>
|
||||
public static int Hash(Variant var) => Godot.GD.Hash(var);
|
||||
|
||||
/// <summary>
|
||||
/// Loads a resource from the filesystem located at <paramref name="path"/>.
|
||||
/// The resource is loaded on the method call (unless it's referenced already
|
||||
/// elsewhere, e.g. in another script or in the scene), which might cause slight delay,
|
||||
/// especially when loading scenes. To avoid unnecessary delays when loading something
|
||||
/// multiple times, either store the resource in a variable.
|
||||
///
|
||||
/// Note: Resource paths can be obtained by right-clicking on a resource in the FileSystem
|
||||
/// dock and choosing "Copy Path" or by dragging the file from the FileSystem dock into the script.
|
||||
///
|
||||
/// Important: The path must be absolute, a local path will just return <see langword="null"/>.
|
||||
/// This method is a simplified version of <see cref="ResourceLoader.Load"/>, which can be used
|
||||
/// for more advanced scenarios.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Load a scene called main located in the root of the project directory and cache it in a variable.
|
||||
/// var main = GD.Load("res://main.tscn"); // main will contain a PackedScene resource.
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="path">Path of the <see cref="Resource"/> to load.</param>
|
||||
/// <returns>The loaded <see cref="Resource"/>.</returns>
|
||||
public static Resource Load(string path) => Godot.GD.Load(path);
|
||||
|
||||
/// <summary>
|
||||
/// Loads a resource from the filesystem located at <paramref name="path"/>.
|
||||
/// The resource is loaded on the method call (unless it's referenced already
|
||||
/// elsewhere, e.g. in another script or in the scene), which might cause slight delay,
|
||||
/// especially when loading scenes. To avoid unnecessary delays when loading something
|
||||
/// multiple times, either store the resource in a variable.
|
||||
///
|
||||
/// Note: Resource paths can be obtained by right-clicking on a resource in the FileSystem
|
||||
/// dock and choosing "Copy Path" or by dragging the file from the FileSystem dock into the script.
|
||||
///
|
||||
/// Important: The path must be absolute, a local path will just return <see langword="null"/>.
|
||||
/// This method is a simplified version of <see cref="ResourceLoader.Load"/>, which can be used
|
||||
/// for more advanced scenarios.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Load a scene called main located in the root of the project directory and cache it in a variable.
|
||||
/// var main = GD.Load<PackedScene>("res://main.tscn"); // main will contain a PackedScene resource.
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="path">Path of the <see cref="Resource"/> to load.</param>
|
||||
/// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Resource"/>.</typeparam>
|
||||
public static T Load<T>(string path) where T : class => Godot.GD.Load<T>(path);
|
||||
|
||||
private static string AppendPrintParams(object[] parameters)
|
||||
{
|
||||
if (parameters == null)
|
||||
{
|
||||
return "null";
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
sb.Append(parameters[i]?.ToString() ?? "null");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string AppendPrintParams(char separator, object[] parameters)
|
||||
{
|
||||
if (parameters == null)
|
||||
{
|
||||
return "null";
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
if (i != 0)
|
||||
sb.Append(separator);
|
||||
sb.Append(parameters[i]?.ToString() ?? "null");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints a message to the console.
|
||||
///
|
||||
/// Note: Consider using <see cref="PushError(string)"/> and <see cref="PushWarning(string)"/>
|
||||
/// to print error and warning messages instead of <see cref="Print(string)"/>.
|
||||
/// This distinguishes them from print messages used for debugging purposes,
|
||||
/// while also displaying a stack trace when an error or warning is printed.
|
||||
/// </summary>
|
||||
/// <param name="what">Message that will be printed.</param>
|
||||
public static void Print(string what)
|
||||
{
|
||||
Debug.WriteLine("Info: " + what);
|
||||
Godot.GD.Print(what);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts one or more arguments of any type to string in the best way possible
|
||||
/// and prints them to the console.
|
||||
///
|
||||
/// Note: Consider using <see cref="PushError(object[])"/> and <see cref="PushWarning(object[])"/>
|
||||
/// to print error and warning messages instead of <see cref="Print(object[])"/>.
|
||||
/// This distinguishes them from print messages used for debugging purposes,
|
||||
/// while also displaying a stack trace when an error or warning is printed.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var a = new Godot.Collections.Array { 1, 2, 3 };
|
||||
/// GD.Print("a", "b", a); // Prints ab[1, 2, 3]
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="what">Arguments that will be printed.</param>
|
||||
public static void Print(params object[] what)
|
||||
{
|
||||
Print(AppendPrintParams(what));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints a message to the console.
|
||||
/// The following BBCode tags are supported: b, i, u, s, indent, code, url, center,
|
||||
/// right, color, bgcolor, fgcolor.
|
||||
/// Color tags only support named colors such as <c>red</c>, not hexadecimal color codes.
|
||||
/// Unsupported tags will be left as-is in standard output.
|
||||
/// When printing to standard output, the supported subset of BBCode is converted to
|
||||
/// ANSI escape codes for the terminal emulator to display. Displaying ANSI escape codes
|
||||
/// is currently only supported on Linux and macOS. Support for ANSI escape codes may vary
|
||||
/// across terminal emulators, especially for italic and strikethrough.
|
||||
///
|
||||
/// Note: Consider using <see cref="PushError(string)"/> and <see cref="PushWarning(string)"/>
|
||||
/// to print error and warning messages instead of <see cref="Print(string)"/> or
|
||||
/// <see cref="PrintRich(string)"/>.
|
||||
/// This distinguishes them from print messages used for debugging purposes,
|
||||
/// while also displaying a stack trace when an error or warning is printed.
|
||||
/// </summary>
|
||||
/// <param name="what">Message that will be printed.</param>
|
||||
public static void PrintRich(string what)
|
||||
{
|
||||
Debug.WriteLine("Info: " + what);
|
||||
Godot.GD.PrintRich(what);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts one or more arguments of any type to string in the best way possible
|
||||
/// and prints them to the console.
|
||||
/// The following BBCode tags are supported: b, i, u, s, indent, code, url, center,
|
||||
/// right, color, bgcolor, fgcolor.
|
||||
/// Color tags only support named colors such as <c>red</c>, not hexadecimal color codes.
|
||||
/// Unsupported tags will be left as-is in standard output.
|
||||
/// When printing to standard output, the supported subset of BBCode is converted to
|
||||
/// ANSI escape codes for the terminal emulator to display. Displaying ANSI escape codes
|
||||
/// is currently only supported on Linux and macOS. Support for ANSI escape codes may vary
|
||||
/// across terminal emulators, especially for italic and strikethrough.
|
||||
///
|
||||
/// Note: Consider using <see cref="PushError(object[])"/> and <see cref="PushWarning(object[])"/>
|
||||
/// to print error and warning messages instead of <see cref="Print(object[])"/> or
|
||||
/// <see cref="PrintRich(object[])"/>.
|
||||
/// This distinguishes them from print messages used for debugging purposes,
|
||||
/// while also displaying a stack trace when an error or warning is printed.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// GD.PrintRich("[code][b]Hello world![/b][/code]"); // Prints out: [b]Hello world![/b]
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="what">Arguments that will be printed.</param>
|
||||
public static void PrintRich(params object[] what)
|
||||
{
|
||||
PrintRich(AppendPrintParams(what));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints a message to standard error line.
|
||||
/// </summary>
|
||||
/// <param name="what">Message that will be printed.</param>
|
||||
public static void PrintErr(string what)
|
||||
{
|
||||
Debug.WriteLine("Error: " + what);
|
||||
Godot.GD.PrintErr(what);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints one or more arguments to strings in the best way possible to standard error line.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// GD.PrintErr("prints to stderr");
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="what">Arguments that will be printed.</param>
|
||||
public static void PrintErr(params object[] what)
|
||||
{
|
||||
PrintErr(AppendPrintParams(what));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints a message to the OS terminal.
|
||||
/// Unlike <see cref="Print(string)"/>, no newline is added at the end.
|
||||
/// </summary>
|
||||
/// <param name="what">Message that will be printed.</param>
|
||||
public static void PrintRaw(string what)
|
||||
{
|
||||
Debug.WriteLine(what);
|
||||
Godot.GD.PrintRaw(what);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints one or more arguments to strings in the best way possible to the OS terminal.
|
||||
/// Unlike <see cref="Print(object[])"/>, no newline is added at the end.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// GD.PrintRaw("A");
|
||||
/// GD.PrintRaw("B");
|
||||
/// GD.PrintRaw("C");
|
||||
/// // Prints ABC to terminal
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="what">Arguments that will be printed.</param>
|
||||
public static void PrintRaw(params object[] what)
|
||||
{
|
||||
PrintRaw(AppendPrintParams(what));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints one or more arguments to the console with a space between each argument.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// GD.PrintS("A", "B", "C"); // Prints A B C
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="what">Arguments that will be printed.</param>
|
||||
public static void PrintS(params object[] what)
|
||||
{
|
||||
string message = AppendPrintParams(' ', what);
|
||||
PrintErr(message);
|
||||
Godot.GD.PrintS(what);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints one or more arguments to the console with a tab between each argument.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// GD.PrintT("A", "B", "C"); // Prints A B C
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="what">Arguments that will be printed.</param>
|
||||
public static void PrintT(params object[] what)
|
||||
{
|
||||
string message = AppendPrintParams('\t', what);
|
||||
PrintErr(message);
|
||||
Godot.GD.PrintT(what);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes an error message to Godot's built-in debugger and to the OS terminal.
|
||||
///
|
||||
/// Note: Errors printed this way will not pause project execution.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// GD.PushError("test error"); // Prints "test error" to debugger and terminal as error call
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="message">Error message.</param>
|
||||
public static void PushError(string message)
|
||||
{
|
||||
Debug.WriteLine("Error: " + message);
|
||||
Godot.GD.PushError(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes an error message to Godot's built-in debugger and to the OS terminal.
|
||||
///
|
||||
/// Note: Errors printed this way will not pause project execution.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// GD.PushError("test_error"); // Prints "test error" to debugger and terminal as error call
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="what">Arguments that form the error message.</param>
|
||||
public static void PushError(params object[] what)
|
||||
{
|
||||
PushError(AppendPrintParams(what));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes a warning message to Godot's built-in debugger and to the OS terminal.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// GD.PushWarning("test warning"); // Prints "test warning" to debugger and terminal as warning call
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="message">Warning message.</param>
|
||||
public static void PushWarning(string message)
|
||||
{
|
||||
Debug.WriteLine("Warning: " + message);
|
||||
Godot.GD.PushWarning(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes a warning message to Godot's built-in debugger and to the OS terminal.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// GD.PushWarning("test warning"); // Prints "test warning" to debugger and terminal as warning call
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="what">Arguments that form the warning message.</param>
|
||||
public static void PushWarning(params object[] what)
|
||||
{
|
||||
PushWarning(AppendPrintParams(what));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random floating point value between <c>0.0</c> and <c>1.0</c> (inclusive).
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// GD.Randf(); // Returns e.g. 0.375671
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <returns>A random <see langword="float"/> number.</returns>
|
||||
public static float Randf() => Godot.GD.Randf();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a normally-distributed pseudo-random floating point value
|
||||
/// using Box-Muller transform with the specified <pararmref name="mean"/>
|
||||
/// and a standard <paramref name="deviation"/>.
|
||||
/// This is also called Gaussian distribution.
|
||||
/// </summary>
|
||||
/// <returns>A random normally-distributed <see langword="float"/> number.</returns>
|
||||
public static double Randfn(double mean, double deviation) => Godot.GD.Randfn(mean, deviation);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random unsigned 32-bit integer.
|
||||
/// Use remainder to obtain a random value in the interval <c>[0, N - 1]</c>
|
||||
/// (where N is smaller than 2^32).
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// GD.Randi(); // Returns random integer between 0 and 2^32 - 1
|
||||
/// GD.Randi() % 20; // Returns random integer between 0 and 19
|
||||
/// GD.Randi() % 100; // Returns random integer between 0 and 99
|
||||
/// GD.Randi() % 100 + 1; // Returns random integer between 1 and 100
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <returns>A random <see langword="uint"/> number.</returns>
|
||||
public static uint Randi() => Godot.GD.Randi();
|
||||
|
||||
/// <summary>
|
||||
/// Randomizes the seed (or the internal state) of the random number generator.
|
||||
/// The current implementation uses a number based on the device's time.
|
||||
///
|
||||
/// Note: This method is called automatically when the project is run.
|
||||
/// If you need to fix the seed to have consistent, reproducible results,
|
||||
/// use <see cref="Seed(ulong)"/> to initialize the random number generator.
|
||||
/// </summary>
|
||||
public static void Randomize() => Godot.GD.Randomize();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random floating point value between <paramref name="from"/>
|
||||
/// and <paramref name="to"/> (inclusive).
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// GD.RandRange(0.0, 20.5); // Returns e.g. 7.45315
|
||||
/// GD.RandRange(-10.0, 10.0); // Returns e.g. -3.844535
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <returns>A random <see langword="double"/> number inside the given range.</returns>
|
||||
public static double RandRange(double from, double to) => Godot.GD.RandRange(from, to);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random signed 32-bit integer between <paramref name="from"/>
|
||||
/// and <paramref name="to"/> (inclusive). If <paramref name="to"/> is lesser than
|
||||
/// <paramref name="from"/>, they are swapped.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// GD.RandRange(0, 1); // Returns either 0 or 1
|
||||
/// GD.RandRange(-10, 1000); // Returns random integer between -10 and 1000
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <returns>A random <see langword="int"/> number inside the given range.</returns>
|
||||
public static int RandRange(int from, int to) => Godot.GD.RandRange(from, to);
|
||||
|
||||
/// <summary>
|
||||
/// Given a <paramref name="seed"/>, returns a randomized <see langword="uint"/>
|
||||
/// value. The <paramref name="seed"/> may be modified.
|
||||
/// Passing the same <paramref name="seed"/> consistently returns the same value.
|
||||
///
|
||||
/// Note: "Seed" here refers to the internal state of the pseudo random number
|
||||
/// generator, currently implemented as a 64 bit integer.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var a = GD.RandFromSeed(4);
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="seed">
|
||||
/// Seed to use to generate the random number.
|
||||
/// If a different seed is used, its value will be modified.
|
||||
/// </param>
|
||||
/// <returns>A random <see langword="uint"/> number.</returns>
|
||||
public static uint RandFromSeed(ref ulong seed) => Godot.GD.RandFromSeed(ref seed);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="IEnumerable{T}"/> that iterates from
|
||||
/// <c>0</c> (inclusive) to <paramref name="end"/> (exclusive)
|
||||
/// in steps of <c>1</c>.
|
||||
/// </summary>
|
||||
/// <param name="end">The last index.</param>
|
||||
public static IEnumerable<int> Range(int end) => Godot.GD.Range(end);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="IEnumerable{T}"/> that iterates from
|
||||
/// <paramref name="start"/> (inclusive) to <paramref name="end"/> (exclusive)
|
||||
/// in steps of <c>1</c>.
|
||||
/// </summary>
|
||||
/// <param name="start">The first index.</param>
|
||||
/// <param name="end">The last index.</param>
|
||||
public static IEnumerable<int> Range(int start, int end) => Godot.GD.Range(start, end);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="IEnumerable{T}"/> that iterates from
|
||||
/// <paramref name="start"/> (inclusive) to <paramref name="end"/> (exclusive)
|
||||
/// in steps of <paramref name="step"/>.
|
||||
/// The argument <paramref name="step"/> can be negative, but not <c>0</c>.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// <paramref name="step"/> is 0.
|
||||
/// </exception>
|
||||
/// <param name="start">The first index.</param>
|
||||
/// <param name="end">The last index.</param>
|
||||
/// <param name="step">The amount by which to increment the index on each iteration.</param>
|
||||
public static IEnumerable<int> Range(int start, int end, int step) => Godot.GD.Range(start, end, step);
|
||||
|
||||
/// <summary>
|
||||
/// Sets seed for the random number generator to <paramref name="seed"/>.
|
||||
/// Setting the seed manually can ensure consistent, repeatable results for
|
||||
/// most random functions.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// ulong mySeed = (ulong)GD.Hash("Godot Rocks");
|
||||
/// GD.Seed(mySeed);
|
||||
/// var a = GD.Randf() + GD.Randi();
|
||||
/// GD.Seed(mySeed);
|
||||
/// var b = GD.Randf() + GD.Randi();
|
||||
/// // a and b are now identical
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="seed">Seed that will be used.</param>
|
||||
public static void Seed(ulong seed) => Godot.GD.Seed(seed);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a formatted string that was returned by <see cref="VarToStr(Variant)"/>
|
||||
/// to the original value.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// string a = "{ \"a\": 1, \"b\": 2 }"; // a is a string
|
||||
/// var b = GD.StrToVar(a).AsGodotDictionary(); // b is a Dictionary
|
||||
/// GD.Print(b["a"]); // Prints 1
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="str">String that will be converted to Variant.</param>
|
||||
/// <returns>The decoded <c>Variant</c>.</returns>
|
||||
public static Variant StrToVar(string str) => Godot.GD.StrToVar(str);
|
||||
|
||||
/// <summary>
|
||||
/// Encodes a <see cref="Variant"/> value to a byte array, without encoding objects.
|
||||
/// Deserialization can be done with <see cref="BytesToVar"/>.
|
||||
/// Note: If you need object serialization, see <see cref="VarToBytesWithObjects"/>.
|
||||
/// </summary>
|
||||
/// <param name="var"><see cref="Variant"/> that will be encoded.</param>
|
||||
/// <returns>The <see cref="Variant"/> encoded as an array of bytes.</returns>
|
||||
public static byte[] VarToBytes(Variant var) => Godot.GD.VarToBytes(var);
|
||||
|
||||
/// <summary>
|
||||
/// Encodes a <see cref="Variant"/>. Encoding objects is allowed (and can potentially
|
||||
/// include executable code). Deserialization can be done with <see cref="BytesToVarWithObjects"/>.
|
||||
/// </summary>
|
||||
/// <param name="var"><see cref="Variant"/> that will be encoded.</param>
|
||||
/// <returns>The <see cref="Variant"/> encoded as an array of bytes.</returns>
|
||||
public static byte[] VarToBytesWithObjects(Variant var) => Godot.GD.VarToBytesWithObjects(var);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a <see cref="Variant"/> <paramref name="var"/> to a formatted string that
|
||||
/// can later be parsed using <see cref="StrToVar(string)"/>.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var a = new Godot.Collections.Dictionary { ["a"] = 1, ["b"] = 2 };
|
||||
/// GD.Print(GD.VarToStr(a));
|
||||
/// // Prints:
|
||||
/// // {
|
||||
/// // "a": 1,
|
||||
/// // "b": 2
|
||||
/// // }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="var">Variant that will be converted to string.</param>
|
||||
/// <returns>The <see cref="Variant"/> encoded as a string.</returns>
|
||||
public static string VarToStr(Variant var) => Godot.GD.VarToStr(var);
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="Variant.Type"/> that corresponds for the given <see cref="Type"/>.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="Variant.Type"/> for the given <paramref name="type"/>.</returns>
|
||||
public static Variant.Type TypeToVariantType(Type type) => Godot.GD.TypeToVariantType(type);
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/GD.cs.uid
Normal file
1
DonkeysAndDroids.Godot/GD.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://djouyufexm7yv
|
||||
99
DonkeysAndDroids.Godot/Gamble.cs
Normal file
99
DonkeysAndDroids.Godot/Gamble.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using DonkeysAndDroids;
|
||||
using Godot;
|
||||
using RobotAndDonkey.Game;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
using RobotAndDonkey.Game.Execution.Commands;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Utils;
|
||||
|
||||
public partial class Gamble : Control, IScreen
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
m_Patches = GetNode<CardRow>("%Patches");
|
||||
m_Add = GetNode<Button>("%Add");
|
||||
m_Add.Pressed += OnAddPressed;
|
||||
m_Skip = GetNode<Button>("%Skip");
|
||||
m_Skip.Pressed += OnSkipPressed;
|
||||
m_Patches.Connect(CardRow.SignalName.SelectionChanged, new(this, nameof(OnSelectionChanged)));
|
||||
}
|
||||
|
||||
private void OnSelectionChanged(CardControl[] selection)
|
||||
{
|
||||
if (selection.Length != 1)
|
||||
{
|
||||
m_BuyCardsCommand = null;
|
||||
return;
|
||||
}
|
||||
|
||||
m_BuyCardsCommand = new(Main.Instance.CurrentRequest.RequestId, selection.Select(c => m_Patches.OrderedCards.IndexOf(c.Card)).ToArray());
|
||||
}
|
||||
|
||||
private void OnAddPressed()
|
||||
{
|
||||
Main.Instance.Execute(m_BuyCardsCommand);
|
||||
}
|
||||
|
||||
private void OnSkipPressed()
|
||||
{
|
||||
Main.Instance.Execute(m_StopGamblingCommand);
|
||||
}
|
||||
|
||||
public void Deactivate()
|
||||
{
|
||||
}
|
||||
|
||||
public void EnableInputs()
|
||||
{
|
||||
m_Add.Disabled = false;
|
||||
m_Skip.Disabled = false;
|
||||
}
|
||||
|
||||
public void DisableInputs()
|
||||
{
|
||||
m_Add.Disabled = true;
|
||||
m_Skip.Disabled = true;
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
}
|
||||
|
||||
public bool HandleResult(Result result, Tween tween)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case ShopResult shopResult:
|
||||
{
|
||||
m_Patches.Configure(shopResult.Shop, tween, false, false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
m_Skip.Disabled = m_StopGamblingCommand == null || !m_StopGamblingCommand.IsValid(Main.Instance.CoreLoop, out _);
|
||||
m_Add.Disabled = m_BuyCardsCommand == null || !m_BuyCardsCommand.IsValid(Main.Instance.CoreLoop, out _);
|
||||
}
|
||||
|
||||
public void Configure(List<Card> hand, Tween tween)
|
||||
{
|
||||
m_Patches.Configure(hand, tween, true, false);
|
||||
m_StopGamblingCommand = new(Main.Instance.CurrentRequest.RequestId);
|
||||
}
|
||||
|
||||
private BuyCardsCommand m_BuyCardsCommand;
|
||||
|
||||
private CardRow m_Patches;
|
||||
private Button m_Add;
|
||||
private Button m_Skip;
|
||||
private StopGamblingCommand m_StopGamblingCommand;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/Gamble.cs.uid
Normal file
1
DonkeysAndDroids.Godot/Gamble.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b7slub0th4nva
|
||||
1
DonkeysAndDroids.Godot/GameController.cs.uid
Normal file
1
DonkeysAndDroids.Godot/GameController.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://vct2p2n8n7iq
|
||||
768
DonkeysAndDroids.Godot/HexBoard3D.cs
Normal file
768
DonkeysAndDroids.Godot/HexBoard3D.cs
Normal file
@@ -0,0 +1,768 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
using RobotAndDonkey.Game;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
using RobotAndDonkey.Game.Utils;
|
||||
|
||||
public partial class HexBoard3D : Node3D
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
if (m_Board == null)
|
||||
{
|
||||
var random = new SRandom(241);
|
||||
var board = Board.Generate(ref random, EDifficulty.Easy);
|
||||
Configure(board);
|
||||
}
|
||||
}
|
||||
|
||||
public void Configure(Cell cell)
|
||||
{
|
||||
m_CellByHex[cell.Hex] = cell;
|
||||
RebuildCellVisual(cell);
|
||||
}
|
||||
|
||||
public void ModifyCell(Cell cell, EModifierId modifier, Tween tween)
|
||||
{
|
||||
m_CellByHex[cell.Hex] = cell;
|
||||
|
||||
tween.TweenCallback(Callable.From(() => RebuildCellVisual(cell)));
|
||||
tween.Parallel();
|
||||
SpawnCellUpdateVfx(cell, tween);
|
||||
tween.Parallel().TweenCallback(Callable.From(() => Main.Instance.Music.Play(modifier)));
|
||||
tween.TweenInterval(TotalAnimationDuration);
|
||||
}
|
||||
|
||||
public void ModifyPoi(Cell cell, Poi poi, Tween tween)
|
||||
{
|
||||
m_CellByHex[cell.Hex] = cell;
|
||||
|
||||
tween.TweenCallback(Callable.From(() => RebuildCellVisual(cell)));
|
||||
tween.Parallel();
|
||||
if (poi != null)
|
||||
{
|
||||
tween.Parallel().TweenCallback(Callable.From(() => { Main.Instance.Music.Play(poi); }));
|
||||
tween.TweenInterval(poi is Donkey ? 2.0 : TotalAnimationDuration);
|
||||
}
|
||||
SpawnCellUpdateVfx(cell, tween);
|
||||
}
|
||||
|
||||
public void Configure(Board board)
|
||||
{
|
||||
var boardChanged = !ReferenceEquals(m_Board, board);
|
||||
m_Board = board;
|
||||
|
||||
if (m_Board == null)
|
||||
{
|
||||
ClearBoard();
|
||||
return;
|
||||
}
|
||||
|
||||
if (boardChanged)
|
||||
BuildBoard();
|
||||
}
|
||||
|
||||
private void BuildBoard()
|
||||
{
|
||||
ClearBoard();
|
||||
|
||||
if (m_Board == null || m_Board.Cells.IsDefaultOrEmpty)
|
||||
{
|
||||
BoardBounds = new();
|
||||
return;
|
||||
}
|
||||
|
||||
m_CellByHex.Clear();
|
||||
m_CellNodes.Clear();
|
||||
|
||||
var minX = float.MaxValue;
|
||||
var maxX = float.MinValue;
|
||||
var minZ = float.MaxValue;
|
||||
var maxZ = float.MinValue;
|
||||
var hasAnyCell = false;
|
||||
|
||||
foreach (var cell in m_Board.Cells)
|
||||
{
|
||||
m_CellByHex[cell.Hex] = cell;
|
||||
|
||||
var world = cell.Hex.ToWorld();
|
||||
BuildCellNode(cell);
|
||||
|
||||
var x = world.X;
|
||||
var z = world.Y;
|
||||
|
||||
if (x < minX)
|
||||
minX = x;
|
||||
if (x > maxX)
|
||||
maxX = x;
|
||||
if (z < minZ)
|
||||
minZ = z;
|
||||
if (z > maxZ)
|
||||
maxZ = z;
|
||||
hasAnyCell = true;
|
||||
}
|
||||
|
||||
if (hasAnyCell)
|
||||
BoardBounds = new(new(minX, minZ), new(maxX - minX, maxZ - minZ));
|
||||
else
|
||||
BoardBounds = new();
|
||||
|
||||
CreateHighlightMesh();
|
||||
CreateSelectedMesh();
|
||||
}
|
||||
|
||||
private void ClearBoard()
|
||||
{
|
||||
foreach (var child in GetChildren())
|
||||
child.QueueFree();
|
||||
|
||||
m_PoiSprites.Clear();
|
||||
m_AvatarSprites.Clear();
|
||||
m_DonkeySprites.Clear();
|
||||
m_CellByHex.Clear();
|
||||
m_CellNodes.Clear();
|
||||
|
||||
m_HighlightInstance = null;
|
||||
m_SelectedInstance = null;
|
||||
|
||||
HighlightedCell = null;
|
||||
SelectedCell = null;
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
var camera = GetViewport()?.GetCamera3D();
|
||||
if (camera == null)
|
||||
return;
|
||||
|
||||
var camPos = camera.GlobalPosition;
|
||||
|
||||
foreach (var sprite in m_PoiSprites)
|
||||
{
|
||||
if (!IsInstanceValid(sprite))
|
||||
continue;
|
||||
|
||||
var pos = sprite.GlobalPosition;
|
||||
var target = new Vector3(camPos.X, pos.Y, camPos.Z);
|
||||
sprite.LookAtFromPosition(sprite.GlobalPosition, target, Vector3.Up, true);
|
||||
}
|
||||
|
||||
if (!m_UseMouseHighlight)
|
||||
UpdateHighlight(camera);
|
||||
}
|
||||
|
||||
public override void _UnhandledInput(InputEvent @event)
|
||||
{
|
||||
if (Input.IsActionJustPressed("cell_select") && HighlightedCell != null)
|
||||
SelectCell(HighlightedCell);
|
||||
|
||||
switch (@event)
|
||||
{
|
||||
case InputEventMouseMotion:
|
||||
case InputEventMouseButton:
|
||||
{
|
||||
m_UseMouseHighlight = true;
|
||||
var camera = GetViewport()?.GetCamera3D();
|
||||
if (camera != null)
|
||||
UpdateHighlight(camera);
|
||||
break;
|
||||
}
|
||||
|
||||
case InputEventJoypadMotion:
|
||||
case InputEventJoypadButton:
|
||||
{
|
||||
m_UseMouseHighlight = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateHighlightMesh()
|
||||
{
|
||||
if (m_HighlightInstance != null)
|
||||
return;
|
||||
|
||||
m_HighlightInstance = new()
|
||||
{
|
||||
Mesh = m_HexMesh,
|
||||
Name = "HighlightHex",
|
||||
Visible = false
|
||||
};
|
||||
|
||||
if (HighlightMaterial != null)
|
||||
m_HighlightInstance.MaterialOverride = HighlightMaterial;
|
||||
else
|
||||
{
|
||||
var mat = new StandardMaterial3D
|
||||
{
|
||||
ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded,
|
||||
Transparency = BaseMaterial3D.TransparencyEnum.Alpha,
|
||||
AlbedoColor = new(1f, 1f, 1f, 0.4f),
|
||||
NoDepthTest = true
|
||||
};
|
||||
m_HighlightInstance.MaterialOverride = mat;
|
||||
}
|
||||
|
||||
AddChild(m_HighlightInstance);
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(m_SelectedInstance))]
|
||||
private void CreateSelectedMesh()
|
||||
{
|
||||
if (m_SelectedInstance != null)
|
||||
return;
|
||||
|
||||
m_SelectedInstance = new()
|
||||
{
|
||||
Mesh = m_HexMesh,
|
||||
Name = "SelectedHex",
|
||||
Visible = false
|
||||
};
|
||||
|
||||
if (SelectedMaterial != null)
|
||||
m_SelectedInstance.MaterialOverride = SelectedMaterial;
|
||||
else
|
||||
{
|
||||
var mat = new StandardMaterial3D
|
||||
{
|
||||
ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded,
|
||||
Transparency = BaseMaterial3D.TransparencyEnum.Alpha,
|
||||
AlbedoColor = new(1f, 1f, 1f, 0.6f),
|
||||
NoDepthTest = true
|
||||
};
|
||||
m_SelectedInstance.MaterialOverride = mat;
|
||||
}
|
||||
|
||||
AddChild(m_SelectedInstance);
|
||||
}
|
||||
|
||||
private void UpdateHighlight(Camera3D camera)
|
||||
{
|
||||
if (m_Board == null || m_Board.Cells.IsDefaultOrEmpty)
|
||||
return;
|
||||
|
||||
var viewport = GetViewport();
|
||||
if (viewport == null)
|
||||
return;
|
||||
|
||||
Vector2 screenPos;
|
||||
if (m_UseMouseHighlight)
|
||||
screenPos = viewport.GetMousePosition();
|
||||
else
|
||||
{
|
||||
var rect = viewport.GetVisibleRect();
|
||||
screenPos = rect.Size * 0.5f;
|
||||
}
|
||||
|
||||
var origin = camera.ProjectRayOrigin(screenPos);
|
||||
var dir = camera.ProjectRayNormal(screenPos);
|
||||
|
||||
if (Mathf.Abs(dir.Y) < 0.0001f)
|
||||
{
|
||||
HideHighlight();
|
||||
return;
|
||||
}
|
||||
|
||||
var t = -origin.Y / dir.Y;
|
||||
if (t < 0)
|
||||
{
|
||||
HideHighlight();
|
||||
return;
|
||||
}
|
||||
|
||||
var hit = origin + dir * t;
|
||||
var hex = Hex.FromWorld(new(hit.X, hit.Z));
|
||||
|
||||
if (!m_CellByHex.TryGetValue(hex, out var cell))
|
||||
{
|
||||
HideHighlight();
|
||||
return;
|
||||
}
|
||||
|
||||
if (HighlightedCell != null && HighlightedCell.Hex.Equals(hex))
|
||||
return;
|
||||
|
||||
HighlightedCell = cell;
|
||||
|
||||
if (m_HighlightInstance != null)
|
||||
{
|
||||
var world = cell.Hex.ToWorld();
|
||||
var pos = new Vector3(world.X, HighlightHeight, world.Y);
|
||||
m_HighlightInstance.Transform = new(Basis.Identity, pos);
|
||||
m_HighlightInstance.Visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectCell(Cell cell)
|
||||
{
|
||||
if (cell == null)
|
||||
return;
|
||||
|
||||
SelectedCell = cell;
|
||||
|
||||
if (m_SelectedInstance == null)
|
||||
CreateSelectedMesh();
|
||||
|
||||
var world = cell.Hex.ToWorld();
|
||||
var pos = new Vector3(world.X, SelectedHeight, world.Y);
|
||||
m_SelectedInstance.Transform = new(Basis.Identity, pos);
|
||||
m_SelectedInstance.Visible = true;
|
||||
|
||||
Tooltip.Instance?.Describe(cell);
|
||||
}
|
||||
|
||||
private void HideHighlight()
|
||||
{
|
||||
HighlightedCell = null;
|
||||
if (m_HighlightInstance != null)
|
||||
m_HighlightInstance.Visible = false;
|
||||
}
|
||||
|
||||
private void BuildCellNode(Cell cell)
|
||||
{
|
||||
var hex = cell.Hex;
|
||||
|
||||
var cellNode = new Node3D { Name = $"Cell_{hex}" };
|
||||
|
||||
cellNode.Transform = BuildCellTransform(cell);
|
||||
|
||||
var meshInstance = new MeshInstance3D
|
||||
{
|
||||
Mesh = m_HexMesh,
|
||||
Name = "HexMesh"
|
||||
};
|
||||
|
||||
var mat = GetMaterialForType(cell.Type);
|
||||
if (mat != null)
|
||||
meshInstance.MaterialOverride = mat;
|
||||
|
||||
cellNode.AddChild(meshInstance);
|
||||
|
||||
if (DebuffScene != null)
|
||||
{
|
||||
foreach (var modifier in cell.Modifiers)
|
||||
{
|
||||
if (modifier is not UnreliableModifierBase && modifier is not CorruptModifierBase)
|
||||
continue;
|
||||
|
||||
if (modifier.DebuffSources.Count > 0)
|
||||
continue;
|
||||
|
||||
var area = DebuffScene.Instantiate<CorruptionArea>();
|
||||
area.Color = modifier is UnreliableModifierBase ? InterferenceColor : CorruptColor;
|
||||
area.Position = new(0f, 0.1f, 0f);
|
||||
cellNode.AddChild(area);
|
||||
}
|
||||
}
|
||||
|
||||
if (cell.Poi is not null)
|
||||
{
|
||||
var (tex, pivot) = GetTextureAndPivotForPoi(cell.Poi);
|
||||
if (tex != null)
|
||||
{
|
||||
var sprite = new Sprite3D
|
||||
{
|
||||
Texture = tex,
|
||||
Billboard = BaseMaterial3D.BillboardModeEnum.Enabled,
|
||||
Centered = true,
|
||||
NoDepthTest = true,
|
||||
PixelSize = 0.01f,
|
||||
Name = $"{cell.Poi.GetType().Name}_Sprite"
|
||||
};
|
||||
|
||||
ApplyPivot(sprite, pivot);
|
||||
sprite.Position = new(0f, PoiHeight, 0f);
|
||||
|
||||
cellNode.AddChild(sprite);
|
||||
m_PoiSprites.Add(sprite);
|
||||
|
||||
if (cell.Poi is Avatar avatar)
|
||||
m_AvatarSprites[avatar] = sprite;
|
||||
|
||||
if (cell.Poi is Donkey donkey)
|
||||
m_DonkeySprites[donkey] = sprite;
|
||||
}
|
||||
|
||||
var infoNode = BuildPoiInfoNode(cell);
|
||||
if (infoNode != null)
|
||||
{
|
||||
infoNode.Position = new(0f, PoiInfoHeight, 0f);
|
||||
cellNode.AddChild(infoNode);
|
||||
}
|
||||
}
|
||||
|
||||
AddChild(cellNode);
|
||||
m_CellNodes[hex] = cellNode;
|
||||
}
|
||||
|
||||
private void RebuildCellVisual(Cell cell)
|
||||
{
|
||||
if (m_CellNodes.TryGetValue(cell.Hex, out var oldNode) && IsInstanceValid(oldNode))
|
||||
oldNode.QueueFree();
|
||||
|
||||
BuildCellNode(cell);
|
||||
}
|
||||
|
||||
private Transform3D BuildCellTransform(Cell cell)
|
||||
{
|
||||
var world = cell.Hex.ToWorld();
|
||||
var position = new Vector3(world.X, 0f, world.Y);
|
||||
var rotation = Basis.Rotated(new(0, 1, 0), MathF.PI * (60 * m_CameraRotationIndex) / 180f);
|
||||
return new(rotation, position);
|
||||
}
|
||||
|
||||
private void UpdateCellTransforms()
|
||||
{
|
||||
if (m_Board == null || m_Board.Cells.IsDefaultOrEmpty)
|
||||
return;
|
||||
|
||||
foreach (var cell in m_Board.Cells)
|
||||
{
|
||||
if (!m_CellNodes.TryGetValue(cell.Hex, out var node) || !IsInstanceValid(node))
|
||||
continue;
|
||||
|
||||
node.Transform = BuildCellTransform(cell);
|
||||
}
|
||||
}
|
||||
|
||||
private Material GetMaterialForType(ECellType type)
|
||||
{
|
||||
if (CellTypeMaterials != null && CellTypeMaterials.Count > (int)type && CellTypeMaterials[(int)type] != null)
|
||||
return CellTypeMaterials[(int)type];
|
||||
|
||||
var mat = new StandardMaterial3D
|
||||
{
|
||||
ShadingMode = BaseMaterial3D.ShadingModeEnum.PerPixel,
|
||||
};
|
||||
return mat;
|
||||
}
|
||||
|
||||
private (Texture2D texture, Vector2 pivot) GetTextureAndPivotForPoi(Poi poi)
|
||||
{
|
||||
return poi switch
|
||||
{
|
||||
Crate => (CrateTexture, CratePivot),
|
||||
Shed => (ShedTexture, ShedPivot),
|
||||
Tower => (TowerTexture, TowerPivot),
|
||||
Donkey d => (GetDonkeyTexture(d.Direction), DonkeyPivot),
|
||||
Avatar a => (GetAvatarTexture(a.Direction), AvatarPivot),
|
||||
_ => (null, default)
|
||||
};
|
||||
}
|
||||
|
||||
private Node3D BuildPoiInfoNode(Cell cell)
|
||||
{
|
||||
if (cell.Poi is null)
|
||||
return null;
|
||||
|
||||
switch (cell.Poi)
|
||||
{
|
||||
case Crate crate:
|
||||
{
|
||||
if (crate.Amount > 0)
|
||||
{
|
||||
var label = new Label3D
|
||||
{
|
||||
Text = crate.Amount.ToString(),
|
||||
FontSize = 72,
|
||||
Name = "Crate_Amount",
|
||||
Billboard = BaseMaterial3D.BillboardModeEnum.Enabled
|
||||
};
|
||||
return label;
|
||||
}
|
||||
|
||||
var check = CreateCheckmarkSprite();
|
||||
if (check != null)
|
||||
check.Name = "Crate_Checkmark";
|
||||
return check;
|
||||
}
|
||||
|
||||
case Shed shed:
|
||||
{
|
||||
var remaining = shed.Remaining;
|
||||
if (remaining > 0)
|
||||
{
|
||||
var label = new Label3D
|
||||
{
|
||||
Text = $"-{remaining}",
|
||||
FontSize = 72,
|
||||
Name = "Shed_Remaining",
|
||||
Billboard = BaseMaterial3D.BillboardModeEnum.Enabled
|
||||
};
|
||||
return label;
|
||||
}
|
||||
|
||||
var check = CreateCheckmarkSprite();
|
||||
if (check != null)
|
||||
check.Name = "Shed_Checkmark";
|
||||
return check;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Sprite3D CreateCheckmarkSprite()
|
||||
{
|
||||
if (CheckmarkTexture == null)
|
||||
return null;
|
||||
|
||||
var sprite = new Sprite3D
|
||||
{
|
||||
Texture = CheckmarkTexture,
|
||||
Billboard = BaseMaterial3D.BillboardModeEnum.Enabled,
|
||||
Centered = true,
|
||||
NoDepthTest = true,
|
||||
PixelSize = 0.01f,
|
||||
Name = "CheckmarkSprite"
|
||||
};
|
||||
|
||||
return sprite;
|
||||
}
|
||||
|
||||
private void ApplyPivot(Sprite3D sprite, Vector2 pivotNormalized)
|
||||
{
|
||||
if (sprite.Texture is not { } tex)
|
||||
return;
|
||||
|
||||
var sizeI = tex.GetSize();
|
||||
var size = new Vector2(sizeI.X, sizeI.Y);
|
||||
|
||||
var pivotFromCenter = pivotNormalized - new Vector2(0.5f, 0.5f);
|
||||
var offset = pivotFromCenter * size;
|
||||
|
||||
sprite.Offset = offset;
|
||||
}
|
||||
|
||||
public void SetCameraRotationIndex(int rotationIndex)
|
||||
{
|
||||
rotationIndex = Mathf.PosMod(rotationIndex, 6);
|
||||
if (m_CameraRotationIndex == rotationIndex)
|
||||
return;
|
||||
|
||||
m_CameraRotationIndex = rotationIndex;
|
||||
|
||||
UpdateCellTransforms();
|
||||
|
||||
foreach (var kvp in m_AvatarSprites)
|
||||
{
|
||||
var avatar = kvp.Key;
|
||||
var sprite = kvp.Value;
|
||||
if (!IsInstanceValid(sprite))
|
||||
continue;
|
||||
|
||||
sprite.Texture = GetAvatarTexture(avatar.Direction);
|
||||
ApplyPivot(sprite, AvatarPivot);
|
||||
}
|
||||
|
||||
foreach (var kvp in m_DonkeySprites)
|
||||
{
|
||||
var donkey = kvp.Key;
|
||||
var sprite = kvp.Value;
|
||||
if (!IsInstanceValid(sprite))
|
||||
continue;
|
||||
|
||||
sprite.Texture = GetDonkeyTexture(donkey.Direction);
|
||||
ApplyPivot(sprite, DonkeyPivot);
|
||||
}
|
||||
}
|
||||
|
||||
private Texture2D GetAvatarTexture(EDirection worldDirection)
|
||||
{
|
||||
var dirIndex = (int)worldDirection;
|
||||
var relative = Mathf.PosMod(dirIndex - m_CameraRotationIndex, 6);
|
||||
|
||||
return (EDirection)relative switch
|
||||
{
|
||||
EDirection.Right => AvatarRightTexture,
|
||||
EDirection.TopRight => AvatarTopRightTexture,
|
||||
EDirection.TopLeft => AvatarTopLeftTexture,
|
||||
EDirection.Left => AvatarLeftTexture,
|
||||
EDirection.BottomLeft => AvatarBottomLeftTexture,
|
||||
EDirection.BottomRight => AvatarBottomRightTexture,
|
||||
_ => throw new UnreachableException()
|
||||
};
|
||||
}
|
||||
|
||||
private Texture2D GetDonkeyTexture(EDirection worldDirection)
|
||||
{
|
||||
var dirIndex = (int)worldDirection;
|
||||
var relative = Mathf.PosMod(dirIndex - m_CameraRotationIndex, 6);
|
||||
|
||||
return (EDirection)relative switch
|
||||
{
|
||||
EDirection.Right => DonkeyRightTexture,
|
||||
EDirection.TopRight => DonkeyTopRightTexture,
|
||||
EDirection.TopLeft => DonkeyTopLeftTexture,
|
||||
EDirection.Left => DonkeyLeftTexture,
|
||||
EDirection.BottomLeft => DonkeyBottomLeftTexture,
|
||||
EDirection.BottomRight => DonkeyBottomRightTexture,
|
||||
_ => throw new UnreachableException()
|
||||
};
|
||||
}
|
||||
|
||||
private void SpawnCellUpdateVfx(Cell cell, Tween tween)
|
||||
{
|
||||
var mesh1 = new MeshInstance3D() { Mesh = m_HexMesh, Visible = false };
|
||||
var mesh2 = new MeshInstance3D() { Mesh = m_HexMesh, Visible = false };
|
||||
|
||||
var mat1 = new StandardMaterial3D
|
||||
{
|
||||
ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded,
|
||||
Transparency = BaseMaterial3D.TransparencyEnum.Alpha,
|
||||
AlbedoColor = new(1f, 1f, 1f, 0.4f),
|
||||
NoDepthTest = true
|
||||
};
|
||||
|
||||
var mat2 = new StandardMaterial3D
|
||||
{
|
||||
ShadingMode = BaseMaterial3D.ShadingModeEnum.Unshaded,
|
||||
Transparency = BaseMaterial3D.TransparencyEnum.Alpha,
|
||||
AlbedoColor = new(1f, 1f, 1f, 0.4f),
|
||||
NoDepthTest = true
|
||||
};
|
||||
|
||||
mesh1.MaterialOverride = mat1;
|
||||
mesh2.MaterialOverride = mat2;
|
||||
|
||||
var world = cell.Hex.ToWorld();
|
||||
var position = new Vector3(world.X, 0.1f, world.Y);
|
||||
mesh1.Position = position;
|
||||
mesh2.Position = position;
|
||||
|
||||
tween.TweenCallback(Callable.From(() =>
|
||||
{
|
||||
AddChild(mesh1);
|
||||
AddChild(mesh2);
|
||||
}));
|
||||
tween.TweenProperty(mesh1, "scale", new Vector3(2, 1, 2), TotalAnimationDuration);
|
||||
tween.Parallel().TweenProperty(mesh2, "position", position + new Vector3(0, 1, 0), TotalAnimationDuration);
|
||||
tween.Parallel().TweenProperty(mat1, "albedo_color", new Color(1, 1, 1, 0.0f), TotalAnimationDuration);
|
||||
tween.Parallel().TweenProperty(mat2, "albedo_color", new Color(1, 1, 1, 0.0f), TotalAnimationDuration);
|
||||
tween.TweenCallback(Callable.From(() =>
|
||||
{
|
||||
mesh1.QueueFree();
|
||||
mesh2.QueueFree();
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
[ExportGroup("POI Pivots (normalized, 0–1)")]
|
||||
[Export]
|
||||
public Vector2 CratePivot { get; set; } = new(0.5f, 1.0f);
|
||||
|
||||
[Export]
|
||||
public Vector2 ShedPivot { get; set; } = new(0.5f, 1.0f);
|
||||
|
||||
[Export]
|
||||
public Vector2 TowerPivot { get; set; } = new(0.5f, 1.0f);
|
||||
|
||||
[Export]
|
||||
public Vector2 DonkeyPivot { get; set; } = new(0.5f, 1.0f);
|
||||
|
||||
[Export]
|
||||
public Vector2 AvatarPivot { get; set; } = new(0.5f, 1.0f);
|
||||
|
||||
[Export]
|
||||
public Array<Material> CellTypeMaterials { get; set; } = new();
|
||||
|
||||
[ExportGroup("POI Textures")]
|
||||
[Export]
|
||||
public Texture2D CrateTexture { get; set; }
|
||||
|
||||
[Export]
|
||||
public Texture2D ShedTexture { get; set; }
|
||||
|
||||
[Export]
|
||||
public Texture2D TowerTexture { get; set; }
|
||||
|
||||
[Export]
|
||||
public Texture2D AvatarRightTexture { get; set; }
|
||||
|
||||
[Export]
|
||||
public Texture2D AvatarTopRightTexture { get; set; }
|
||||
|
||||
[Export]
|
||||
public Texture2D AvatarTopLeftTexture { get; set; }
|
||||
|
||||
[Export]
|
||||
public Texture2D AvatarLeftTexture { get; set; }
|
||||
|
||||
[Export]
|
||||
public Texture2D AvatarBottomLeftTexture { get; set; }
|
||||
|
||||
[Export]
|
||||
public Texture2D AvatarBottomRightTexture { get; set; }
|
||||
|
||||
[Export]
|
||||
public Texture2D DonkeyRightTexture { get; set; }
|
||||
|
||||
[Export]
|
||||
public Texture2D DonkeyTopRightTexture { get; set; }
|
||||
|
||||
[Export]
|
||||
public Texture2D DonkeyTopLeftTexture { get; set; }
|
||||
|
||||
[Export]
|
||||
public Texture2D DonkeyLeftTexture { get; set; }
|
||||
|
||||
[Export]
|
||||
public Texture2D DonkeyBottomLeftTexture { get; set; }
|
||||
|
||||
[Export]
|
||||
public Texture2D DonkeyBottomRightTexture { get; set; }
|
||||
|
||||
[Export]
|
||||
public Texture2D CheckmarkTexture { get; set; }
|
||||
|
||||
[ExportGroup("Cell effects")]
|
||||
[Export]
|
||||
public PackedScene DebuffScene { get; set; }
|
||||
|
||||
[Export]
|
||||
public Color CorruptColor { get; set; } = new(0.6f, 0.1f, 0.8f, 0.45f);
|
||||
|
||||
[Export]
|
||||
public Color InterferenceColor { get; set; } = new(0.1f, 0.8f, 0.6f, 0.45f);
|
||||
|
||||
[ExportGroup("Highlight")]
|
||||
[Export]
|
||||
public Material HighlightMaterial { get; set; }
|
||||
|
||||
[Export]
|
||||
public float HighlightHeight { get; set; } = 0.02f;
|
||||
|
||||
[Export]
|
||||
public Material SelectedMaterial { get; set; }
|
||||
|
||||
[Export]
|
||||
public float SelectedHeight { get; set; } = 0.03f;
|
||||
|
||||
public float TotalAnimationDuration => 0.375f / SettingsManager.Instance.GameSpeed;
|
||||
|
||||
public Cell HighlightedCell { get; private set; }
|
||||
public Cell SelectedCell { get; private set; }
|
||||
public Rect2 BoardBounds { get; private set; }
|
||||
|
||||
private const float PoiHeight = 0.15f;
|
||||
private const float PoiInfoHeight = 0.25f;
|
||||
|
||||
private readonly System.Collections.Generic.Dictionary<Avatar, Sprite3D> m_AvatarSprites = new();
|
||||
private readonly System.Collections.Generic.Dictionary<Hex, Cell> m_CellByHex = new();
|
||||
private readonly System.Collections.Generic.Dictionary<Hex, Node3D> m_CellNodes = new();
|
||||
private readonly System.Collections.Generic.Dictionary<Donkey, Sprite3D> m_DonkeySprites = new();
|
||||
private readonly ArrayMesh m_HexMesh = HexMeshBuilder.CreateFlatHexMesh();
|
||||
private readonly List<Sprite3D> m_PoiSprites = new();
|
||||
|
||||
private Board m_Board;
|
||||
private int m_CameraRotationIndex;
|
||||
private MeshInstance3D m_HighlightInstance;
|
||||
private MeshInstance3D m_SelectedInstance;
|
||||
private bool m_UseMouseHighlight = true;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/HexBoard3D.cs.uid
Normal file
1
DonkeysAndDroids.Godot/HexBoard3D.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c3b2l0t2bjhy5
|
||||
75
DonkeysAndDroids.Godot/HexMeshBuilder.cs
Normal file
75
DonkeysAndDroids.Godot/HexMeshBuilder.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using Godot;
|
||||
using Array = Godot.Collections.Array;
|
||||
|
||||
public static class HexMeshBuilder
|
||||
{
|
||||
public static ArrayMesh CreateFlatHexMesh()
|
||||
{
|
||||
var uvs = new Vector2[19];
|
||||
var vertices = new Vector3[19];
|
||||
var normals = new Vector3[19];
|
||||
|
||||
const float height = 0.02f;
|
||||
vertices[0] = new(0f, height, 0f);
|
||||
uvs[0] = new(0.5f, 0.5f);
|
||||
normals[0] = Vector3.Up;
|
||||
|
||||
for (var i = 0; i < 18; i++)
|
||||
{
|
||||
var angleDeg = 60f * i - 30f;
|
||||
var angleRad = Mathf.DegToRad(angleDeg);
|
||||
var x = MathF.Cos(angleRad);
|
||||
var z = MathF.Sin(angleRad);
|
||||
|
||||
var y = i >= 12 ? height : 0.0f;
|
||||
var s = 1.0f - y;
|
||||
vertices[i + 1] = new(x * s, y, z * s);
|
||||
uvs[i + 1] =
|
||||
i < 6 ? new((i % 6), 0.5f) :
|
||||
i >= 12 ? new Vector2(x * 0.5f + 0.5f, z * 0.5f + 0.5f) :
|
||||
new((i % 6), 0.5f + height);
|
||||
normals[i + 1] =
|
||||
i < 6 ? Vector3.Up :
|
||||
i >= 12 ? new Vector3(x, s, z).Normalized() :
|
||||
new Vector3(x, 0.75f, z).Normalized();
|
||||
}
|
||||
|
||||
var indices = new int[18 * 3];
|
||||
var idx = 0;
|
||||
for (var i = 0; i < 6; i++)
|
||||
{
|
||||
var curr = i + 1;
|
||||
var next = (i + 1) % 6 + 1;
|
||||
|
||||
indices[idx++] = 0;
|
||||
indices[idx++] = curr + 12;
|
||||
indices[idx++] = next + 12;
|
||||
}
|
||||
|
||||
for (var j = 0; j < 6; j++)
|
||||
{
|
||||
var curr = j + 7;
|
||||
var next = (j + 1) % 6 + 7;
|
||||
|
||||
indices[idx++] = curr;
|
||||
indices[idx++] = next;
|
||||
indices[idx++] = next + 6;
|
||||
indices[idx++] = curr;
|
||||
indices[idx++] = next + 6;
|
||||
indices[idx++] = curr + 6;
|
||||
}
|
||||
|
||||
var arrays = new Array();
|
||||
arrays.Resize((int)Mesh.ArrayType.Max);
|
||||
arrays[(int)Mesh.ArrayType.Vertex] = vertices;
|
||||
arrays[(int)Mesh.ArrayType.Normal] = normals;
|
||||
arrays[(int)Mesh.ArrayType.Index] = indices;
|
||||
arrays[(int)Mesh.ArrayType.TexUV] = uvs;
|
||||
|
||||
var mesh = new ArrayMesh();
|
||||
mesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, arrays);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/HexMeshBuilder.cs.uid
Normal file
1
DonkeysAndDroids.Godot/HexMeshBuilder.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bofj561u323iw
|
||||
19
DonkeysAndDroids.Godot/IScreen.cs
Normal file
19
DonkeysAndDroids.Godot/IScreen.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Godot;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
namespace DonkeysAndDroids
|
||||
{
|
||||
public interface IScreen
|
||||
{
|
||||
void Deactivate();
|
||||
void EnableInputs();
|
||||
void DisableInputs();
|
||||
void Activate();
|
||||
bool HandleResult(Result result, Tween tween);
|
||||
}
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/IScreen.cs.uid
Normal file
1
DonkeysAndDroids.Godot/IScreen.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://v20uerfcj7lx
|
||||
150
DonkeysAndDroids.Godot/Improve.cs
Normal file
150
DonkeysAndDroids.Godot/Improve.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using DonkeysAndDroids;
|
||||
using Godot;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
using RobotAndDonkey.Game.Execution.Commands;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public partial class Improve : Control, IScreen
|
||||
{
|
||||
// Called when the node enters the scene tree for the first time.
|
||||
public override void _Ready()
|
||||
{
|
||||
m_ShopCards = GetNode<CardRow>("%Shop");
|
||||
m_Deck = GetNode<CardRow>("%Deck");
|
||||
m_BuyButton = GetNode<Button>("%BuyButton");
|
||||
m_CosmicRaysButton = GetNode<Button>("%CosmicRaysButton");
|
||||
m_RerollButton = GetNode<Button>("%RerollButton");
|
||||
m_BufferOverflowButton = GetNode<Button>("%BufferOverflowButton");
|
||||
m_SkipButton = GetNode<Button>("%SkipButton");
|
||||
|
||||
m_BuyButton.Disabled = true;
|
||||
m_BuyButton.Pressed += OnBuyButtonPressed;
|
||||
m_CosmicRaysButton.Pressed += OnCosmicRaysButtonPressed;
|
||||
m_RerollButton.Pressed += OnRerollButtonPressed;
|
||||
m_BufferOverflowButton.Pressed += OnBufferOverflowButtonPressed;
|
||||
m_SkipButton.Pressed += OnSkipButtonPressed;
|
||||
|
||||
m_ShopCards.Connect(CardRow.SignalName.SelectionChanged, new(this, nameof(OnPatchSelectionChanged)));
|
||||
}
|
||||
|
||||
private void UpdateButtonTexts()
|
||||
{
|
||||
m_CosmicRaysButton.Text = $"Cosmic Rays\n({Balancing.Instance.GambleEnergyCost} energy)";
|
||||
m_RerollButton.Text = $"Reroll ({Balancing.Instance.GetRerollEnergyCost(Main.Instance.CoreLoop.RerollCount)} energy)";
|
||||
m_BufferOverflowButton.Text = $"Buffer Overflow\n({Balancing.Instance.BufferOverflowEnergyCost} energy)";
|
||||
}
|
||||
|
||||
public void Deactivate()
|
||||
{
|
||||
}
|
||||
|
||||
public void EnableInputs()
|
||||
{
|
||||
m_ShopCards.Disabled = false;
|
||||
Deck.Disabled = false;
|
||||
m_BuyButton.Disabled = false;
|
||||
m_CosmicRaysButton.Disabled = !Main.Instance.CoreLoop.CanGamble;
|
||||
m_RerollButton.Disabled = false;
|
||||
m_BufferOverflowButton.Disabled = !Main.Instance.CoreLoop.CanBufferOverflow;
|
||||
m_SkipButton.Disabled = false;
|
||||
|
||||
UpdateButtonTexts();
|
||||
}
|
||||
|
||||
public void DisableInputs()
|
||||
{
|
||||
m_ShopCards.Disabled = true;
|
||||
Deck.Disabled = true;
|
||||
m_BuyButton.Disabled = true;
|
||||
m_CosmicRaysButton.Disabled = true;
|
||||
m_RerollButton.Disabled = true;
|
||||
m_BufferOverflowButton.Disabled = true;
|
||||
m_SkipButton.Disabled = true;
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
}
|
||||
|
||||
private void OnPatchSelectionChanged(CardControl[] selection)
|
||||
{
|
||||
var cost = selection.Sum(c => c.Card.ShopCost);
|
||||
m_BuyButton.Disabled = cost == 0 || cost > Main.Instance.CoreLoop.Currency.Energy;
|
||||
m_BuyButton.Text = $"Buy ({cost} energy)";
|
||||
}
|
||||
|
||||
private void OnBuyButtonPressed()
|
||||
{
|
||||
var cards = m_ShopCards.SelectedCardIndices.ToArray();
|
||||
Main.Instance.Execute(new BuyCardsCommand(Main.Instance.CurrentRequest.RequestId, cards));
|
||||
m_BuyButton.Text = "Buy";
|
||||
m_BuyButton.Disabled = true;
|
||||
}
|
||||
|
||||
private void OnCosmicRaysButtonPressed()
|
||||
{
|
||||
Main.Instance.Execute(new StartGamblingCommand(Main.Instance.CurrentRequest.RequestId));
|
||||
}
|
||||
|
||||
private void OnRerollButtonPressed()
|
||||
{
|
||||
Main.Instance.Execute(new RerollCommand(Main.Instance.CurrentRequest.RequestId));
|
||||
}
|
||||
|
||||
private void OnBufferOverflowButtonPressed()
|
||||
{
|
||||
Main.Instance.Execute(new StartBufferOverflowCommand(Main.Instance.CurrentRequest.RequestId));
|
||||
}
|
||||
|
||||
private void OnSkipButtonPressed()
|
||||
{
|
||||
Main.Instance.Execute(new PreviewProgramCommand(Main.Instance.CurrentRequest.RequestId));
|
||||
}
|
||||
|
||||
public void Configure(IReadOnlyList<Card> shop, IReadOnlyList<Card> patches, Tween tween)
|
||||
{
|
||||
m_ShopCards.Configure(shop, tween, false, false);
|
||||
Deck.Configure(CardRow.GetSortedCards(patches), tween, false, false);
|
||||
}
|
||||
|
||||
public bool HandleResult(Result result, Tween tween)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case ShopResult shopResult:
|
||||
{
|
||||
m_ShopCards.Configure(shopResult.Shop, tween, false, false);
|
||||
return true;
|
||||
}
|
||||
case DeckResult deckResult:
|
||||
{
|
||||
Deck.Configure(CardRow.GetSortedCards(deckResult.Deck), tween, false, false);
|
||||
return true;
|
||||
}
|
||||
case ModifyCardResult modifyCardResult:
|
||||
{
|
||||
Deck.ModifyCard(modifyCardResult.Card, modifyCardResult.Modifier, tween);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
}
|
||||
|
||||
public CardRow Deck => m_Deck;
|
||||
|
||||
private CardRow m_Deck;
|
||||
private CardRow m_ShopCards;
|
||||
private Button m_BuyButton;
|
||||
private Button m_CosmicRaysButton;
|
||||
private Button m_RerollButton;
|
||||
private Button m_BufferOverflowButton;
|
||||
private Button m_SkipButton;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/Improve.cs.uid
Normal file
1
DonkeysAndDroids.Godot/Improve.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c58lu37yfgtsw
|
||||
47
DonkeysAndDroids.Godot/LogoScreen.cs
Normal file
47
DonkeysAndDroids.Godot/LogoScreen.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using DonkeysAndDroids;
|
||||
using Godot;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
|
||||
public partial class LogoScreen : Control, IScreen
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
StartButton.Pressed += OnStartButtonPressed;
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnStartButtonPressed()
|
||||
{
|
||||
GD.Print("Started!");
|
||||
Main.Instance.StartMetaGame();
|
||||
}
|
||||
|
||||
public void Deactivate()
|
||||
{
|
||||
}
|
||||
|
||||
public void EnableInputs()
|
||||
{
|
||||
StartButton.Disabled = false;
|
||||
}
|
||||
|
||||
public void DisableInputs()
|
||||
{
|
||||
StartButton.Disabled = true;
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
}
|
||||
|
||||
public bool HandleResult(Result result, Tween tween)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
[ExportGroup("Buttons")] [Export]
|
||||
private Button StartButton;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/LogoScreen.cs.uid
Normal file
1
DonkeysAndDroids.Godot/LogoScreen.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bj0r4vjvf85dc
|
||||
232
DonkeysAndDroids.Godot/Main.cs
Normal file
232
DonkeysAndDroids.Godot/Main.cs
Normal file
@@ -0,0 +1,232 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using DonkeysAndDroids;
|
||||
using Godot;
|
||||
using RobotAndDonkey.Game;
|
||||
using RobotAndDonkey.Game.Execution;
|
||||
using RobotAndDonkey.Game.Execution.Commands;
|
||||
using RobotAndDonkey.Game.Execution.Requests;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Utils;
|
||||
|
||||
public partial class Main : CanvasLayer
|
||||
{
|
||||
public enum EScreenId
|
||||
{
|
||||
Logo,
|
||||
MetaGame,
|
||||
CoreLoop,
|
||||
Victory
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
#if DEBUG
|
||||
if (!Trace.Listeners.OfType<DefaultTraceListener>().Any())
|
||||
Trace.Listeners.Add(new DefaultTraceListener());
|
||||
#endif
|
||||
Instance = this;
|
||||
|
||||
m_Runtime = new();
|
||||
m_Runtime.Published += (_, e) => OnGameEvent(e);
|
||||
|
||||
m_Background = GetNode<Background>("Background");
|
||||
m_LogoScreen = GetNode<LogoScreen>("LogoScreen");
|
||||
m_MetaGameScreen = GetNode<MetaGameScreen>("MetaGameScreen");
|
||||
m_CoreLoopScreen = GetNode<CoreLoopScreen>("CoreLoopScreen");
|
||||
m_VictoryScreen = GetNode<VictoryScreen>("VictoryScreen");
|
||||
OptionsMenu = GetNode<OptionsMenu>("OptionsMenu");
|
||||
m_CurrentScreen = m_LogoScreen;
|
||||
|
||||
Music.Init(this);
|
||||
Music.PlaySong(MusicManager.ESong.MetaGame);
|
||||
}
|
||||
|
||||
public void StartMetaGame()
|
||||
{
|
||||
Music.PlaySong(MusicManager.ESong.MetaGame);
|
||||
var tween = GetTree().CreateTween();
|
||||
TransitionScreen(m_MetaGameScreen, tween);
|
||||
}
|
||||
|
||||
public void TransitionScreen<T>(T newScreen, Tween tween) where T : Control, IScreen
|
||||
{
|
||||
newScreen.EnableInputs();
|
||||
if (m_CurrentScreen == newScreen)
|
||||
return;
|
||||
|
||||
MoveChild(newScreen, 1);
|
||||
var oldControl = (Control)m_CurrentScreen;
|
||||
var oldScreen = m_CurrentScreen;
|
||||
m_CurrentScreen = newScreen;
|
||||
newScreen.Modulate = new(1, 1, 1);
|
||||
newScreen.Visible = true;
|
||||
newScreen.Activate();
|
||||
tween.TweenProperty(oldControl, "modulate", new Color(0, 0, 0, 0), 0.25);
|
||||
tween.TweenCallback(Callable.From(() =>
|
||||
{
|
||||
oldScreen.Deactivate();
|
||||
oldControl.Visible = false;
|
||||
oldControl.Modulate = new(1, 1, 1);
|
||||
}));
|
||||
}
|
||||
|
||||
public void StartCoreLoop(int seed, EDifficulty difficulty, ERobotType selectedRobot)
|
||||
{
|
||||
Seed = seed;
|
||||
CoreLoop = new(new((ulong)seed), new(selectedRobot, difficulty, seed));
|
||||
m_Runtime.Start(CoreLoop);
|
||||
}
|
||||
|
||||
private void OnGameEvent(GameEvent gameEvent)
|
||||
{
|
||||
switch (gameEvent.Payload)
|
||||
{
|
||||
case NextStepIssued { Step: { } request }:
|
||||
{
|
||||
m_NextRequest = request;
|
||||
if (m_Tween != null)
|
||||
break;
|
||||
|
||||
ProcessNextRequest();
|
||||
break;
|
||||
}
|
||||
|
||||
case StepApplied { Results: var results }:
|
||||
{
|
||||
GD.Print("Results available");
|
||||
foreach (var result in results)
|
||||
{
|
||||
Results.Enqueue(result);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case StateChanged:
|
||||
{
|
||||
GD.Print("State changed");
|
||||
m_Tween = GetTree().CreateTween();
|
||||
while (Results.TryDequeue(out var result))
|
||||
{
|
||||
GD.Print(result.ToString());
|
||||
if (m_CurrentScreen.HandleResult(result, m_Tween))
|
||||
m_Tween.SetParallel(false);
|
||||
else
|
||||
GD.Print("> Unhandled result!");
|
||||
}
|
||||
|
||||
m_Tween.TweenCallback(Callable.From(() =>
|
||||
{
|
||||
m_Tween = null;
|
||||
ProcessNextRequest();
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case StepRejected r:
|
||||
{
|
||||
GD.Print($"Invalid action: {r.Reason}");
|
||||
break;
|
||||
}
|
||||
|
||||
case GameOver:
|
||||
{
|
||||
GD.Print("Game over");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessNextRequest()
|
||||
{
|
||||
m_CurrentScreen.DisableInputs();
|
||||
CurrentRequest = m_NextRequest;
|
||||
m_NextRequest = null;
|
||||
GD.Print($"Next step: {CurrentRequest}");
|
||||
m_Tween = GetTree().CreateTween();
|
||||
var tween = m_Tween;
|
||||
switch (CurrentRequest)
|
||||
{
|
||||
case DrawGlitchRequest drawCardRequest:
|
||||
{
|
||||
Music.PlaySong(MusicManager.ESong.Shop);
|
||||
TransitionScreen(m_CoreLoopScreen, tween);
|
||||
m_CoreLoopScreen.DrawGlitch(drawCardRequest.Card, tween);
|
||||
break;
|
||||
}
|
||||
case ImproveRequest:
|
||||
{
|
||||
TransitionScreen(m_CoreLoopScreen, tween);
|
||||
m_CoreLoopScreen.Improve(tween);
|
||||
break;
|
||||
}
|
||||
case GambleRequest:
|
||||
{
|
||||
TransitionScreen(m_CoreLoopScreen, tween);
|
||||
m_CoreLoopScreen.CosmicRays(CoreLoop.BoosterPack, tween);
|
||||
break;
|
||||
}
|
||||
case BufferOverflowRequest:
|
||||
{
|
||||
TransitionScreen(m_CoreLoopScreen, tween);
|
||||
m_CoreLoopScreen.BufferOverflow(tween);
|
||||
break;
|
||||
}
|
||||
case ExecuteProgramRequest:
|
||||
{
|
||||
Music.PlaySong(MusicManager.ESong.CoreLoop);
|
||||
TransitionScreen(m_CoreLoopScreen, tween);
|
||||
m_CoreLoopScreen.ExecuteProgram(tween);
|
||||
break;
|
||||
}
|
||||
case ScoringRequest:
|
||||
{
|
||||
Music.PlaySong(MusicManager.ESong.Scoring);
|
||||
TransitionScreen(m_VictoryScreen, tween);
|
||||
m_VictoryScreen.Configure(CoreLoop, tween);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Execute(Command command)
|
||||
{
|
||||
if (command == null)
|
||||
return;
|
||||
|
||||
GD.Print($"Execute {command}");
|
||||
m_Runtime.Submit(command);
|
||||
}
|
||||
|
||||
public static Main Instance { get; set; }
|
||||
|
||||
public Request CurrentRequest { get; private set; } = Request.s_Empty;
|
||||
|
||||
public Queue<Result> Results { get; } = [];
|
||||
|
||||
public CoreLoop CoreLoop { get; private set; }
|
||||
|
||||
public int Seed { get; private set; }
|
||||
|
||||
public string StringSeed => SeedString.ToString(Seed);
|
||||
|
||||
public MusicManager Music { get; } = new();
|
||||
|
||||
public OptionsMenu OptionsMenu { get; private set; }
|
||||
|
||||
private Background m_Background;
|
||||
private CoreLoopScreen m_CoreLoopScreen;
|
||||
private IScreen m_CurrentScreen;
|
||||
private LogoScreen m_LogoScreen;
|
||||
private MetaGameScreen m_MetaGameScreen;
|
||||
private Request m_NextRequest;
|
||||
|
||||
private GameRuntime m_Runtime;
|
||||
|
||||
private Tween m_Tween;
|
||||
private VictoryScreen m_VictoryScreen;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/Main.cs.uid
Normal file
1
DonkeysAndDroids.Godot/Main.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b362eqi3w8p1n
|
||||
182
DonkeysAndDroids.Godot/MetaGameScreen.cs
Normal file
182
DonkeysAndDroids.Godot/MetaGameScreen.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
using System;
|
||||
using DonkeysAndDroids;
|
||||
using Godot;
|
||||
using RobotAndDonkey.Game;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.Utils;
|
||||
|
||||
public partial class MetaGameScreen : Control, IScreen
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
m_SeedLineEdit = GetNode<LineEdit>("MarginContainer/MarginContainer/MainVBox/SeedRow/SeedLineEdit");
|
||||
m_RandomizeButton = GetNode<Button>("MarginContainer/MarginContainer/MainVBox/SeedRow/RandomizeButton");
|
||||
m_DifficultyOptionButton = GetNode<OptionButton>("MarginContainer/MarginContainer/MainVBox/DifficultyRow/DifficultyOptionButton");
|
||||
m_StartButton = GetNode<Button>("%StartButton");
|
||||
m_OptionsButton = GetNode<Button>("%OptionsButton");
|
||||
|
||||
m_RobotContainer = GetNode<HBoxContainer>("MarginContainer/MarginContainer/MainVBox/RobotsSection/RobotsContainer");
|
||||
foreach (var robot in Enum.GetValues<ERobotType>())
|
||||
{
|
||||
var currentRobot = robot;
|
||||
var robotInstance = (Robot)RobotScene.Instantiate<Button>();
|
||||
robotInstance.Pressed += () => OnRobotButtonPressed(currentRobot);
|
||||
robotInstance.Name = robot.ToString();
|
||||
robotInstance.Texture = robot switch
|
||||
{
|
||||
ERobotType.Vintage => ResourceLoader.Load<Texture2D>("uid://b51acya8abb3p"),
|
||||
ERobotType.Courier => ResourceLoader.Load<Texture2D>("uid://c6h1eqa6n2ca8"),
|
||||
ERobotType.Analyst => ResourceLoader.Load<Texture2D>("uid://c6h1eqa6n2ca8"),
|
||||
ERobotType.Ranger => ResourceLoader.Load<Texture2D>("uid://c6h1eqa6n2ca8"),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
m_RobotContainer.AddChild(robotInstance);
|
||||
}
|
||||
|
||||
m_RobotButtons = new Button[m_RobotContainer.GetChildren().Count];
|
||||
for (var i = 0; i < m_RobotButtons.Length; i++)
|
||||
{
|
||||
m_RobotButtons[i] = (Button)m_RobotContainer.GetChildren()[i];
|
||||
// JAM
|
||||
|
||||
if (i > 0)
|
||||
m_RobotButtons[i].Disabled = true;
|
||||
}
|
||||
|
||||
m_DifficultyOptionButton.Clear();
|
||||
foreach (var difficulty in Enum.GetValues<EDifficulty>())
|
||||
m_DifficultyOptionButton.AddItem(difficulty.ToString(), (int)difficulty);
|
||||
m_DifficultyOptionButton.Selected = 0;
|
||||
|
||||
m_RandomizeButton.Pressed += OnRandomizePressed;
|
||||
m_StartButton.Pressed += OnStartPressed;
|
||||
m_OptionsButton.Pressed += OnOptionsPressed;
|
||||
m_SeedLineEdit.TextChanged += OnSeedTextChanged;
|
||||
|
||||
m_StartButton.Disabled = true;
|
||||
OnRandomizePressed();
|
||||
}
|
||||
|
||||
private void OnOptionsPressed()
|
||||
{
|
||||
Main.Instance.OptionsMenu.ShowMenu();
|
||||
}
|
||||
|
||||
public override void _UnhandledKeyInput(InputEvent @event)
|
||||
{
|
||||
if (@event.IsActionPressed("options") && !m_StartButton.Disabled)
|
||||
{
|
||||
Main.Instance.OptionsMenu.ShowMenu();
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRandomizePressed()
|
||||
{
|
||||
m_Seed = m_Random.Next(int.MaxValue);
|
||||
m_SeedLineEdit.Text = SeedString.ToString(m_Seed);
|
||||
}
|
||||
|
||||
private void OnSeedTextChanged(string newText)
|
||||
{
|
||||
if (!SeedString.TryParse(newText, out m_Seed, true))
|
||||
m_Seed = 0;
|
||||
|
||||
UpdateStartButtonState();
|
||||
}
|
||||
|
||||
private void OnRobotButtonPressed(ERobotType robot)
|
||||
{
|
||||
m_SelectedRobot = robot;
|
||||
UpdateRobotButtonsVisual();
|
||||
UpdateStartButtonState();
|
||||
}
|
||||
|
||||
private void UpdateRobotButtonsVisual()
|
||||
{
|
||||
var index = 0;
|
||||
foreach (var node in m_RobotContainer.GetChildren())
|
||||
{
|
||||
var robot = (ERobotType)index++;
|
||||
if (node is not Button button)
|
||||
continue;
|
||||
|
||||
button.ButtonPressed = robot == m_SelectedRobot;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateStartButtonState()
|
||||
{
|
||||
var hasSeed = m_Seed > 0;
|
||||
var hasRobot = m_SelectedRobot >= 0;
|
||||
|
||||
m_StartButton.Disabled = !(hasSeed && hasRobot);
|
||||
}
|
||||
|
||||
private void OnStartPressed()
|
||||
{
|
||||
if (m_StartButton.Disabled)
|
||||
return;
|
||||
|
||||
var seed = m_SeedLineEdit.Text.Trim();
|
||||
var difficulty = (EDifficulty)m_DifficultyOptionButton.Selected;
|
||||
|
||||
GD.Print($"Starting game with Seed={seed}, Difficulty={difficulty}, Robot={m_SelectedRobot}");
|
||||
Main.Instance.StartCoreLoop(m_Seed, difficulty, m_SelectedRobot);
|
||||
}
|
||||
|
||||
public void Deactivate()
|
||||
{
|
||||
}
|
||||
|
||||
public void EnableInputs()
|
||||
{
|
||||
m_DifficultyOptionButton.Disabled = false;
|
||||
m_RandomizeButton.Disabled = false;
|
||||
m_StartButton.Disabled = false;
|
||||
|
||||
// JAM
|
||||
m_RobotButtons[0].Disabled = false;
|
||||
//foreach (var button in m_RobotButtons)
|
||||
// button.Disabled = false;
|
||||
|
||||
m_SeedLineEdit.Editable = true;
|
||||
}
|
||||
|
||||
public void DisableInputs()
|
||||
{
|
||||
m_DifficultyOptionButton.Disabled = true;
|
||||
m_RandomizeButton.Disabled = true;
|
||||
m_StartButton.Disabled = true;
|
||||
foreach (var button in m_RobotButtons)
|
||||
button.Disabled = true;
|
||||
m_SeedLineEdit.Editable = false;
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
}
|
||||
|
||||
public bool HandleResult(Result result, Tween tween)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
[Export]
|
||||
public PackedScene RobotScene { get; set; }
|
||||
|
||||
public string StringSeed => SeedString.ToString(m_Seed);
|
||||
|
||||
private OptionButton m_DifficultyOptionButton;
|
||||
private Button m_RandomizeButton;
|
||||
private Button m_OptionsButton;
|
||||
private Button m_StartButton;
|
||||
private Button[] m_RobotButtons;
|
||||
private HBoxContainer m_RobotContainer;
|
||||
private LineEdit m_SeedLineEdit;
|
||||
|
||||
private SRandom m_Random = new((ulong)DateTime.UtcNow.ToFileTimeUtc());
|
||||
private int m_Seed;
|
||||
private ERobotType m_SelectedRobot = ERobotType.Vintage;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/MetaGameScreen.cs.uid
Normal file
1
DonkeysAndDroids.Godot/MetaGameScreen.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bmxs8bmhtxvxw
|
||||
64
DonkeysAndDroids.Godot/ModifierIcon.cs
Normal file
64
DonkeysAndDroids.Godot/ModifierIcon.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using Godot;
|
||||
using RobotAndDonkey.Game;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
|
||||
public partial class ModifierIcon : TextureRect
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
UpdateTooltip();
|
||||
}
|
||||
|
||||
public void Configure(Modifier modifier)
|
||||
{
|
||||
Name = $"Modifier_{modifier.Id}";
|
||||
m_Modifier = modifier;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
if (m_Modifier == null)
|
||||
return;
|
||||
|
||||
Texture = GD.Load<Texture2D>(m_Modifier.Id switch
|
||||
{
|
||||
EModifierId.Corrupt => "res://images/corrupt.png",
|
||||
EModifierId.Unreliable => "res://images/unreliable.png",
|
||||
EModifierId.RaceCondition => "res://images/race-condition.png",
|
||||
EModifierId.Throttled => "res://images/throttled.png",
|
||||
EModifierId.Effective => "res://images/effective.png",
|
||||
EModifierId.Optimized => "res://images/optimized.png",
|
||||
EModifierId.Efficient => "res://images/efficient.png",
|
||||
EModifierId.Persistent => "res://images/persistent.png",
|
||||
EModifierId.Analytic => string.Empty, //"res://images/analytic.png",
|
||||
EModifierId.Rain => "res://images/rain.png",
|
||||
EModifierId.Drought => "res://images/drought.png",
|
||||
EModifierId.Pest => "res://images/pest.png",
|
||||
EModifierId.Gravity => "res://images/gravity.png",
|
||||
EModifierId.HeatWave => "res://images/heat-wave.png",
|
||||
EModifierId.CourierOverspill => string.Empty, //"res://images/courier-overspill.png",
|
||||
EModifierId.RangerFertileRest => string.Empty, //"res://images/ranger-fertile-rest.png",
|
||||
EModifierId.GlobalImmunity => string.Empty, //"res://images/global-immunity.png",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
});
|
||||
Description = $"{m_Modifier.Id}\n{m_Modifier.ToolTip}";
|
||||
UpdateTooltip();
|
||||
}
|
||||
|
||||
private void UpdateTooltip()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Description))
|
||||
TooltipText = Description;
|
||||
else
|
||||
TooltipText = m_Modifier.Id.ToString();
|
||||
}
|
||||
|
||||
[Export(PropertyHint.MultilineText)]
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
public Modifier Modifier => m_Modifier;
|
||||
|
||||
private Modifier m_Modifier;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/ModifierIcon.cs.uid
Normal file
1
DonkeysAndDroids.Godot/ModifierIcon.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bl8mg6iej6u01
|
||||
15
DonkeysAndDroids.Godot/ModifierIcon.tscn
Normal file
15
DonkeysAndDroids.Godot/ModifierIcon.tscn
Normal file
@@ -0,0 +1,15 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://yo336l0oucfq"]
|
||||
|
||||
[ext_resource type="Texture2D" uid="uid://c7wwcxywr2nh7" path="res://images/efficient.png" id="1_icon"]
|
||||
[ext_resource type="Script" uid="uid://bl8mg6iej6u01" path="res://ModifierIcon.cs" id="2_script"]
|
||||
|
||||
[node name="ModifierIcon" type="TextureRect"]
|
||||
texture_filter = 1
|
||||
custom_minimum_size = Vector2(32, 32)
|
||||
offset_right = 32.0
|
||||
offset_bottom = 32.0
|
||||
size_flags_vertical = 0
|
||||
texture = ExtResource("1_icon")
|
||||
expand_mode = 5
|
||||
stretch_mode = 5
|
||||
script = ExtResource("2_script")
|
||||
170
DonkeysAndDroids.Godot/MusicManager.cs
Normal file
170
DonkeysAndDroids.Godot/MusicManager.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using Godot;
|
||||
using RobotAndDonkey.Game;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
public class MusicManager
|
||||
{
|
||||
public enum ESong
|
||||
{
|
||||
MetaGame,
|
||||
Shop,
|
||||
CoreLoop,
|
||||
Scoring
|
||||
}
|
||||
|
||||
public enum ESound
|
||||
{
|
||||
Energy,
|
||||
Carry,
|
||||
Delivery,
|
||||
TapeLength,
|
||||
Hand,
|
||||
Program,
|
||||
Card,
|
||||
Rest
|
||||
}
|
||||
|
||||
public void Init(Main main)
|
||||
{
|
||||
m_Player1 = new() { Bus = "Music" };
|
||||
main.AddChild(m_Player1);
|
||||
|
||||
m_Player2 = new() { Bus = "Music" };
|
||||
main.AddChild(m_Player2);
|
||||
|
||||
m_SfxPlayer = new() { Bus = "Sfx" };
|
||||
main.AddChild(m_SfxPlayer);
|
||||
|
||||
m_Tree = main.GetTree();
|
||||
}
|
||||
|
||||
public void PlaySong(ESong song)
|
||||
{
|
||||
if (m_Song == song)
|
||||
return;
|
||||
|
||||
var tween = m_Tree.CreateTween();
|
||||
tween.SetParallel();
|
||||
m_Song = song;
|
||||
if (m_CurrentPlayer)
|
||||
CrossFade(m_Player1, m_Player2, song, tween);
|
||||
else
|
||||
CrossFade(m_Player2, m_Player1, song, tween);
|
||||
m_CurrentPlayer = !m_CurrentPlayer;
|
||||
}
|
||||
|
||||
private static void EnableLooping(AudioStream stream)
|
||||
{
|
||||
if (stream is AudioStreamOggVorbis ogg)
|
||||
{
|
||||
ogg.Loop = true;
|
||||
}
|
||||
else if (stream is AudioStreamMP3 mp3)
|
||||
{
|
||||
mp3.Loop = true;
|
||||
}
|
||||
else if (stream is AudioStreamWav wav)
|
||||
{
|
||||
wav.LoopMode = AudioStreamWav.LoopModeEnum.Forward;
|
||||
}
|
||||
}
|
||||
|
||||
public void Play(EModifierId modifier)
|
||||
{
|
||||
m_SfxPlayer.Stream = ResourceLoader.Load<AudioStream>(modifier switch
|
||||
{
|
||||
EModifierId.Corrupt => "uid://dnc8ksbbt8rxg",
|
||||
EModifierId.Unreliable => "uid://b8cgqw2meefkw",
|
||||
EModifierId.RaceCondition => "uid://c5061hog183xe",
|
||||
EModifierId.Throttled => "uid://djkcjn5niyhje",
|
||||
EModifierId.Effective => "uid://7toyoaj4xv5t",
|
||||
EModifierId.Optimized => "uid://cij77w63o5ahu",
|
||||
EModifierId.Efficient => "uid://iccli4sklikc",
|
||||
EModifierId.Persistent => "uid://bmkqvi2y64wxk",
|
||||
_ => null
|
||||
});
|
||||
|
||||
if (m_SfxPlayer.Stream != null)
|
||||
m_SfxPlayer.Play();
|
||||
}
|
||||
|
||||
public void Play(ESound sound)
|
||||
{
|
||||
m_SfxPlayer.Stream = ResourceLoader.Load<AudioStream>(sound switch
|
||||
{
|
||||
ESound.Energy => "uid://prjkw6o6m53s",
|
||||
ESound.Carry => "uid://taabst8vqf07",
|
||||
ESound.Delivery => "uid://cr7ajk4lac6e0",
|
||||
ESound.TapeLength => "uid://bvt2nl4geonil",
|
||||
ESound.Hand => "uid://bu2tncw11cgqt",
|
||||
ESound.Program => "uid://6cqu6nualhm2",
|
||||
ESound.Card => "uid://b7508mnlvtm26",
|
||||
ESound.Rest => "uid://bp1dme3dnek76",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(sound), sound, null)
|
||||
});
|
||||
m_SfxPlayer.Play();
|
||||
}
|
||||
|
||||
public void Play(ECellType cellType)
|
||||
{
|
||||
m_SfxPlayer.Stream = ResourceLoader.Load<AudioStream>(cellType switch
|
||||
{
|
||||
ECellType.Grass => "uid://xc27unw7jisd",
|
||||
ECellType.Dry => "uid://b1licxrv5a5kx",
|
||||
ECellType.Fertile => "uid://ufefxts5f0op",
|
||||
ECellType.Mud => "uid://bkr83d0ylrgoh",
|
||||
ECellType.Blocked => "uid://fvia4q78bwmj",
|
||||
ECellType.Rocky => "uid://c22k1uvleut6w",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(cellType), cellType, null)
|
||||
});
|
||||
m_SfxPlayer.Play();
|
||||
}
|
||||
|
||||
public void Play(Poi poi)
|
||||
{
|
||||
m_SfxPlayer.Stream = ResourceLoader.Load<AudioStream>(poi switch
|
||||
{
|
||||
Crate => "uid://2g4nuqy03if5",
|
||||
Shed => "uid://bv03vbh5us6ym",
|
||||
Tower => "uid://dqolgkxab6gfy",
|
||||
Donkey => "uid://bg8eu2auayu2k",
|
||||
Avatar => "uid://nw8iofm1vaj8",
|
||||
_ => throw new NotImplementedException()
|
||||
});
|
||||
m_SfxPlayer.Play();
|
||||
}
|
||||
|
||||
private void CrossFade(AudioStreamPlayer player1, AudioStreamPlayer player2, ESong song, Tween tween)
|
||||
{
|
||||
var stream = GetStream(song);
|
||||
EnableLooping(stream);
|
||||
player2.Stream = stream;
|
||||
player2.Play();
|
||||
tween.TweenProperty(player1, "volume_db", -80.0f, 1.0).SetEase(Tween.EaseType.In).SetTrans(Tween.TransitionType.Sine);
|
||||
tween.TweenProperty(player2, "volume_db", 0.0f, 1.0).SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Expo);
|
||||
}
|
||||
|
||||
private AudioStream GetStream(ESong song)
|
||||
{
|
||||
return ResourceLoader.Load<AudioStream>(song switch
|
||||
{
|
||||
ESong.MetaGame => "uid://bnrm544axjxoi",
|
||||
ESong.Shop => "uid://62fyj028yf7w",
|
||||
ESong.CoreLoop => "uid://5w305h2two2l",
|
||||
ESong.Scoring => "uid://b4qv7oicdvqg6",
|
||||
_ => throw new NotImplementedException()
|
||||
});
|
||||
}
|
||||
|
||||
private bool m_CurrentPlayer;
|
||||
private AudioStreamPlayer m_Player1;
|
||||
private AudioStreamPlayer m_Player2;
|
||||
private AudioStreamPlayer m_SfxPlayer;
|
||||
|
||||
private ESong m_Song = (ESong)(-1);
|
||||
private SceneTree m_Tree;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/MusicManager.cs.uid
Normal file
1
DonkeysAndDroids.Godot/MusicManager.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b62jg2i30qsu7
|
||||
104
DonkeysAndDroids.Godot/OptionsMenu.cs
Normal file
104
DonkeysAndDroids.Godot/OptionsMenu.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using Godot;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
public partial class OptionsMenu : Control
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
m_MasterSlider = GetNode<HSlider>("%MasterSlider");
|
||||
m_MusicSlider = GetNode<HSlider>("%MusicSlider");
|
||||
m_SfxSlider = GetNode<HSlider>("%SfxSlider");
|
||||
m_GameSpeedSlider = GetNode<HSlider>("%GameSpeedSlider");
|
||||
m_TutorialScreen = GetNode<TutorialScreen>("%TutorialScreen");
|
||||
|
||||
m_ContinueButton = GetNode<Button>("%ContinueButton");
|
||||
m_ExitToMainButton = GetNode<Button>("%ExitToMainButton");
|
||||
m_ExitGameButton = GetNode<Button>("%ExitGameButton");
|
||||
m_TutorialButton = GetNode<Button>("%TutorialButton");
|
||||
|
||||
m_MasterSlider.ValueChanged += OnMasterSliderChanged;
|
||||
m_MusicSlider.ValueChanged += OnMusicSliderChanged;
|
||||
m_SfxSlider.ValueChanged += OnSfxSliderChanged;
|
||||
m_GameSpeedSlider.ValueChanged += OnGameSpeedSliderChanged;
|
||||
|
||||
m_ContinueButton.Pressed += OnContinuePressed;
|
||||
m_ExitToMainButton.Pressed += OnExitToMainPressed;
|
||||
m_ExitGameButton.Pressed += OnExitGamePressed;
|
||||
m_TutorialButton.Pressed += OnTutorialPressed;
|
||||
|
||||
HideMenu();
|
||||
}
|
||||
|
||||
public void ShowMenu()
|
||||
{
|
||||
Visible = true;
|
||||
|
||||
var settings = SettingsManager.Instance;
|
||||
if (settings != null)
|
||||
{
|
||||
m_MasterSlider.Value = settings.MasterVolumeDb;
|
||||
m_MusicSlider.Value = settings.MusicVolumeDb;
|
||||
m_SfxSlider.Value = settings.SfxVolumeDb;
|
||||
m_GameSpeedSlider.Value = settings.GameSpeed;
|
||||
}
|
||||
m_MasterSlider.GrabFocus();
|
||||
}
|
||||
|
||||
public void HideMenu()
|
||||
{
|
||||
Visible = false;
|
||||
}
|
||||
|
||||
private void OnMasterSliderChanged(double value)
|
||||
{
|
||||
SettingsManager.Instance?.SetMasterVolume((float)value);
|
||||
}
|
||||
|
||||
private void OnMusicSliderChanged(double value)
|
||||
{
|
||||
SettingsManager.Instance?.SetMusicVolume((float)value);
|
||||
}
|
||||
|
||||
private void OnSfxSliderChanged(double value)
|
||||
{
|
||||
SettingsManager.Instance?.SetSfxVolume((float)value);
|
||||
Main.Instance.Music.Play(new Donkey());
|
||||
}
|
||||
|
||||
private void OnGameSpeedSliderChanged(double value)
|
||||
{
|
||||
SettingsManager.Instance?.SetGameSpeed((float)value);
|
||||
}
|
||||
|
||||
private void OnContinuePressed()
|
||||
{
|
||||
HideMenu();
|
||||
}
|
||||
|
||||
private void OnExitToMainPressed()
|
||||
{
|
||||
Main.Instance.StartMetaGame();
|
||||
HideMenu();
|
||||
}
|
||||
|
||||
private void OnExitGamePressed()
|
||||
{
|
||||
GetTree().Quit();
|
||||
}
|
||||
|
||||
private void OnTutorialPressed()
|
||||
{
|
||||
m_TutorialScreen.Restart();
|
||||
m_TutorialScreen.Visible = true;
|
||||
}
|
||||
|
||||
private TutorialScreen m_TutorialScreen;
|
||||
private Button m_ContinueButton;
|
||||
private Button m_ExitGameButton;
|
||||
private Button m_ExitToMainButton;
|
||||
private Button m_TutorialButton;
|
||||
private HSlider m_MasterSlider;
|
||||
private HSlider m_MusicSlider;
|
||||
private HSlider m_SfxSlider;
|
||||
private HSlider m_GameSpeedSlider;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/OptionsMenu.cs.uid
Normal file
1
DonkeysAndDroids.Godot/OptionsMenu.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://1fyo1un7vvcl
|
||||
243
DonkeysAndDroids.Godot/ProgramScreen.cs
Normal file
243
DonkeysAndDroids.Godot/ProgramScreen.cs
Normal file
@@ -0,0 +1,243 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using DonkeysAndDroids;
|
||||
using Godot;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Execution;
|
||||
using RobotAndDonkey.Game.Execution.Commands;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.Utils;
|
||||
|
||||
public partial class ProgramScreen : Control, IScreen
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
m_ProgramRow = GetNode<CardRow>("%ProgramRow");
|
||||
m_Execute = GetNode<Button>("%Execute");
|
||||
m_Discard = GetNode<Button>("%Discard");
|
||||
m_Sort = GetNode<Button>("%Sort");
|
||||
m_TapeLabel = GetNode<Label>("%TapeLabel");
|
||||
m_HandLabel = GetNode<Label>("%HandLabel");
|
||||
m_FlameBackground = GetNode<Node2D>("%FlameBackground");
|
||||
m_Execute.Pressed += OnExecutePressed;
|
||||
m_Discard.Pressed += OnDiscardPressed;
|
||||
m_Sort.Pressed += OnSortPressed;
|
||||
m_ProgramRow.Connect(CardRow.SignalName.SelectionChanged, new(this, nameof(OnSelectionChanged)));
|
||||
m_ProgramRow.Connect(CardRow.SignalName.OrderChanged, new(this, nameof(OnOrderChanged)));
|
||||
m_ProgramRow.CanChangeSelection = CanChangeSelection;
|
||||
}
|
||||
|
||||
private void OnOrderChanged()
|
||||
{
|
||||
UpdateLabels();
|
||||
SendMoveCommand();
|
||||
}
|
||||
|
||||
private void SendMoveCommand()
|
||||
{
|
||||
if (Main.Instance.CurrentRequest == null)
|
||||
return;
|
||||
|
||||
var ordered = m_ProgramRow.OrderedCards.Select(card => card.CardId).ToArray();
|
||||
var selected = m_ProgramRow.SelectedCardIds.ToArray();
|
||||
Main.Instance.Execute(new MoveCardsCommand(Main.Instance.CurrentRequest.RequestId, ordered, selected));
|
||||
}
|
||||
|
||||
private void UpdateLabels()
|
||||
{
|
||||
var selectedIds = m_ProgramRow.SelectedCardIds.ToHashSet();
|
||||
var tapeSpace = 0;
|
||||
var handSpace = 0;
|
||||
|
||||
foreach (var card in m_ProgramRow.OrderedCards)
|
||||
{
|
||||
if (selectedIds.Contains(card.CardId))
|
||||
tapeSpace += card.OccupiedSpace;
|
||||
else
|
||||
handSpace += card.OccupiedSpace;
|
||||
}
|
||||
|
||||
m_TapeLabel.Text = $"{tapeSpace} / {Main.Instance.CoreLoop.Currency.TapeLength}";
|
||||
m_HandLabel.Text = $"{handSpace} / {Main.Instance.CoreLoop.Currency.HandSize}";
|
||||
m_Discard.Text = Main.Instance.CoreLoop.PatchDeck.Count.ToString();
|
||||
m_Discard.TooltipText = $"Discard {selectedIds.Count} instructions. Costs {Balancing.Instance.DiscardEnergyCost} each.";
|
||||
}
|
||||
|
||||
private void RefreshDiscardCommand()
|
||||
{
|
||||
if (Main.Instance.CurrentRequest == null)
|
||||
{
|
||||
m_DiscardCommand = null;
|
||||
return;
|
||||
}
|
||||
|
||||
m_DiscardCommand = new(Main.Instance.CurrentRequest.RequestId, m_ProgramRow.SelectedCardIds.ToArray());
|
||||
|
||||
if (m_Discard != null)
|
||||
m_Discard.Disabled = m_DiscardCommand == null || !m_DiscardCommand.IsValid(Main.Instance.CoreLoop, out _);
|
||||
}
|
||||
|
||||
private void OnSelectionChanged(CardControl[] cardControls)
|
||||
{
|
||||
RefreshDiscardCommand();
|
||||
UpdateLabels();
|
||||
SendMoveCommand();
|
||||
}
|
||||
|
||||
private bool CanChangeSelection(CardControl cardControl, bool selected)
|
||||
{
|
||||
var selectedIds = m_ProgramRow.SelectedCardIds.ToHashSet();
|
||||
if (selected)
|
||||
selectedIds.Add(cardControl.Card.CardId);
|
||||
else
|
||||
selectedIds.Remove(cardControl.Card.CardId);
|
||||
|
||||
var tapeSpace = 0;
|
||||
var handSpace = 0;
|
||||
|
||||
foreach (var card in m_ProgramRow.OrderedCards)
|
||||
{
|
||||
if (selectedIds.Contains(card.CardId))
|
||||
tapeSpace += card.OccupiedSpace;
|
||||
else
|
||||
handSpace += card.OccupiedSpace;
|
||||
}
|
||||
|
||||
return tapeSpace <= Main.Instance.CoreLoop.Currency.TapeLength && handSpace <= Main.Instance.CoreLoop.Currency.HandSize;
|
||||
}
|
||||
|
||||
private void OnExecutePressed()
|
||||
{
|
||||
Main.Instance.Execute(m_RunProgramCommand);
|
||||
}
|
||||
|
||||
private void OnDiscardPressed()
|
||||
{
|
||||
Main.Instance.Execute(m_DiscardCommand);
|
||||
}
|
||||
|
||||
private void OnSortPressed()
|
||||
{
|
||||
var selectedIds = m_ProgramRow.SelectedCardIds.ToHashSet();
|
||||
var unselected = m_ProgramRow.OrderedCards.Where(card => !selectedIds.Contains(card.CardId)).ToList();
|
||||
var sortedUnselected = CardExtensions.SortForHand(unselected);
|
||||
var ordered = new List<Card>(m_ProgramRow.OrderedCards.Count);
|
||||
var unselectedIndex = 0;
|
||||
|
||||
foreach (var card in m_ProgramRow.OrderedCards)
|
||||
{
|
||||
if (selectedIds.Contains(card.CardId))
|
||||
{
|
||||
ordered.Add(card);
|
||||
continue;
|
||||
}
|
||||
|
||||
ordered.Add(sortedUnselected[unselectedIndex]);
|
||||
unselectedIndex += 1;
|
||||
}
|
||||
|
||||
m_ProgramRow.Configure(ordered, GetTree().CreateTween(), false, false);
|
||||
m_ProgramRow.ApplySelection(selectedIds, true);
|
||||
OnOrderChanged();
|
||||
}
|
||||
|
||||
public void Deactivate()
|
||||
{
|
||||
}
|
||||
|
||||
public void EnableInputs()
|
||||
{
|
||||
m_RunProgramCommand ??= new(Main.Instance.CurrentRequest.RequestId);
|
||||
RefreshDiscardCommand();
|
||||
m_Sort.Disabled = false;
|
||||
}
|
||||
|
||||
public void DisableInputs()
|
||||
{
|
||||
m_RunProgramCommand = null;
|
||||
m_DiscardCommand = null;
|
||||
m_Sort.Disabled = true;
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
}
|
||||
|
||||
public bool HandleResult(Result result, Tween tween)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case RunCardResult runCardResult:
|
||||
{
|
||||
m_ProgramRow.RunCard(runCardResult.Card, tween);
|
||||
RefreshDiscardCommand();
|
||||
UpdateLabels();
|
||||
return true;
|
||||
}
|
||||
case ProgramRowResult programRowResult:
|
||||
{
|
||||
m_ProgramRow.Configure(programRowResult.OrderedCards, tween, false, false);
|
||||
m_ProgramRow.ApplySelection(programRowResult.TapeCardIds, true);
|
||||
RefreshDiscardCommand();
|
||||
UpdateLabels();
|
||||
return true;
|
||||
}
|
||||
case ModifyCardResult modifyCardResult:
|
||||
{
|
||||
m_ProgramRow.ModifyCard(modifyCardResult.Card, modifyCardResult.Modifier, tween);
|
||||
UpdateLabels();
|
||||
return true;
|
||||
}
|
||||
case HandResult:
|
||||
case TapeResult:
|
||||
{
|
||||
UpdateLabels();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (m_Execute != null)
|
||||
{
|
||||
m_Execute.Disabled = m_RunProgramCommand == null;
|
||||
var lastRun = m_RunProgramCommand?.Preview(Main.Instance.CoreLoop).Any(r => r is RunPhaseResult { NewRunPhase: ERunPhase.Scoring }) ?? false;
|
||||
if (lastRun)
|
||||
{
|
||||
m_FlameBackground.Visible = true;
|
||||
m_Execute.TooltipText = "Execute last program";
|
||||
}
|
||||
else
|
||||
{
|
||||
m_FlameBackground.Visible = false;
|
||||
m_Execute.TooltipText = "Execute";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Configure(Tween tween)
|
||||
{
|
||||
var coreLoop = Main.Instance.CoreLoop;
|
||||
var ordered = coreLoop.ProgramRow.ToList();
|
||||
m_ProgramRow.Configure(ordered, tween, false, false);
|
||||
m_ProgramRow.ApplySelection(coreLoop.TapeCardIds.ToArray(), true);
|
||||
m_RunProgramCommand = new(Main.Instance.CurrentRequest.RequestId);
|
||||
RefreshDiscardCommand();
|
||||
UpdateLabels();
|
||||
}
|
||||
|
||||
private RunProgramCommand m_RunProgramCommand;
|
||||
private DiscardCommand m_DiscardCommand;
|
||||
|
||||
private CardRow m_ProgramRow;
|
||||
private Button m_Execute;
|
||||
private Button m_Discard;
|
||||
private Button m_Sort;
|
||||
private Label m_TapeLabel;
|
||||
private Label m_HandLabel;
|
||||
private Node2D m_FlameBackground;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/ProgramScreen.cs.uid
Normal file
1
DonkeysAndDroids.Godot/ProgramScreen.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://vdiphjbwoqh1
|
||||
16
DonkeysAndDroids.Godot/Properties/launchSettings.json
Normal file
16
DonkeysAndDroids.Godot/Properties/launchSettings.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Godot (console)": {
|
||||
"commandName": "Executable",
|
||||
"executablePath": "D:\\Code\\Godot_v4.5.1-stable_mono_win64\\Godot_v4.5.1-stable_mono_win64_console.exe",
|
||||
"workingDirectory": ".",
|
||||
"nativeDebugging": true
|
||||
},
|
||||
"Godot (debug)": {
|
||||
"commandName": "Executable",
|
||||
"executablePath": "D:\\Code\\Godot_v4.5.1-stable_mono_win64\\Godot_v4.5.1-stable_mono_win64.exe",
|
||||
"workingDirectory": ".",
|
||||
"nativeDebugging": false
|
||||
}
|
||||
}
|
||||
}
|
||||
37
DonkeysAndDroids.Godot/Robot.cs
Normal file
37
DonkeysAndDroids.Godot/Robot.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Godot;
|
||||
|
||||
public partial class Robot : Button
|
||||
{
|
||||
// Called when the node enters the scene tree for the first time.
|
||||
public override void _Ready()
|
||||
{
|
||||
m_TextureRect = GetNode<TextureRect>("%TextureRect");
|
||||
m_Label = GetNode<Label>("%Label");
|
||||
}
|
||||
|
||||
// Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (m_TextureRect != null)
|
||||
m_TextureRect.Texture = Texture;
|
||||
if (m_Label != null)
|
||||
m_Label.Text = Name;
|
||||
}
|
||||
|
||||
[Export]
|
||||
public Texture2D Texture
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Export]
|
||||
public string RobotName
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = "Robot";
|
||||
|
||||
private Label m_Label;
|
||||
private TextureRect m_TextureRect;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/Robot.cs.uid
Normal file
1
DonkeysAndDroids.Godot/Robot.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cxg6ixmxgaxtw
|
||||
117
DonkeysAndDroids.Godot/SettingsManager.cs
Normal file
117
DonkeysAndDroids.Godot/SettingsManager.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using Godot;
|
||||
|
||||
public partial class SettingsManager : Node
|
||||
{
|
||||
public override void _EnterTree()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
QueueFree();
|
||||
return;
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
LoadSettings();
|
||||
ApplyVolumes();
|
||||
}
|
||||
|
||||
private void LoadSettings()
|
||||
{
|
||||
var config = new ConfigFile();
|
||||
var err = config.Load(c_ConfigPath);
|
||||
|
||||
if (err == Error.Ok)
|
||||
{
|
||||
MasterVolumeDb = (float)config.GetValue(c_SectionAudio, "master_db", c_DefaultMasterVolume);
|
||||
MusicVolumeDb = (float)config.GetValue(c_SectionAudio, "music_db", c_DefaultMusicVolume);
|
||||
SfxVolumeDb = (float)config.GetValue(c_SectionAudio, "sfx_db", c_DefaultSfxVolume);
|
||||
GameSpeed = (float)config.GetValue(c_SectionGame, "speed", c_DefaultGameSpeed);
|
||||
}
|
||||
else
|
||||
{
|
||||
MasterVolumeDb = c_DefaultMasterVolume;
|
||||
MusicVolumeDb = c_DefaultMusicVolume;
|
||||
SfxVolumeDb = c_DefaultSfxVolume;
|
||||
GameSpeed = c_DefaultGameSpeed;
|
||||
SaveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveSettings()
|
||||
{
|
||||
var config = new ConfigFile();
|
||||
|
||||
config.SetValue(c_SectionAudio, "master_db", MasterVolumeDb);
|
||||
config.SetValue(c_SectionAudio, "music_db", MusicVolumeDb);
|
||||
config.SetValue(c_SectionAudio, "sfx_db", SfxVolumeDb);
|
||||
config.SetValue(c_SectionGame, "speed", GameSpeed);
|
||||
|
||||
config.Save(c_ConfigPath);
|
||||
}
|
||||
|
||||
public void SetMasterVolume(float db)
|
||||
{
|
||||
MasterVolumeDb = db;
|
||||
ApplyVolumes();
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
public void SetMusicVolume(float db)
|
||||
{
|
||||
MusicVolumeDb = db;
|
||||
ApplyVolumes();
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
public void SetSfxVolume(float db)
|
||||
{
|
||||
SfxVolumeDb = db;
|
||||
ApplyVolumes();
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
public void SetGameSpeed(float gameSpeed)
|
||||
{
|
||||
GameSpeed = gameSpeed;
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
private void ApplyVolumes()
|
||||
{
|
||||
var masterBus = AudioServer.GetBusIndex("Master");
|
||||
var musicBus = AudioServer.GetBusIndex("Music");
|
||||
var sfxBus = AudioServer.GetBusIndex("Sfx");
|
||||
|
||||
if (masterBus != -1)
|
||||
AudioServer.SetBusVolumeDb(masterBus, MasterVolumeDb);
|
||||
|
||||
if (musicBus != -1)
|
||||
AudioServer.SetBusVolumeDb(musicBus, MusicVolumeDb);
|
||||
|
||||
if (sfxBus != -1)
|
||||
AudioServer.SetBusVolumeDb(sfxBus, SfxVolumeDb);
|
||||
}
|
||||
|
||||
public static SettingsManager Instance { get; private set; }
|
||||
|
||||
public float MasterVolumeDb { get; private set; }
|
||||
|
||||
public float MusicVolumeDb { get; private set; }
|
||||
|
||||
public float SfxVolumeDb { get; private set; }
|
||||
|
||||
public float GameSpeed { get; private set; }
|
||||
|
||||
private const float c_DefaultMusicVolume = -16.0f;
|
||||
private const float c_DefaultSfxVolume = 0.0f;
|
||||
private const float c_DefaultMasterVolume = 0.0f;
|
||||
private const float c_DefaultGameSpeed = 1.0f;
|
||||
|
||||
private const string c_ConfigPath = "user://settings.cfg";
|
||||
private const string c_SectionAudio = "audio";
|
||||
private const string c_SectionGame = "game";
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/SettingsManager.cs.uid
Normal file
1
DonkeysAndDroids.Godot/SettingsManager.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bkhsu7257yto6
|
||||
349
DonkeysAndDroids.Godot/Tooltip.cs
Normal file
349
DonkeysAndDroids.Godot/Tooltip.cs
Normal file
@@ -0,0 +1,349 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Godot;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
public partial class Tooltip : Control
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
m_Label = GetNode<RichTextLabel>("RichText");
|
||||
m_Label.BbcodeEnabled = true;
|
||||
m_Label.AutowrapMode = TextServer.AutowrapMode.Word;
|
||||
m_Label.ScrollActive = true;
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
UpdateGamepadScroll((float)delta);
|
||||
}
|
||||
|
||||
private void UpdateGamepadScroll(float delta)
|
||||
{
|
||||
if (m_Label == null || !Visible)
|
||||
return;
|
||||
|
||||
var axis = Input.GetActionStrength("tooltip_scroll_down") - Input.GetActionStrength("tooltip_scroll_up");
|
||||
|
||||
if (Mathf.Abs(axis) < 0.1f)
|
||||
return;
|
||||
|
||||
var vbar = m_Label.GetVScrollBar();
|
||||
if (vbar == null)
|
||||
return;
|
||||
|
||||
vbar.Value += axis * GamepadScrollSpeed * delta;
|
||||
}
|
||||
|
||||
public void Describe(Cell cell)
|
||||
{
|
||||
Begin();
|
||||
AppendCell(cell);
|
||||
Commit();
|
||||
}
|
||||
|
||||
public void Describe(Card card)
|
||||
{
|
||||
Begin();
|
||||
AppendCard(card);
|
||||
Commit();
|
||||
}
|
||||
|
||||
private void Begin()
|
||||
{
|
||||
m_Builder.Clear();
|
||||
}
|
||||
|
||||
private void Commit()
|
||||
{
|
||||
if (m_Label == null)
|
||||
return;
|
||||
|
||||
m_Label.Text = m_Builder.ToString();
|
||||
m_Label.ScrollToLine(0);
|
||||
}
|
||||
|
||||
private static string WrapColor(string text, string colorHex)
|
||||
{
|
||||
return $"[color={colorHex}]{text}[/color]";
|
||||
}
|
||||
|
||||
private void AppendTitle(string title, string subtitle = null)
|
||||
{
|
||||
m_Builder.Append(WrapColor($"[b]{title}[/b]", ColorTitle));
|
||||
|
||||
if (!string.IsNullOrEmpty(subtitle))
|
||||
{
|
||||
m_Builder.Append(' ');
|
||||
m_Builder.Append(WrapColor(subtitle, ColorSubtitle));
|
||||
}
|
||||
|
||||
m_Builder.Append('\n');
|
||||
}
|
||||
|
||||
private void AppendSectionHeader(string title)
|
||||
{
|
||||
m_Builder.Append('\n');
|
||||
m_Builder.Append(WrapColor($"[b]{title}[/b]", ColorHeader));
|
||||
m_Builder.Append('\n');
|
||||
}
|
||||
|
||||
private void AppendKeyValue(string key, string value)
|
||||
{
|
||||
m_Builder.Append(WrapColor($"{key}: ", ColorLabel)).Append(WrapColor(value, ColorText)).Append('\n');
|
||||
}
|
||||
|
||||
private void AppendBlankLine()
|
||||
{
|
||||
m_Builder.Append('\n');
|
||||
}
|
||||
|
||||
private void AppendCell(Cell cell)
|
||||
{
|
||||
var subtitle = cell.Hex.ToString();
|
||||
AppendTitle("Cell", subtitle);
|
||||
|
||||
var surfaceColor = GetCellColor(cell.Type);
|
||||
AppendKeyValue("Surface", WrapColor(cell.Type.ToString(), surfaceColor));
|
||||
m_Builder.Append(WrapColor(GetCellDescription(cell.Type), ColorText));
|
||||
m_Builder.Append('\n');
|
||||
|
||||
if (cell.Poi != null)
|
||||
{
|
||||
Main.Instance.Music.Play(cell.Poi);
|
||||
AppendPoi(cell.Poi);
|
||||
}
|
||||
else
|
||||
{
|
||||
Main.Instance.Music.Play(cell.Type);
|
||||
}
|
||||
|
||||
AppendModifiers("Cell", cell);
|
||||
}
|
||||
|
||||
public static string GetCellDescription(ECellType cellType)
|
||||
{
|
||||
return cellType switch
|
||||
{
|
||||
ECellType.Grass => "Plain and simple terrain, suitable for most tasks.",
|
||||
ECellType.Dry => $"Resting replenishes {WrapColor($"{Balancing.Instance.DryRestEnergyMalus} less energy", ColorBad)} and interacting costs {WrapColor($"{Balancing.Instance.DryInteractEnergyMalus} more energy", ColorBad)}.",
|
||||
ECellType.Fertile => $"Replenish {WrapColor($"{Balancing.Instance.FertileRestEnergyReplenish} more energy", ColorGood)} while resting here.",
|
||||
ECellType.Mud => $"Movement is very difficult and costs {WrapColor($"{Balancing.Instance.MudMoveEnergyCost} more energy", ColorBad)}.",
|
||||
ECellType.Blocked => "Can't walk here, try jumping or flying.",
|
||||
ECellType.Rocky => "When entering, programs have a high risk of race conditions.",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(cellType), cellType, null)
|
||||
};
|
||||
}
|
||||
|
||||
private string GetCellColor(ECellType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
ECellType.Grass => "#9ccc65",
|
||||
ECellType.Dry => "#ffcc80",
|
||||
ECellType.Fertile => "#a5d6a7",
|
||||
ECellType.Mud => "#8d6e63",
|
||||
ECellType.Blocked => "#b0bec5",
|
||||
ECellType.Rocky => "#b39ddb",
|
||||
_ => ColorText
|
||||
};
|
||||
}
|
||||
|
||||
private void AppendPoi(Poi poi)
|
||||
{
|
||||
switch (poi)
|
||||
{
|
||||
case Avatar:
|
||||
AppendRobot(Main.Instance.CoreLoop.Robot);
|
||||
break;
|
||||
|
||||
case Shed shed:
|
||||
{
|
||||
var summary = $"Requested {shed.Requested}\n" + $"Received {shed.Received}\n" + $"Remaining {shed.Remaining}";
|
||||
AppendPoiBlock("Shed", summary, poi);
|
||||
break;
|
||||
}
|
||||
|
||||
case Crate crate:
|
||||
AppendPoiBlock("Crate", $"Contains {crate.Amount} goods", poi);
|
||||
break;
|
||||
|
||||
case Tower tower:
|
||||
AppendPoiBlock("Tower", tower.Active ? WrapColor("Active", ColorBad) : WrapColor("Disabled", ColorGood), poi);
|
||||
break;
|
||||
|
||||
case Donkey:
|
||||
AppendPoiBlock("Donkey", string.Empty, poi);
|
||||
break;
|
||||
|
||||
default:
|
||||
AppendPoiBlock(poi.GetType().Name, string.Empty, poi);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendPoiBlock(string title, string body, Poi poi)
|
||||
{
|
||||
AppendSectionHeader(title);
|
||||
m_Builder.Append("[indent]\n");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(body))
|
||||
{
|
||||
m_Builder.Append(WrapColor(body, ColorText));
|
||||
m_Builder.Append("\n\n");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(poi.Tooltip))
|
||||
{
|
||||
m_Builder.Append(WrapColor(poi.Tooltip, ColorText));
|
||||
m_Builder.Append('\n');
|
||||
|
||||
var tooltipModifiers = poi.TooltipModifiers;
|
||||
if (tooltipModifiers.Length > 0)
|
||||
{
|
||||
AppendSectionHeader("Related:");
|
||||
foreach (var modifier in tooltipModifiers)
|
||||
AppendModifier(modifier);
|
||||
}
|
||||
}
|
||||
|
||||
m_Builder.Append("[/indent]\n");
|
||||
}
|
||||
|
||||
private void AppendRobot(RobotAndDonkey.Game.Robots.Robot robot)
|
||||
{
|
||||
AppendSectionHeader(Main.Instance.CoreLoop.Robot.Type.ToString());
|
||||
m_Builder.Append("[indent]\n");
|
||||
|
||||
AppendKeyValue("Programs", robot.ProgramCount.ToString());
|
||||
AppendCurrency(robot.Currency);
|
||||
|
||||
AppendModifiers("Robot", robot);
|
||||
|
||||
m_Builder.Append("[/indent]\n");
|
||||
}
|
||||
|
||||
private void AppendCurrency(Currency currency)
|
||||
{
|
||||
AppendKeyValue("Remaining energy", currency.Energy.ToString());
|
||||
AppendKeyValue("Good carried", $"{currency.Carry}/{currency.MaxCarry}");
|
||||
AppendKeyValue("Completed deliveries", currency.Delivery.ToString());
|
||||
AppendKeyValue("Tape length", currency.TapeLength.ToString());
|
||||
AppendKeyValue("Hand size", currency.HandSize.ToString());
|
||||
}
|
||||
|
||||
private void AppendCard(Card card)
|
||||
{
|
||||
var rarityColor = GetRarityColor(card.Rarity);
|
||||
var rarityText = WrapColor(card.Rarity.ToString(), rarityColor);
|
||||
var subtitle = $"\n{rarityText} {card.CardType}";
|
||||
|
||||
AppendTitle(card.Name, subtitle);
|
||||
|
||||
AppendSectionHeader("Costs");
|
||||
m_Builder.Append("[indent]\n");
|
||||
AppendKeyValue("Shop", card.ShopCost.ToString());
|
||||
AppendKeyValue("Play", card.PlayCost.ToString());
|
||||
m_Builder.Append("[/indent]\n");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(card.ToolTip))
|
||||
{
|
||||
AppendSectionHeader("Effect");
|
||||
m_Builder.Append("[indent]\n");
|
||||
m_Builder.Append(WrapColor(card.ToolTip, ColorText));
|
||||
m_Builder.Append('\n');
|
||||
|
||||
var tooltipModifiers = card.TooltipModifiers;
|
||||
if (tooltipModifiers.Length > 0)
|
||||
{
|
||||
AppendSectionHeader("Related:");
|
||||
foreach (var modifier in tooltipModifiers)
|
||||
AppendModifier(modifier);
|
||||
}
|
||||
|
||||
m_Builder.Append("[/indent]\n");
|
||||
}
|
||||
|
||||
AppendModifiers("Card", card);
|
||||
}
|
||||
|
||||
private string GetRarityColor(ERarity rarity)
|
||||
{
|
||||
return rarity switch
|
||||
{
|
||||
ERarity.Common => "#FFFFFF",
|
||||
ERarity.Magic => "#4CFF4C",
|
||||
ERarity.Uncommon => "#4C99FF",
|
||||
ERarity.Rare => "#CC4CFF",
|
||||
ERarity.Legendary => "#FF8033",
|
||||
_ => ColorText
|
||||
};
|
||||
}
|
||||
|
||||
private void AppendModifiers(string entityName, Entity entity)
|
||||
{
|
||||
if (entity.Modifiers.Count == 0)
|
||||
return;
|
||||
|
||||
AppendSectionHeader($"{entityName} modifiers");
|
||||
m_Builder.Append("[indent]\n");
|
||||
foreach (var modifier in entity.Modifiers)
|
||||
AppendModifier(modifier);
|
||||
m_Builder.Append("[/indent]\n");
|
||||
}
|
||||
|
||||
private void AppendModifier(Modifier modifier)
|
||||
{
|
||||
var debuffed = modifier.DebuffSources.Count > 0;
|
||||
var header = $"{modifier.Id} [{modifier.Duration}]";
|
||||
|
||||
if (debuffed)
|
||||
{
|
||||
m_Builder.Append(WrapColor($"[s]{header}[/s]", ColorMuted));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Builder.Append(WrapColor(header, ColorModifier));
|
||||
}
|
||||
|
||||
m_Builder.Append('\n');
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(modifier.ToolTip))
|
||||
{
|
||||
m_Builder.Append(WrapColor(modifier.ToolTip, ColorText));
|
||||
m_Builder.Append('\n');
|
||||
}
|
||||
|
||||
if (debuffed)
|
||||
{
|
||||
m_Builder.Append(WrapColor(" (Debuffed)", ColorMuted));
|
||||
m_Builder.Append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
public static Tooltip Instance { get; private set; }
|
||||
|
||||
private const string ColorTitle = "#ffd65c";
|
||||
private const string ColorSubtitle = "#aaaaaa";
|
||||
private const string ColorHeader = "#a5d6ff";
|
||||
private const string ColorLabel = "#cccccc";
|
||||
private const string ColorText = "#ffffff";
|
||||
private const string ColorGood = "#a5d6a7";
|
||||
private const string ColorBad = "#ef9a9a";
|
||||
private const string ColorMuted = "#9e9e9e";
|
||||
private const string ColorModifier = "#ffcc80";
|
||||
|
||||
private readonly StringBuilder m_Builder = new();
|
||||
|
||||
[Export]
|
||||
public float GamepadScrollSpeed = 800f;
|
||||
|
||||
private RichTextLabel m_Label;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/Tooltip.cs.uid
Normal file
1
DonkeysAndDroids.Godot/Tooltip.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b1g3ewcbtqagh
|
||||
98
DonkeysAndDroids.Godot/TutorialScreen.cs
Normal file
98
DonkeysAndDroids.Godot/TutorialScreen.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Godot;
|
||||
using RobotAndDonkey.Game.Board;
|
||||
|
||||
public partial class TutorialScreen : Control
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
m_CounterLabel = GetNode<Label>("%CounterLabel");
|
||||
m_Text = GetNode<RichTextLabel>("%Text");
|
||||
m_Text.BbcodeEnabled = true;
|
||||
m_Text.AutowrapMode = TextServer.AutowrapMode.Word;
|
||||
m_Text.ScrollActive = true;
|
||||
m_NextButton = GetNode<Button>("%NextButton");
|
||||
m_NextButton.Pressed += OnNextButtonPressed;
|
||||
m_CloseButton = GetNode<Button>("%CloseButton");
|
||||
m_CloseButton.Pressed += OnCloseButtonPressed;
|
||||
|
||||
if (m_Text != null)
|
||||
{
|
||||
m_Text.Text = Text;
|
||||
Configure(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCloseButtonPressed()
|
||||
{
|
||||
Visible = false;
|
||||
}
|
||||
|
||||
private void OnNextButtonPressed()
|
||||
{
|
||||
var tween = GetTree().CreateTween();
|
||||
tween.TweenProperty(m_Text, "modulate", new Color(1, 1, 1, 0), 0.25f);
|
||||
tween.TweenCallback(Callable.From(() =>
|
||||
{
|
||||
m_CurrentHint += 1;
|
||||
Configure(m_CurrentHint);
|
||||
}));
|
||||
tween.TweenProperty(m_Text, "modulate", new Color(1, 1, 1), 0.25f);
|
||||
}
|
||||
|
||||
public void Restart()
|
||||
{
|
||||
m_Text.Modulate = new(1, 1, 1);
|
||||
m_CurrentHint = 0;
|
||||
Configure(m_CurrentHint);
|
||||
}
|
||||
|
||||
public void Configure(int hint)
|
||||
{
|
||||
if (hint >= s_Hints.Length)
|
||||
return;
|
||||
|
||||
if (hint == s_Hints.Length - 1)
|
||||
{
|
||||
m_NextButton.Visible = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_NextButton.Visible = true;
|
||||
}
|
||||
|
||||
Text = s_Hints[hint];
|
||||
|
||||
if (m_Text != null)
|
||||
m_Text.Text = Text;
|
||||
|
||||
if (m_CounterLabel != null)
|
||||
m_CounterLabel.Text = $"{hint + 1}/{s_Hints.Length}";
|
||||
}
|
||||
|
||||
public string Text { get; private set; }
|
||||
|
||||
private static readonly string[] s_Hints =
|
||||
[
|
||||
$"Your robot does not take direct orders – it runs {c_On}programs{c_Off}.\nFor each assignment, your goal is to {c_On}deliver enough cargo{c_Off} to meet the target before you run out of {c_Energy}Energy{c_Off}.\nMove around the hex map, use {c_On}Interact{c_Off} to work with sheds, crates and towers, and try to do it in {c_On}as few instructions{c_Off} as possible. Efficient routes and short programs are rewarded in the final score.",
|
||||
$"The board consists of hexagonal shapes.\n\n - You can move the camera around with the {c_On}WASD{c_Off} keys.\n - You can rotate the view by 60 degrees using the {c_On}Q{c_Off} and {c_On}E{c_Off} keys.\n - Zoom in and out using the {c_On}R{c_Off} and {c_On}F{c_Off} keys or the {c_On}mouse wheel{c_Off}\n - Interact with the board and the cards with a {c_On}left mouse button click{c_Off}",
|
||||
$"Each program is a single pass through your {c_On}command tape{c_Off}.\n\n - Draw cards into your {c_On}hand{c_Off}.\n - Drag cards onto the tape up to its maximum {c_On}length{c_Off}.\n - Reorder, remove, or discard cards as needed (discarding costs {c_Energy}Energy{c_Off}).",
|
||||
$"When you press {c_On}Run{c_Off}, the tape executes left to right:\n\n - Most instructions spend {c_Energy}Energy{c_Off} to move, turn or interact.\n - Some cards are marked {c_On}Persistent{c_Off} and return to your deck after use. Others are discarded.\n - Zone and glitch effects may change how the remaining instructions behave.\n\nAfter execution, you return to planning the next program with whatever {c_Energy}Energy{c_Off} and map state you have left. Every program is a chance to refine your route.",
|
||||
$"Different hexes change how your robot behaves:\n\n{string.Join("\n", Enum.GetValues<ECellType>().Select(c => $" - {c_On}{c}{c_Off}: {Tooltip.GetCellDescription(c)}"))}\nPlan your path so you rest on helpful tiles and cross bad terrain only when it’s worth the cost.",
|
||||
$"Points of interest are where the real work happens:\n\n - {c_On}Sheds{c_Off}: load and deliver cargo using {c_On}Interact{c_Off}.\n - {c_On}Crates{c_Off}: adjust your {c_On}Carry{c_Off} by picking up or dropping off contents.\n - {c_On}Donkeys{c_Off}: increase your {c_On}Max Carry{c_Off} so each trip is more productive.\n - {c_On}Interference towers{c_Off}: entering them applies special glitches to the {c_On}remaining{c_Off} tape.\n\nShort, high-value delivery loops between these points are usually better than wandering everywhere.",
|
||||
$"At the end of an assignment you’re judged on more than just success or failure. The game tracks:\n\n - Instructions and programs used (fewer is better).\n - Path length and tiles visited.\n - {c_Energy}Energy{c_Off} wasted by moving while carrying nothing.\n - Overspill from delivering more than requested..",
|
||||
$"In the {c_On}Improve{c_Off} step you upgrade your deck using {c_Energy}Energy{c_Off} as currency.\n\n - Buy {c_On}patch cards{c_Off} to add new instructions or upgrades.\n - {c_On}Reroll{c_Off} to see a new set of patches, at an increasing {c_Energy}Energy{c_Off} cost.\n - Use {c_On}Cosmic Rays{c_Off} to add a fancy new random card to your deck.\n - Use {c_On}Buffer Overflow{c_Off} to permanently destroy a card.\n\nStronger cards are great, but a bloated deck is harder to program with. Trimming bad cards can be as powerful as buying new ones.",
|
||||
$"At the end of every round you draw a {c_On}Glitch{c_Off} card.\nYou must decide:\n\n - {c_On}Accept{c_Off}: apply the effect immediately (it may change the map or your instructions).\n - {c_On}Defer{c_Off}: put it back on the stack. The next time you defer a glitch in this run, it costs more {c_Energy}Energy{c_Off}.\n\nGlitches can be temporary map-wide storms or permanent defects on a single instruction. Sometimes taking a small hit now is better than paying {c_Energy}Energy{c_Off} to delay it."
|
||||
];
|
||||
|
||||
private const string c_Energy = "[color=#a5d6a7]";
|
||||
private const string c_On = "[color=#ffcc80]";
|
||||
private const string c_Off = "[/color]";
|
||||
|
||||
private int m_CurrentHint;
|
||||
private Button m_NextButton;
|
||||
private Button m_CloseButton;
|
||||
private RichTextLabel m_Text;
|
||||
private Label m_CounterLabel;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/TutorialScreen.cs.uid
Normal file
1
DonkeysAndDroids.Godot/TutorialScreen.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://biagw2yiy1bs5
|
||||
220
DonkeysAndDroids.Godot/VictoryScreen.cs
Normal file
220
DonkeysAndDroids.Godot/VictoryScreen.cs
Normal file
@@ -0,0 +1,220 @@
|
||||
using System.Linq;
|
||||
using DonkeysAndDroids;
|
||||
using Godot;
|
||||
using RobotAndDonkey.Game.Execution.Commands;
|
||||
using RobotAndDonkey.Game.Execution.Results;
|
||||
using RobotAndDonkey.Game.GameState;
|
||||
using System.Text;
|
||||
using RobotAndDonkey.Game.Data;
|
||||
using RobotAndDonkey.Game.Pois;
|
||||
|
||||
public partial class VictoryScreen : Control, IScreen
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
ContinueButton.Pressed += OnContinueButtonPressed;
|
||||
|
||||
if (StatsLabel != null)
|
||||
{
|
||||
StatsLabel.BbcodeEnabled = true;
|
||||
StatsLabel.AutowrapMode = TextServer.AutowrapMode.Word;
|
||||
StatsLabel.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
UpdateGamepadScroll((float)delta);
|
||||
}
|
||||
|
||||
private void UpdateGamepadScroll(float delta)
|
||||
{
|
||||
if (StatsLabel == null || !Visible)
|
||||
return;
|
||||
|
||||
var axis = Input.GetActionStrength("tooltip_scroll_down") - Input.GetActionStrength("tooltip_scroll_up");
|
||||
|
||||
if (Mathf.Abs(axis) < 0.1f)
|
||||
return;
|
||||
|
||||
var verticalScrollBar = StatsLabel.GetVScrollBar();
|
||||
if (verticalScrollBar == null)
|
||||
return;
|
||||
|
||||
verticalScrollBar.Value += axis * GamepadScrollSpeed * delta;
|
||||
}
|
||||
|
||||
public void OnContinueButtonPressed()
|
||||
{
|
||||
if (m_Victory)
|
||||
{
|
||||
Main.Instance.StartMetaGame();
|
||||
}
|
||||
else
|
||||
{
|
||||
Main.Instance.Execute(new NextAssignmentCommand(Main.Instance.CurrentRequest.RequestId));
|
||||
}
|
||||
}
|
||||
|
||||
public void Deactivate()
|
||||
{
|
||||
}
|
||||
|
||||
public void EnableInputs()
|
||||
{
|
||||
ContinueButton.Disabled = false;
|
||||
}
|
||||
|
||||
public void DisableInputs()
|
||||
{
|
||||
ContinueButton.Disabled = true;
|
||||
}
|
||||
|
||||
public void Activate()
|
||||
{
|
||||
}
|
||||
|
||||
public void Configure(CoreLoop coreLoop, Tween tween)
|
||||
{
|
||||
if (StatsLabel == null || tween == null)
|
||||
return;
|
||||
|
||||
DisableInputs();
|
||||
|
||||
BuildStats(coreLoop);
|
||||
|
||||
StatsLabel.Modulate = new(1f, 1f, 1f, 0f);
|
||||
ContinueButton.Modulate = new(1f, 1f, 1f, 0f);
|
||||
|
||||
tween.TweenProperty(StatsLabel, "modulate:a", 1.0f, 0.6f).SetTrans(Tween.TransitionType.Cubic).SetEase(Tween.EaseType.Out);
|
||||
tween.TweenInterval(0.2f);
|
||||
tween.TweenProperty(ContinueButton, "modulate:a", 1.0f, 0.4f).SetTrans(Tween.TransitionType.Cubic).SetEase(Tween.EaseType.Out);
|
||||
tween.TweenCallback(Callable.From(EnableInputs));
|
||||
}
|
||||
|
||||
public bool HandleResult(Result result, Tween tween)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
private void BuildStats(CoreLoop coreLoop)
|
||||
{
|
||||
var sheds = coreLoop.Board.Cells.Select(c => c.Poi as Shed).Where(s => s != null).ToArray();
|
||||
m_Victory = sheds.Sum(s => s.Remaining) == 0;
|
||||
ContinueButton.Text = m_Victory ? "New assignment" : "Continue";
|
||||
|
||||
m_Builder.Clear();
|
||||
|
||||
AppendTitle("Execution report", GetAssignmentSubtitle(coreLoop));
|
||||
|
||||
AppendSectionHeader("Programs");
|
||||
AppendKeyValue("Programs executed", coreLoop.ProgramsExecuted.ToString());
|
||||
AppendKeyValue("Programs remaining", coreLoop.ProgramCount.ToString());
|
||||
AppendKeyValue("Instructions used", coreLoop.InstructionsUsed.ToString());
|
||||
AppendKeyValue("Instructions remaining", coreLoop.PatchDeck.Count.ToString());
|
||||
|
||||
AppendSectionHeader("Stats");
|
||||
var deliveries = coreLoop.Board.Cells.Select(c => c.Poi as Shed).Where(s => s != null).Sum(s => s!.Remaining);
|
||||
var deliveryColor = deliveries == 0 ? ColorGood : ColorBad;
|
||||
AppendKeyValue("Deliveries pending", WrapColor(deliveries.ToString(), deliveryColor));
|
||||
var patchColor = coreLoop.PatchDeck.Count == 0 ? ColorBad : ColorGood;
|
||||
AppendKeyValue("Cards not drawn", WrapColor(coreLoop.PatchDeck.Count.ToString(), patchColor));
|
||||
var energyLeft = coreLoop.Currency.Energy - Balancing.Instance.EndOfProgramEnergyReplenish;
|
||||
var energyLeftColor = energyLeft == 0 ? ColorBad : ColorGood;
|
||||
AppendKeyValue("Remaining energy", WrapColor(energyLeft.ToString(), energyLeftColor));
|
||||
|
||||
AppendSectionHeader("Movement");
|
||||
AppendKeyValue("Path length", coreLoop.PathLength.ToString());
|
||||
AppendKeyValue("Tiles visited", coreLoop.CellsVisited.Count.ToString());
|
||||
|
||||
AppendSectionHeader("Efficiency");
|
||||
|
||||
var energyColor = coreLoop.EnergyWasted == 0 ? ColorGood : ColorBad;
|
||||
AppendKeyValue("Energy wasted", WrapColor(coreLoop.EnergyWasted.ToString(), energyColor));
|
||||
|
||||
var overspillText = coreLoop.Overspill > 0 ? WrapColor(coreLoop.Overspill.ToString(), ColorBad) : WrapColor(coreLoop.Overspill.ToString(), ColorGood);
|
||||
AppendKeyValue("Overspill", overspillText);
|
||||
|
||||
// Assignment / deliveries
|
||||
AppendSectionHeader("Assignment");
|
||||
AppendKeyValue("Total delivery", coreLoop.Currency.Delivery.ToString());
|
||||
AppendKeyValue("Glitches deferred", coreLoop.DeferGlitchCount.ToString());
|
||||
AppendKeyValue("Shop rerolls", coreLoop.RerollCount.ToString());
|
||||
|
||||
StatsLabel.Text = m_Builder.ToString();
|
||||
StatsLabel.ScrollToLine(0);
|
||||
}
|
||||
|
||||
private string GetAssignmentSubtitle(CoreLoop coreLoop)
|
||||
{
|
||||
if (m_Victory)
|
||||
return WrapColor("Victory! All goods delivered", ColorGood);
|
||||
|
||||
if (coreLoop.Currency.Delivery <= 0)
|
||||
return WrapColor("No deliveries completed", ColorBad);
|
||||
|
||||
if (coreLoop.EnergyWasted == 0 && coreLoop.Overspill == 0)
|
||||
return WrapColor("Perfectly efficient run", ColorGood);
|
||||
|
||||
if (coreLoop.EnergyWasted <= 3 && coreLoop.Overspill <= 1)
|
||||
return WrapColor("Very efficient run", ColorGood);
|
||||
|
||||
return WrapColor("Lots of room for optimization", ColorSubtitle);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Small helpers, mirroring Tooltip.cs style
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
private static string WrapColor(string text, string colorHex)
|
||||
{
|
||||
return $"[color={colorHex}]{text}[/color]";
|
||||
}
|
||||
|
||||
private void AppendTitle(string title, string subtitle = null)
|
||||
{
|
||||
m_Builder.Append(WrapColor($"[b]{title}[/b]", ColorTitle));
|
||||
|
||||
if (!string.IsNullOrEmpty(subtitle))
|
||||
{
|
||||
m_Builder.Append('\n');
|
||||
m_Builder.Append(subtitle);
|
||||
}
|
||||
|
||||
m_Builder.Append('\n');
|
||||
}
|
||||
|
||||
private void AppendSectionHeader(string title)
|
||||
{
|
||||
m_Builder.Append('\n');
|
||||
m_Builder.Append(WrapColor($"[b]{title}[/b]", ColorHeader));
|
||||
m_Builder.Append('\n');
|
||||
}
|
||||
|
||||
private void AppendKeyValue(string key, string value)
|
||||
{
|
||||
m_Builder.Append(WrapColor($"{key}: ", ColorLabel)).Append(WrapColor(value, ColorText)).Append('\n');
|
||||
}
|
||||
|
||||
private const string ColorTitle = "#ffd65c";
|
||||
private const string ColorSubtitle = "#aaaaaa";
|
||||
private const string ColorHeader = "#a5d6ff";
|
||||
private const string ColorLabel = "#cccccc";
|
||||
private const string ColorText = "#ffffff";
|
||||
private const string ColorGood = "#a5d6a7";
|
||||
private const string ColorBad = "#ef9a9a";
|
||||
private const string ColorMuted = "#9e9e9e";
|
||||
|
||||
private readonly StringBuilder m_Builder = new();
|
||||
|
||||
[Export]
|
||||
public float GamepadScrollSpeed = 800f;
|
||||
|
||||
[ExportGroup("Buttons")] [Export]
|
||||
private Button ContinueButton;
|
||||
|
||||
[ExportGroup("Stats")] [Export]
|
||||
private RichTextLabel StatsLabel;
|
||||
|
||||
private bool m_Victory;
|
||||
}
|
||||
1
DonkeysAndDroids.Godot/VictoryScreen.cs.uid
Normal file
1
DonkeysAndDroids.Godot/VictoryScreen.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bdkn652vxxxjc
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
BIN
DonkeysAndDroids.Godot/at01.ttf
Normal file
BIN
DonkeysAndDroids.Godot/at01.ttf
Normal file
Binary file not shown.
36
DonkeysAndDroids.Godot/at01.ttf.import
Normal file
36
DonkeysAndDroids.Godot/at01.ttf.import
Normal file
@@ -0,0 +1,36 @@
|
||||
[remap]
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://cv3yontw8gqm6"
|
||||
path="res://.godot/imported/at01.ttf-69bf5315ce67bdb110359b105000e1d3.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://at01.ttf"
|
||||
dest_files=["res://.godot/imported/at01.ttf-69bf5315ce67bdb110359b105000e1d3.fontdata"]
|
||||
|
||||
[params]
|
||||
|
||||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
allow_system_fallback=true
|
||||
force_autohinter=false
|
||||
modulate_color_glyphs=false
|
||||
hinting=1
|
||||
subpixel_positioning=4
|
||||
keep_rounding_remainders=true
|
||||
oversampling=0.0
|
||||
Fallbacks=null
|
||||
fallbacks=[]
|
||||
Compress=null
|
||||
compress=true
|
||||
preload=[]
|
||||
language_support={}
|
||||
script_support={}
|
||||
opentype_features={}
|
||||
BIN
DonkeysAndDroids.Godot/audio/avatar.mp3
Normal file
BIN
DonkeysAndDroids.Godot/audio/avatar.mp3
Normal file
Binary file not shown.
19
DonkeysAndDroids.Godot/audio/avatar.mp3.import
Normal file
19
DonkeysAndDroids.Godot/audio/avatar.mp3.import
Normal file
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://nw8iofm1vaj8"
|
||||
path="res://.godot/imported/avatar.mp3-82fc7beafe069bc85893f17912be83ed.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/avatar.mp3"
|
||||
dest_files=["res://.godot/imported/avatar.mp3-82fc7beafe069bc85893f17912be83ed.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
BIN
DonkeysAndDroids.Godot/audio/blocked.mp3
Normal file
BIN
DonkeysAndDroids.Godot/audio/blocked.mp3
Normal file
Binary file not shown.
19
DonkeysAndDroids.Godot/audio/blocked.mp3.import
Normal file
19
DonkeysAndDroids.Godot/audio/blocked.mp3.import
Normal file
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://fvia4q78bwmj"
|
||||
path="res://.godot/imported/blocked.mp3-4922bb74916eb5379179e0f2b2fa6b2f.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/blocked.mp3"
|
||||
dest_files=["res://.godot/imported/blocked.mp3-4922bb74916eb5379179e0f2b2fa6b2f.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
BIN
DonkeysAndDroids.Godot/audio/button.mp3
Normal file
BIN
DonkeysAndDroids.Godot/audio/button.mp3
Normal file
Binary file not shown.
19
DonkeysAndDroids.Godot/audio/button.mp3.import
Normal file
19
DonkeysAndDroids.Godot/audio/button.mp3.import
Normal file
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://bl82gfd0kls8q"
|
||||
path="res://.godot/imported/button.mp3-60d245a4565b2782b55f77cfd0a0e536.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/button.mp3"
|
||||
dest_files=["res://.godot/imported/button.mp3-60d245a4565b2782b55f77cfd0a0e536.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
BIN
DonkeysAndDroids.Godot/audio/card.mp3
Normal file
BIN
DonkeysAndDroids.Godot/audio/card.mp3
Normal file
Binary file not shown.
19
DonkeysAndDroids.Godot/audio/card.mp3.import
Normal file
19
DonkeysAndDroids.Godot/audio/card.mp3.import
Normal file
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://b7508mnlvtm26"
|
||||
path="res://.godot/imported/card.mp3-ee897fd8303b9bb06cb94b3ffd71f454.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/card.mp3"
|
||||
dest_files=["res://.godot/imported/card.mp3-ee897fd8303b9bb06cb94b3ffd71f454.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
BIN
DonkeysAndDroids.Godot/audio/carry.mp3
Normal file
BIN
DonkeysAndDroids.Godot/audio/carry.mp3
Normal file
Binary file not shown.
19
DonkeysAndDroids.Godot/audio/carry.mp3.import
Normal file
19
DonkeysAndDroids.Godot/audio/carry.mp3.import
Normal file
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://taabst8vqf07"
|
||||
path="res://.godot/imported/carry.mp3-86982d8575e24723d16442839f60bd1a.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/carry.mp3"
|
||||
dest_files=["res://.godot/imported/carry.mp3-86982d8575e24723d16442839f60bd1a.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
BIN
DonkeysAndDroids.Godot/audio/coreloop.mp3
Normal file
BIN
DonkeysAndDroids.Godot/audio/coreloop.mp3
Normal file
Binary file not shown.
19
DonkeysAndDroids.Godot/audio/coreloop.mp3.import
Normal file
19
DonkeysAndDroids.Godot/audio/coreloop.mp3.import
Normal file
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://5w305h2two2l"
|
||||
path="res://.godot/imported/coreloop.mp3-3141325ff71f7640aa9a3b111c62b467.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/coreloop.mp3"
|
||||
dest_files=["res://.godot/imported/coreloop.mp3-3141325ff71f7640aa9a3b111c62b467.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
BIN
DonkeysAndDroids.Godot/audio/corrupt.mp3
Normal file
BIN
DonkeysAndDroids.Godot/audio/corrupt.mp3
Normal file
Binary file not shown.
19
DonkeysAndDroids.Godot/audio/corrupt.mp3.import
Normal file
19
DonkeysAndDroids.Godot/audio/corrupt.mp3.import
Normal file
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://dnc8ksbbt8rxg"
|
||||
path="res://.godot/imported/corrupt.mp3-99ab57b242f1168d4e91c8d185f8f82e.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/corrupt.mp3"
|
||||
dest_files=["res://.godot/imported/corrupt.mp3-99ab57b242f1168d4e91c8d185f8f82e.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
BIN
DonkeysAndDroids.Godot/audio/crate.mp3
Normal file
BIN
DonkeysAndDroids.Godot/audio/crate.mp3
Normal file
Binary file not shown.
19
DonkeysAndDroids.Godot/audio/crate.mp3.import
Normal file
19
DonkeysAndDroids.Godot/audio/crate.mp3.import
Normal file
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://2g4nuqy03if5"
|
||||
path="res://.godot/imported/crate.mp3-a58fe30d4bf1a98178c288fd12e1febd.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/crate.mp3"
|
||||
dest_files=["res://.godot/imported/crate.mp3-a58fe30d4bf1a98178c288fd12e1febd.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
BIN
DonkeysAndDroids.Godot/audio/delivery.mp3
Normal file
BIN
DonkeysAndDroids.Godot/audio/delivery.mp3
Normal file
Binary file not shown.
19
DonkeysAndDroids.Godot/audio/delivery.mp3.import
Normal file
19
DonkeysAndDroids.Godot/audio/delivery.mp3.import
Normal file
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://cr7ajk4lac6e0"
|
||||
path="res://.godot/imported/delivery.mp3-43c96710f31d1291fb88afa80fbaecda.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/delivery.mp3"
|
||||
dest_files=["res://.godot/imported/delivery.mp3-43c96710f31d1291fb88afa80fbaecda.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
BIN
DonkeysAndDroids.Godot/audio/donkey.mp3
Normal file
BIN
DonkeysAndDroids.Godot/audio/donkey.mp3
Normal file
Binary file not shown.
19
DonkeysAndDroids.Godot/audio/donkey.mp3.import
Normal file
19
DonkeysAndDroids.Godot/audio/donkey.mp3.import
Normal file
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://bg8eu2auayu2k"
|
||||
path="res://.godot/imported/donkey.mp3-fd68a08fc18189af6fc8435c1c6b79a4.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/donkey.mp3"
|
||||
dest_files=["res://.godot/imported/donkey.mp3-fd68a08fc18189af6fc8435c1c6b79a4.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
BIN
DonkeysAndDroids.Godot/audio/dry.mp3
Normal file
BIN
DonkeysAndDroids.Godot/audio/dry.mp3
Normal file
Binary file not shown.
19
DonkeysAndDroids.Godot/audio/dry.mp3.import
Normal file
19
DonkeysAndDroids.Godot/audio/dry.mp3.import
Normal file
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://b1licxrv5a5kx"
|
||||
path="res://.godot/imported/dry.mp3-5d8bfb5ebda62c34969013d3be6bae84.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/dry.mp3"
|
||||
dest_files=["res://.godot/imported/dry.mp3-5d8bfb5ebda62c34969013d3be6bae84.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
BIN
DonkeysAndDroids.Godot/audio/effective.mp3
Normal file
BIN
DonkeysAndDroids.Godot/audio/effective.mp3
Normal file
Binary file not shown.
19
DonkeysAndDroids.Godot/audio/effective.mp3.import
Normal file
19
DonkeysAndDroids.Godot/audio/effective.mp3.import
Normal file
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://7toyoaj4xv5t"
|
||||
path="res://.godot/imported/effective.mp3-d738386a1cb0076d1c87831691994f50.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/effective.mp3"
|
||||
dest_files=["res://.godot/imported/effective.mp3-d738386a1cb0076d1c87831691994f50.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
BIN
DonkeysAndDroids.Godot/audio/efficient.mp3
Normal file
BIN
DonkeysAndDroids.Godot/audio/efficient.mp3
Normal file
Binary file not shown.
19
DonkeysAndDroids.Godot/audio/efficient.mp3.import
Normal file
19
DonkeysAndDroids.Godot/audio/efficient.mp3.import
Normal file
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://iccli4sklikc"
|
||||
path="res://.godot/imported/efficient.mp3-429684dcebfdf9b4498d780235f8bd3c.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/efficient.mp3"
|
||||
dest_files=["res://.godot/imported/efficient.mp3-429684dcebfdf9b4498d780235f8bd3c.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
BIN
DonkeysAndDroids.Godot/audio/energy.mp3
Normal file
BIN
DonkeysAndDroids.Godot/audio/energy.mp3
Normal file
Binary file not shown.
19
DonkeysAndDroids.Godot/audio/energy.mp3.import
Normal file
19
DonkeysAndDroids.Godot/audio/energy.mp3.import
Normal file
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://prjkw6o6m53s"
|
||||
path="res://.godot/imported/energy.mp3-f443ba3bb7ca91c2cfd01561cdefc91d.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/energy.mp3"
|
||||
dest_files=["res://.godot/imported/energy.mp3-f443ba3bb7ca91c2cfd01561cdefc91d.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
BIN
DonkeysAndDroids.Godot/audio/fertile.mp3
Normal file
BIN
DonkeysAndDroids.Godot/audio/fertile.mp3
Normal file
Binary file not shown.
19
DonkeysAndDroids.Godot/audio/fertile.mp3.import
Normal file
19
DonkeysAndDroids.Godot/audio/fertile.mp3.import
Normal file
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://ufefxts5f0op"
|
||||
path="res://.godot/imported/fertile.mp3-604529cfe31a2de59cda44e683bb80bf.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/fertile.mp3"
|
||||
dest_files=["res://.godot/imported/fertile.mp3-604529cfe31a2de59cda44e683bb80bf.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
BIN
DonkeysAndDroids.Godot/audio/grass.mp3
Normal file
BIN
DonkeysAndDroids.Godot/audio/grass.mp3
Normal file
Binary file not shown.
19
DonkeysAndDroids.Godot/audio/grass.mp3.import
Normal file
19
DonkeysAndDroids.Godot/audio/grass.mp3.import
Normal file
@@ -0,0 +1,19 @@
|
||||
[remap]
|
||||
|
||||
importer="mp3"
|
||||
type="AudioStreamMP3"
|
||||
uid="uid://xc27unw7jisd"
|
||||
path="res://.godot/imported/grass.mp3-31ad6b07e71c52498d71381079ce8c44.mp3str"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/grass.mp3"
|
||||
dest_files=["res://.godot/imported/grass.mp3-31ad6b07e71c52498d71381079ce8c44.mp3str"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user