Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Build Simitone

on:
workflow_dispatch:
inputs:
configuration:
description: 'Build configuration'
required: false
default: 'Release'
type: choice
options:
- Release
- Debug

jobs:
build:
runs-on: windows-latest

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive

- name: Setup .NET 9
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'

- name: Run Protobuild
shell: pwsh
run: |
cd FreeSO/Other/libs/FSOMonoGame/
./protobuild.exe --generate
continue-on-error: true

- name: Restore Simitone dependencies
run: dotnet restore Client/Simitone/Simitone.sln

- name: Restore FreeSO dependencies
run: dotnet restore FreeSO/TSOClient/FreeSO.sln
continue-on-error: true

- name: Restore Roslyn dependencies
shell: pwsh
run: |
cd FreeSO/TSOClient/FSO.SimAntics.JIT.Roslyn/
dotnet restore
continue-on-error: true

- name: Build
run: dotnet build Client/Simitone/Simitone.sln -c ${{ inputs.configuration || 'Release' }} --no-restore

- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: SimitoneWindows-${{ inputs.configuration || 'Release' }}
path: Client/Simitone/Simitone.Windows/bin/${{ inputs.configuration || 'Release' }}/net9.0-windows/
if-no-files-found: error
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions Client/Simitone/Simitone.Client/Simitone.Client.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
</PropertyGroup>

<ItemGroup>
<Content Include="Content\Cursors\eyedropper.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\3D\TEX_0.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
Expand Down Expand Up @@ -89,6 +92,9 @@
<Content Include="Content\uigraphics\desktop\d_live_buy.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\uigraphics\desktop\d_live_eyedropper.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Content\uigraphics\desktop\d_live_floordown.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
Expand Down
2 changes: 2 additions & 0 deletions Client/Simitone/Simitone.Client/SimitoneGame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using MSDFData;
using FSO.LotView.Model;
using Simitone.Client.UI.Panels;
using Simitone.Client.Utils;

namespace Simitone.Client
{
Expand Down Expand Up @@ -148,6 +149,7 @@ protected override void Initialize()
{
CurLoader.BmpLoaderFunc = ImageLoader.BaseFunction;
GameFacade.Cursor.Init(GlobalSettings.Default.TS1HybridPath, true);
SimitoneCursors.Init(GraphicsDevice);
}

/** Init any computed values **/
Expand Down
25 changes: 25 additions & 0 deletions Client/Simitone/Simitone.Client/UI/Controls/UITouchScroll.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,31 @@ public void SetScroll(float value)
Scroll = value;
}

public void ScrollToItem(int itemIndex)
{
// Center the item in the visible area
var targetScroll = itemIndex * ItemWidth - (GetPAxis(Size) / 2) + (ItemWidth / 2);
// Clamp to valid scroll range
var length = LengthProvider();
Scroll = Math.Max(-Margin, Math.Min(length * ItemWidth - GetPAxis(Size) + Margin, targetScroll));
ScrollVelocity = 0; // Stop any ongoing momentum
}

/// <summary>
/// Selects an item by its index programmatically, triggering the visual highlight.
/// </summary>
public void SelectItem(int itemIndex)
{
if (itemIndex < 0 || itemIndex >= LengthProvider()) return;
var rItem = GetOrPrepare(itemIndex);
if (rItem != null)
{
LastSelected?.Deselected();
rItem.Selected();
LastSelected = rItem;
}
}

public override void Draw(UISpriteBatch batch)
{
if (!Visible) return;
Expand Down
75 changes: 59 additions & 16 deletions Client/Simitone/Simitone.Client/UI/Panels/Desktop/UIDesktopUCP.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class UIDesktopUCP : UICachedContainer
{
public UIImage Background;
public UIImage FriendIcon;
public UIButton EyedropperButton;

public UIButton LiveButton;
public UIButton BuyButton;
Expand Down Expand Up @@ -90,32 +91,45 @@ public UIDesktopUCP(TS1GameScreen screen)
FriendIcon = new UIImage(ui.Get("d_live_friend.png").Get(gd)) { Position = new Vector2(156, 186) };
Add(FriendIcon);

Add(LiveButton = new UIButton(ui.Get("d_live_live.png").Get(gd)) { Position = new Vector2(15, 2) });
Add(BuyButton = new UIButton(ui.Get("d_live_buy.png").Get(gd)) { Position = new Vector2(107, 27) });
Add(BuildButton = new UIButton(ui.Get("d_live_build.png").Get(gd)) { Position = new Vector2(179, 80) });
Add(OptionsButton = new UIButton(ui.Get("d_live_opt.png").Get(gd)) { Position = new Vector2(242, 165) });
// Eyedropper button - visible in Buy and Build modes
Add(EyedropperButton = new UIStencilButton(ui.Get("d_live_eyedropper.png").Get(gd))
{
Position = new Vector2(155, 164),
Shadow = true,
ShadowParam = sDir,
Tooltip = "Eyedropper Tool (E)"
});
EyedropperButton.OnButtonClick += ToggleEyedropper;
EyedropperButton.Visible = false; // Hidden by default (LIVE mode)

Add(FloorUpButton = new UIStencilButton(ui.Get("d_live_floorup.png").Get(gd)) { Position = new Vector2(16, 150), Shadow = true, ShadowParam = sDir });
Add(FloorDownButton = new UIStencilButton(ui.Get("d_live_floordown.png").Get(gd)) { Position = new Vector2(16, 192), Shadow = true, ShadowParam = sDir });
Add(LiveButton = new UIButton(ui.Get("d_live_live.png").Get(gd)) { Position = new Vector2(15, 2), Tooltip = "Live Mode" });
Add(BuyButton = new UIButton(ui.Get("d_live_buy.png").Get(gd)) { Position = new Vector2(107, 27), Tooltip = "Buy Mode" });
Add(BuildButton = new UIButton(ui.Get("d_live_build.png").Get(gd)) { Position = new Vector2(179, 80), Tooltip = "Build Mode" });
Add(OptionsButton = new UIButton(ui.Get("d_live_opt.png").Get(gd)) { Position = new Vector2(242, 165), Tooltip = "Options" });

Add(RoofButton = new UIStencilButton(ui.Get("d_live_w1.png").Get(gd)) { Position = new Vector2(15, 111), Shadow = true, ShadowParam = sDir });
Add(WallsUpButton = new UIStencilButton(ui.Get("d_live_w2.png").Get(gd)) { Position = new Vector2(50, 107), Shadow = true, ShadowParam = sDir });
Add(WallsCutButton = new UIStencilButton(ui.Get("d_live_w3.png").Get(gd)) { Position = new Vector2(86, 112), Shadow = true, ShadowParam = sDir });
Add(WallsDownButton = new UIStencilButton(ui.Get("d_live_w4.png").Get(gd)) { Position = new Vector2(117, 122), Shadow = true, ShadowParam = sDir });
Add(FloorUpButton = new UIStencilButton(ui.Get("d_live_floorup.png").Get(gd)) { Position = new Vector2(16, 150), Shadow = true, ShadowParam = sDir, Tooltip = "Floor Up" });
Add(FloorDownButton = new UIStencilButton(ui.Get("d_live_floordown.png").Get(gd)) { Position = new Vector2(16, 192), Shadow = true, ShadowParam = sDir, Tooltip = "Floor Down" });

Add(ZoomInButton = new UIStencilButton(ui.Get("d_live_zoomp.png").Get(gd)) { Position = new Vector2(87, 154) });
Add(ZoomOutButton = new UIStencilButton(ui.Get("d_live_zoomm.png").Get(gd)) { Position = new Vector2(87, 196) });
Add(RotateCWButton = new UIStencilButton(ui.Get("d_live_rotcw.png").Get(gd)) { Position = new Vector2(62, 175) });
Add(RotateCCWButton = new UIStencilButton(ui.Get("d_live_rotccw.png").Get(gd)) { Position = new Vector2(114, 175) });
Add(RoofButton = new UIStencilButton(ui.Get("d_live_w1.png").Get(gd)) { Position = new Vector2(15, 111), Shadow = true, ShadowParam = sDir, Tooltip = "Roof" });
Add(WallsUpButton = new UIStencilButton(ui.Get("d_live_w2.png").Get(gd)) { Position = new Vector2(50, 107), Shadow = true, ShadowParam = sDir, Tooltip = "Walls Up" });
Add(WallsCutButton = new UIStencilButton(ui.Get("d_live_w3.png").Get(gd)) { Position = new Vector2(86, 112), Shadow = true, ShadowParam = sDir, Tooltip = "Walls Cutaway" });
Add(WallsDownButton = new UIStencilButton(ui.Get("d_live_w4.png").Get(gd)) { Position = new Vector2(117, 122), Shadow = true, ShadowParam = sDir, Tooltip = "Walls Down" });

Add(ZoomInButton = new UIStencilButton(ui.Get("d_live_zoomp.png").Get(gd)) { Position = new Vector2(87, 154), Tooltip = "Zoom In" });
Add(ZoomOutButton = new UIStencilButton(ui.Get("d_live_zoomm.png").Get(gd)) { Position = new Vector2(87, 196), Tooltip = "Zoom Out" });
Add(RotateCWButton = new UIStencilButton(ui.Get("d_live_rotcw.png").Get(gd)) { Position = new Vector2(62, 175), Tooltip = "Rotate Clockwise" });
Add(RotateCCWButton = new UIStencilButton(ui.Get("d_live_rotccw.png").Get(gd)) { Position = new Vector2(114, 175), Tooltip = "Rotate Counter-Clockwise" });

SpeedButtons = new UIButton[4];
var speedTooltips = new string[] { "Normal Speed", "Fast", "Ultra Fast", "Pause" };
for (int i=0; i<4; i++)
{
Add(SpeedButtons[i] = new UIStencilButton(ui.Get($"d_live_speed{i+1}.png").Get(gd))
{
Position = new Vector2(158 + 30 * i, 246),
Shadow = true,
ShadowParam = sDir
ShadowParam = sDir,
Tooltip = speedTooltips[i]
});
var speed = i + 1;
SpeedButtons[i].OnButtonClick += (btn) =>
Expand Down Expand Up @@ -322,18 +336,30 @@ public override void Update(UpdateState state)

if (LastZoom != Game.ZoomLevel) UpdateZoomButton();

// Sync eyedropper button state with ObjectHolder (for when it's auto-disabled after pick)
if (EyedropperButton.Visible && Game.LotControl?.ObjectHolder != null)
{
EyedropperButton.Selected = Game.LotControl.ObjectHolder.EyedropperMode;
}

base.Update(state);

//KEY SHORTCUTS
var keys = state.NewKeys;
var nofocus = true;
var nofocus = state.InputManager.GetFocus() == null; // No text input (like the cheat menu!) has focus
if (Game.InLot)
{
if (keys.Contains(Keys.F1) && !LiveButton.Disabled) OnModeClick?.Invoke(UIMainPanelMode.LIVE);
if (keys.Contains(Keys.F2) && !BuyButton.Disabled) OnModeClick?.Invoke(UIMainPanelMode.BUY);
if (keys.Contains(Keys.F3) && !BuildButton.Disabled) OnModeClick?.Invoke(UIMainPanelMode.BUILD);
if (keys.Contains(Keys.F4)) OnModeClick?.Invoke(UIMainPanelMode.OPTIONS); // Options Panel

// E key for eyedropper (only in Buy/Build mode and when no text input is focused)
if (nofocus && keys.Contains(Keys.E) && EyedropperButton.Visible)
{
ToggleEyedropper(EyedropperButton);
}

if (nofocus)
{
var world = Game.vm.Context.World;
Expand Down Expand Up @@ -390,12 +416,29 @@ public void UpdateZoomButton()
LastZoom = Game.ZoomLevel;
}

private void ToggleEyedropper(UIElement btn)
{
var holder = Game.LotControl.ObjectHolder;
holder.EyedropperMode = !holder.EyedropperMode;
EyedropperButton.Selected = holder.EyedropperMode;
}

public void SetMode(UIMainPanelMode mode)
{
LiveButton.Selected = mode == UIMainPanelMode.LIVE;
BuyButton.Selected = mode == UIMainPanelMode.BUY;
BuildButton.Selected = mode == UIMainPanelMode.BUILD;
OptionsButton.Selected = mode == UIMainPanelMode.OPTIONS;

// Show eyedropper in BUY and BUILD modes
EyedropperButton.Visible = (mode == UIMainPanelMode.BUY || mode == UIMainPanelMode.BUILD);
// Reset selection when changing modes
if (!EyedropperButton.Visible)
{
EyedropperButton.Selected = false;
if (Game.LotControl?.ObjectHolder != null)
Game.LotControl.ObjectHolder.EyedropperMode = false;
}
}

public void DisplayChange(int change)
Expand Down
Loading