Rewrote to be based on Avalonia UI

This commit is contained in:
Tony Bark 2026-05-15 00:10:49 -04:00
parent fb013f2b15
commit cf5866ef3f
55 changed files with 417 additions and 790 deletions

View file

@ -14,7 +14,6 @@ indent_style = space
# C# files # C# files
[*.cs] [*.cs]
# Nullable reference types # Nullable reference types
csharp_nullable_reference_types = enable
# Naming conventions # Naming conventions
dotnet_naming_rule.async_methods_end_in_async.severity = suggestion dotnet_naming_rule.async_methods_end_in_async.severity = suggestion
@ -27,6 +26,23 @@ dotnet_naming_symbols.async_methods.required_modifiers = async
dotnet_naming_style.end_in_async.required_suffix = Async dotnet_naming_style.end_in_async.required_suffix = Async
dotnet_naming_style.end_in_async.capitalization = pascal_case dotnet_naming_style.end_in_async.capitalization = pascal_case
dotnet_naming_symbols.locals.applicable_kinds = local, parameter
dotnet_naming_symbols.const_fields.applicable_kinds = field
dotnet_naming_symbols.const_fields.required_modifiers = const
dotnet_naming_style.camel_case.capitalization = camel_case
dotnet_naming_style.all_caps.capitalization = all_upper
dotnet_naming_rule.locals_must_be_camel.severity = warning
dotnet_naming_rule.locals_must_be_camel.symbols = locals
dotnet_naming_rule.locals_must_be_camel.style = camel_case
dotnet_naming_rule.consts_must_be_all_caps.severity = warning
dotnet_naming_rule.consts_must_be_all_caps.symbols = const_fields
dotnet_naming_rule.consts_must_be_all_caps.style = all_caps
# Formatting # Formatting
csharp_new_line_before_open_brace = all csharp_new_line_before_open_brace = all
csharp_indent_case_contents = true csharp_indent_case_contents = true

79
.gitignore vendored
View file

@ -1,37 +1,18 @@
# Created by https://www.toptal.com/developers/gitignore/api/godot,rider,dotnetcore,visualstudio,visualstudiocode,obsidian ### VisualStudioCode template
# Edit at https://www.toptal.com/developers/gitignore?templates=godot,rider,dotnetcore,visualstudio,visualstudiocode,obsidian .vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
### DotnetCore ### # Local History for Visual Studio Code
# .NET Core build folders .history/
bin/
obj/
# Common node modules locations # Built Visual Studio Code Extensions
/node_modules *.vsix
/wwwroot/node_modules
### Godot ### ### JetBrains template
# Godot 4+ specific ignores
.godot/
# Godot-specific ignores
.import/
export.cfg
export_presets.cfg
# Imported translations (automatically generated from CSV files)
*.translation
# Mono-specific ignores
.mono/
data_*/
mono_crash.*.json
### Obsidian ###
# config dir
.obsidian/
### Rider ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
@ -110,26 +91,7 @@ fabric.properties
# Android studio 3.1+ serialized cache file # Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser .idea/caches/build_file_checksums.ser
### VisualStudioCode ### ### VisualStudio template
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
### VisualStudio ###
## Ignore Visual Studio temporary files, build results, and ## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons. ## files generated by popular Visual Studio add-ons.
## ##
@ -319,7 +281,7 @@ publish/
*.pubxml *.pubxml
*.publishproj *.publishproj
# Microslop Azure Web App publish settings. Comment the next line if you want to # Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained # checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted # in these scripts will be unencrypted
PublishScripts/ PublishScripts/
@ -338,11 +300,11 @@ PublishScripts/
*.nuget.props *.nuget.props
*.nuget.targets *.nuget.targets
# Microslop Azure Build Output # Microsoft Azure Build Output
csx/ csx/
*.build.csdef *.build.csdef
# Microslop Azure Emulator # Microsoft Azure Emulator
ecf/ ecf/
rcf/ rcf/
@ -407,7 +369,7 @@ ServiceFabricBackup/
*- [Bb]ackup ([0-9]).rdl *- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl *- [Bb]ackup ([0-9][0-9]).rdl
# Microslop Fakes # Microsoft Fakes
FakesAssemblies/ FakesAssemblies/
# GhostDoc plugin setting file # GhostDoc plugin setting file
@ -521,10 +483,3 @@ FodyWeavers.xsd
# JetBrains Rider # JetBrains Rider
*.sln.iml *.sln.iml
### VisualStudio Patch ###
# Additional files built by Visual Studio
# End of https://www.toptal.com/developers/gitignore/api/godot,rider,dotnetcore,visualstudio,visualstudiocode,obsidian
**/*.toml
*.pck

View file

@ -1,15 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/projectSettingsUpdater.xml
/contentModel.xml
/modules.xml
/.idea.CyberBits.iml
# Editor-based HTTP Client requests
/httpRequests/
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

10
.idea/.idea.CyberBits/.idea/avalonia.xml generated Normal file
View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AvaloniaProject">
<option name="projectPerEditor">
<map>
<entry key="CyberBits/Views/MainWindow.axaml" value="CyberBits/CyberBits.csproj" />
</map>
</option>
</component>
</project>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RiderProjectSettingsUpdater">
<option name="singleClickDiffPreview" value="1" />
<option name="unhandledExceptionsIgnoreList" value="1" />
<option name="vcsConfiguration" value="3" />
</component>
</project>

View file

@ -2,5 +2,6 @@
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" /> <mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component> </component>
</project> </project>

View file

@ -1,6 +0,0 @@
namespace CyberBits.Common;
public record Addon(
[property: JsonPropertyName("cyberware")]
string[] Cyberware
);

View file

@ -1,12 +0,0 @@
namespace CyberBits.Common;
public record Bits(
[property: JsonPropertyName("image")]
string Image,
[property: JsonPropertyName("feat")]
string Feat,
[property: JsonPropertyName("base")]
string[] Base,
[property: JsonPropertyName("style")]
string[] Style
);

View file

@ -1,22 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<RollForward>LatestMajor</RollForward>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<Using Remove="System.IO" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="GitInfo" Version="3.6.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="GodotSharp" Version="4.6.2" />
</ItemGroup>
</Project>

View file

@ -1,31 +0,0 @@
namespace CyberBits.Common;
public static class FileFetcher
{
public static string LoadTextFile(string filename, bool userDir = false)
{
var location = "res";
if (userDir)
location = "user";
using var file = FileAccess.Open($"{location}://{filename}", FileAccess.ModeFlags.Read);
var contents = file.GetAsText();
file.Close();
return contents;
}
public static ConfigFile LoadConfig(string filename, bool userDir = false)
{
var config = new ConfigFile();
// Load data from a file.
var err = config.Load(LoadTextFile(filename, userDir));
if (err != Error.Ok)
sys.Environment.Exit(sys.Environment.ExitCode);
return config;
}
}

View file

@ -1,6 +0,0 @@
// System
global using sys = System;
global using System.Text.Json.Serialization;
// Godot
global using Godot;

View file

@ -1,8 +0,0 @@
namespace CyberBits.Common;
public struct ResourceFiles
{
public const string COCK_JSON = "resources/cock.json";
public const string PUSSY_JSON = "resources/pussy.json";
public const string ADDONS_JSON = "resources/addons.json";
}

View file

@ -1,10 +0,0 @@
<Project Sdk="Godot.NET.Sdk/4.6.2">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
<RollForward>LatestMajor</RollForward>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\CyberBits.Common\CyberBits.Common.csproj" />
</ItemGroup>
</Project>

View file

@ -1,22 +0,0 @@
[gd_scene format=3]
[node name="Main" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="CenterContainer" type="CenterContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Label" type="Label" parent="CenterContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 32
text = "Hello from 2dog!"

View file

@ -1,26 +0,0 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
custom_features="dotnet"
[application]
config/name="CyberBits"
run/main_scene="res://main.tscn"
config/features=PackedStringArray("4.6", "C#")
[dotnet]
project/assembly_name="CyberBits.Godot"
[display]
window/size/viewport_width=1280
window/size/viewport_height=720

View file

@ -1,52 +0,0 @@
using Godot;
using twodog.xunit;
namespace CyberBits.Tests;
[Collection("GodotHeadless")]
public class BasicTests(GodotHeadlessFixture godot)
{
[Fact]
public void LoadMainScene_Succeeds()
{
// Arrange
var scene = GD.Load<PackedScene>("res://main.tscn");
// Act
var instance = scene.Instantiate();
godot.Tree.Root.AddChild(instance);
// Assert
Assert.NotNull(instance);
Assert.NotNull(instance.GetParent());
}
[Fact]
public void PhysicsIteration_Succeeds()
{
// Arrange & Act
godot.Tree.Root.PhysicsInterpolationMode = Node.PhysicsInterpolationModeEnum.Off;
godot.GodotInstance.Iteration();
// Assert - if we get here without crashing, test passes
Assert.True(true);
}
[Fact]
public void CreateNode_AddsToTree()
{
// Arrange
var node = new Node();
node.Name = "TestNode";
// Act
godot.Tree.Root.AddChild(node);
// Assert
Assert.True(godot.Tree.Root.HasNode("TestNode"));
Assert.Equal("TestNode", (string)godot.Tree.Root.GetNode("TestNode").Name);
// Cleanup
node.QueueFree();
}
}

View file

@ -1,60 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<RollForward>LatestMajor</RollForward>
</PropertyGroup>
<!-- 2dog packages -->
<ItemGroup>
<PackageReference Include="2dog" Version="0.1.23-pre"/>
<PackageReference Include="2dog.xunit" Version="0.1.23-pre"/>
<PackageReference Include="GodotSharp" Version="4.6.2"/>
</ItemGroup>
<!-- Platform-specific native library packages -->
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('Windows'))">
<PackageReference Include="2dog.win-x64" Version="4.6.2.0"/>
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('Linux'))">
<PackageReference Include="2dog.linux-x64" Version="4.6.2.0"/>
</ItemGroup>
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('OSX'))">
<PackageReference Include="2dog.osx-arm64" Version="4.6.2.0"/>
</ItemGroup>
<!-- Test framework packages -->
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0"/>
<PackageReference Include="xunit" Version="2.9.2"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2"/>
</ItemGroup>
<ItemGroup>
<Using Include="Xunit"/>
</ItemGroup>
<ItemGroup>
<Content Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../CyberBits.Godot/CyberBits.Godot.csproj"/>
</ItemGroup>
<!-- Godot project location -->
<PropertyGroup>
<GodotProjectDir>../CyberBits.Godot</GodotProjectDir>
</PropertyGroup>
<!-- Remove duplicate Godot.SourceGenerators that come from GodotSharp package
(2dog package already embeds them) -->
<Target Name="RemoveDuplicateGodotAnalyzers" BeforeTargets="CoreCompile">
<ItemGroup>
<Analyzer Remove="@(Analyzer)" Condition="$([System.String]::Copy('%(Analyzer.Identity)').Contains('Godot.SourceGenerators'))" />
</ItemGroup>
</Target>
</Project>

View file

@ -1,7 +0,0 @@
using twodog.xunit;
using Xunit;
namespace CyberBits.Tests;
[CollectionDefinition("GodotHeadless", DisableParallelization = true)]
public class GodotHeadlessCollection : ICollectionFixture<GodotHeadlessFixture>;

View file

@ -1,4 +0,0 @@
{
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
"parallelizeTestCollections": false
}

View file

@ -1,12 +1,9 @@
<Solution> <Solution>
<Folder Name="/Solution Items/"> <Folder Name="/Solution Items/">
<File Path="GitInfo.txt" />
<File Path=".gitignore" /> <File Path=".gitignore" />
<File Path="README.md" /> <File Path="README.md" />
<File Path=".editorconfig" />
<File Path="GitInfo.txt" />
</Folder> </Folder>
<Project Path="CyberBits.Common/CyberBits.Common.csproj" /> <Project Path="CyberBits/CyberBits.csproj" />
<Project Path="CyberBits.Godot/CyberBits.Godot.csproj" />
<Project Path="CyberBits.Tests/CyberBits.Tests.csproj" />
<Project Path="CyberBits/CyberBits.csproj" />
<Project Path="CyberBitsOld/CyberBitsOld.csproj" />
</Solution> </Solution>

15
CyberBits/App.axaml Normal file
View file

@ -0,0 +1,15 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="CyberBits.App"
xmlns:local="using:CyberBits"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>

28
CyberBits/App.axaml.cs Normal file
View file

@ -0,0 +1,28 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using CyberBits.ViewModels;
using CyberBits.Views;
namespace CyberBits;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = new MainWindowViewModel(),
};
}
base.OnFrameworkInitializationCompleted();
}
}

View file

@ -1,6 +1,6 @@
namespace CyberBits.Common; namespace CyberBits;
public struct CBConsts public struct AppConsts
{ {
// Ignore VSCode if it complains about "ThisAssembly" not being found. // Ignore VSCode if it complains about "ThisAssembly" not being found.
public const string VERSION = public const string VERSION =

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

View file

Before

Width:  |  Height:  |  Size: 252 KiB

After

Width:  |  Height:  |  Size: 252 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 334 KiB

After

Width:  |  Height:  |  Size: 334 KiB

Before After
Before After

View file

@ -1,12 +1,17 @@
namespace Cyberbits; namespace CyberBits;
public record Bits( public record Bits(
[property: JsonPropertyName("image")] [property: JsonPropertyName("image")]
string Image, string Image,
[property: JsonPropertyName("feat")] [property: JsonPropertyName("feat")]
string Feat, string Feat,
[property: JsonPropertyName("base")] [property: JsonPropertyName("model")]
string[] Base, string[] Model,
[property: JsonPropertyName("style")] [property: JsonPropertyName("style")]
string[] Style string[] Style
); );
public record Addon(
[property: JsonPropertyName("cyberware")]
string[] Cyberware
);

View file

@ -1,36 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <OutputType>WinExe</OutputType>
<OutputType>Exe</OutputType> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<IsPackable>false</IsPackable> <ApplicationManifest>app.manifest</ApplicationManifest>
<RollForward>LatestMajor</RollForward> <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="2dog" Version="0.1.23-pre"/> <AvaloniaResource Include="Resources\addons.json" />
<AvaloniaResource Include="Resources\cock.json" />
<AvaloniaResource Include="Resources\pussy.json" />
<Folder Include="Models\"/>
<AvaloniaResource Include="Assets\**"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Using Remove="System.IO" /> <PackageReference Include="Avalonia" Version="12.0.3"/>
<PackageReference Include="Avalonia.Desktop" Version="12.0.3"/>
<PackageReference Include="Avalonia.Themes.Fluent" Version="12.0.3"/>
<PackageReference Include="Avalonia.Fonts.Inter" Version="12.0.3"/>
<PackageReference Include="AvaloniaUI.DiagnosticsSupport" Version="2.2.1">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.1"/>
<PackageReference Include="GitInfo" Version="3.6.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="../CyberBits.Godot/CyberBits.Godot.csproj"/>
<ProjectReference Include="..\CyberBits.Common\CyberBits.Common.csproj" />
</ItemGroup>
<!-- Godot project location -->
<PropertyGroup>
<GodotProjectDir>../CyberBits.Godot</GodotProjectDir>
</PropertyGroup>
<!-- Remove duplicate Godot.SourceGenerators that come from the Godot project
(2dog package already embeds them) -->
<Target Name="RemoveDuplicateGodotAnalyzers" BeforeTargets="CoreCompile">
<ItemGroup>
<Analyzer Remove="@(Analyzer)" Condition="$([System.String]::Copy('%(Analyzer.Identity)').Contains('Godot.SourceGenerators'))" />
</ItemGroup>
</Target>
</Project> </Project>

1
CyberBits/GlobalUsing.cs Normal file
View file

@ -0,0 +1 @@
global using System.Text.Json.Serialization;

View file

@ -1,5 +0,0 @@
global using CyberBits.Common;
global using Godot;
global using Engine = twodog.Engine;
global using Env = System.Environment;
global using System.Text.Json;

34
CyberBits/ImageHelper.cs Normal file
View file

@ -0,0 +1,34 @@
namespace CyberBits;
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
public static class ImageHelper
{
private static readonly HttpClient HttpClient = new();
public static Bitmap LoadFromResource(Uri resourceUri)
{
return new Bitmap(AssetLoader.Open(resourceUri));
}
public static async Task<Bitmap?> LoadFromWeb(Uri url)
{
try
{
var response = await HttpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
var data = await response.Content.ReadAsByteArrayAsync();
return new Bitmap(new MemoryStream(data));
}
catch (HttpRequestException ex)
{
Console.WriteLine($"An error occurred while downloading image '{url}' : {ex.Message}");
return null;
}
}
}

View file

@ -1,55 +1,24 @@
// Create and start the Godot engine with your project using Avalonia;
using var engine = new Engine("CyberBits", Engine.ResolveProjectDir()); using System;
using var godot = engine.Start();
// Load your main scene namespace CyberBits;
var scene = GD.Load<PackedScene>("res://main.tscn");
engine.Tree.Root.AddChild(scene.Instantiate());
var load = engine.Tree.CurrentScene; sealed class Program
var bitsImage = load.GetNode<TextureRect>("BitsImage");
// var bitsSelection = curScene.GetNode<OptionButton>("BitsSelection");
var unlockedFeatLbl = load.GetNode<Label>("UnlockedFeatLbl");
var cyberwareList = load.GetNode<ItemList>("CyberwareList");
var genitalList = load.GetNode<ItemList>("GenitalList");
var baseContents = FileFetcher.LoadTextFile(ResourceFiles.COCK_JSON);
var addonContents = FileFetcher.LoadTextFile(ResourceFiles.ADDONS_JSON);
var screenSize = DisplayServer.ScreenGetSize();
var window = load.GetWindow();
window.Size = new Vector2I(screenSize.X - 66, screenSize.Y - 1);
ProjectSettings.SetSetting("application/config/version", CBConsts.VERSION);
if (!FileAccess.FileExists(baseContents)
|| !FileAccess.FileExists(addonContents))
Env.Exit(Env.ExitCode);
var bits = JsonSerializer.Deserialize<Bits>(baseContents);
var addon = JsonSerializer.Deserialize<Addon>(addonContents);
bitsImage.Texture.ResourcePath = bits.Image;
unlockedFeatLbl.Text = $"Feat: {bits?.Feat}";
foreach (var selection in bits.Base)
genitalList.AddItem(selection);
foreach (var cyberware in addon.Cyberware)
cyberwareList.AddItem(cyberware);
GD.Print("2dog is running! Close window or press 'Q' to quit.");
Console.WriteLine("Press 'Q' to quit.");
// Main game loop - runs until window closes or 'Q' is pressed
while (!godot.Iteration())
{ {
if (Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.Q) // Initialization code. Don't use any Avalonia, third-party APIs or any
break; // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Your per-frame logic here // Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
#if DEBUG
.WithDeveloperTools()
#endif
.WithInterFont()
.LogToTrace();
} }
Console.WriteLine("Shutting down...");

View file

@ -0,0 +1,8 @@
namespace CyberBits;
public struct ResourceFiles
{
public const string COCK_JSON = "Resources/cock.json";
public const string PUSSY_JSON = "Resources/pussy.json";
public const string ADDONS_JSON = "Resources/addons.json";
}

View file

@ -1,6 +1,6 @@
{ {
"cyberware": [ "cyberware": [
"Piercings", "Piercings",
"Datajack" "Datajack"
] ]
} }

View file

@ -1,7 +1,7 @@
{ {
"image": "res://sprites/cock.jpg", "image": "avares://CyberBits/Assets/cock.jpg",
"feat": "Aim Piss", "feat": "Aim Piss",
"base": [ "model": [
"Au Natural", "Au Natural",
"Les Americaines" "Les Americaines"
], ],

View file

@ -0,0 +1,12 @@
{
"image": "avares://CyberBits/Assets/pussy.jpg",
"feat": "Boom of the Mother Godness",
"model": [
"Au Natural",
"Les Americaines"
],
"style": [
"Bioware",
"Cyberware"
]
}

37
CyberBits/ViewLocator.cs Normal file
View file

@ -0,0 +1,37 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using CyberBits.ViewModels;
namespace CyberBits;
/// <summary>
/// Given a view model, returns the corresponding view if possible.
/// </summary>
[RequiresUnreferencedCode(
"Default implementation of ViewLocator involves reflection which may be trimmed away.",
Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")]
public class ViewLocator : IDataTemplate
{
public Control? Build(object? param)
{
if (param is null)
return null;
var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
var type = Type.GetType(name);
if (type != null)
{
return (Control)Activator.CreateInstance(type)!;
}
return new TextBlock { Text = "Not Found: " + name };
}
public bool Match(object? data)
{
return data is ViewModelBase;
}
}

View file

@ -0,0 +1,6 @@
namespace CyberBits.ViewModels;
public partial class MainWindowViewModel : ViewModelBase
{
}

View file

@ -0,0 +1,35 @@
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
namespace CyberBits.ViewModels;
public partial class ViewModelBase : ObservableObject
{
public ObservableCollection<string> Genitals { get; } = ["Cock", "Pussy"];
public ObservableCollection<string> Models { get; } =
[
"Au Natural",
"Les Americaines"
];
public ObservableCollection<string> Styles { get; } =
[
"Bioware",
"Cyberware"
];
public ObservableCollection<string> Cyberware { get; } =
[
"Piercings",
"Datajack"
];
[ObservableProperty] private string? _selectedBits;
[ObservableProperty] private string? _selectedModels;
[ObservableProperty] private string? _selectedStyles;
[ObservableProperty] private string? _selectedCyberware;
}

View file

@ -0,0 +1,70 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:CyberBits.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="720" d:DesignHeight="430"
Width="720" Height="430" CanResize="False"
ExtendClientAreaToDecorationsHint="True"
x:Class="CyberBits.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="CyberBits">
<Design.DataContext>
<!-- This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
<vm:MainWindowViewModel/>
</Design.DataContext>
<StackPanel>
<Border IsHitTestVisible="False" Height="35"
WindowDecorationProperties.ElementRole="TitleBar">
<TextBlock Margin="12,0" FontSize="17"
VerticalAlignment="Center" HorizontalAlignment="Center"
Text="CyberBits" >
</TextBlock>
</Border>
<Grid ColumnDefinitions="*,*">
<Image x:Name="BitsImage" Grid.Column="0" Source="avares://CyberBits/Assets/cock.jpg" />
<GridSplitter Grid.Column="1" ResizeDirection="Columns"/>
<StackPanel Grid.Column="1">
<Grid ColumnDefinitions="*,*">
<GridSplitter ResizeDirection="Columns"/>
<StackPanel Grid.Column="0" >
<TextBlock Margin="2" Text="Feat: Aim Piss" />
<Border Margin="3" />
<ComboBox ItemsSource="{Binding Genitals}"
SelectedItem="{Binding SelectedBits}"
PlaceholderText="Select your bits"
SelectedValue="0"
x:Name="BitSelection"
SelectionChanged="SelectedBitsControl_OnSelectionChanged"
Margin="5"/>
<ComboBox ItemsSource="{Binding Models}"
SelectedItem="{Binding SelectedModels}"
PlaceholderText="Select your model"
Margin="5"/>
</StackPanel>
<GridSplitter Grid.Column="1" ResizeDirection="Columns"/>
<StackPanel Grid.Column="1">
<TextBlock Margin="2" Text="Bio-Essence: 10" />
<Border Margin="3" />
<ComboBox ItemsSource="{Binding Styles}"
SelectedItem="{Binding SelectedStyles}"
PlaceholderText="Select your style"
Margin="5"/>
<!-- <ComboBox ItemsSource="{Binding Cyberware}"
SelectedItem="{Binding SelectedCyberware}"
PlaceholderText="Select your Cyberware"
Margin="5"/> -->
</StackPanel>
</Grid>
</StackPanel>
</Grid>
<Border Margin="2" VerticalAlignment="Bottom">
<TextBlock HorizontalAlignment="Right" Name="VersionBox" Margin="10" Text="v0.1.100" />
</Border>
</StackPanel>
</Window>

View file

@ -0,0 +1,30 @@
using System;
using Avalonia.Controls;
namespace CyberBits.Views;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
VersionBox.Text = $"v{AppConsts.VERSION}";
}
private void SelectedBitsControl_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
switch (BitSelection.SelectionBoxItem)
{
case "Pussy":
var pussy = ImageHelper.LoadFromResource(new Uri("avares://CyberBits/Assets/pussy.jpg"));
BitsImage.Source = pussy;
break;
case "Cock":
var cock = ImageHelper.LoadFromResource(new Uri("avares://CyberBits/Assets/cock.jpg"));
BitsImage.Source = cock;
break;
}
}
}

18
CyberBits/app.manifest Normal file
View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="CyberBits.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>

View file

@ -1,6 +0,0 @@
namespace Cyberbits;
public record Addon(
[property: JsonPropertyName("cyberware")]
string[] Cyberware
);

View file

@ -1,19 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<RollForward>LatestMajor</RollForward>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="2dog" Version="0.1.14-pre" />
</ItemGroup>
<PropertyGroup>
<GodotProjectDir>./project</GodotProjectDir>
</PropertyGroup>
</Project>

View file

@ -1,31 +0,0 @@
namespace Cyberbits;
public static class FileFetcher
{
public static string LoadTextFile(string filename, bool userDir = false)
{
var location = "res";
if (userDir)
location = "user";
using var file = FileAccess.Open($"{location}://{filename}", FileAccess.ModeFlags.Read);
var contents = file.GetAsText();
file.Close();
return contents;
}
public static ConfigFile LoadConfig(string filename, bool userDir = false)
{
var config = new ConfigFile();
// Load data from a file.
var err = config.Load(LoadTextFile(filename, userDir));
if (err != Error.Ok)
sys.Environment.Exit(sys.Environment.ExitCode);
return config;
}
}

View file

@ -1,10 +0,0 @@
// System
global using System;
global using sys = System;
global using System.Text.Json;
global using System.Text.Json.Serialization;
// Godot
global using Godot;
global using Godot.Collections;
global using Engine = twodog.Engine;

View file

@ -1,45 +0,0 @@
using Cyberbits;
using var engine = new Engine("Cyberbits", Engine.ResolveProjectDir());
using var godot = engine.Start();
// Load a scene
var scene = GD.Load<PackedScene>("res://main.tscn");
engine.Tree.Root.AddChild(scene.Instantiate());
var load = engine.Tree.CurrentScene;
var bitsImage = load.GetNode<TextureRect>("BitsImage");
// var bitsSelection = curScene.GetNode<OptionButton>("BitsSelection");
var unlockedFeatLbl = load.GetNode<Label>("UnlockedFeatLbl");
var cyberwareList = load.GetNode<ItemList>("CyberwareList");
var genitalList = load.GetNode<ItemList>("GenitalList");
var baseContents = FileFetcher.LoadTextFile(ResourceFiles.COCK_JSON);
var addonContents = FileFetcher.LoadTextFile(ResourceFiles.ADDONS_JSON);
var screenSize = DisplayServer.ScreenGetSize();
var window = load.GetWindow();
window.Size = new Vector2I(screenSize.X - 66, screenSize.Y - 1);
if (!FileAccess.FileExists(baseContents)
|| !FileAccess.FileExists(addonContents))
sys.Environment.Exit(sys.Environment.ExitCode);
var bits = JsonSerializer.Deserialize<Bits>(baseContents);
var addon = JsonSerializer.Deserialize<Addon>(addonContents);
bitsImage.Texture.ResourcePath = bits.Image;
unlockedFeatLbl.Text = $"Feat: {bits?.Feat}";
foreach (var selection in bits.Base)
genitalList.AddItem(selection);
foreach (var cyberware in addon.Cyberware)
cyberwareList.AddItem(cyberware);
// Run the main loop
while (!godot.Iteration())
{
if (Console.KeyAvailable && Console.ReadKey(true).Key == ConsoleKey.Q)
sys.Environment.Exit(sys.Environment.ExitCode);
}

View file

@ -1,8 +0,0 @@
namespace Cyberbits;
public struct ResourceFiles
{
public const string COCK_JSON = "resources/cock.json";
public const string PUSSY_JSON = "resources/pussy.json";
public const string ADDONS_JSON = "resources/addons.json";
}

View file

@ -1 +0,0 @@
hidpi = true

View file

@ -1,103 +0,0 @@
[gd_scene format=3 uid="uid://eosyt5tlfb0s"]
[ext_resource type="Texture2D" uid="uid://dy3yusiqudfy7" path="res://sprites/cock.jpg" id="1_ig7tw"]
[node name="Main" type="Control" unique_id=1203792895]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
metadata/_edit_lock_ = true
[node name="BitsImage" type="TextureRect" parent="." unique_id=420796857]
layout_mode = 1
anchors_preset = -1
anchor_left = 0.02578125
anchor_top = 0.2125
anchor_right = 0.2921875
anchor_bottom = 0.6861111
texture = ExtResource("1_ig7tw")
expand_mode = 3
metadata/_edit_use_anchors_ = true
[node name="GenitalList" type="ItemList" parent="." unique_id=1657021535]
layout_mode = 1
anchors_preset = -1
anchor_left = 0.3140625
anchor_top = 0.29722223
anchor_right = 0.5804688
anchor_bottom = 0.6611111
metadata/_edit_use_anchors_ = true
[node name="CyberwareList" type="ItemList" parent="." unique_id=1780192556]
layout_mode = 1
anchors_preset = -1
anchor_left = 0.5921875
anchor_top = 0.2986111
anchor_right = 0.78984374
anchor_bottom = 0.6194445
metadata/_edit_use_anchors_ = true
[node name="UnlockedFeatLbl" type="Label" parent="." unique_id=1358090682]
layout_mode = 1
anchors_preset = -1
anchor_left = 0.40546876
anchor_top = 0.23888889
anchor_right = 0.5726563
anchor_bottom = 0.27083334
text = "Feat:"
metadata/_edit_use_anchors_ = true
[node name="BitsSelection" type="OptionButton" parent="." unique_id=1632883177]
layout_mode = 1
anchors_preset = -1
anchor_left = 0.315625
anchor_top = 0.23055555
anchor_right = 0.37578124
anchor_bottom = 0.28194445
selected = 0
fit_to_longest_item = false
allow_reselect = true
item_count = 2
popup/item_0/text = "Dick"
popup/item_0/id = 0
popup/item_1/text = "Pussy"
popup/item_1/id = 1
metadata/_edit_use_anchors_ = true
[node name="CyberwareLbl" type="Label" parent="." unique_id=1646946419]
layout_mode = 1
anchors_preset = -1
anchor_left = 0.5921875
anchor_top = 0.25277779
anchor_right = 0.7867187
anchor_bottom = 0.29166666
text = "Cyberware"
horizontal_alignment = 1
metadata/_edit_use_anchors_ = true
[node name="BioEssenceLbl" type="Label" parent="." unique_id=1183351215]
layout_mode = 0
offset_left = 759.0
offset_top = 458.0
offset_right = 877.0
offset_bottom = 481.0
text = "Bio-Essence: 10"
[node name="MegaDebtLbl" type="Label" parent="." unique_id=1875640511]
layout_mode = 0
offset_left = 896.0
offset_top = 458.0
offset_right = 1014.0
offset_bottom = 481.0
text = "Mega Debt: 10"
[node name="ActionRatingLbl" type="Label" parent="." unique_id=865651652]
layout_mode = 0
offset_left = 762.0
offset_top = 489.0
offset_right = 880.0
offset_bottom = 512.0
text = "Action Rating: 0"

View file

@ -1,31 +0,0 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
custom_features="dotnet"
[animation]
compatibility/default_parent_skeleton_in_mesh_instance_3d=true
[application]
config/name="Cyberbits"
run/main_scene="res://main.tscn"
config/features=PackedStringArray("4.6")
[display]
window/size/viewport_width=1280
window/size/viewport_height=720
window/stretch/mode="viewport"
[dotnet]
project/assembly_name="CyberBits"

View file

@ -1,12 +0,0 @@
{
"image": "res://sprites/pussy.jpg",
"feat": "Boom of the Mother Goddness",
"base": [
"Au Natural",
"Les Americaines"
],
"style": [
"Bioware",
"Cyberware"
]
}

View file

@ -1,40 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dy3yusiqudfy7"
path="res://.godot/imported/cock.jpg-dc731d312fd1d89d74474e6b2228fd74.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://sprites/cock.jpg"
dest_files=["res://.godot/imported/cock.jpg-dc731d312fd1d89d74474e6b2228fd74.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View file

@ -1,40 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dkopqn7vnw8e1"
path="res://.godot/imported/pussy.jpg-8a250a58765a4818bd67de2eff4bf0dc.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://sprites/pussy.jpg"
dest_files=["res://.godot/imported/pussy.jpg-8a250a58765a4818bd67de2eff4bf0dc.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View file

@ -2,14 +2,21 @@
Loosely based on [Penis 2.0.77](https://glaiveguisarme.itch.io/penis-two-point-zero-point-seven-seven), CyberBits lets you customize your character's bits with the latest cyberware. Loosely based on [Penis 2.0.77](https://glaiveguisarme.itch.io/penis-two-point-zero-point-seven-seven), CyberBits lets you customize your character's bits with the latest cyberware.
## Quick Start ## Minimum Requirements
### Prerequisites ### Development
- .NET 8 or later - NET 10
- [Godot (.NET)](https://godotengine.org/)
Supported platforms: `win-x64`, `linux-x64`, and `osx-arm64`. ### Deployment
| Target | Version | Architectures |
|---------|-----------|---------------|
| macOS | 14 | x64, ARM64 |
| Windows | 10 (22H2) | x64, ARM64 |
| Ubuntu | 16.04 | x64, ARM64 |
| Debian | 9 | x64, ARM64 |
| Fedora | 30 | x64 |
## License ## License