Compare commits

...
Sign in to create a new pull request.

10 commits

Author SHA1 Message Date
eeb17a1d50 Minor reorganization 2025-04-29 03:29:57 -04:00
9d9f951e9d Going the UI route 2025-04-29 03:19:23 -04:00
970303d5ec v0.5 LTS
We're going to be on 0.5 for a while.
2025-04-28 19:06:22 -04:00
3690f96e42 Fixed conversion problem
- Zodiac option isn’t showing for some reason
2025-04-28 15:00:49 -04:00
45fdf40af2 Zodiac option (buggy) 2025-04-28 13:56:46 -04:00
0ae58c67fa Zodiac signs JSON
- Preemptive start on reading JSON files
2025-04-28 12:43:51 -04:00
b3f189aa94 Rewrote "Why This Exists" section 2025-04-28 12:12:35 -04:00
8c33834975 Export option
- Moved archetype variables into handler
2025-04-28 11:58:42 -04:00
499782924f Added shell example to README 2025-04-28 11:46:38 -04:00
6a9c9a4533 CLI tool
- Dotnet tool support
- Moved profile import/load to after 1.0
2025-04-28 11:45:04 -04:00
38 changed files with 1129 additions and 75 deletions

View file

@ -0,0 +1,12 @@
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>

View file

@ -0,0 +1,16 @@
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
</div>
<article class="content px-4">
@Body
</article>
</main>
</div>

View file

@ -0,0 +1,77 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}
.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row {
justify-content: space-between;
}
.top-row ::deep a, .top-row ::deep .btn-link {
margin-left: 0;
}
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.top-row.auth ::deep a:first-child {
flex: 1;
text-align: right;
width: 0;
}
.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}

View file

@ -0,0 +1,39 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">PersonaForge.Blazor</a>
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div>
<div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="weather">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather
</NavLink>
</div>
</nav>
</div>
@code {
private bool collapseNavMenu = true;
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}

View file

@ -0,0 +1,83 @@
.navbar-toggler {
background-color: rgba(255, 255, 255, 0.1);
}
.top-row {
height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.bi {
display: inline-block;
position: relative;
width: 1.25rem;
height: 1.25rem;
margin-right: 0.75rem;
top: -1px;
background-size: cover;
}
.bi-house-door-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
}
.bi-plus-square-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
}
.bi-list-nested-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
}
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item ::deep a {
color: #d7d7d7;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.37);
color: white;
}
.nav-item ::deep a:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
@media (min-width: 641px) {
.navbar-toggler {
display: none;
}
.collapse {
/* Never collapse the sidebar for wide screens */
display: block;
}
.nav-scrollable {
/* Allow sidebar to scroll for tall menus */
height: calc(100vh - 3.5rem);
overflow-y: auto;
}
}

View file

@ -0,0 +1,18 @@
@page "/counter"
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}

View file

@ -0,0 +1,7 @@
@page "/"
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.

View file

@ -0,0 +1,57 @@
@page "/craft"
@inject HttpClient Http
<PageTitle>Sims2Craft</PageTitle>
<h1>Craft</h1>
<p>This component demonstrates fetching data from the server.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
}
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}

View file

@ -0,0 +1,57 @@
@page "/weather"
@inject HttpClient Http
<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>This component demonstrates fetching data from the server.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
}
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}

View file

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.0" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PersonaForge\PersonaForge.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using PersonaForge.Blazor;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
await builder.Build().RunAsync();

View file

@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:55547",
"sslPort": 44325
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "http://localhost:5139",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:7023;http://localhost:5139",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View file

@ -0,0 +1,11 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using PersonaForge.Blazor
@using PersonaForge.Blazor.Layout
@using PersonaForge

View file

@ -0,0 +1,103 @@
html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
h1:focus {
outline: none;
}
a, .btn-link {
color: #0071c1;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}
.content {
padding-top: 1.1rem;
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid red;
}
.validation-message {
color: red;
}
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
}
.blazor-error-boundary::after {
content: "An error has occurred."
}
.loading-progress {
position: relative;
display: block;
width: 8rem;
height: 8rem;
margin: 20vh auto 1rem auto;
}
.loading-progress circle {
fill: none;
stroke: #e0e0e0;
stroke-width: 0.6rem;
transform-origin: 50% 50%;
transform: rotate(-90deg);
}
.loading-progress circle:last-child {
stroke: #1b6ec2;
stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
transition: stroke-dasharray 0.05s ease-in-out;
}
.loading-progress-text {
position: absolute;
text-align: center;
font-weight: bold;
inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
}
.loading-progress-text:after {
content: var(--blazor-load-percentage-text, "Loading");
}
code {
color: #c02d76;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -14,8 +14,8 @@
"TraitBiases": {
"Outgoing": 0.9,
"Playful": 0.8,
"Active": 0.7,
"Nice": 0.5,
"Active": 0.5,
"Nice": 0.7,
"Neat": 0.3
}
},

View file

@ -0,0 +1,122 @@
[
{
"Name": "Aries",
"TraitBiases": {
"Outgoing": 8.0,
"Playful": 3.0,
"Active": 7.0,
"Nice": 3.0,
"Neat": 4.0
}
},
{
"Name": "Taurus",
"TraitBiases": {
"Outgoing": 4.0,
"Playful": 4.0,
"Active": 4.0,
"Nice": 7.0,
"Neat": 6.0
}
},
{
"Name": "Gemini",
"TraitBiases": {
"Outgoing": 7.0,
"Playful": 7.0,
"Active": 4.0,
"Nice": 3.0,
"Neat": 4.0
}
},
{
"Name": "Cancer",
"TraitBiases": {
"Outgoing": 4.0,
"Playful": 3.0,
"Active": 4.0,
"Nice": 8.0,
"Neat": 6.0
}
},
{
"Name": "Leo",
"TraitBiases": {
"Outgoing": 8.0,
"Playful": 7.0,
"Active": 4.0,
"Nice": 2.0,
"Neat": 4.0
}
},
{
"Name": "Virgo",
"TraitBiases": {
"Outgoing": 2.0,
"Playful": 2.0,
"Active": 4.0,
"Nice": 8.0,
"Neat": 9.0
}
},
{
"Name": "Libra",
"TraitBiases": {
"Outgoing": 8.0,
"Playful": 6.0,
"Active": 4.0,
"Nice": 5.0,
"Neat": 2.0
}
},
{
"Name": "Scorpio",
"TraitBiases": {
"Outgoing": 5.0,
"Playful": 2.0,
"Active": 8.0,
"Nice": 4.0,
"Neat": 6.0
}
},
{
"Name": "Sagittarius",
"TraitBiases": {
"Outgoing": 7.0,
"Playful": 7.0,
"Active": 7.0,
"Nice": 2.0,
"Neat": 2.0
}
},
{
"Name": "Capricorn",
"TraitBiases": {
"Outgoing": 3.0,
"Playful": 2.0,
"Active": 6.0,
"Nice": 7.0,
"Neat": 7.0
}
},
{
"Name": "Aquarius",
"TraitBiases": {
"Outgoing": 6.0,
"Playful": 7.0,
"Active": 4.0,
"Nice": 5.0,
"Neat": 3.0
}
},
{
"Name": "Pisces",
"TraitBiases": {
"Outgoing": 4.0,
"Playful": 8.0,
"Active": 4.0,
"Nice": 5.0,
"Neat": 4.0
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PersonaForge.Blazor</title>
<base href="/" />
<link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
<link rel="stylesheet" href="css/app.css" />
<link rel="icon" type="image/png" href="favicon.png" />
<link href="PersonaForge.Blazor.styles.css" rel="stylesheet" />
</head>
<body>
<div id="app">
<svg class="loading-progress">
<circle r="40%" cx="50%" cy="50%" />
<circle r="40%" cx="50%" cy="50%" />
</svg>
<div class="loading-progress-text"></div>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webassembly.js"></script>
</body>
</html>

View file

@ -0,0 +1,27 @@
[
{
"date": "2022-01-06",
"temperatureC": 1,
"summary": "Freezing"
},
{
"date": "2022-01-07",
"temperatureC": 14,
"summary": "Bracing"
},
{
"date": "2022-01-08",
"temperatureC": -13,
"summary": "Freezing"
},
{
"date": "2022-01-09",
"temperatureC": -16,
"summary": "Balmy"
},
{
"date": "2022-01-10",
"temperatureC": -2,
"summary": "Chilly"
}
]

View file

@ -0,0 +1,5 @@
{
"version": 1,
"isRoot": true,
"tools": {}
}

View file

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<Version>0.5.103</Version>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackAsTool>true</PackAsTool>
<ToolCommandName>personaforge</ToolCommandName>
<PackageId>PersonaForge</PackageId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PersonaForge\PersonaForge.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,68 @@
// .NET 8 C# - Random Sims 2 Personality Allocator
// Clear, minimal, portable, with basic validation.
using PersonaForge;
var rootCommand = new RootCommand("PersonaForge: Sims 2 Personality Generator");
var nameOption = new Option<string>(
name: "--name",
description: "Enter a name for the Sim"
);
var archetypeOption = new Option<string>(
name: "--archetype",
description: "The archetype template to base traits on",
getDefaultValue: () => "Random"
);
var zodiacOption = new Option<bool>(
name: "--zodiac",
description: "Use zodiac signs instead of archetypes"
)
{ IsRequired = false };
var exportOption = new Option<bool>(
name: "--export",
description: "Export the generated profile to a file"
)
{ IsRequired = false };
rootCommand.AddOption(nameOption);
rootCommand.AddOption(archetypeOption);
rootCommand.AddOption(exportOption);
rootCommand.SetHandler(async (
string name,
string archetype,
bool zodiac,
bool export
) =>
{
var archetypeDefs = Archetypes.LoadFromJson("archetypes.json");
var archetypeDict = new Dictionary<string, Dictionary<string, double>>();
if (zodiac)
archetypeDefs = Archetypes.LoadFromJson("zodiacs.json");
var traits = PersonalityGen.GenerateRandom(archetypeDict.GetValueOrDefault(archetype, new()));
var profile = new PersonaProfile
{
Name = name,
Qualities = traits,
Archetype = archetype
};
Console.WriteLine($"--- Generated Profile ---{Environment.NewLine}{PersonaProfile.ToJson(profile)}");
if (export)
{
var safeName = name.Replace(" ", "_").Replace("\"", "");
await File.WriteAllTextAsync($"{safeName}.json", PersonaProfile.ToJson(profile));
}
}, nameOption, archetypeOption, zodiacOption, exportOption);
return await rootCommand.InvokeAsync(args);

View file

@ -0,0 +1,72 @@
[
{
"Name": "Social Butterfly",
"TraitBiases": {
"Outgoing": 0.9,
"Playful": 0.8,
"Active": 0.6,
"Nice": 0.7,
"Neat": 0.5
}
},
{
"Name": "Party Animal",
"TraitBiases": {
"Outgoing": 0.9,
"Playful": 0.8,
"Active": 0.5,
"Nice": 0.7,
"Neat": 0.3
}
},
{
"Name": "Laidback",
"TraitBiases": {
"Outgoing": 0.4,
"Playful": 0.3,
"Active": 0.4,
"Nice": 0.8,
"Neat": 0.4
}
},
{
"Name": "Grumpy",
"TraitBiases": {
"Nice": 0.2,
"Playful": 0.4,
"Outgoing": 0.6,
"Neat": 0.7,
"Active": 0.5
}
},
{
"Name": "Artistic",
"TraitBiases": {
"Outgoing": 0.3,
"Playful": 0.7,
"Active": 0.5,
"Nice": 0.9,
"Neat": 0.6
}
},
{
"Name": "Ambitious",
"TraitBiases": {
"Outgoing": 0.6,
"Playful": 0.4,
"Active": 0.8,
"Nice": 0.5,
"Neat": 0.7
}
},
{
"Name": "Adventurous",
"TraitBiases": {
"Outgoing": 0.8,
"Playful": 0.8,
"Active": 0.9,
"Nice": 0.6,
"Neat": 0.4
}
}
]

View file

@ -0,0 +1,11 @@
{
"Name": "Max Casey",
"Archetype": "Party Animal",
"Qualities": {
"Outgoing": 7,
"Nice": 6,
"Playful": 6,
"Neat": 3,
"Active": 3
}
}

View file

@ -0,0 +1,11 @@
{
"Name": "Zack Casey",
"Archetype": "Laidback",
"Qualities": {
"Outgoing": 5,
"Nice": 8,
"Playful": 4,
"Neat": 4,
"Active": 4
}
}

View file

@ -0,0 +1,122 @@
[
{
"Name": "Aries",
"TraitBiases": {
"Outgoing": 8.0,
"Playful": 3.0,
"Active": 7.0,
"Nice": 3.0,
"Neat": 4.0
}
},
{
"Name": "Taurus",
"TraitBiases": {
"Outgoing": 4.0,
"Playful": 4.0,
"Active": 4.0,
"Nice": 7.0,
"Neat": 6.0
}
},
{
"Name": "Gemini",
"TraitBiases": {
"Outgoing": 7.0,
"Playful": 7.0,
"Active": 4.0,
"Nice": 3.0,
"Neat": 4.0
}
},
{
"Name": "Cancer",
"TraitBiases": {
"Outgoing": 4.0,
"Playful": 3.0,
"Active": 4.0,
"Nice": 8.0,
"Neat": 6.0
}
},
{
"Name": "Leo",
"TraitBiases": {
"Outgoing": 8.0,
"Playful": 7.0,
"Active": 4.0,
"Nice": 2.0,
"Neat": 4.0
}
},
{
"Name": "Virgo",
"TraitBiases": {
"Outgoing": 2.0,
"Playful": 2.0,
"Active": 4.0,
"Nice": 8.0,
"Neat": 9.0
}
},
{
"Name": "Libra",
"TraitBiases": {
"Outgoing": 8.0,
"Playful": 6.0,
"Active": 4.0,
"Nice": 5.0,
"Neat": 2.0
}
},
{
"Name": "Scorpio",
"TraitBiases": {
"Outgoing": 5.0,
"Playful": 2.0,
"Active": 8.0,
"Nice": 4.0,
"Neat": 6.0
}
},
{
"Name": "Sagittarius",
"TraitBiases": {
"Outgoing": 7.0,
"Playful": 7.0,
"Active": 7.0,
"Nice": 2.0,
"Neat": 2.0
}
},
{
"Name": "Capricorn",
"TraitBiases": {
"Outgoing": 3.0,
"Playful": 2.0,
"Active": 6.0,
"Nice": 7.0,
"Neat": 7.0
}
},
{
"Name": "Aquarius",
"TraitBiases": {
"Outgoing": 6.0,
"Playful": 7.0,
"Active": 4.0,
"Nice": 5.0,
"Neat": 3.0
}
},
{
"Name": "Pisces",
"TraitBiases": {
"Outgoing": 4.0,
"Playful": 8.0,
"Active": 4.0,
"Nice": 5.0,
"Neat": 4.0
}
}
]

34
PersonaForge.sln Normal file
View file

@ -0,0 +1,34 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PersonaForge.Console", "PersonaForge.Console\PersonaForge.Console.csproj", "{129AB949-1C7E-4E52-82E0-9339F43918AA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PersonaForge", "PersonaForge\PersonaForge.csproj", "{316741E2-8BE3-4FBF-A10D-F8FF2AD6979E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PersonaForge.Blazor", "PersonaForge.Blazor\PersonaForge.Blazor.csproj", "{381CCDD1-BB1A-45F0-8501-77769AB4578F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{129AB949-1C7E-4E52-82E0-9339F43918AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{129AB949-1C7E-4E52-82E0-9339F43918AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{129AB949-1C7E-4E52-82E0-9339F43918AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{129AB949-1C7E-4E52-82E0-9339F43918AA}.Release|Any CPU.Build.0 = Release|Any CPU
{316741E2-8BE3-4FBF-A10D-F8FF2AD6979E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{316741E2-8BE3-4FBF-A10D-F8FF2AD6979E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{316741E2-8BE3-4FBF-A10D-F8FF2AD6979E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{316741E2-8BE3-4FBF-A10D-F8FF2AD6979E}.Release|Any CPU.Build.0 = Release|Any CPU
{381CCDD1-BB1A-45F0-8501-77769AB4578F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{381CCDD1-BB1A-45F0-8501-77769AB4578F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{381CCDD1-BB1A-45F0-8501-77769AB4578F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{381CCDD1-BB1A-45F0-8501-77769AB4578F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View file

@ -5,7 +5,21 @@ public class Archetypes
public string Name { get; set; }
public Dictionary<string, double> TraitBiases { get; set; } = new();
// TODO: Load default archetypes from a file or database
public static List<Archetypes> LoadFromJson(string json)
{
var opt = new JsonSerializerOptions()
{
PropertyNameCaseInsensitive = false
};
var definitions = JsonSerializer.Deserialize<List<Archetypes>>(json, opt);
if (definitions is null)
throw new InvalidOperationException("Failed to deserialize Archetype JSON.");
return definitions;
}
public static List<Archetypes> DefaultArchetypes() => new()
{
new Archetypes

View file

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<Version>0.4.102</Version>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Version>0.5.103</Version>
<VersionSuffix>lts</VersionSuffix>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

View file

@ -2,11 +2,11 @@ namespace PersonaForge;
public class PersonalityTraits
{
public int Outgoing { get; set; }
public int Nice { get; set; }
public int Playful { get; set; }
public int Neat { get; set; }
public int Outgoing { get; set; }
public int Active { get; set; }
public int Playful { get; set; }
public int Nice { get; set; }
public static PersonalityTraits Create() => new();

View file

@ -1,51 +0,0 @@
// .NET 8 C# - Random Sims 2 Personality Allocator
// Clear, minimal, portable, with basic validation.
using PersonaForge;
Console.WriteLine("=== Welcome to The Sims 2 Personality Generator ===");
Console.Write("Enter Sim's name: ");
string name = Console.ReadLine() ?? "Unndanmed Sim";
// Move the conversion logic to a separate method
Console.WriteLine("\nAvailable Archetypes:");
var archetypeList = Archetypes.DefaultArchetypes();
var archetypeDict = new Dictionary<string, Dictionary<string, double>>();
foreach (var archetype in archetypeList)
{
Console.WriteLine($"- {archetype.Name}");
archetypeDict.Add(archetype.Name, archetype.TraitBiases);
}
Console.Write("Choose an Archetype (or 'Random'): ");
string archetypeInput = Console.ReadLine() ?? "Random";
if (!archetypeDict.ContainsKey(archetypeInput))
{
Console.WriteLine("Invalid archetype. Using Random.");
archetypeInput = "Random";
}
Console.WriteLine($"{Environment.NewLine}Generating {archetypeInput} profile...");
var qualities = PersonalityGen.GenerateRandom(archetypeDict[archetypeInput]);
var profile = new PersonaProfile
{
Name = name,
Qualities = qualities,
Archetype = archetypeInput
};
Console.WriteLine($"{Environment.NewLine}--- Generated Profile ---{Environment.NewLine}{PersonaProfile.ToJson(profile)}");
Console.Write("Save profile? (y/n): ");
var saveInput = Console.ReadLine()?.Trim().ToLowerInvariant();
if (saveInput == "y")
{
var safeName = name.Replace(" ", "_").Replace("\"", "");
File.WriteAllText($"{safeName}.json", PersonaProfile.ToJson(profile));
Console.WriteLine($"Profile saved as {name}.json");
}
Console.WriteLine($"Done. Forge on! 🔥{Environment.NewLine}Press any key to exit...");
Console.ReadKey();

View file

@ -10,18 +10,24 @@ It helps if you want to:
## 💡 Why This Exists
With Sims 3 and later, creating personalities is as simple as selecting a trait. Sims 2 doesnt have traits in the same way later installments do. You had to figure out what kind of Sim you wanted based on certain qualities and points you had to spend. Even if you knew what you wanted, you didnt know how to spend the rest. This was created to solve that.
Because The Sims 2 doesnt have traits in the same way later installments do, you had to figure out what kind of Sim you wanted based on personality stats with a strict 25 point budget. Even if you knew what you wanted, you didnt always know how to spend the rest. PersonaForge aimed to solve this problem.
## 🛣️ Project Roadmap
| Phase | Goal | Status |
| ----- | ------------------------------------------- | ------ |
| v0.1 | Core random point generator (working) | ✅ |
| -------- | ------------------------------------------- | ------ |
| v0.1 | Core random point generator | ✅ |
| v0.2 | PersonalityProfile class | ✅ |
| v0.3 | Interactive JSON export | ✅ |
| v0.4 | Archetypes with weighted biases | ✅ |
| v0.5 | Profile import/load from JSON | 🔜 |
| v0.5 LTS | Command line tool | ✅ |
| v1.0 | Stable "Release" version with documentation | 🔜 |
| v1.x | Profile import/load from JSON | 🔜 |
## 📌 Stretch Goals
- [x] Import The Sims' Zodiac signs
- [x] Cross-platform dotnet tool. (e.g. `dotnet tool install`)
## 🧩 Tech Stack
@ -38,7 +44,11 @@ With Sims 3 and later, creating personalities is as simple as selecting a trait.
- Futureproof for new archetypes, quirks, or expansions
- Portable and clean
## 📦 Example JSON Output
## 📦 Example
```shell
personaforge --name "Max Casey" --archetype "Party Animal" (--export)
```
```json
{
@ -57,13 +67,8 @@ With Sims 3 and later, creating personalities is as simple as selecting a trait.
## 🚫 Not Planned
- No Sim 3-style traits.
- No UI. (Maybe a fork)
- No CAS-style tool.
## 📌 Stretch Goals
- Cross-platform dotnet tool. (e.g. `dotnet tool install`)
## 🗓️ Update Cycle
| Type | Frequency | Notes |