Replace selectable name sets with a country that owns names and climate presets.

Create picks a country; climate is rolled from the school seed and stored. Old Slavic saves lift as Russia.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-20 02:55:13 +03:00
co-authored by Cursor
parent 450495f666
commit 1b66c89cd7
58 changed files with 902 additions and 477 deletions
+11 -9
View File
@@ -122,7 +122,7 @@ internal sealed record CatalogResponse(
IReadOnlyList<DefInfoResponse> Floors,
IReadOnlyList<RoomInfoResponse> Rooms,
IReadOnlyList<DefInfoResponse> Things,
IReadOnlyList<NameSetInfoResponse> NameSets,
IReadOnlyList<CountryInfoResponse> Countries,
IReadOnlyList<SubjectInfoResponse> Subjects,
DayFrameResponse? DayFrame,
IReadOnlyList<HolidayInfoResponse> Holidays,
@@ -135,7 +135,7 @@ internal sealed record CatalogResponse(
Placeable(catalog.Floors.Values, catalog, locale),
PlaceableRooms(catalog, locale),
PlaceableThings(catalog, locale),
PlaceableNameSets(catalog, locale),
PlaceableCountries(catalog, locale),
PlaceableSubjects(catalog, locale),
MapDayFrame(catalog, locale),
PlaceableHolidays(catalog, locale),
@@ -149,20 +149,21 @@ internal sealed record CatalogResponse(
.Select(def => new DefInfoResponse(def.DefName, catalog.Label(locale, def)))
.ToArray();
private static IReadOnlyList<NameSetInfoResponse> PlaceableNameSets(DefCatalog catalog, string locale) =>
catalog.NameSets.Values
private static IReadOnlyList<CountryInfoResponse> PlaceableCountries(DefCatalog catalog, string locale) =>
catalog.Countries.Values
.Where(def => !def.Abstract)
.OrderBy(def => def.DefName, StringComparer.Ordinal)
.Select(def => new NameSetInfoResponse(
.Select(def => new CountryInfoResponse(
def.DefName,
catalog.Label(locale, def),
def.Spoken
def.Names.Spoken
.Select(skill => new DefInfoResponse(
skill,
catalog.Skills.TryGetValue(skill, out var language)
? catalog.Label(locale, language)
: skill))
.ToArray()))
.ToArray(),
def.ClimatePresets.ToArray()))
.ToArray();
private static IReadOnlyList<DefInfoResponse> PlaceableThings(DefCatalog catalog, string locale) =>
@@ -230,10 +231,11 @@ internal sealed record CatalogResponse(
internal sealed record DefInfoResponse(string DefName, string Label, int PupilSlots = 0);
internal sealed record NameSetInfoResponse(
internal sealed record CountryInfoResponse(
string DefName,
string Label,
IReadOnlyList<DefInfoResponse> NativeLanguages);
IReadOnlyList<DefInfoResponse> NativeLanguages,
IReadOnlyList<string> ClimatePresets);
internal sealed record RoomInfoResponse(
string DefName,
+5 -5
View File
@@ -53,7 +53,7 @@ internal static class SchoolEndpoints
DateTime.SpecifyKind(request.StartDate, DateTimeKind.Utc),
request.ModIds,
request.Map,
request.NameSetId,
request.CountryId,
request.NativeLanguage,
request.Seed,
NewCompletion<SchoolCreationOutcome>());
@@ -77,10 +77,10 @@ internal static class SchoolEndpoints
Problem(StatusCodes.Status400BadRequest, "unknown-mod", "A selected mod is missing."),
SchoolCreationError.InvalidCatalog =>
Problem(StatusCodes.Status400BadRequest, "invalid-catalog", "The selected packs could not be loaded."),
SchoolCreationError.UnknownNameSet =>
Problem(StatusCodes.Status400BadRequest, "unknown-name-set", "The selected name set is not in the catalog."),
SchoolCreationError.UnknownCountry =>
Problem(StatusCodes.Status400BadRequest, "unknown-country", "The selected country is not in the catalog."),
SchoolCreationError.UnknownNativeLanguage =>
Problem(StatusCodes.Status400BadRequest, "unknown-native-language", "The selected native language is not in that name set."),
Problem(StatusCodes.Status400BadRequest, "unknown-native-language", "The selected native language is not in that country."),
SchoolCreationError.MissingMod =>
Problem(
StatusCodes.Status400BadRequest,
@@ -508,7 +508,7 @@ internal sealed record CreateSchoolRequest(
DateTime StartDate,
IReadOnlyList<string>? ModIds,
MapLayout? Map,
string? NameSetId,
string? CountryId,
string? NativeLanguage,
int? Seed);
+1 -1
View File
@@ -16,7 +16,7 @@ internal abstract record GameCommand
DateTime StartDate,
IReadOnlyList<string>? ExtraModIds,
MapLayout? Map,
string? NameSetId,
string? CountryId,
string? NativeLanguage,
int? Seed,
TaskCompletionSource<SchoolCreationOutcome> Result) : GameCommand;
+17 -19
View File
@@ -357,20 +357,14 @@ internal sealed class GameLoopService(
return;
}
var nameSetId = ResolveNameSetId(catalog, command.NameSetId);
if (nameSetId is null)
var countryId = ResolveCountryId(catalog, command.CountryId);
if (countryId is null || !catalog.Countries.TryGetValue(countryId, out var country) || country.Abstract)
{
command.Result.TrySetResult(new SchoolCreationOutcome(null, SchoolCreationError.UnknownNameSet));
command.Result.TrySetResult(new SchoolCreationOutcome(null, SchoolCreationError.UnknownCountry));
return;
}
if (!catalog.NameSets.TryGetValue(nameSetId, out var names))
{
command.Result.TrySetResult(new SchoolCreationOutcome(null, SchoolCreationError.UnknownNameSet));
return;
}
if (!string.IsNullOrWhiteSpace(command.NativeLanguage) && !NativeLanguages.Allows(names, command.NativeLanguage))
if (!string.IsNullOrWhiteSpace(command.NativeLanguage) && !NativeLanguages.Allows(country.Names, command.NativeLanguage))
{
command.Result.TrySetResult(new SchoolCreationOutcome(null, SchoolCreationError.UnknownNativeLanguage));
return;
@@ -379,9 +373,10 @@ internal sealed class GameLoopService(
var id = _nextId++;
store.WriteNextId(_nextId);
var seed = command.Seed ?? Random.Shared.Next();
var nativeLanguage = NativeLanguages.Pick(names, seed, command.NativeLanguage, rollIfOmitted: true);
var nativeLanguage = NativeLanguages.Pick(country.Names, seed, command.NativeLanguage, rollIfOmitted: true);
var climatePresetId = CountryClimate.Pick(country, seed, rollIfOmitted: true);
var worker = SpawnWorker(id, normalized, command.StartDate, running: true, ClockSpeed.DefaultIndex, isNew: true, packIds, command.Map, nameSetId, nativeLanguage, seed);
var worker = SpawnWorker(id, normalized, command.StartDate, running: true, ClockSpeed.DefaultIndex, isNew: true, packIds, command.Map, countryId, climatePresetId, nativeLanguage, seed);
Track(worker);
worker.Start();
@@ -548,7 +543,8 @@ internal sealed class GameLoopService(
isNew: false,
save.ModIds,
save.Map,
save.NameSetId,
save.CountryId,
save.ClimatePresetId,
save.NativeLanguage,
createSeed: null,
save.Presence);
@@ -598,7 +594,8 @@ internal sealed class GameLoopService(
bool isNew,
IReadOnlyList<string>? modIds,
MapLayout? map,
string? nameSetId,
string? countryId,
string? climatePresetId,
string? nativeLanguage,
int? createSeed = null,
IReadOnlyList<PresenceSnapshot>? presence = null) =>
@@ -611,7 +608,8 @@ internal sealed class GameLoopService(
isNew,
modIds,
map,
nameSetId,
countryId,
climatePresetId,
nativeLanguage,
createSeed,
presence,
@@ -624,12 +622,12 @@ internal sealed class GameLoopService(
loggerFactory.CreateLogger($"HSchool.Server.Game.SchoolWorker.{id}"));
/// <summary>
/// Empty request uses the first placeable set (core's Slavic). A named id must exist in the
/// catalog already loaded for this pack list — unknown extras were rejected above.
/// Empty request uses the first placeable country (core's Russia). A named id must exist in
/// the catalog already loaded for this pack list — unknown extras were rejected above.
/// </summary>
internal static string? ResolveNameSetId(DefCatalog catalog, string? requested)
internal static string? ResolveCountryId(DefCatalog catalog, string? requested)
{
var available = catalog.NameSets.Values
var available = catalog.Countries.Values
.Where(def => !def.Abstract)
.Select(def => def.DefName)
.OrderBy(name => name, StringComparer.Ordinal)
+41 -5
View File
@@ -26,6 +26,11 @@ internal sealed class SchoolSave
public MapLayout? Map { get; init; }
public string? CountryId { get; init; }
public string? ClimatePresetId { get; init; }
/// <summary>Legacy field. Format 2 and older; mapped to <see cref="CountryId"/> on load.</summary>
public string? NameSetId { get; init; }
public string? NativeLanguage { get; init; }
@@ -42,7 +47,7 @@ internal sealed record SchoolSaveIndex(int NextId);
/// </summary>
internal sealed class SchoolStore
{
public const int CurrentFormat = 2;
public const int CurrentFormat = 3;
private const string IndexFileName = "index.json";
@@ -178,6 +183,8 @@ internal sealed class SchoolStore
SpeedIndex = save.SpeedIndex,
ModIds = save.ModIds,
Map = save.Map,
CountryId = save.CountryId,
ClimatePresetId = save.ClimatePresetId,
NameSetId = save.NameSetId,
NativeLanguage = save.NativeLanguage,
Presence = save.Presence,
@@ -194,11 +201,40 @@ internal sealed class SchoolStore
}
/// <summary>
/// Named upgrade seam for saves older than <see cref="CurrentFormat"/>. Empty on purpose:
/// missing fields already default and extra fields are ignored. Put a migration here when a
/// format bump actually needs one.
/// Format 2 stored a name set. Slavic becomes Russia; any other id is tried as a country
/// (the example pack kept its defName). Climate is filled on the worker from the country's
/// first preset so the file is not rewritten until the school saves itself.
/// </summary>
internal static SchoolSave UpgradeOlderSave(SchoolSave save) => save;
internal static SchoolSave UpgradeOlderSave(SchoolSave save)
{
if (!string.IsNullOrWhiteSpace(save.CountryId))
{
return save;
}
var countryId = save.NameSetId switch
{
"Slavic" or null or "" => "Russia",
_ => save.NameSetId,
};
return new SchoolSave
{
Format = save.Format,
Id = save.Id,
Name = save.Name,
GameTime = save.GameTime,
Running = save.Running,
SpeedIndex = save.SpeedIndex,
ModIds = save.ModIds,
Map = save.Map,
CountryId = countryId,
ClimatePresetId = save.ClimatePresetId,
NameSetId = save.NameSetId,
NativeLanguage = save.NativeLanguage,
Presence = save.Presence,
};
}
public void Save(SchoolSave save)
{
+42 -37
View File
@@ -32,7 +32,8 @@ internal sealed class SchoolWorker
private readonly bool _isNew;
private readonly IReadOnlyList<string>? _modIds;
private readonly MapLayout? _savedMap;
private readonly string? _nameSetId;
private readonly string? _countryId;
private string? _climatePresetId;
private string? _nativeLanguage;
private readonly int? _createSeed;
private readonly IReadOnlyList<PresenceSnapshot>? _savedPresence;
@@ -66,7 +67,8 @@ internal sealed class SchoolWorker
bool isNew,
IReadOnlyList<string>? modIds,
MapLayout? savedMap,
string? nameSetId,
string? countryId,
string? climatePresetId,
string? nativeLanguage,
int? createSeed,
IReadOnlyList<PresenceSnapshot>? savedPresence,
@@ -86,7 +88,8 @@ internal sealed class SchoolWorker
_isNew = isNew;
_modIds = modIds;
_savedMap = savedMap;
_nameSetId = nameSetId;
_countryId = countryId;
_climatePresetId = climatePresetId;
_nativeLanguage = nativeLanguage;
_createSeed = createSeed;
_savedPresence = savedPresence;
@@ -857,12 +860,19 @@ internal sealed class SchoolWorker
private bool InstallPeople(School school, DefCatalog catalog, MapLayout map)
{
var nameSetId = ResolveNameSetId(catalog, _nameSetId);
if (nameSetId is null)
var countryId = ResolveCountryId(catalog, _countryId);
if (countryId is null)
{
throw new SchoolContentUnavailableException($"School {_id} has no name set in its catalog.");
throw new SchoolContentUnavailableException($"School {_id} has no country in its catalog.");
}
if (!catalog.Countries.TryGetValue(countryId, out var country) || country.Abstract)
{
throw new SchoolContentUnavailableException($"School {_id} has no country in its catalog.");
}
_climatePresetId = ResolveClimatePreset(country, _climatePresetId);
var demand = SchoolDemand.From(catalog, map);
Roster roster;
ApplicantPool applicants;
@@ -878,10 +888,10 @@ internal sealed class SchoolWorker
}
seed = createSeed;
native = ResolveNative(catalog, nameSetId, seed, _nativeLanguage, generating: true);
native = ResolveNative(country, seed, _nativeLanguage, generating: true);
_nativeLanguage = native;
roster = RosterGenerator.Generate(catalog, map, seed, nameSetId, school.Clock.Time, native);
applicants = ApplicantPool.Create(catalog, roster, seed, nameSetId, school.Clock.Time, native);
roster = RosterGenerator.Generate(catalog, map, seed, countryId, school.Clock.Time, native);
applicants = ApplicantPool.Create(catalog, roster, seed, countryId, school.Clock.Time, native);
generated = true;
}
else
@@ -890,16 +900,16 @@ internal sealed class SchoolWorker
if (loaded is null)
{
seed = school.Id;
native = ResolveNative(catalog, nameSetId, seed, _nativeLanguage, generating: true);
native = ResolveNative(country, seed, _nativeLanguage, generating: true);
_nativeLanguage = native;
roster = RosterGenerator.Generate(catalog, map, seed, nameSetId, school.Clock.Time, native);
applicants = ApplicantPool.Create(catalog, roster, seed, nameSetId, school.Clock.Time, native);
roster = RosterGenerator.Generate(catalog, map, seed, countryId, school.Clock.Time, native);
applicants = ApplicantPool.Create(catalog, roster, seed, countryId, school.Clock.Time, native);
generated = true;
}
else
{
seed = loaded.Seed;
native = ResolveNative(catalog, nameSetId, seed, _nativeLanguage, generating: false);
native = ResolveNative(country, seed, _nativeLanguage, generating: false);
_nativeLanguage = native;
roster = loaded.ToRoster();
if (loaded.Applicants is { Applicants.Count: > 0 })
@@ -908,7 +918,7 @@ internal sealed class SchoolWorker
}
else
{
applicants = ApplicantPool.Create(catalog, roster, seed, nameSetId, school.Clock.Time, native);
applicants = ApplicantPool.Create(catalog, roster, seed, countryId, school.Clock.Time, native);
generated = true;
}
}
@@ -920,47 +930,41 @@ internal sealed class SchoolWorker
$"School {_id} roster does not match its map; the people file was left untouched.");
}
school.InstallPeople(roster, seed, nameSetId, applicants, _nativeLanguage);
school.InstallPeople(roster, seed, countryId, applicants, _nativeLanguage, _climatePresetId);
InstallTimetable(school);
school.ConfigurePresence(_options.SchoolWeekDays, _options.MaxDecisionsPerTick);
school.RestorePresence(_savedPresence);
return generated;
}
private static string? ResolveNameSetId(DefCatalog catalog, string? requested)
private static string? ResolveCountryId(DefCatalog catalog, string? requested)
{
var available = catalog.NameSets.Values
.Where(def => !def.Abstract)
.Select(def => def.DefName)
.OrderBy(name => name, StringComparer.Ordinal)
.ToArray();
if (available.Length == 0)
if (string.IsNullOrWhiteSpace(requested))
{
return null;
}
if (string.IsNullOrWhiteSpace(requested))
return catalog.Countries.TryGetValue(requested, out var country) && !country.Abstract
? requested
: null;
}
private static string? ResolveClimatePreset(CountryDef country, string? requested)
{
if (!string.IsNullOrWhiteSpace(requested) && country.ClimatePresets.Contains(requested, StringComparer.Ordinal))
{
return available[0];
return requested;
}
return available.Contains(requested, StringComparer.Ordinal) ? requested : null;
return CountryClimate.Pick(country, schoolSeed: 0, rollIfOmitted: false);
}
private static string? ResolveNative(
DefCatalog catalog,
string nameSetId,
CountryDef country,
int schoolSeed,
string? requested,
bool generating)
{
if (!catalog.NameSets.TryGetValue(nameSetId, out var names))
{
return null;
}
return NativeLanguages.Pick(names, schoolSeed, requested, rollIfOmitted: generating && string.IsNullOrWhiteSpace(requested));
}
bool generating) =>
NativeLanguages.Pick(country.Names, schoolSeed, requested, rollIfOmitted: generating && string.IsNullOrWhiteSpace(requested));
private void Persist()
{
@@ -983,7 +987,8 @@ internal sealed class SchoolWorker
SpeedIndex = school.Clock.SpeedIndex,
ModIds = school.Catalog?.PackIds,
Map = school.Map,
NameSetId = _nameSetId,
CountryId = school.CountryId,
ClimatePresetId = school.ClimatePresetId,
NativeLanguage = _nativeLanguage,
Presence = school.CapturePresence(),
});
@@ -0,0 +1,3 @@
{
"defName": "ContinentalCold",
}
@@ -0,0 +1,3 @@
{
"defName": "TemperateContinental",
}
@@ -0,0 +1,139 @@
{
"defName": "Russia",
"climatePresets": ["TemperateContinental", "ContinentalCold"],
"names": {
"patronymicRule": "slavic",
"defaultGivenDeclension": "hard",
"defaultSurnameDeclension": "ov",
"nativeLanguages": ["RussianLanguage", "BelarusianLanguage", "UkrainianLanguage"],
"relatedLanguageChance": 0.6,
"relatedLanguageMean": 22,
"relatedLanguageStdDev": 8,
"relatedLanguageMax": 40,
"maleGiven": [
{ "form": "Александр" },
{ "form": "Алексей" },
{ "form": "Андрей" },
{ "form": "Антон" },
{ "form": "Артём" },
{ "form": "Борис" },
{ "form": "Вадим" },
{ "form": "Валерий" },
{ "form": "Виктор" },
{ "form": "Виталий" },
{ "form": "Владимир" },
{ "form": "Владислав" },
{ "form": "Григорий" },
{ "form": "Денис" },
{ "form": "Дмитрий" },
{ "form": "Евгений" },
{ "form": "Егор" },
{ "form": "Иван" },
{ "form": "Игорь", "declension": "soft" },
{ "form": "Илья", "declension": "ya" },
{ "form": "Кирилл" },
{ "form": "Константин" },
{ "form": "Максим" },
{ "form": "Михаил" },
{ "form": "Никита", "declension": "a" },
{ "form": "Николай" },
{ "form": "Олег" },
{ "form": "Павел" },
{ "form": "Пётр" },
{ "form": "Роман" },
{ "form": "Сергей" },
{ "form": "Станислав" },
{ "form": "Степан" },
{ "form": "Фёдор" },
{ "form": "Юрий" },
],
"femaleGiven": [
{ "form": "Александра", "declension": "a" },
{ "form": "Алина", "declension": "a" },
{ "form": "Анастасия", "declension": "iya" },
{ "form": "Анна", "declension": "a" },
{ "form": "Валентина", "declension": "a" },
{ "form": "Валерия", "declension": "iya" },
{ "form": "Вера", "declension": "a" },
{ "form": "Виктория", "declension": "iya" },
{ "form": "Дарья", "declension": "ya" },
{ "form": "Екатерина", "declension": "a" },
{ "form": "Елена", "declension": "a" },
{ "form": "Елизавета", "declension": "a" },
{ "form": "Ирина", "declension": "a" },
{ "form": "Ксения", "declension": "iya" },
{
"form": "Любовь",
"cases": {
"nom": "Любовь",
"gen": "Любови",
"dat": "Любови",
"acc": "Любовь",
"ins": "Любовью",
"pre": "Любови",
},
},
{ "form": "Людмила", "declension": "a" },
{ "form": "Маргарита", "declension": "a" },
{ "form": "Мария", "declension": "iya" },
{ "form": "Надежда", "declension": "a" },
{ "form": "Наталья", "declension": "ya" },
{ "form": "Нина", "declension": "a" },
{ "form": "Оксана", "declension": "a" },
{ "form": "Ольга", "declension": "a" },
{ "form": "Полина", "declension": "a" },
{ "form": "Светлана", "declension": "a" },
{ "form": "София", "declension": "iya" },
{ "form": "Татьяна", "declension": "a" },
{ "form": "Юлия", "declension": "iya" },
{ "form": "Яна", "declension": "a" },
{ "form": "Вероника", "declension": "a" },
{ "form": "Диана", "declension": "a" },
{ "form": "Марина", "declension": "a" },
],
"surnames": [
{ "male": "Иванов", "female": "Иванова" },
{ "male": "Петров", "female": "Петрова" },
{ "male": "Смирнов", "female": "Смирнова" },
{ "male": "Кузнецов", "female": "Кузнецова" },
{ "male": "Попов", "female": "Попова" },
{ "male": "Васильев", "female": "Васильева" },
{ "male": "Соколов", "female": "Соколова" },
{ "male": "Михайлов", "female": "Михайлова" },
{ "male": "Новиков", "female": "Новикова" },
{ "male": "Фёдоров", "female": "Фёдорова" },
{ "male": "Морозов", "female": "Морозова" },
{ "male": "Волков", "female": "Волкова" },
{ "male": "Алексеев", "female": "Алексеева" },
{ "male": "Лебедев", "female": "Лебедева" },
{ "male": "Семёнов", "female": "Семёнова" },
{ "male": "Егоров", "female": "Егорова" },
{ "male": "Павлов", "female": "Павлова" },
{ "male": "Козлов", "female": "Козлова" },
{ "male": "Степанов", "female": "Степанова" },
{ "male": "Николаев", "female": "Николаева" },
{ "male": "Орлов", "female": "Орлова" },
{ "male": "Андреев", "female": "Андреева" },
{ "male": "Макаров", "female": "Макарова" },
{ "male": "Никитин", "female": "Никитина", "declension": "in" },
{ "male": "Захаров", "female": "Захарова" },
{ "male": "Зайцев", "female": "Зайцева" },
{ "male": "Соловьёв", "female": "Соловьёва" },
{ "male": "Борисов", "female": "Борисова" },
{ "male": "Яковлев", "female": "Яковлева" },
{ "male": "Григорьев", "female": "Григорьева" },
{ "male": "Романов", "female": "Романова" },
{ "male": "Воробьёв", "female": "Воробьёва" },
{ "male": "Сергеев", "female": "Сергеева" },
{ "male": "Кузьмин", "female": "Кузьмина", "declension": "in" },
{ "male": "Фролов", "female": "Фролова" },
{ "male": "Александров", "female": "Александрова" },
{ "male": "Дмитриев", "female": "Дмитриева" },
{ "male": "Королёв", "female": "Королёва" },
{ "male": "Громов", "female": "Громова" },
{ "male": "Ильин", "female": "Ильина", "declension": "in" },
{ "male": "Козловский", "female": "Козловская", "declension": "sky" },
{ "male": "Орловский", "female": "Орловская", "declension": "sky" },
],
},
}
@@ -1,136 +0,0 @@
{
"defName": "Slavic",
"patronymicRule": "slavic",
"defaultGivenDeclension": "hard",
"defaultSurnameDeclension": "ov",
"nativeLanguages": ["RussianLanguage", "BelarusianLanguage", "UkrainianLanguage"],
"relatedLanguageChance": 0.6,
"relatedLanguageMean": 22,
"relatedLanguageStdDev": 8,
"relatedLanguageMax": 40,
"maleGiven": [
{ "form": "Александр" },
{ "form": "Алексей" },
{ "form": "Андрей" },
{ "form": "Антон" },
{ "form": "Артём" },
{ "form": "Борис" },
{ "form": "Вадим" },
{ "form": "Валерий" },
{ "form": "Виктор" },
{ "form": "Виталий" },
{ "form": "Владимир" },
{ "form": "Владислав" },
{ "form": "Григорий" },
{ "form": "Денис" },
{ "form": "Дмитрий" },
{ "form": "Евгений" },
{ "form": "Егор" },
{ "form": "Иван" },
{ "form": "Игорь", "declension": "soft" },
{ "form": "Илья", "declension": "ya" },
{ "form": "Кирилл" },
{ "form": "Константин" },
{ "form": "Максим" },
{ "form": "Михаил" },
{ "form": "Никита", "declension": "a" },
{ "form": "Николай" },
{ "form": "Олег" },
{ "form": "Павел" },
{ "form": "Пётр" },
{ "form": "Роман" },
{ "form": "Сергей" },
{ "form": "Станислав" },
{ "form": "Степан" },
{ "form": "Фёдор" },
{ "form": "Юрий" },
],
"femaleGiven": [
{ "form": "Александра", "declension": "a" },
{ "form": "Алина", "declension": "a" },
{ "form": "Анастасия", "declension": "iya" },
{ "form": "Анна", "declension": "a" },
{ "form": "Валентина", "declension": "a" },
{ "form": "Валерия", "declension": "iya" },
{ "form": "Вера", "declension": "a" },
{ "form": "Виктория", "declension": "iya" },
{ "form": "Дарья", "declension": "ya" },
{ "form": "Екатерина", "declension": "a" },
{ "form": "Елена", "declension": "a" },
{ "form": "Елизавета", "declension": "a" },
{ "form": "Ирина", "declension": "a" },
{ "form": "Ксения", "declension": "iya" },
{
"form": "Любовь",
"cases": {
"nom": "Любовь",
"gen": "Любови",
"dat": "Любови",
"acc": "Любовь",
"ins": "Любовью",
"pre": "Любови",
},
},
{ "form": "Людмила", "declension": "a" },
{ "form": "Маргарита", "declension": "a" },
{ "form": "Мария", "declension": "iya" },
{ "form": "Надежда", "declension": "a" },
{ "form": "Наталья", "declension": "ya" },
{ "form": "Нина", "declension": "a" },
{ "form": "Оксана", "declension": "a" },
{ "form": "Ольга", "declension": "a" },
{ "form": "Полина", "declension": "a" },
{ "form": "Светлана", "declension": "a" },
{ "form": "София", "declension": "iya" },
{ "form": "Татьяна", "declension": "a" },
{ "form": "Юлия", "declension": "iya" },
{ "form": "Яна", "declension": "a" },
{ "form": "Вероника", "declension": "a" },
{ "form": "Диана", "declension": "a" },
{ "form": "Марина", "declension": "a" },
],
"surnames": [
{ "male": "Иванов", "female": "Иванова" },
{ "male": "Петров", "female": "Петрова" },
{ "male": "Смирнов", "female": "Смирнова" },
{ "male": "Кузнецов", "female": "Кузнецова" },
{ "male": "Попов", "female": "Попова" },
{ "male": "Васильев", "female": "Васильева" },
{ "male": "Соколов", "female": "Соколова" },
{ "male": "Михайлов", "female": "Михайлова" },
{ "male": "Новиков", "female": "Новикова" },
{ "male": "Фёдоров", "female": "Фёдорова" },
{ "male": "Морозов", "female": "Морозова" },
{ "male": "Волков", "female": "Волкова" },
{ "male": "Алексеев", "female": "Алексеева" },
{ "male": "Лебедев", "female": "Лебедева" },
{ "male": "Семёнов", "female": "Семёнова" },
{ "male": "Егоров", "female": "Егорова" },
{ "male": "Павлов", "female": "Павлова" },
{ "male": "Козлов", "female": "Козлова" },
{ "male": "Степанов", "female": "Степанова" },
{ "male": "Николаев", "female": "Николаева" },
{ "male": "Орлов", "female": "Орлова" },
{ "male": "Андреев", "female": "Андреева" },
{ "male": "Макаров", "female": "Макарова" },
{ "male": "Никитин", "female": "Никитина", "declension": "in" },
{ "male": "Захаров", "female": "Захарова" },
{ "male": "Зайцев", "female": "Зайцева" },
{ "male": "Соловьёв", "female": "Соловьёва" },
{ "male": "Борисов", "female": "Борисова" },
{ "male": "Яковлев", "female": "Яковлева" },
{ "male": "Григорьев", "female": "Григорьева" },
{ "male": "Романов", "female": "Романова" },
{ "male": "Воробьёв", "female": "Воробьёва" },
{ "male": "Сергеев", "female": "Сергеева" },
{ "male": "Кузьмин", "female": "Кузьмина", "declension": "in" },
{ "male": "Фролов", "female": "Фролова" },
{ "male": "Александров", "female": "Александрова" },
{ "male": "Дмитриев", "female": "Дмитриева" },
{ "male": "Королёв", "female": "Королёва" },
{ "male": "Громов", "female": "Громова" },
{ "male": "Ильин", "female": "Ильина", "declension": "in" },
{ "male": "Козловский", "female": "Козловская", "declension": "sky" },
{ "male": "Орловский", "female": "Орловская", "declension": "sky" },
],
}
@@ -104,7 +104,9 @@
"Hunger": "Hunger",
"Toilet": "Toilet",
"Social": "Social",
"Slavic": "Slavic",
"Russia": "Russia",
"TemperateContinental": "Temperate continental",
"ContinentalCold": "Cold continental",
"Build": "Build",
"Skinny": "Skinny",
"Average": "Average",
@@ -104,7 +104,9 @@
"Hunger": "Голод",
"Toilet": "Туалет",
"Social": "Общение",
"Slavic": "Славянский",
"Russia": "Россия",
"TemperateContinental": "Умеренно-континентальный",
"ContinentalCold": "Континентальный холодный",
"Build": "Телосложение",
"Skinny": "Худощавое",
"Average": "Обычное",
+1 -1
View File
@@ -10,7 +10,7 @@ example/
pack.jsonc # version + requires: ["core"]
localizations/{ru,en}.jsonc
defs/traits/traits.jsonc # two traits
defs/namesets/example.jsonc
defs/countries/example.jsonc
defs/rooms/store.jsonc # one placeable room
defs/things/chair.jsonc # last-wins: same defName as core
patches/principals-office.jsonc
@@ -0,0 +1,20 @@
{
"defName": "ExampleNames",
"climatePresets": ["TemperateContinental"],
"names": {
"patronymicRule": "slavic",
"defaultGivenDeclension": "hard",
"defaultSurnameDeclension": "ov",
"nativeLanguages": ["RussianLanguage"],
"relatedLanguageChance": 0,
"maleGiven": [
{ "form": "Иван" },
],
"femaleGiven": [
{ "form": "Анна", "declension": "a" },
],
"surnames": [
{ "male": "Тестов", "female": "Тестова" },
],
},
}
@@ -1,17 +0,0 @@
{
"defName": "ExampleNames",
"patronymicRule": "slavic",
"defaultGivenDeclension": "hard",
"defaultSurnameDeclension": "ov",
"nativeLanguages": ["RussianLanguage"],
"relatedLanguageChance": 0,
"maleGiven": [
{ "form": "Иван" },
],
"femaleGiven": [
{ "form": "Анна", "declension": "a" },
],
"surnames": [
{ "male": "Тестов", "female": "Тестова" },
],
}