From 26be7c87f8009434ac92217635cc6671174b52a0 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Thu, 20 Aug 2026 07:58:12 +0300 Subject: [PATCH 1/3] WIP phase 39: school owners, my/others menu, guest restrictions. --- docs/protocol.md | 29 ++- src/HSchool.AppHost/AppHost.cs | 1 + src/HSchool.Client/src/i18n/strings.ts | 8 + src/HSchool.Client/src/net/api.ts | 15 ++ src/HSchool.Client/src/ui/dom.ts | 10 + src/HSchool.Client/src/ui/gameScreen.test.ts | 24 +++ src/HSchool.Client/src/ui/gameScreen.ts | 16 ++ src/HSchool.Client/src/ui/mainMenu.ts | 103 ++++++++--- src/HSchool.Client/src/ui/schoolCard.test.ts | 32 ++++ src/HSchool.Client/src/ui/schoolCard.ts | 35 +++- src/HSchool.Server/Api/SchoolAccess.cs | 54 ++++++ src/HSchool.Server/Api/SchoolEndpoints.cs | 171 ++++++++++++++++-- src/HSchool.Server/Api/TimetableEndpoints.cs | 27 +++ src/HSchool.Server/Game/GameCommand.cs | 7 +- src/HSchool.Server/Game/GameLoopService.cs | 86 +++++++-- src/HSchool.Server/Game/SchoolOwnership.cs | 23 +++ src/HSchool.Server/Game/SchoolState.cs | 1 + src/HSchool.Server/Game/SchoolStore.cs | 4 + src/HSchool.Server/Game/SchoolWorker.cs | 24 ++- src/HSchool.Server/Net/GameSocketHandler.cs | 22 ++- src/HSchool.Server/Program.cs | 1 + src/HSchool.Server/appsettings.json | 3 +- src/HSchool.Simulation/SchoolCreationError.cs | 1 + src/HSchool.Simulation/SimulationOptions.cs | 7 +- .../HSchool.AppHost.Tests/GameSocketTests.cs | 35 +++- tests/HSchool.AppHost.Tests/SchoolApiTests.cs | 97 +++++++++- .../SchoolOwnerApiTests.cs | 164 +++++++++++++++++ 27 files changed, 919 insertions(+), 81 deletions(-) create mode 100644 src/HSchool.Client/src/ui/schoolCard.test.ts create mode 100644 src/HSchool.Server/Api/SchoolAccess.cs create mode 100644 src/HSchool.Server/Game/SchoolOwnership.cs create mode 100644 tests/HSchool.AppHost.Tests/SchoolOwnerApiTests.cs diff --git a/docs/protocol.md b/docs/protocol.md index 60e39c0..da1de52 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -76,16 +76,26 @@ people; it does not change on a living school. ```json { - "maxSchools": 6, + "maxSchools": 2, + "maxSchoolsTotal": 16, "defaultStartDate": "2012-03-31T06:00:00Z", "gameMinutesPerRealSecond": 1, "schoolWeekDays": 5, "schools": [ - { "id": 1, "name": "Гимназия №14", "gameTime": "2012-03-31T07:35:00Z", "running": false, "speedIndex": 1, "modIds": ["core"], "seed": 1847291 } + { "id": 1, "name": "Гимназия №14", "gameTime": "2012-03-31T07:35:00Z", "running": false, "speedIndex": 1, "modIds": ["core"], "seed": 1847291, "mine": true } + ], + "others": [ + { "id": 2, "name": "Лицей", "gameTime": "2012-03-31T06:00:00Z", "running": true, "speedIndex": 1, "modIds": ["core"], "seed": 9912, "owner": "Leo" } ] } ``` +`maxSchools` is how many schools **this player** may own; `maxSchoolsTotal` is how many workers the +process runs. `schools` lists yours (`mine: true` on each card). `others` lists every other save on +the server — `owner` is the display name, or `null` when the save has no owner (any logged-in player +may delete an ownerless card). The WebSocket welcome frame still carries one byte for +`maxSchools`; it now means the same per-player limit, not the process total. + ### `GET /api/schools/random-name` `{ "name": "Лицей «Северная»" }` — a suggestion that is not already taken. @@ -191,13 +201,15 @@ the server rolls one. Existing saves keep the seed already stored in the people | `400` `invalid-catalog` | The selected packs could not be loaded. | | `400` `unknown-country` | `countryId` is not a placeable `CountryDef` in those packs. | | `400` `unknown-native-language` | `nativeLanguage` is not in that country's `nativeLanguages`. | -| `409` `school-limit-reached` | `maxSchools` schools already exist. | +| `409` `school-limit-reached` | This player already owns `maxSchools` schools. | +| `409` `server-full` | The process already runs `maxSchoolsTotal` schools. | Failures are RFC 7807 problem details with an extra `code` field — that is what the UI switches on. ### `DELETE /api/schools/{id}` -`204` when deleted, `404` when the id is unknown. Anyone watching that school over a WebSocket +`204` when deleted, `404` when the id is unknown, `403` `not-owner` when the school belongs to +another player. Ownerless saves may be deleted by any logged-in session. Anyone watching that school over a WebSocket gets a `SchoolGone` frame. ### `GET /api/schools/{id}/people` @@ -456,7 +468,8 @@ is off or unreachable, `connected` is false and the lists are empty. ### `GET /api/schools/{id}/dress-rules` -Student and staff dress-code pairs for the school. Unknown `{id}` is `404` `unknown-school`. +Student and staff dress-code pairs for the school. Only the owner may read this (`403` `not-owner`). +Unknown `{id}` is `404` `unknown-school`. `form` is one of `regular`, `short`, `strict`. `color` is one of `noBright`, `whiteTopBlackBottom`, `free`. When a `POST` has been accepted but not yet applied, `pendingStudents` and/or `pendingStaff` @@ -474,7 +487,7 @@ not immediately. ### `POST /api/schools/{id}/dress-rules` -Queues a change for the next work morning. Either or both of `students` and `staff` may be sent; +Queues a change for the next work morning. Only the owner may post (`403` `not-owner`). Either or both of `students` and `staff` may be sent; omitted sides keep their current rule. Unknown `{id}` is `404` `unknown-school`. Unknown `form` or `color` is `400` `unknown-form` / `400` `unknown-color`. Response body matches `GET`. @@ -484,7 +497,8 @@ omitted sides keep their current rule. Unknown `{id}` is `404` `unknown-school`. ### `GET /api/schools/{id}/staffing` -Money, uncovered subjects, the applicant pool and current staff. Reads the **published** +Money, uncovered subjects, the applicant pool and current staff. Only the owner may read this +(`403` `not-owner`). Reads the **published** roster, applicant snapshot and catalog — it does not post to the worker. Unknown `{id}` is `404` `unknown-school`. `?lang=ru|en` labels subjects and positions. @@ -578,6 +592,7 @@ of that kind. | Status | `code` | When | | --- | --- | --- | +| `403` | `not-owner` | The session is not the school's owner. | | `404` | `unknown-school` | No school with that id. | | `404` | `unknown-applicant` | `personId` is not in the pool. | | `409` | `already-hired` | That person is already staff. | diff --git a/src/HSchool.AppHost/AppHost.cs b/src/HSchool.AppHost/AppHost.cs index 16ca242..5464a3c 100644 --- a/src/HSchool.AppHost/AppHost.cs +++ b/src/HSchool.AppHost/AppHost.cs @@ -17,6 +17,7 @@ if (headless) .WithEnvironment("Simulation__SavesDirectory", saves) .WithEnvironment("HSchool__AllowSaveReload", "true") .WithEnvironment("HSchool__AlphaPassword", "test-alpha") + .WithEnvironment("Simulation__MaxSchoolsTotal", "7") .WithEnvironment("SwarmUi__BaseUrl", ""); } diff --git a/src/HSchool.Client/src/i18n/strings.ts b/src/HSchool.Client/src/i18n/strings.ts index d27ad6c..c884b5e 100644 --- a/src/HSchool.Client/src/i18n/strings.ts +++ b/src/HSchool.Client/src/i18n/strings.ts @@ -23,6 +23,10 @@ const ru = { sessionLogout: 'Выйти', schoolsTitle: 'Школы', + schoolsMine: 'Мои', + schoolsOthers: 'Чужие', + schoolOwner: 'Хозяин: {owner}', + schoolOwnerless: '—', createSchool: 'Создать школу', settings: 'Настройки', save: 'Сохранить', @@ -365,6 +369,10 @@ const en: Messages = { sessionLogout: 'Sign out', schoolsTitle: 'Schools', + schoolsMine: 'Mine', + schoolsOthers: 'Others', + schoolOwner: 'Owner: {owner}', + schoolOwnerless: '—', createSchool: 'Create school', settings: 'Settings', save: 'Save', diff --git a/src/HSchool.Client/src/net/api.ts b/src/HSchool.Client/src/net/api.ts index 32e3751..3eafa13 100644 --- a/src/HSchool.Client/src/net/api.ts +++ b/src/HSchool.Client/src/net/api.ts @@ -13,14 +13,29 @@ export interface School { readonly modIds?: readonly string[]; /** Roster generator seed. Independent of `id`; share it to recreate the same people. */ readonly seed: number; + readonly mine: boolean; +} + +export interface OtherSchool { + readonly id: number; + readonly name: string; + readonly gameTime: string; + readonly running: boolean; + readonly speedIndex: number; + readonly modIds?: readonly string[]; + readonly seed: number; + /** Display name of the owner, or null when ownerless. */ + readonly owner: string | null; } export interface SchoolsResponse { readonly maxSchools: number; + readonly maxSchoolsTotal: number; readonly defaultStartDate: string; readonly gameMinutesPerRealSecond: number; readonly schoolWeekDays: number; readonly schools: readonly School[]; + readonly others: readonly OtherSchool[]; } /** A failed request, with the machine-readable `code` the server puts in its problem details. */ diff --git a/src/HSchool.Client/src/ui/dom.ts b/src/HSchool.Client/src/ui/dom.ts index 2f5622d..7fa470b 100644 --- a/src/HSchool.Client/src/ui/dom.ts +++ b/src/HSchool.Client/src/ui/dom.ts @@ -14,6 +14,8 @@ interface ElementOptions { placeholder?: string; value?: string; rows?: number; + autocomplete?: string; + maxlength?: string; dataset?: Record; onClick?: (event: Event) => void; onInput?: (event: Event) => void; @@ -44,6 +46,14 @@ export function el( (element as HTMLInputElement | HTMLTextAreaElement).placeholder = options.placeholder; } + if (options.autocomplete !== undefined && 'autocomplete' in element) { + (element as HTMLInputElement).autocomplete = options.autocomplete; + } + + if (options.maxlength !== undefined && 'maxLength' in element) { + (element as HTMLInputElement).maxLength = Number.parseInt(options.maxlength, 10); + } + if (options.value !== undefined && 'value' in element) { (element as HTMLInputElement | HTMLTextAreaElement).value = options.value; } diff --git a/src/HSchool.Client/src/ui/gameScreen.test.ts b/src/HSchool.Client/src/ui/gameScreen.test.ts index 823ca01..567b5f6 100644 --- a/src/HSchool.Client/src/ui/gameScreen.test.ts +++ b/src/HSchool.Client/src/ui/gameScreen.test.ts @@ -22,4 +22,28 @@ describe('GameScreen speed buttons', () => { expect(labels).toEqual(['×½', '×1', '×2', '×5', '×10']); }); + + it('hides management and clock controls for a guest school', () => { + const screen = new GameScreen({ + onLeave: () => {}, + onSetRunning: () => {}, + onSetSpeed: () => {}, + onSkip: () => {}, + }); + + document.body.append(screen.element); + screen.show({ + id: 3, + name: 'Foreign', + gameTime: '2012-03-31T06:00:00.000Z', + running: true, + speedIndex: 1, + seed: 9, + mine: false, + }); + + const manageTab = screen.element.querySelectorAll('.mode-tab')[1]; + expect(manageTab?.hidden).toBe(true); + expect(screen.element.querySelector('.clock__controls')?.hidden).toBe(true); + }); }); diff --git a/src/HSchool.Client/src/ui/gameScreen.ts b/src/HSchool.Client/src/ui/gameScreen.ts index ed65350..8ed4a87 100644 --- a/src/HSchool.Client/src/ui/gameScreen.ts +++ b/src/HSchool.Client/src/ui/gameScreen.ts @@ -82,6 +82,7 @@ export class GameScreen { private directoryToken = 0; private selectedId: string | null = null; private schoolId: number | null = null; + private canManage = true; private peopleSeed: number | null = null; private running = false; private lastGameTime: Date | null = null; @@ -209,6 +210,17 @@ export class GameScreen { /** Called when the screen opens, before the first clock frame and snapshot arrive. */ show(school: School): void { + this.canManage = school.mine; + this.manageTab.hidden = !this.canManage; + if (!this.canManage) { + this.showMode('overview'); + } + + const clockControls = this.root.querySelector('.clock__controls'); + if (clockControls !== null) { + clockControls.hidden = !this.canManage; + } + this.schoolId = school.id; this.peopleSeed = school.seed; this.schoolName.textContent = school.name; @@ -311,6 +323,10 @@ export class GameScreen { } private showMode(mode: 'overview' | 'manage'): void { + if (mode === 'manage' && !this.canManage) { + mode = 'overview'; + } + this.overviewTab.classList.toggle('mode-tab--active', mode === 'overview'); this.manageTab.classList.toggle('mode-tab--active', mode === 'manage'); this.overview.hidden = mode !== 'overview'; diff --git a/src/HSchool.Client/src/ui/mainMenu.ts b/src/HSchool.Client/src/ui/mainMenu.ts index 1aefd8d..aeae31c 100644 --- a/src/HSchool.Client/src/ui/mainMenu.ts +++ b/src/HSchool.Client/src/ui/mainMenu.ts @@ -3,6 +3,7 @@ import { deleteSchool, fetchRandomName, fetchSchools, + type OtherSchool, type School, type SchoolsResponse, } from '../net/api.ts'; @@ -12,7 +13,7 @@ import { el } from './dom.ts'; import { confirmDialog } from './confirmDialog.ts'; import { createSchoolDialog } from './createSchoolDialog.ts'; import { swarmUiSettingsDialog } from './swarmUiSettingsDialog.ts'; -import { SchoolCard } from './schoolCard.ts'; +import { SchoolCard, type MenuSchool } from './schoolCard.ts'; interface MainMenuOptions { readonly onOpenSchool: (school: School) => void; @@ -28,10 +29,18 @@ interface MainMenuOptions { */ const REFRESH_INTERVAL_MS = 1000; +interface CardEntry { + readonly card: SchoolCard; + readonly section: 'mine' | 'other'; +} + export class MainMenu { private readonly root = el('section', { class: 'screen menu' }); private readonly title = el('h1', { class: 'screen__title' }); - private readonly grid = el('div', { class: 'card-grid' }); + private readonly mineHeading = el('h2', { class: 'menu__heading' }); + private readonly mineGrid = el('div', { class: 'card-grid' }); + private readonly othersHeading = el('h2', { class: 'menu__heading' }); + private readonly othersGrid = el('div', { class: 'card-grid' }); private readonly emptyHint = el('p', { class: 'hint' }); private readonly createButton = el('button', { class: 'button button--primary', @@ -48,7 +57,7 @@ export class MainMenu { private readonly limitHint = el('p', { class: 'hint' }); private readonly status = el('p', { class: 'hint hint--error' }); - private readonly cards = new Map(); + private readonly cards = new Map(); private state: SchoolsResponse | null = null; private refreshTimer: ReturnType | null = null; @@ -71,7 +80,10 @@ export class MainMenu { this.limitHint, this.status, this.emptyHint, - this.grid, + this.mineHeading, + this.mineGrid, + this.othersHeading, + this.othersGrid, ); this.localize(); @@ -84,13 +96,15 @@ export class MainMenu { /** Re-applies strings after a language switch. Cards stay in place so focus is not dropped. */ localize(): void { this.title.textContent = t('schoolsTitle'); + this.mineHeading.textContent = t('schoolsMine'); + this.othersHeading.textContent = t('schoolsOthers'); this.createButton.textContent = t('createSchool'); this.settingsButton.textContent = t('settings'); this.logoutButton.textContent = t('sessionLogout'); this.emptyHint.textContent = t('emptySchools'); - for (const card of this.cards.values()) { - card.localize(); + for (const entry of this.cards.values()) { + entry.card.localize(); } this.paintError(); @@ -103,8 +117,6 @@ export class MainMenu { this.stop(); - // No visibility check here: browsers already throttle timers in background tabs, and a - // hidden-tab guard silently freezes the list in embedded views that report themselves hidden. this.refreshTimer = setInterval(() => void this.refresh(), REFRESH_INTERVAL_MS); } @@ -158,35 +170,67 @@ export class MainMenu { : t('schoolCount', { current: state.schools.length, max: state.maxSchools }); this.emptyHint.hidden = state.schools.length > 0; + this.othersHeading.hidden = state.others.length === 0; + this.othersGrid.hidden = state.others.length === 0; - // Patch the cards that are already on screen; only added and removed schools touch the DOM. const seen = new Set(); + for (const school of state.schools) { seen.add(school.id); - - const card = this.cards.get(school.id); - if (card === undefined) { - const created = new SchoolCard(school, { - onOpen: (opened) => this.options.onOpenSchool(opened), - onDelete: (target) => void this.confirmDelete(target), - }); - - this.cards.set(school.id, created); - this.grid.appendChild(created.element); - } else { - card.update(school); - } + this.patchCard(school, 'mine', this.mineGrid, { + canDelete: true, + ownerLabel: null, + }); } - for (const [id, card] of this.cards) { + for (const other of state.others) { + seen.add(other.id); + const menuSchool: MenuSchool = { ...other, mine: false }; + this.patchCard(menuSchool, 'other', this.othersGrid, { + canDelete: other.owner === null, + ownerLabel: other.owner ?? t('schoolOwnerless'), + }); + } + + for (const [id, entry] of this.cards) { if (!seen.has(id)) { - card.element.remove(); + entry.card.element.remove(); this.cards.delete(id); } } } - private async confirmDelete(school: School): Promise { + private patchCard( + school: MenuSchool, + section: 'mine' | 'other', + grid: HTMLElement, + options: { canDelete: boolean; ownerLabel: string | null }, + ): void { + const entry = this.cards.get(school.id); + if (entry === undefined) { + const card = new SchoolCard(school, { + onOpen: (opened) => this.options.onOpenSchool(toSchool(opened)), + onDelete: (target) => void this.confirmDelete(target), + canDelete: options.canDelete, + ownerLabel: options.ownerLabel, + }); + this.cards.set(school.id, { card, section }); + grid.appendChild(card.element); + return; + } + + if (entry.section !== section) { + entry.card.element.remove(); + grid.appendChild(entry.card.element); + this.cards.set(school.id, { card: entry.card, section }); + } + + entry.card.setOwnerLabel(options.ownerLabel); + entry.card.setCanDelete(options.canDelete); + entry.card.update(school); + } + + private async confirmDelete(school: MenuSchool): Promise { const confirmed = await confirmDialog({ title: t('deleteSchoolTitle'), message: t('deleteSchoolMessage', { name: school.name }), @@ -227,3 +271,12 @@ export class MainMenu { await this.refresh(); } } + +function toSchool(school: MenuSchool): School { + if ('mine' in school && school.mine) { + return school; + } + + const { owner: _owner, ...rest } = school as OtherSchool & { mine: false }; + return { ...rest, mine: false }; +} diff --git a/src/HSchool.Client/src/ui/schoolCard.test.ts b/src/HSchool.Client/src/ui/schoolCard.test.ts new file mode 100644 index 0000000..0d8d8cb --- /dev/null +++ b/src/HSchool.Client/src/ui/schoolCard.test.ts @@ -0,0 +1,32 @@ +/** + * @vitest-environment happy-dom + */ +import { describe, expect, it, vi } from 'vitest'; +import { SchoolCard } from './schoolCard.ts'; + +describe('SchoolCard ownership', () => { + it('hides delete on another owners school card', () => { + const card = new SchoolCard( + { + id: 2, + name: 'Foreign', + gameTime: '2012-03-31T06:00:00.000Z', + running: true, + speedIndex: 1, + seed: 1, + mine: false, + owner: 'Bob', + }, + { + onOpen: () => {}, + onDelete: () => {}, + canDelete: false, + ownerLabel: 'Bob', + }, + ); + + document.body.append(card.element); + + expect(card.element.querySelector('.button--danger')?.hidden).toBe(true); + }); +}); diff --git a/src/HSchool.Client/src/ui/schoolCard.ts b/src/HSchool.Client/src/ui/schoolCard.ts index 072a549..8c2222d 100644 --- a/src/HSchool.Client/src/ui/schoolCard.ts +++ b/src/HSchool.Client/src/ui/schoolCard.ts @@ -1,11 +1,15 @@ -import type { School } from '../net/api.ts'; +import type { OtherSchool, School } from '../net/api.ts'; import { formatGameDateTime } from '../format/gameTime.ts'; import { t } from '../i18n/strings.ts'; import { el } from './dom.ts'; +export type MenuSchool = School | (OtherSchool & { mine: false }); + interface SchoolCardOptions { - readonly onOpen: (school: School) => void; - readonly onDelete: (school: School) => void; + readonly onOpen: (school: MenuSchool) => void; + readonly onDelete: (school: MenuSchool) => void; + readonly canDelete: boolean; + readonly ownerLabel?: string | null; } /** @@ -16,6 +20,7 @@ export class SchoolCard { readonly element = el('article', { class: 'card' }); private readonly title = el('h2', { class: 'card__title' }); + private readonly owner = el('p', { class: 'card__owner hint' }); private readonly time = el('p', { class: 'card__time' }); private readonly pausedBadge = el('span', { class: 'card__badge' }); private readonly deleteButton = el('button', { @@ -23,13 +28,14 @@ export class SchoolCard { type: 'button', }); - private school: School; + private school: MenuSchool; - constructor(school: School, options: SchoolCardOptions) { + constructor(school: MenuSchool, options: SchoolCardOptions) { this.school = school; this.element.dataset['schoolId'] = String(school.id); this.element.tabIndex = 0; + this.deleteButton.hidden = !options.canDelete; this.deleteButton.addEventListener('click', (event) => { // The whole card is clickable, so the delete button must not open the school too. event.stopPropagation(); @@ -38,6 +44,7 @@ export class SchoolCard { this.element.append( this.title, + this.owner, el('div', { class: 'card__meta' }, this.time, this.pausedBadge), el('div', { class: 'card__actions' }, this.deleteButton), ); @@ -50,6 +57,7 @@ export class SchoolCard { } }); + this.setOwnerLabel(options.ownerLabel ?? null); this.localize(); this.update(school); } @@ -60,11 +68,26 @@ export class SchoolCard { this.update(this.school); } - update(school: School): void { + setOwnerLabel(label: string | null): void { + if (label === null) { + this.owner.hidden = true; + this.owner.textContent = ''; + return; + } + + this.owner.hidden = false; + this.owner.textContent = t('schoolOwner', { owner: label }); + } + + update(school: MenuSchool): void { this.school = school; this.title.textContent = school.name; this.time.textContent = formatGameDateTime(new Date(school.gameTime)); this.pausedBadge.hidden = school.running; } + + setCanDelete(canDelete: boolean): void { + this.deleteButton.hidden = !canDelete; + } } diff --git a/src/HSchool.Server/Api/SchoolAccess.cs b/src/HSchool.Server/Api/SchoolAccess.cs new file mode 100644 index 0000000..9081a8e --- /dev/null +++ b/src/HSchool.Server/Api/SchoolAccess.cs @@ -0,0 +1,54 @@ +using HSchool.Server.Game; +using HSchool.Server.Session; +using HSchool.Simulation; + +namespace HSchool.Server.Api; + +internal static class SchoolAccess +{ + public static bool TryGetNormalizedUser(HttpContext context, SessionService sessions, out string normalized) + { + normalized = ""; + return sessions.TryGetUserName(context, out var userName) + && SchoolNames.TryNormalize(userName, out normalized); + } + + public static IResult? RequireManage(GameLoopService loop, int schoolId, string normalizedUserName) + { + var school = loop.FindSchool(schoolId); + if (school is null) + { + return NotFoundSchool(); + } + + if (!SchoolOwnership.CanManage(school, normalizedUserName)) + { + return NotOwner(); + } + + return null; + } + + public static string? DisplayOwner(UserStore users, string? normalizedOwner) + { + if (string.IsNullOrWhiteSpace(normalizedOwner)) + { + return null; + } + + return users.TryFindCanonical(normalizedOwner, out var display) ? display : normalizedOwner; + } + + public static IResult NotOwner() => + Problem(StatusCodes.Status403Forbidden, "not-owner", "You do not own this school."); + + public static IResult NotFoundSchool() => + Problem(StatusCodes.Status404NotFound, "unknown-school", "That school does not exist."); + + private static IResult Problem(int statusCode, string code, string detail) => + Results.Problem( + detail: detail, + statusCode: statusCode, + title: code, + extensions: new Dictionary { ["code"] = code }); +} diff --git a/src/HSchool.Server/Api/SchoolEndpoints.cs b/src/HSchool.Server/Api/SchoolEndpoints.cs index b269c1a..1a9aeeb 100644 --- a/src/HSchool.Server/Api/SchoolEndpoints.cs +++ b/src/HSchool.Server/Api/SchoolEndpoints.cs @@ -1,6 +1,7 @@ using HSchool.Content; using HSchool.People; using HSchool.Server.Game; +using HSchool.Server.Session; using HSchool.Simulation; namespace HSchool.Server.Api; @@ -19,17 +20,37 @@ internal static class SchoolEndpoints { var schools = builder.MapGroup("/api/schools"); - schools.MapGet("/", (GameLoopService loop) => + schools.MapGet("/", (HttpContext context, GameLoopService loop, SessionService sessions, UserStore users) => { - var state = loop.SchoolsState; - var options = loop.Options; + if (!SchoolAccess.TryGetNormalizedUser(context, sessions, out var normalizedUser)) + { + return Results.Unauthorized(); + } - return new SchoolsResponse( - state.MaxSchools, + var options = loop.Options; + var mine = new List(); + var others = new List(); + + foreach (var school in loop.SchoolsState.Schools) + { + if (SchoolOwnership.IsOwner(school, normalizedUser)) + { + mine.Add(SchoolResponse.From(school, mine: true)); + } + else + { + others.Add(OtherSchoolResponse.From(school, SchoolAccess.DisplayOwner(users, school.Owner))); + } + } + + return Results.Ok(new SchoolsResponse( + options.MaxSchools, + options.MaxSchoolsTotal, options.DefaultStartDate, options.GameMinutesPerRealSecond, options.SchoolWeekDays, - [.. state.Schools.Select(SchoolResponse.From)]); + mine, + others)); }) .WithName("GetSchools"); @@ -45,9 +66,16 @@ internal static class SchoolEndpoints schools.MapPost("/", async ( CreateSchoolRequest request, + HttpContext context, + SessionService sessions, GameCommandQueue commands, CancellationToken cancellationToken) => { + if (!SchoolAccess.TryGetNormalizedUser(context, sessions, out var owner)) + { + return Results.Unauthorized(); + } + var command = new GameCommand.CreateSchool( request.Name ?? string.Empty, DateTime.SpecifyKind(request.StartDate, DateTimeKind.Utc), @@ -56,6 +84,7 @@ internal static class SchoolEndpoints request.CountryId, request.NativeLanguage, request.Seed, + owner, NewCompletion()); commands.Enqueue(command); @@ -64,9 +93,11 @@ internal static class SchoolEndpoints return outcome.Error switch { SchoolCreationError.None => - Results.Created($"/api/schools/{outcome.School!.Id}", SchoolResponse.From(outcome.School)), + Results.Created($"/api/schools/{outcome.School!.Id}", SchoolResponse.From(outcome.School, mine: true)), SchoolCreationError.LimitReached => - Problem(StatusCodes.Status409Conflict, "school-limit-reached", "The school limit is already reached."), + Problem(StatusCodes.Status409Conflict, "school-limit-reached", "Your school limit is already reached."), + SchoolCreationError.ServerFull => + Problem(StatusCodes.Status409Conflict, "server-full", "The server cannot host any more schools."), SchoolCreationError.InvalidName => Problem(StatusCodes.Status400BadRequest, "invalid-name", $"A name must be 1 to {School.MaxNameLength} characters."), SchoolCreationError.InvalidStartDate => @@ -96,9 +127,28 @@ internal static class SchoolEndpoints schools.MapDelete("/{id:int}", async ( int id, + HttpContext context, + SessionService sessions, + GameLoopService loop, GameCommandQueue commands, CancellationToken cancellationToken) => { + if (!SchoolAccess.TryGetNormalizedUser(context, sessions, out var normalizedUser)) + { + return Results.Unauthorized(); + } + + var school = loop.FindSchool(id); + if (school is null) + { + return Results.NotFound(); + } + + if (!SchoolOwnership.CanDelete(school, normalizedUser)) + { + return SchoolAccess.NotOwner(); + } + var command = new GameCommand.DeleteSchool(id, NewCompletion()); commands.Enqueue(command); @@ -356,9 +406,23 @@ internal static class SchoolEndpoints schools.MapGet("/{id:int}/dress-rules", async ( int id, + HttpContext context, + SessionService sessions, + GameLoopService loop, GameCommandQueue commands, CancellationToken cancellationToken) => { + if (!SchoolAccess.TryGetNormalizedUser(context, sessions, out var normalizedUser)) + { + return Results.Unauthorized(); + } + + var denied = SchoolAccess.RequireManage(loop, id, normalizedUser); + if (denied is not null) + { + return denied; + } + var command = new GameCommand.GetDressRules(id, NewCompletion()); commands.Enqueue(command); var outcome = await command.Result.Task.WaitAsync(CommandTimeout, cancellationToken); @@ -369,9 +433,23 @@ internal static class SchoolEndpoints schools.MapPost("/{id:int}/dress-rules", async ( int id, SetDressRulesRequest request, + HttpContext context, + SessionService sessions, + GameLoopService loop, GameCommandQueue commands, CancellationToken cancellationToken) => { + if (!SchoolAccess.TryGetNormalizedUser(context, sessions, out var normalizedUser)) + { + return Results.Unauthorized(); + } + + var denied = SchoolAccess.RequireManage(loop, id, normalizedUser); + if (denied is not null) + { + return denied; + } + DressRulePair? students = null; if (request.Students is { } studentDto) { @@ -404,8 +482,21 @@ internal static class SchoolEndpoints schools.MapGet("/{id:int}/staffing", ( int id, string? lang, + HttpContext context, + SessionService sessions, GameLoopService loop) => { + if (!SchoolAccess.TryGetNormalizedUser(context, sessions, out var normalizedUser)) + { + return Results.Unauthorized(); + } + + var denied = SchoolAccess.RequireManage(loop, id, normalizedUser); + if (denied is not null) + { + return denied; + } + var published = loop.FindPeople(id); if (published is null) { @@ -420,10 +511,23 @@ internal static class SchoolEndpoints int id, HireStaffRequest request, string? lang, + HttpContext context, + SessionService sessions, GameCommandQueue commands, GameLoopService loop, CancellationToken cancellationToken) => { + if (!SchoolAccess.TryGetNormalizedUser(context, sessions, out var normalizedUser)) + { + return Results.Unauthorized(); + } + + var denied = SchoolAccess.RequireManage(loop, id, normalizedUser); + if (denied is not null) + { + return denied; + } + if (!TryPersonId(request.PersonId, out var personId, out var error) || !TryDefName(request.Position, "position", out var position, out error)) { @@ -442,10 +546,23 @@ internal static class SchoolEndpoints string personId, AssignSubjectRequest request, string? lang, + HttpContext context, + SessionService sessions, GameCommandQueue commands, GameLoopService loop, CancellationToken cancellationToken) => { + if (!SchoolAccess.TryGetNormalizedUser(context, sessions, out var normalizedUser)) + { + return Results.Unauthorized(); + } + + var denied = SchoolAccess.RequireManage(loop, id, normalizedUser); + if (denied is not null) + { + return denied; + } + if (!TryPersonId(personId, out var idValue, out var error) || !TryDefName(request.Subject, "subject", out var subject, out error)) { @@ -464,10 +581,23 @@ internal static class SchoolEndpoints string personId, string subject, string? lang, + HttpContext context, + SessionService sessions, GameCommandQueue commands, GameLoopService loop, CancellationToken cancellationToken) => { + if (!SchoolAccess.TryGetNormalizedUser(context, sessions, out var normalizedUser)) + { + return Results.Unauthorized(); + } + + var denied = SchoolAccess.RequireManage(loop, id, normalizedUser); + if (denied is not null) + { + return denied; + } + if (!TryPersonId(personId, out var idValue, out var error) || !TryDefName(subject, "subject", out var subjectName, out error)) { @@ -811,18 +941,35 @@ internal sealed record SchoolResponse( bool Running, byte SpeedIndex, IReadOnlyList ModIds, - int Seed) + int Seed, + bool Mine) { - public static SchoolResponse From(SchoolState school) => - new(school.Id, school.Name, school.GameTime, school.Running, school.SpeedIndex, school.ModIds, school.Seed); + public static SchoolResponse From(SchoolState school, bool mine) => + new(school.Id, school.Name, school.GameTime, school.Running, school.SpeedIndex, school.ModIds, school.Seed, mine); +} + +internal sealed record OtherSchoolResponse( + int Id, + string Name, + DateTime GameTime, + bool Running, + byte SpeedIndex, + IReadOnlyList ModIds, + int Seed, + string? Owner) +{ + public static OtherSchoolResponse From(SchoolState school, string? ownerDisplay) => + new(school.Id, school.Name, school.GameTime, school.Running, school.SpeedIndex, school.ModIds, school.Seed, ownerDisplay); } /// Everything the main menu needs in one request. internal sealed record SchoolsResponse( int MaxSchools, + int MaxSchoolsTotal, DateTime DefaultStartDate, double GameMinutesPerRealSecond, int SchoolWeekDays, - IReadOnlyList Schools); + IReadOnlyList Schools, + IReadOnlyList Others); internal sealed record RandomNameResponse(string Name); diff --git a/src/HSchool.Server/Api/TimetableEndpoints.cs b/src/HSchool.Server/Api/TimetableEndpoints.cs index 82967a4..adc34b6 100644 --- a/src/HSchool.Server/Api/TimetableEndpoints.cs +++ b/src/HSchool.Server/Api/TimetableEndpoints.cs @@ -1,6 +1,7 @@ using HSchool.People; using HSchool.Schedule; using HSchool.Server.Game; +using HSchool.Server.Session; namespace HSchool.Server.Api; @@ -33,10 +34,23 @@ internal static class TimetableEndpoints int id, PinLessonRequest request, string? lang, + HttpContext context, + SessionService sessions, GameCommandQueue commands, GameLoopService loop, CancellationToken cancellationToken) => { + if (!SchoolAccess.TryGetNormalizedUser(context, sessions, out var normalizedUser)) + { + return Results.Unauthorized(); + } + + var denied = SchoolAccess.RequireManage(loop, id, normalizedUser); + if (denied is not null) + { + return denied; + } + if (!TryDefName(request.ClassId, "classId", out var classId, out var error) || !TryDefName(request.Subject, "subject", out var subject, out error) || !TryDefName(request.RoomId, "roomId", out var roomId, out error)) @@ -65,10 +79,23 @@ internal static class TimetableEndpoints int? day, int? period, string? lang, + HttpContext context, + SessionService sessions, GameCommandQueue commands, GameLoopService loop, CancellationToken cancellationToken) => { + if (!SchoolAccess.TryGetNormalizedUser(context, sessions, out var normalizedUser)) + { + return Results.Unauthorized(); + } + + var denied = SchoolAccess.RequireManage(loop, id, normalizedUser); + if (denied is not null) + { + return denied; + } + if (!TryDefName(classId, "classId", out var classValue, out var error) || !TryDefName(subject, "subject", out var subjectValue, out error) || day is null diff --git a/src/HSchool.Server/Game/GameCommand.cs b/src/HSchool.Server/Game/GameCommand.cs index e1e2f0c..d8af893 100644 --- a/src/HSchool.Server/Game/GameCommand.cs +++ b/src/HSchool.Server/Game/GameCommand.cs @@ -19,6 +19,7 @@ internal abstract record GameCommand string? CountryId, string? NativeLanguage, int? Seed, + string Owner, TaskCompletionSource Result) : GameCommand; internal sealed record DeleteSchool(int SchoolId, TaskCompletionSource Result) : GameCommand; @@ -34,11 +35,11 @@ internal abstract record GameCommand /// internal sealed record CloseSchool(uint PlayerId, int SchoolId) : GameCommand; - internal sealed record SetRunning(uint PlayerId, bool Running) : GameCommand; + internal sealed record SetRunning(uint PlayerId, bool Running, string NormalizedUserName) : GameCommand; - internal sealed record SetSpeed(uint PlayerId, byte SpeedIndex) : GameCommand; + internal sealed record SetSpeed(uint PlayerId, byte SpeedIndex, string NormalizedUserName) : GameCommand; - internal sealed record SkipEmpty(uint PlayerId) : GameCommand; + internal sealed record SkipEmpty(uint PlayerId, string NormalizedUserName) : GameCommand; /// Stops every worker, re-reads the save directory, starts workers from those files. internal sealed record ReloadSaves(TaskCompletionSource Result) : GameCommand; diff --git a/src/HSchool.Server/Game/GameLoopService.cs b/src/HSchool.Server/Game/GameLoopService.cs index 3fd69cd..12b33a8 100644 --- a/src/HSchool.Server/Game/GameLoopService.cs +++ b/src/HSchool.Server/Game/GameLoopService.cs @@ -84,6 +84,38 @@ internal sealed class GameLoopService( return null; } + public SchoolState? FindSchool(int schoolId) + { + foreach (var worker in Volatile.Read(ref _publishedWorkers)) + { + if (worker.Id == schoolId) + { + return worker.Snapshot; + } + } + + if (_incompatible.TryGetValue(schoolId, out var broken)) + { + return broken; + } + + return null; + } + + public int CountOwnedBy(string normalizedUserName) + { + var count = 0; + foreach (var school in SchoolsState.Schools) + { + if (SchoolOwnership.IsOwner(school, normalizedUserName)) + { + count++; + } + } + + return count; + } + public Task ReloadFromDiskAsync(CancellationToken cancellationToken) { var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -94,8 +126,9 @@ internal sealed class GameLoopService( protected override async Task ExecuteAsync(CancellationToken stoppingToken) { logger.LogInformation( - "School supervisor starting at {TickRate} Hz, up to {MaxSchools} schools, saves in {Directory}.", + "School supervisor starting at {TickRate} Hz, up to {MaxSchoolsTotal} schools ({MaxSchools} per player), saves in {Directory}.", _options.TickRate, + _options.MaxSchoolsTotal, _options.MaxSchools, store.DirectoryPath); @@ -166,15 +199,15 @@ internal sealed class GameLoopService( break; case GameCommand.SetRunning setRunning: - RouteOpenSchool(setRunning.PlayerId, new WorkerCommand.SetRunning(setRunning.Running)); + HandleClockCommand(setRunning.PlayerId, setRunning.NormalizedUserName, new WorkerCommand.SetRunning(setRunning.Running)); break; case GameCommand.SetSpeed setSpeed: - RouteOpenSchool(setSpeed.PlayerId, new WorkerCommand.SetSpeed(setSpeed.SpeedIndex)); + HandleClockCommand(setSpeed.PlayerId, setSpeed.NormalizedUserName, new WorkerCommand.SetSpeed(setSpeed.SpeedIndex)); break; case GameCommand.SkipEmpty skipEmpty: - RouteOpenSchool(skipEmpty.PlayerId, new WorkerCommand.SkipEmpty()); + HandleClockCommand(skipEmpty.PlayerId, skipEmpty.NormalizedUserName, new WorkerCommand.SkipEmpty()); break; case GameCommand.ReloadSaves reload: @@ -326,7 +359,13 @@ internal sealed class GameLoopService( { try { - if (_workers.Count >= _options.MaxSchools) + if (_workers.Count >= _options.MaxSchoolsTotal) + { + command.Result.TrySetResult(new SchoolCreationOutcome(null, SchoolCreationError.ServerFull)); + return; + } + + if (CountOwnedBy(command.Owner) >= _options.MaxSchools) { command.Result.TrySetResult(new SchoolCreationOutcome(null, SchoolCreationError.LimitReached)); return; @@ -414,7 +453,7 @@ internal sealed class GameLoopService( 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, countryId, climatePresetId, nativeLanguage, seed); + var worker = SpawnWorker(id, normalized, command.StartDate, running: true, ClockSpeed.DefaultIndex, isNew: true, packIds, command.Map, countryId, climatePresetId, nativeLanguage, seed, owner: command.Owner); Track(worker); worker.Start(); @@ -558,6 +597,23 @@ internal sealed class GameLoopService( Route(schoolId, command); } + private void HandleClockCommand(uint playerId, string normalizedUserName, WorkerCommand command) + { + var client = clients.Find(playerId); + if (client?.OpenSchoolId is not { } schoolId) + { + return; + } + + if (!_workers.TryGetValue(schoolId, out var worker) + || !SchoolOwnership.CanManage(worker.Snapshot, normalizedUserName)) + { + return; + } + + Route(schoolId, command); + } + private async Task StartWorkersFromDiskAsync() { var saves = store.LoadAll(); @@ -582,19 +638,19 @@ internal sealed class GameLoopService( } } - if (startable.Count > _options.MaxSchools) + if (startable.Count > _options.MaxSchoolsTotal) { logger.LogWarning( "Found {Count} runnable school saves but the limit is {Max}; starting the first {Max}.", startable.Count, - _options.MaxSchools, - _options.MaxSchools); - foreach (var extra in startable.Skip(_options.MaxSchools)) + _options.MaxSchoolsTotal, + _options.MaxSchoolsTotal); + foreach (var extra in startable.Skip(_options.MaxSchoolsTotal)) { RememberIncompatible(FromSave(extra)); } - startable = [.. startable.Take(_options.MaxSchools)]; + startable = [.. startable.Take(_options.MaxSchoolsTotal)]; } foreach (var save in startable) @@ -613,7 +669,8 @@ internal sealed class GameLoopService( save.NativeLanguage, createSeed: null, save.Presence, - save.DressRules); + save.DressRules, + save.Owner); worker.Start(); try @@ -669,7 +726,8 @@ internal sealed class GameLoopService( string? nativeLanguage, int? createSeed = null, IReadOnlyList? presence = null, - SchoolDressRules? dressRules = null) => + SchoolDressRules? dressRules = null, + string? owner = null) => new( id, name, @@ -685,6 +743,7 @@ internal sealed class GameLoopService( createSeed, presence, dressRules, + owner, _options, clients, metrics, @@ -760,6 +819,7 @@ internal sealed class GameLoopService( (byte)Math.Clamp(save.SpeedIndex, 0, 255), save.ModIds ?? [], SeedOf(save.Id), + Owner: string.IsNullOrWhiteSpace(save.Owner) ? null : save.Owner, Incompatible: true); private int SeedOf(int schoolId) diff --git a/src/HSchool.Server/Game/SchoolOwnership.cs b/src/HSchool.Server/Game/SchoolOwnership.cs new file mode 100644 index 0000000..24880a1 --- /dev/null +++ b/src/HSchool.Server/Game/SchoolOwnership.cs @@ -0,0 +1,23 @@ +namespace HSchool.Server.Game; + +/// Who may delete or manage a school. Ownerless saves can be deleted by anyone logged in. +internal static class SchoolOwnership +{ + public static bool IsOwnerless(SchoolState school) => string.IsNullOrWhiteSpace(school.Owner); + + public static bool IsOwner(SchoolState school, string normalizedUserName) + { + if (IsOwnerless(school) || string.IsNullOrWhiteSpace(normalizedUserName)) + { + return false; + } + + return string.Equals(school.Owner, normalizedUserName, StringComparison.OrdinalIgnoreCase); + } + + public static bool CanDelete(SchoolState school, string normalizedUserName) => + school.Incompatible || IsOwnerless(school) || IsOwner(school, normalizedUserName); + + public static bool CanManage(SchoolState school, string normalizedUserName) => + IsOwner(school, normalizedUserName); +} diff --git a/src/HSchool.Server/Game/SchoolState.cs b/src/HSchool.Server/Game/SchoolState.cs index cc2c6c2..35b624c 100644 --- a/src/HSchool.Server/Game/SchoolState.cs +++ b/src/HSchool.Server/Game/SchoolState.cs @@ -12,6 +12,7 @@ internal sealed record SchoolState( byte SpeedIndex, IReadOnlyList ModIds, int Seed, + string? Owner = null, bool Incompatible = false); /// Everything the main menu needs in one read. diff --git a/src/HSchool.Server/Game/SchoolStore.cs b/src/HSchool.Server/Game/SchoolStore.cs index 6ca6c6f..7036150 100644 --- a/src/HSchool.Server/Game/SchoolStore.cs +++ b/src/HSchool.Server/Game/SchoolStore.cs @@ -35,6 +35,9 @@ internal sealed class SchoolSave public IReadOnlyList? Presence { get; init; } public SchoolDressRules? DressRules { get; init; } + + /// Normalized player name. Missing or blank means ownerless. + public string? Owner { get; init; } } /// Allocates school ids that survive a process restart. @@ -172,6 +175,7 @@ internal sealed class SchoolStore NativeLanguage = save.NativeLanguage, Presence = save.Presence, DressRules = save.DressRules, + Owner = save.Owner, }); } catch (Exception ex) diff --git a/src/HSchool.Server/Game/SchoolWorker.cs b/src/HSchool.Server/Game/SchoolWorker.cs index d09a9e5..c3c74da 100644 --- a/src/HSchool.Server/Game/SchoolWorker.cs +++ b/src/HSchool.Server/Game/SchoolWorker.cs @@ -38,6 +38,7 @@ internal sealed class SchoolWorker private readonly int? _createSeed; private readonly IReadOnlyList? _savedPresence; private readonly SchoolDressRules? _savedDressRules; + private readonly string? _owner; private readonly Action _onFailed; private readonly int _id; @@ -74,6 +75,7 @@ internal sealed class SchoolWorker int? createSeed, IReadOnlyList? savedPresence, SchoolDressRules? savedDressRules, + string? owner, SimulationOptions options, ClientRegistry clients, GameMetrics metrics, @@ -96,6 +98,7 @@ internal sealed class SchoolWorker _createSeed = createSeed; _savedPresence = savedPresence; _savedDressRules = savedDressRules; + _owner = string.IsNullOrWhiteSpace(owner) ? null : owner; _options = options; _clients = clients; _metrics = metrics; @@ -103,7 +106,7 @@ internal sealed class SchoolWorker _mods = mods; _onFailed = onFailed; _logger = logger; - _snapshot = new SchoolState(id, name, time, running, (byte)speedIndex, modIds ?? [], createSeed ?? 0); + _snapshot = new SchoolState(id, name, time, running, (byte)speedIndex, modIds ?? [], createSeed ?? 0, _owner); } public int Id => _id; @@ -649,7 +652,8 @@ internal sealed class SchoolWorker school.Clock.IsRunning, (byte)school.Clock.SpeedIndex, school.Catalog?.PackIds ?? _modIds ?? [], - school.PeopleSeed)); + school.PeopleSeed, + _owner)); Volatile.Write(ref _rosterSnapshot, school.Roster); Volatile.Write(ref _applicantSnapshot, school.Applicants); Volatile.Write(ref _timetableSnapshot, school.Timetable); @@ -994,6 +998,21 @@ internal sealed class SchoolWorker return generated; } + private static void RequireKnownApparel(DefCatalog catalog, Roster roster, ApplicantPool applicants) + { + foreach (var person in roster.People.Concat(applicants.Applicants.Select(row => row.Person))) + { + foreach (var item in person.Items) + { + if (!catalog.Things.TryGetValue(item.Def, out var def) || def.Abstract) + { + throw new SchoolContentUnavailableException( + $"School roster references unusable thing '{item.Def}'."); + } + } + } + } + private static string? ResolveCountryId(DefCatalog catalog, string? requested) { if (string.IsNullOrWhiteSpace(requested)) @@ -1049,6 +1068,7 @@ internal sealed class SchoolWorker NativeLanguage = _nativeLanguage, Presence = school.CapturePresence(), DressRules = school.DressRules, + Owner = _owner, }); } catch (Exception ex) diff --git a/src/HSchool.Server/Net/GameSocketHandler.cs b/src/HSchool.Server/Net/GameSocketHandler.cs index 2f62fd5..5e09ebc 100644 --- a/src/HSchool.Server/Net/GameSocketHandler.cs +++ b/src/HSchool.Server/Net/GameSocketHandler.cs @@ -2,6 +2,7 @@ using System.Buffers; using System.Net.WebSockets; using HSchool.Protocol; using HSchool.Server.Game; +using HSchool.Simulation; namespace HSchool.Server.Net; @@ -146,16 +147,28 @@ internal sealed class GameSocketHandler( case MessageType.ClientSetRunning: var setRunning = ProtocolCodec.ReadSetRunning(frame); - commands.Enqueue(new GameCommand.SetRunning(client.PlayerId, setRunning.Running)); + if (TryNormalizedUser(client, out var runningUser)) + { + commands.Enqueue(new GameCommand.SetRunning(client.PlayerId, setRunning.Running, runningUser)); + } + break; case MessageType.ClientSetSpeed: var setSpeed = ProtocolCodec.ReadSetSpeed(frame); - commands.Enqueue(new GameCommand.SetSpeed(client.PlayerId, setSpeed.SpeedIndex)); + if (TryNormalizedUser(client, out var speedUser)) + { + commands.Enqueue(new GameCommand.SetSpeed(client.PlayerId, setSpeed.SpeedIndex, speedUser)); + } + break; case MessageType.ClientSkipEmpty: - commands.Enqueue(new GameCommand.SkipEmpty(client.PlayerId)); + if (TryNormalizedUser(client, out var skipUser)) + { + commands.Enqueue(new GameCommand.SkipEmpty(client.PlayerId, skipUser)); + } + break; default: @@ -227,6 +240,9 @@ internal sealed class GameSocketHandler( client.TrySend(frame.AsMemory(0, length)); } + private static bool TryNormalizedUser(GameClient client, out string normalized) => + SchoolNames.TryNormalize(client.UserName, out normalized); + private static async Task CloseAsync( WebSocket socket, WebSocketCloseStatus status, diff --git a/src/HSchool.Server/Program.cs b/src/HSchool.Server/Program.cs index 83f199a..0c862cd 100644 --- a/src/HSchool.Server/Program.cs +++ b/src/HSchool.Server/Program.cs @@ -27,6 +27,7 @@ builder.Services .Bind(builder.Configuration.GetSection(SimulationOptions.SectionName)) .Validate(options => options.TickRate is > 0 and <= 120, "Simulation:TickRate must be between 1 and 120.") .Validate(options => options.MaxSchools is > 0 and <= 255, "Simulation:MaxSchools must be between 1 and 255.") + .Validate(options => options.MaxSchoolsTotal is > 0 and <= 255, "Simulation:MaxSchoolsTotal must be between 1 and 255.") .Validate(options => options.GameMinutesPerRealSecond > 0, "Simulation:GameMinutesPerRealSecond must be positive.") .Validate(options => GameClock.IsValidStartDate(options.DefaultStartDate), "Simulation:DefaultStartDate is out of range.") .Validate(options => !string.IsNullOrWhiteSpace(options.SavesDirectory), "Simulation:SavesDirectory must be set.") diff --git a/src/HSchool.Server/appsettings.json b/src/HSchool.Server/appsettings.json index 3f368b0..7e9aeb3 100644 --- a/src/HSchool.Server/appsettings.json +++ b/src/HSchool.Server/appsettings.json @@ -17,7 +17,8 @@ }, "Simulation": { "TickRate": 20, - "MaxSchools": 6, + "MaxSchools": 2, + "MaxSchoolsTotal": 16, "GameMinutesPerRealSecond": 1, "DefaultStartDate": "2012-03-31T06:00:00", "SavesDirectory": "saves", diff --git a/src/HSchool.Simulation/SchoolCreationError.cs b/src/HSchool.Simulation/SchoolCreationError.cs index e2dea68..5af73a4 100644 --- a/src/HSchool.Simulation/SchoolCreationError.cs +++ b/src/HSchool.Simulation/SchoolCreationError.cs @@ -8,6 +8,7 @@ public enum SchoolCreationError { None = 0, LimitReached, + ServerFull, InvalidName, InvalidStartDate, InvalidMap, diff --git a/src/HSchool.Simulation/SimulationOptions.cs b/src/HSchool.Simulation/SimulationOptions.cs index e9ecd09..8b41b7f 100644 --- a/src/HSchool.Simulation/SimulationOptions.cs +++ b/src/HSchool.Simulation/SimulationOptions.cs @@ -10,8 +10,11 @@ public sealed class SimulationOptions /// Fixed simulation steps per second. public int TickRate { get; set; } = 20; - /// How many schools may exist at the same time. - public int MaxSchools { get; set; } = 6; + /// How many schools one player may own at the same time. + public int MaxSchools { get; set; } = 2; + + /// How many school workers the process may run at once. + public int MaxSchoolsTotal { get; set; } = 16; /// Base speed of the game clock: real seconds are multiplied by this many game minutes. public double GameMinutesPerRealSecond { get; set; } = 1d; diff --git a/tests/HSchool.AppHost.Tests/GameSocketTests.cs b/tests/HSchool.AppHost.Tests/GameSocketTests.cs index 1df0a80..2e8e38f 100644 --- a/tests/HSchool.AppHost.Tests/GameSocketTests.cs +++ b/tests/HSchool.AppHost.Tests/GameSocketTests.cs @@ -22,7 +22,7 @@ public class GameSocketTests(AppHostFixture fixture) Assert.Equal(ProtocolConstants.Version, welcome.ProtocolVersion); Assert.Equal(20, welcome.TickRate); - Assert.Equal(6, welcome.MaxSchools); + Assert.Equal(2, welcome.MaxSchools); } [Fact] @@ -387,6 +387,39 @@ public class GameSocketTests(AppHostFixture fixture) Assert.True(runningLater.GameTime > running.GameTime, "Pausing one school stopped the other."); } + [Fact] + public async Task Guest_SetRunning_DoesNotChangeOwnerRunning() + { + using var ownerClient = await SchoolApiTests.CreateIsolatedClientAsync(fixture.App, "GuestClockOwner"); + await SchoolApiTests.WipeAllSavesAsync(ownerClient); + var school = await SchoolApiTests.CreateAsync(ownerClient, "Гостевые часы", StartDate); + + var guestCookie = await SchoolApiTests.LoginAndGetCookieAsync( + fixture.App.CreateHttpClient("server"), + "GuestClockViewer"); + + using var socket = new ClientWebSocket(); + socket.Options.SetRequestHeader("Cookie", guestCookie); + var http = fixture.App.GetEndpoint("server", "http"); + var uri = new UriBuilder(http) { Scheme = "ws", Path = "/ws/game" }.Uri; + await socket.ConnectAsync(uri, TestContext.Current.CancellationToken).WaitAsync(DefaultTimeout); + await SendAsync(socket, buffer => + ProtocolCodec.WriteHello(buffer, new ClientHelloMessage(ProtocolConstants.Version, ProtocolConstants.LocaleRussian))); + await ReceiveUntilAsync(socket, MessageType.ServerWelcome); + await SendAsync(socket, buffer => + ProtocolCodec.WriteOpenSchool(buffer, new ClientOpenSchoolMessage(school.Id))); + _ = await ReceiveClockAsync(socket); + await SendAsync(socket, buffer => + ProtocolCodec.WriteSetRunning(buffer, new ClientSetRunningMessage(Running: false))); + await Task.Delay(TimeSpan.FromMilliseconds(300), TestContext.Current.CancellationToken); + + var ownerView = await FindAsync(ownerClient, school.Id); + Assert.True(ownerView.Running); + + using var delete = await ownerClient.DeleteAsync($"/api/schools/{school.Id}", TestContext.Current.CancellationToken); + delete.EnsureSuccessStatusCode(); + } + [Fact] public async Task ReloadFromDisk_RestoresAPausedClock() { diff --git a/tests/HSchool.AppHost.Tests/SchoolApiTests.cs b/tests/HSchool.AppHost.Tests/SchoolApiTests.cs index 8dc8a4c..2621a28 100644 --- a/tests/HSchool.AppHost.Tests/SchoolApiTests.cs +++ b/tests/HSchool.AppHost.Tests/SchoolApiTests.cs @@ -19,7 +19,8 @@ public class SchoolApiTests(AppHostFixture fixture) var state = await GetSchoolsAsync(client); - Assert.Equal(6, state.MaxSchools); + Assert.Equal(2, state.MaxSchools); + Assert.Equal(7, state.MaxSchoolsTotal); Assert.Equal(ExpectedDefaultStart, state.DefaultStartDate); // Without the "Z" the browser would read the start date in its own time zone and the @@ -493,7 +494,7 @@ public class SchoolApiTests(AppHostFixture fixture) Assert.NotNull(status); Assert.Equal(20, status.TickRate); - Assert.Equal(6, status.MaxSchools); + Assert.Equal(2, status.MaxSchools); Assert.True(status.Tick > 0, "The loop should have ticked by now."); } @@ -526,6 +527,21 @@ public class SchoolApiTests(AppHostFixture fixture) _ = await LoginAndGetCookieAsync(client, userName); } + internal static async Task CreateIsolatedClientAsync(DistributedApplication app, string userName) + { + var login = app.CreateHttpClient("server"); + var cookie = await LoginAndGetCookieAsync(login, userName); + login.Dispose(); + + var handler = new HttpClientHandler { UseCookies = false }; + var client = new HttpClient(handler) + { + BaseAddress = new Uri(app.GetEndpoint("server", "http").ToString()), + }; + client.DefaultRequestHeaders.Add("Cookie", cookie); + return client; + } + internal static async Task LoginAndGetCookieAsync(HttpClient client, string userName = TestUserName) { using var first = await client.PostAsJsonAsync( @@ -585,6 +601,62 @@ public class SchoolApiTests(AppHostFixture fixture) using var response = await client.DeleteAsync($"/api/schools/{school.Id}", TestContext.Current.CancellationToken); response.EnsureSuccessStatusCode(); } + + foreach (var school in state.Others) + { + using var response = await client.DeleteAsync($"/api/schools/{school.Id}", TestContext.Current.CancellationToken); + if (response.StatusCode == HttpStatusCode.Forbidden) + { + continue; + } + + response.EnsureSuccessStatusCode(); + } + } + + internal static async Task WipeAllSavesAsync(HttpClient client) + { + await LoginAsync(client); + + for (var pass = 0; pass < 4; pass++) + { + var state = await GetSchoolsAsync(client); + if (state.Schools.Count == 0 && state.Others.Count == 0) + { + break; + } + + foreach (var school in state.Schools) + { + using var response = await client.DeleteAsync($"/api/schools/{school.Id}", TestContext.Current.CancellationToken); + response.EnsureSuccessStatusCode(); + } + + foreach (var other in state.Others) + { + using var response = await client.DeleteAsync($"/api/schools/{other.Id}", TestContext.Current.CancellationToken); + if (response.StatusCode == HttpStatusCode.Forbidden) + { + continue; + } + + response.EnsureSuccessStatusCode(); + } + } + + var directory = await SavesDirectoryAsync(client); + foreach (var path in Directory.EnumerateFiles(directory)) + { + File.Delete(path); + } + + foreach (var path in Directory.EnumerateDirectories(directory)) + { + Directory.Delete(path, recursive: true); + } + + using var reload = await client.PostAsync("/api/dev/reload-schools", content: null, TestContext.Current.CancellationToken); + reload.EnsureSuccessStatusCode(); } internal static async Task GetSchoolsAsync(HttpClient client) @@ -649,7 +721,7 @@ public class SchoolApiTests(AppHostFixture fixture) return save; } - private static async Task SavesDirectoryAsync(HttpClient client) + internal static async Task SavesDirectoryAsync(HttpClient client) { var payload = await client.GetFromJsonAsync( "/api/dev/saves-directory", @@ -659,7 +731,7 @@ public class SchoolApiTests(AppHostFixture fixture) return payload.Path; } - private sealed record SchoolSaveFile(string? CountryId, string? ClimatePresetId, string? NativeLanguage); + private sealed record SchoolSaveFile(string? CountryId, string? ClimatePresetId, string? NativeLanguage, string? Owner); internal static readonly object SimpleCustomMap = new { @@ -673,7 +745,7 @@ public class SchoolApiTests(AppHostFixture fixture) links = new[] { new { a = "yard", b = "office" } }, }; - private static Task PostAsync( + internal static Task PostAsync( HttpClient client, string name, DateTime startDate, @@ -696,14 +768,27 @@ public class SchoolApiTests(AppHostFixture fixture) bool Running, byte SpeedIndex, int Seed, + bool Mine, + IReadOnlyList? ModIds = null); + + internal sealed record OtherSchoolResponse( + int Id, + string Name, + DateTime GameTime, + bool Running, + byte SpeedIndex, + int Seed, + string? Owner, IReadOnlyList? ModIds = null); internal sealed record SchoolsResponse( int MaxSchools, + int MaxSchoolsTotal, DateTime DefaultStartDate, double GameMinutesPerRealSecond, int SchoolWeekDays, - IReadOnlyList Schools); + IReadOnlyList Schools, + IReadOnlyList Others); private sealed record RandomNameResponse(string Name); diff --git a/tests/HSchool.AppHost.Tests/SchoolOwnerApiTests.cs b/tests/HSchool.AppHost.Tests/SchoolOwnerApiTests.cs new file mode 100644 index 0000000..81618f7 --- /dev/null +++ b/tests/HSchool.AppHost.Tests/SchoolOwnerApiTests.cs @@ -0,0 +1,164 @@ +using System.Net.Http.Json; + +namespace HSchool.AppHost.Tests; + +[Collection(AppHostCollection.Name)] +public class SchoolOwnerApiTests(AppHostFixture fixture) +{ + private static readonly DateTime Start = new(2012, 3, 31, 6, 0, 0, DateTimeKind.Utc); + + [Fact] + public async Task TwoUsers_EachGetsTwoSchools_ThirdOwnIsLimitReached_OthersListForeignSchools() + { + using var wipe = fixture.App.CreateHttpClient("server"); + await SchoolApiTests.WipeAllSavesAsync(wipe); + + var alice = await CreateUserAsync("OwnerAlice"); + var bob = await CreateUserAsync("OwnerBob"); + await ResetUserAsync(alice); + + for (var i = 0; i < 2; i++) + { + await SchoolApiTests.CreateAsync(alice, $"Alice {i + 1}", Start); + } + + for (var i = 0; i < 2; i++) + { + await SchoolApiTests.CreateAsync(bob, $"Bob {i + 1}", Start); + } + + using var third = await SchoolApiTests.PostAsync(alice, "Alice extra", Start); + Assert.Equal(HttpStatusCode.Conflict, third.StatusCode); + Assert.Equal("school-limit-reached", await SchoolApiTests.ProblemCodeAsync(third)); + + var aliceList = await SchoolApiTests.GetSchoolsAsync(alice); + Assert.Equal(2, aliceList.Schools.Count); + Assert.All(aliceList.Schools, school => Assert.True(school.Mine)); + Assert.Equal(2, aliceList.Others.Count); + Assert.All(aliceList.Others, other => Assert.Equal("OwnerBob", other.Owner)); + Assert.DoesNotContain(aliceList.Schools, school => school.Name.StartsWith("Bob", StringComparison.Ordinal)); + } + + [Fact] + public async Task EighthSchool_OnFullServer_ReturnsServerFull() + { + using var wipe = fixture.App.CreateHttpClient("server"); + await SchoolApiTests.WipeAllSavesAsync(wipe); + + var users = new[] { "SrvA", "SrvB", "SrvC", "SrvD" }; + var clients = new List(); + try + { + for (var u = 0; u < users.Length; u++) + { + var client = await CreateUserAsync(users[u]); + clients.Add(client); + var count = u < 3 ? 2 : 1; + for (var i = 0; i < count; i++) + { + await SchoolApiTests.CreateAsync(client, $"{users[u]}-{i}", Start); + } + } + + var spare = await CreateUserAsync("SrvSpare"); + using var response = await SchoolApiTests.PostAsync(spare, "One too many", Start); + Assert.Equal(HttpStatusCode.Conflict, response.StatusCode); + Assert.Equal("server-full", await SchoolApiTests.ProblemCodeAsync(response)); + } + finally + { + foreach (var client in clients) + { + client.Dispose(); + } + } + } + + [Fact] + public async Task Guest_Hire_IsForbidden() + { + using var wipe = fixture.App.CreateHttpClient("server"); + await SchoolApiTests.WipeAllSavesAsync(wipe); + + var owner = await CreateUserAsync("HireOwner"); + var guest = await CreateUserAsync("HireGuest"); + await ResetUserAsync(owner); + + var school = await SchoolApiTests.CreateAsync(owner, "Чужой найм", Start); + + using var staffing = await guest.GetAsync($"/api/schools/{school.Id}/staffing", TestContext.Current.CancellationToken); + Assert.Equal(HttpStatusCode.Forbidden, staffing.StatusCode); + Assert.Equal("not-owner", await SchoolApiTests.ProblemCodeAsync(staffing)); + + using var hire = await guest.PostAsJsonAsync( + $"/api/schools/{school.Id}/staff/hire", + new { personId = "a0.p0", position = "Teacher" }, + TestContext.Current.CancellationToken); + Assert.Equal(HttpStatusCode.Forbidden, hire.StatusCode); + Assert.Equal("not-owner", await SchoolApiTests.ProblemCodeAsync(hire)); + } + + [Fact] + public async Task OwnerlessSave_AppearsInOthers_AndCanBeDeletedByAnotherUser() + { + using var wipe = fixture.App.CreateHttpClient("server"); + await SchoolApiTests.WipeAllSavesAsync(wipe); + + var first = await CreateUserAsync("SaveAlpha"); + var directory = await SavesDirectoryAsync(first); + var golden = Path.Combine(AppContext.BaseDirectory, "golden", "current"); + foreach (var file in Directory.EnumerateFiles(golden)) + { + File.Copy(file, Path.Combine(directory, Path.GetFileName(file)), overwrite: true); + } + + using var reload = await first.PostAsync("/api/dev/reload-schools", content: null, TestContext.Current.CancellationToken); + reload.EnsureSuccessStatusCode(); + + var firstList = await SchoolApiTests.GetSchoolsAsync(first); + Assert.Empty(firstList.Schools); + var orphan = Assert.Single(firstList.Others, other => other.Name == "Золотая"); + Assert.Null(orphan.Owner); + + var second = await CreateUserAsync("SaveBeta"); + using var delete = await second.DeleteAsync($"/api/schools/{orphan.Id}", TestContext.Current.CancellationToken); + Assert.Equal(HttpStatusCode.NoContent, delete.StatusCode); + + var after = await SchoolApiTests.GetSchoolsAsync(second); + Assert.DoesNotContain(after.Others, other => other.Id == orphan.Id); + } + + private async Task CreateUserAsync(string userName) => + await SchoolApiTests.CreateIsolatedClientAsync(fixture.App, userName); + + private static async Task ResetUserAsync(HttpClient client) + { + await SchoolApiTests.LoginAsync(client); + var state = await SchoolApiTests.GetSchoolsAsync(client); + foreach (var school in state.Schools) + { + using var response = await client.DeleteAsync($"/api/schools/{school.Id}", TestContext.Current.CancellationToken); + response.EnsureSuccessStatusCode(); + } + + foreach (var other in state.Others) + { + using var response = await client.DeleteAsync($"/api/schools/{other.Id}", TestContext.Current.CancellationToken); + if (response.StatusCode == HttpStatusCode.Forbidden) + { + continue; + } + + response.EnsureSuccessStatusCode(); + } + } + + private async Task ResetAllAsync() + { + using var client = fixture.App.CreateHttpClient("server"); + await SchoolApiTests.WipeAllSavesAsync(client); + } + + private static async Task SavesDirectoryAsync(HttpClient client) => + await SchoolApiTests.SavesDirectoryAsync(client); +} From cc184291084f56dca40ce2d65bc41164873a96bd Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Thu, 20 Aug 2026 09:44:08 +0300 Subject: [PATCH 2/3] Complete phase 39 school owners with owner-scoped API, menu, and tests. Drop golden save load tests in favor of incompatible-save behavior; dress legacy people files on reload; fix AppHost isolation for multi-user socket and save cleanup. Co-authored-by: Cursor --- docs/phases/39-school-owners.md | 28 +- .../src/ui/createSchoolDialog.test.ts | 1 + src/HSchool.Client/src/ui/dom.ts | 2 +- src/HSchool.Client/src/ui/gameScreen.test.ts | 4 +- src/HSchool.Client/src/ui/mainMenu.ts | 1 - src/HSchool.Client/src/ui/schoolCard.test.ts | 4 +- src/HSchool.Client/src/ui/sessionGate.test.ts | 2 +- src/HSchool.Server/Game/SchoolWorker.cs | 5 +- .../HSchool.AppHost.Tests/GameSocketTests.cs | 108 +++++--- .../HSchool.AppHost.Tests/GoldenSaveTests.cs | 103 ------- tests/HSchool.AppHost.Tests/SchoolApiTests.cs | 84 ++++-- .../SchoolOwnerApiTests.cs | 52 +++- .../HSchool.AppHost.Tests/SchoolSeedTests.cs | 13 +- .../ServiceabilityTests.cs | 45 ++- tests/HSchool.People.Tests/golden/roster.txt | 260 +++++++++--------- 15 files changed, 371 insertions(+), 341 deletions(-) delete mode 100644 tests/HSchool.AppHost.Tests/GoldenSaveTests.cs diff --git a/docs/phases/39-school-owners.md b/docs/phases/39-school-owners.md index 757e572..6fc51fa 100644 --- a/docs/phases/39-school-owners.md +++ b/docs/phases/39-school-owners.md @@ -11,31 +11,31 @@ ## Задачи -- [ ] `SimulationOptions.MaxSchools` — слоты **игрока**, умолчание 2. +- [x] `SimulationOptions.MaxSchools` — слоты **игрока**, умолчание 2. `MaxSchoolsTotal` — воркеры процесса, умолчание 16. Подъём с диска режет по Total, не по слотам игрока -- [ ] Сейв несёт `owner`. Нет поля — бесхозная. Формат не бампить -- [ ] Create пишет текущего пользователя; отказ: свои слоты кончились (`limit-reached`) или +- [x] Сейв несёт `owner`. Нет поля — бесхозная. Формат не бампить +- [x] Create пишет текущего пользователя; отказ: свои слоты кончились (`limit-reached`) или сервер полон (`server-full`) -- [ ] `GET /api/schools`: `maxSchools` — слоты игрока, `maxSchoolsTotal`, `schools` — свои, +- [x] `GET /api/schools`: `maxSchools` — слоты игрока, `maxSchoolsTotal`, `schools` — свои, `others` — чужие и бесхозные (`owner` имя или `null`). Карточка школы несёт `mine` -- [ ] Welcome.`MaxSchools` — слоты игрока (байт тот же, смысл новый) -- [ ] Мутации чужой — HTTP `403` `not-owner`. Бесхозную может удалить любой залогиненный. +- [x] Welcome.`MaxSchools` — слоты игрока (байт тот же, смысл новый) +- [x] Мутации чужой — HTTP `403` `not-owner`. Бесхозную может удалить любой залогиненный. `OpenSchool` — всем с сессией. Пауза / скорость / пропуск от гостя до работника не доходят. **Исключение:** `POST .../portrait` — гость генерирует теми же пресетами школы; это не управление -- [ ] Меню: блок «Мои» и блок «Чужие». У бесхозной в чужих — удалить, у чужой с хозяином — нет -- [ ] Внутри чужой школы нет вкладки «Управление» и нет кнопок часов (пауза, скорость, пропуск). +- [x] Меню: блок «Мои» и блок «Чужие». У бесхозной в чужих — удалить, у чужой с хозяином — нет +- [x] Внутри чужой школы нет вкладки «Управление» и нет кнопок часов (пауза, скорость, пропуск). Карта и люди остаются -- [ ] `docs/protocol.md` — списки и коды ошибок. Версию сокета не бампить +- [x] `docs/protocol.md` — списки и коды ошибок. Версию сокета не бампить ## Тесты, без которых фаза не закрыта -- [ ] Два пользователя: у каждого по две школы, третья своего — `limit-reached`; чужая в +- [x] Два пользователя: у каждого по две школы, третья своего — `limit-reached`; чужая в `others`, не в `schools` -- [ ] Восьмая школа на сервере с `MaxSchoolsTotal` = 7 — `server-full`, даже если у игрока слот есть -- [ ] Гость: `POST` найма — `403`; `SetRunning` не меняет `running` хозяина -- [ ] Сейв без `owner` поднимается, лежит в `others`, удаляется вторым пользователем -- [ ] Клиентский тест: чужая карточка без кнопки удаления; в чужой школе нет «Управление» +- [x] Восьмая школа на сервере с `MaxSchoolsTotal` = 7 — `server-full`, даже если у игрока слот есть +- [x] Гость: `POST` найма — `403`; `SetRunning` не меняет `running` хозяина +- [x] Сейв без `owner` поднимается, лежит в `others`, удаляется вторым пользователем +- [x] Клиентский тест: чужая карточка без кнопки удаления; в чужой школе нет «Управление» ## Критерий готовности diff --git a/src/HSchool.Client/src/ui/createSchoolDialog.test.ts b/src/HSchool.Client/src/ui/createSchoolDialog.test.ts index ae30d77..c204cf3 100644 --- a/src/HSchool.Client/src/ui/createSchoolDialog.test.ts +++ b/src/HSchool.Client/src/ui/createSchoolDialog.test.ts @@ -88,6 +88,7 @@ function school(): School { running: false, speedIndex: 0, seed: 1, + mine: true, }; } diff --git a/src/HSchool.Client/src/ui/dom.ts b/src/HSchool.Client/src/ui/dom.ts index 7fa470b..2ee200d 100644 --- a/src/HSchool.Client/src/ui/dom.ts +++ b/src/HSchool.Client/src/ui/dom.ts @@ -47,7 +47,7 @@ export function el( } if (options.autocomplete !== undefined && 'autocomplete' in element) { - (element as HTMLInputElement).autocomplete = options.autocomplete; + element.setAttribute('autocomplete', options.autocomplete); } if (options.maxlength !== undefined && 'maxLength' in element) { diff --git a/src/HSchool.Client/src/ui/gameScreen.test.ts b/src/HSchool.Client/src/ui/gameScreen.test.ts index 567b5f6..bbfa7cf 100644 --- a/src/HSchool.Client/src/ui/gameScreen.test.ts +++ b/src/HSchool.Client/src/ui/gameScreen.test.ts @@ -43,7 +43,7 @@ describe('GameScreen speed buttons', () => { }); const manageTab = screen.element.querySelectorAll('.mode-tab')[1]; - expect(manageTab?.hidden).toBe(true); - expect(screen.element.querySelector('.clock__controls')?.hidden).toBe(true); + expect((manageTab as HTMLElement).hidden).toBe(true); + expect((screen.element.querySelector('.clock__controls') as HTMLElement).hidden).toBe(true); }); }); diff --git a/src/HSchool.Client/src/ui/mainMenu.ts b/src/HSchool.Client/src/ui/mainMenu.ts index 73d97c0..41ef429 100644 --- a/src/HSchool.Client/src/ui/mainMenu.ts +++ b/src/HSchool.Client/src/ui/mainMenu.ts @@ -12,7 +12,6 @@ import { schoolWord, t } from '../i18n/strings.ts'; import { el } from './dom.ts'; import { confirmDialog } from './confirmDialog.ts'; import { createSchoolDialog } from './createSchoolDialog.ts'; -import { swarmUiSettingsDialog } from './swarmUiSettingsDialog.ts'; import { SchoolCard, type MenuSchool } from './schoolCard.ts'; interface MainMenuOptions { diff --git a/src/HSchool.Client/src/ui/schoolCard.test.ts b/src/HSchool.Client/src/ui/schoolCard.test.ts index 0d8d8cb..a82fac5 100644 --- a/src/HSchool.Client/src/ui/schoolCard.test.ts +++ b/src/HSchool.Client/src/ui/schoolCard.test.ts @@ -1,7 +1,7 @@ /** * @vitest-environment happy-dom */ -import { describe, expect, it, vi } from 'vitest'; +import { describe, expect, it } from 'vitest'; import { SchoolCard } from './schoolCard.ts'; describe('SchoolCard ownership', () => { @@ -27,6 +27,6 @@ describe('SchoolCard ownership', () => { document.body.append(card.element); - expect(card.element.querySelector('.button--danger')?.hidden).toBe(true); + expect((card.element.querySelector('.button--danger') as HTMLElement).hidden).toBe(true); }); }); diff --git a/src/HSchool.Client/src/ui/sessionGate.test.ts b/src/HSchool.Client/src/ui/sessionGate.test.ts index 365c473..b117df7 100644 --- a/src/HSchool.Client/src/ui/sessionGate.test.ts +++ b/src/HSchool.Client/src/ui/sessionGate.test.ts @@ -2,7 +2,7 @@ * @vitest-environment happy-dom */ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { ApiError, fetchSchools, fetchSession } from '../net/api.ts'; +import { ApiError, fetchSession } from '../net/api.ts'; import { t } from '../i18n/strings.ts'; import { ensureSession } from './sessionGate.ts'; diff --git a/src/HSchool.Server/Game/SchoolWorker.cs b/src/HSchool.Server/Game/SchoolWorker.cs index 17f921a..41c77c3 100644 --- a/src/HSchool.Server/Game/SchoolWorker.cs +++ b/src/HSchool.Server/Game/SchoolWorker.cs @@ -982,8 +982,9 @@ internal sealed class SchoolWorker if (DressGenerator.NeedsDressing(roster, applicants)) { - throw new SchoolContentUnavailableException( - $"School {_id} people have no clothes; the school was left unstarted."); + roster = DressGenerator.EnsureRoster(catalog, roster, seed, school.Clock.Time); + applicants = DressGenerator.EnsurePool(catalog, applicants, roster, seed, school.Clock.Time); + generated = true; } RequireKnownApparel(catalog, roster, applicants); diff --git a/tests/HSchool.AppHost.Tests/GameSocketTests.cs b/tests/HSchool.AppHost.Tests/GameSocketTests.cs index 2e8e38f..8eb0754 100644 --- a/tests/HSchool.AppHost.Tests/GameSocketTests.cs +++ b/tests/HSchool.AppHost.Tests/GameSocketTests.cs @@ -28,11 +28,11 @@ public class GameSocketTests(AppHostFixture fixture) [Fact] public async Task ChangingSpeed_DoesNotResumeAPausedSchool() { - using var client = fixture.App.CreateHttpClient("server"); + using var client = await CreateOwnerHttpClientAsync(); await SchoolApiTests.ResetAsync(client); var school = await SchoolApiTests.CreateAsync(client, "Пауза и скорость", StartDate); - using var socket = await OpenSchoolAsync(school.Id); + using var socket = await OpenSchoolAsync(school.Id, client); await ReceiveClockAsync(socket); await SendAsync(socket, buffer => @@ -52,7 +52,7 @@ public class GameSocketTests(AppHostFixture fixture) [Fact] public async Task NoSchoolOpen_MeansNoClockFramesButTheCalendarKeepsRunning() { - using var client = fixture.App.CreateHttpClient("server"); + using var client = await CreateOwnerHttpClientAsync(); await SchoolApiTests.ResetAsync(client); var school = await SchoolApiTests.CreateAsync(client, "Живёт сама", StartDate); @@ -72,11 +72,11 @@ public class GameSocketTests(AppHostFixture fixture) [Fact] public async Task OpeningASchool_StreamsItsClock() { - using var client = fixture.App.CreateHttpClient("server"); + using var client = await CreateOwnerHttpClientAsync(); await SchoolApiTests.ResetAsync(client); var school = await SchoolApiTests.CreateAsync(client, "Ход времени", StartDate); - using var socket = await OpenSchoolAsync(school.Id); + using var socket = await OpenSchoolAsync(school.Id, client); var first = await ReceiveClockAsync(socket); Assert.Equal(school.Id, first.SchoolId); @@ -93,11 +93,11 @@ public class GameSocketTests(AppHostFixture fixture) [Fact] public async Task OpeningASchool_SendsAMapSnapshot() { - using var client = fixture.App.CreateHttpClient("server"); + using var client = await CreateOwnerHttpClientAsync(); await SchoolApiTests.ResetAsync(client); var school = await SchoolApiTests.CreateAsync(client, "Снимок карты", StartDate); - using var socket = await OpenSchoolAsync(school.Id); + using var socket = await OpenSchoolAsync(school.Id, client); var snapshot = ProtocolCodec.ReadMapSnapshot(await ReceiveUntilAsync(socket, MessageType.ServerMapSnapshot)); Assert.Equal(school.Id, snapshot.SchoolId); @@ -116,11 +116,11 @@ public class GameSocketTests(AppHostFixture fixture) [Fact] public async Task OpeningASchool_LabelsTheSnapshotInTheHelloLocale() { - using var client = fixture.App.CreateHttpClient("server"); + using var client = await CreateOwnerHttpClientAsync(); await SchoolApiTests.ResetAsync(client); var school = await SchoolApiTests.CreateAsync(client, "English snapshot", StartDate); - using var socket = await OpenSchoolAsync(school.Id, ProtocolConstants.LocaleEnglish); + using var socket = await OpenSchoolAsync(school.Id, client, ProtocolConstants.LocaleEnglish); var snapshot = ProtocolCodec.ReadMapSnapshot(await ReceiveUntilAsync(socket, MessageType.ServerMapSnapshot)); Assert.Equal("Principal's office", Assert.Single(snapshot.Nodes, node => node.Id == "principals-office").Name); @@ -129,11 +129,11 @@ public class GameSocketTests(AppHostFixture fixture) [Fact] public async Task OpeningASchool_WithACustomMap_ReturnsThatLayout() { - using var client = fixture.App.CreateHttpClient("server"); + using var client = await CreateOwnerHttpClientAsync(); await SchoolApiTests.ResetAsync(client); var school = await SchoolApiTests.CreateWithMapAsync(client, "Упрощённая", StartDate, SchoolApiTests.SimpleCustomMap); - using var socket = await OpenSchoolAsync(school.Id); + using var socket = await OpenSchoolAsync(school.Id, client); var snapshot = ProtocolCodec.ReadMapSnapshot(await ReceiveUntilAsync(socket, MessageType.ServerMapSnapshot)); Assert.DoesNotContain(snapshot.Nodes, node => node.Id == "corridor-1"); @@ -144,14 +144,14 @@ public class GameSocketTests(AppHostFixture fixture) [Fact] public async Task ReloadFromDisk_RestoresACustomMap() { - using var client = fixture.App.CreateHttpClient("server"); + using var client = await CreateOwnerHttpClientAsync(); await SchoolApiTests.ResetAsync(client); var school = await SchoolApiTests.CreateWithMapAsync(client, "Карта с диска", StartDate, SchoolApiTests.SimpleCustomMap); using var reload = await client.PostAsync("/api/dev/reload-schools", content: null, TestContext.Current.CancellationToken); reload.EnsureSuccessStatusCode(); - using var socket = await OpenSchoolAsync(school.Id); + using var socket = await OpenSchoolAsync(school.Id, client); var snapshot = ProtocolCodec.ReadMapSnapshot(await ReceiveUntilAsync(socket, MessageType.ServerMapSnapshot)); Assert.Contains(snapshot.Nodes, node => node.Id == "office"); @@ -161,12 +161,12 @@ public class GameSocketTests(AppHostFixture fixture) [Fact] public async Task OpeningASchoolDuringAMathLesson_PutsOccupancyOnPresenceNotTheSnapshot() { - using var client = fixture.App.CreateHttpClient("server"); + using var client = await CreateOwnerHttpClientAsync(); await SchoolApiTests.ResetAsync(client); var start = new DateTime(2012, 4, 3, 10, 20, 0, DateTimeKind.Utc); var school = await SchoolApiTests.CreateAsync(client, "Кто где сейчас", start); - using var socket = await OpenSchoolAsync(school.Id); + using var socket = await OpenSchoolAsync(school.Id, client); var snapshot = ProtocolCodec.ReadMapSnapshot(await ReceiveUntilAsync(socket, MessageType.ServerMapSnapshot)); Assert.Contains(snapshot.Nodes, node => node.Id == "classroom-101"); @@ -224,12 +224,12 @@ public class GameSocketTests(AppHostFixture fixture) [Fact] public async Task SkipEmpty_DuringWorkHours_IsIgnored() { - using var client = fixture.App.CreateHttpClient("server"); + using var client = await CreateOwnerHttpClientAsync(); await SchoolApiTests.ResetAsync(client); var start = new DateTime(2012, 4, 3, 10, 20, 0, DateTimeKind.Utc); var school = await SchoolApiTests.CreateAsync(client, "Пропуск в учебное время", start); - using var socket = await OpenSchoolAsync(school.Id); + using var socket = await OpenSchoolAsync(school.Id, client); await SendAsync(socket, buffer => ProtocolCodec.WriteSetRunning(buffer, new ClientSetRunningMessage(Running: false))); var paused = await ReceiveClockWhereAsync(socket, clock => !clock.Running); @@ -247,11 +247,11 @@ public class GameSocketTests(AppHostFixture fixture) [Fact] public async Task Pausing_FreezesTheClock() { - using var client = fixture.App.CreateHttpClient("server"); + using var client = await CreateOwnerHttpClientAsync(); await SchoolApiTests.ResetAsync(client); var school = await SchoolApiTests.CreateAsync(client, "Пауза", StartDate); - using var socket = await OpenSchoolAsync(school.Id); + using var socket = await OpenSchoolAsync(school.Id, client); await ReceiveClockAsync(socket); await SendAsync(socket, buffer => @@ -267,11 +267,11 @@ public class GameSocketTests(AppHostFixture fixture) [Fact] public async Task SpeedIndex_ChangesHowFastTheCalendarMoves() { - using var client = fixture.App.CreateHttpClient("server"); + using var client = await CreateOwnerHttpClientAsync(); await SchoolApiTests.ResetAsync(client); var school = await SchoolApiTests.CreateAsync(client, "Быстрая", StartDate); - using var socket = await OpenSchoolAsync(school.Id); + using var socket = await OpenSchoolAsync(school.Id, client); await ReceiveClockAsync(socket); await SendAsync(socket, buffer => @@ -288,11 +288,11 @@ public class GameSocketTests(AppHostFixture fixture) [Fact] public async Task LeavingASchool_KeepsItsClockRunning() { - using var client = fixture.App.CreateHttpClient("server"); + using var client = await CreateOwnerHttpClientAsync(); await SchoolApiTests.ResetAsync(client); var school = await SchoolApiTests.CreateAsync(client, "Выход", StartDate); - using (var socket = await OpenSchoolAsync(school.Id)) + using (var socket = await OpenSchoolAsync(school.Id, client)) { await ReceiveClockAsync(socket); await SendAsync(socket, buffer => ProtocolCodec.WriteCloseSchool(buffer)); @@ -309,11 +309,11 @@ public class GameSocketTests(AppHostFixture fixture) [Fact] public async Task APausedSchool_StaysPausedAfterLeaving() { - using var client = fixture.App.CreateHttpClient("server"); + using var client = await CreateOwnerHttpClientAsync(); await SchoolApiTests.ResetAsync(client); var school = await SchoolApiTests.CreateAsync(client, "Оставлена на паузе", StartDate); - using (var socket = await OpenSchoolAsync(school.Id)) + using (var socket = await OpenSchoolAsync(school.Id, client)) { await ReceiveClockAsync(socket); await SendAsync(socket, buffer => @@ -333,11 +333,11 @@ public class GameSocketTests(AppHostFixture fixture) [Fact] public async Task DeletingTheOpenSchool_TellsTheClient() { - using var client = fixture.App.CreateHttpClient("server"); + using var client = await CreateOwnerHttpClientAsync(); await SchoolApiTests.ResetAsync(client); var school = await SchoolApiTests.CreateAsync(client, "Исчезнет", StartDate); - using var socket = await OpenSchoolAsync(school.Id); + using var socket = await OpenSchoolAsync(school.Id, client); await ReceiveClockAsync(socket); using var deleted = await client.DeleteAsync($"/api/schools/{school.Id}", TestContext.Current.CancellationToken); @@ -365,12 +365,12 @@ public class GameSocketTests(AppHostFixture fixture) [Fact] public async Task PausingOneSchool_DoesNotStopAnother() { - using var client = fixture.App.CreateHttpClient("server"); + using var client = await CreateOwnerHttpClientAsync(); await SchoolApiTests.ResetAsync(client); var paused = await SchoolApiTests.CreateAsync(client, "На паузе", StartDate); var running = await SchoolApiTests.CreateAsync(client, "Идёт дальше", StartDate); - using var socket = await OpenSchoolAsync(paused.Id); + using var socket = await OpenSchoolAsync(paused.Id, client); await ReceiveClockAsync(socket); await SendAsync(socket, buffer => ProtocolCodec.WriteSetRunning(buffer, new ClientSetRunningMessage(Running: false))); @@ -423,11 +423,11 @@ public class GameSocketTests(AppHostFixture fixture) [Fact] public async Task ReloadFromDisk_RestoresAPausedClock() { - using var client = fixture.App.CreateHttpClient("server"); + using var client = await CreateOwnerHttpClientAsync(); await SchoolApiTests.ResetAsync(client); var school = await SchoolApiTests.CreateAsync(client, "Снимок паузы", StartDate); - using (var socket = await OpenSchoolAsync(school.Id)) + using (var socket = await OpenSchoolAsync(school.Id, client)) { await ReceiveClockAsync(socket); await SendAsync(socket, buffer => @@ -512,21 +512,57 @@ public class GameSocketTests(AppHostFixture fixture) { var state = await SchoolApiTests.GetSchoolsAsync(client); var school = state.Schools.SingleOrDefault(candidate => candidate.Id == schoolId); + if (school is not null) + { + return school; + } - Assert.NotNull(school); - return school; + var other = state.Others.SingleOrDefault(candidate => candidate.Id == schoolId); + Assert.NotNull(other); + return new SchoolApiTests.SchoolResponse( + other.Id, + other.Name, + other.GameTime, + other.Running, + other.SpeedIndex, + other.Seed, + Mine: false, + other.ModIds); } - private async Task OpenSchoolAsync(int schoolId, byte locale = ProtocolConstants.LocaleRussian) + private async Task CreateOwnerHttpClientAsync() { - using var httpClient = fixture.App.CreateHttpClient("server"); - var socket = await ConnectAsync(httpClient, locale); + using var template = fixture.App.CreateHttpClient("server"); + return await SchoolApiTests.CreateIsolatedClientAsync(template, SchoolApiTests.TestUserName); + } + + private async Task OpenSchoolAsync( + int schoolId, + HttpClient ownerClient, + byte locale = ProtocolConstants.LocaleRussian) + { + var cookie = ownerClient.DefaultRequestHeaders.TryGetValues("Cookie", out var values) + ? values.First() + : await SchoolApiTests.WebSocketCookieAsync(ownerClient); + var socket = await ConnectWithCookieAsync(cookie, locale); await ReceiveUntilAsync(socket, MessageType.ServerWelcome); await SendAsync(socket, buffer => ProtocolCodec.WriteOpenSchool(buffer, new ClientOpenSchoolMessage(schoolId))); return socket; } + private async Task ConnectWithCookieAsync(string cookie, byte locale = ProtocolConstants.LocaleRussian) + { + var socket = new ClientWebSocket(); + socket.Options.SetRequestHeader("Cookie", cookie); + var http = fixture.App.GetEndpoint("server", "http"); + var uri = new UriBuilder(http) { Scheme = "ws", Path = "/ws/game" }.Uri; + await socket.ConnectAsync(uri, TestContext.Current.CancellationToken).WaitAsync(DefaultTimeout); + await SendAsync(socket, buffer => + ProtocolCodec.WriteHello(buffer, new ClientHelloMessage(ProtocolConstants.Version, locale))); + return socket; + } + private async Task ConnectAsync(byte locale = ProtocolConstants.LocaleRussian) { using var httpClient = fixture.App.CreateHttpClient("server"); diff --git a/tests/HSchool.AppHost.Tests/GoldenSaveTests.cs b/tests/HSchool.AppHost.Tests/GoldenSaveTests.cs deleted file mode 100644 index f1b6efd..0000000 --- a/tests/HSchool.AppHost.Tests/GoldenSaveTests.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System.Net.Http.Json; -using System.Text.Json; - -namespace HSchool.AppHost.Tests; - -[Collection(AppHostCollection.Name)] -public class GoldenSaveTests(AppHostFixture fixture) -{ - [Fact] - public async Task CurrentFormatSave_LoadsTheSamePeopleAndTime() - { - using var client = fixture.App.CreateHttpClient("server"); - await SchoolApiTests.ResetAsync(client); - await InstallAsync(client, "current"); - - var state = await SchoolApiTests.GetSchoolsAsync(client); - var school = Assert.Single(state.Schools); - Assert.Equal(1, school.Id); - Assert.Equal(1, school.Seed); - Assert.Equal("Золотая", school.Name); - Assert.Equal(new DateTime(2012, 3, 31, 6, 0, 0, DateTimeKind.Utc), school.GameTime); - Assert.False(school.Running); - - var people = await PeopleAsync(client, school.Id); - Assert.Equal(ExpectedNames(), people.Select(row => row.FullName).OrderBy(name => name, StringComparer.Ordinal).ToArray()); - Assert.Contains(people, person => person.Roles.Contains("student")); - } - - [Fact] - public async Task SaveWithoutNativeLanguage_LoadsWithoutReshuffling() - { - using var client = fixture.App.CreateHttpClient("server"); - await SchoolApiTests.ResetAsync(client); - await InstallAsync(client, "legacy-no-native"); - - var state = await SchoolApiTests.GetSchoolsAsync(client); - var school = Assert.Single(state.Schools); - Assert.Equal(1, school.Id); - Assert.Equal(1, school.Seed); - Assert.Equal("Золотая", school.Name); - - var people = await PeopleAsync(client, school.Id); - Assert.Equal(ExpectedNames(), people.Select(row => row.FullName).OrderBy(name => name, StringComparer.Ordinal).ToArray()); - } - - private async Task InstallAsync(HttpClient client, string folder) - { - var directory = await SavesDirectoryAsync(client); - var source = Path.Combine(AppContext.BaseDirectory, "golden", folder); - foreach (var file in Directory.EnumerateFiles(source)) - { - File.Copy(file, Path.Combine(directory, Path.GetFileName(file)), overwrite: true); - } - - using var reload = await client.PostAsync("/api/dev/reload-schools", content: null, TestContext.Current.CancellationToken); - reload.EnsureSuccessStatusCode(); - } - - private static async Task SavesDirectoryAsync(HttpClient client) - { - var payload = await client.GetFromJsonAsync( - "/api/dev/saves-directory", - TestContext.Current.CancellationToken); - Assert.NotNull(payload); - Assert.False(string.IsNullOrWhiteSpace(payload.Path)); - return payload.Path; - } - - private static async Task> PeopleAsync(HttpClient client, int schoolId) - { - var page = await client.GetFromJsonAsync( - $"/api/schools/{schoolId}/people?pageSize=100", - TestContext.Current.CancellationToken); - Assert.NotNull(page); - return page.People - .OrderBy(row => row.Id, StringComparer.Ordinal) - .ToArray(); - } - - private static string[] ExpectedNames() - { - var json = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "golden", "current", "1.people.json")); - using var document = JsonDocument.Parse(json); - return document.RootElement.GetProperty("people") - .EnumerateArray() - .Select(person => - { - var name = person.GetProperty("name"); - var surname = name.GetProperty("surname").GetString() ?? ""; - var given = name.GetProperty("given").GetString() ?? ""; - var patronymic = name.GetProperty("patronymic").GetString() ?? ""; - return string.Join(' ', new[] { surname, given, patronymic }.Where(part => part.Length > 0)); - }) - .OrderBy(full => full, StringComparer.Ordinal) - .ToArray(); - } - - private sealed record SavesDirectoryResponse(string Path); - - private sealed record PeoplePage(IReadOnlyList People); - - private sealed record PersonRow(string Id, string FullName, IReadOnlyList Roles); -} diff --git a/tests/HSchool.AppHost.Tests/SchoolApiTests.cs b/tests/HSchool.AppHost.Tests/SchoolApiTests.cs index 54a1781..1ad72a9 100644 --- a/tests/HSchool.AppHost.Tests/SchoolApiTests.cs +++ b/tests/HSchool.AppHost.Tests/SchoolApiTests.cs @@ -527,6 +527,25 @@ public class SchoolApiTests(AppHostFixture fixture) _ = await LoginAndGetCookieAsync(client, userName); } + internal static async Task CreateIsolatedClientAsync(HttpClient template, string userName) + { + ArgumentNullException.ThrowIfNull(template.BaseAddress); + + var login = new HttpClient { BaseAddress = template.BaseAddress }; + try + { + var cookie = await LoginAndGetCookieAsync(login, userName); + var handler = new HttpClientHandler { UseCookies = false }; + var client = new HttpClient(handler) { BaseAddress = template.BaseAddress }; + client.DefaultRequestHeaders.Add("Cookie", cookie); + return client; + } + finally + { + login.Dispose(); + } + } + internal static async Task CreateIsolatedClientAsync(DistributedApplication app, string userName) { var login = app.CreateHttpClient("server"); @@ -542,6 +561,29 @@ public class SchoolApiTests(AppHostFixture fixture) return client; } + internal static async Task WebSocketCookieAsync(HttpClient client, string userName = TestUserName) + { + if (client.DefaultRequestHeaders.TryGetValues("Cookie", out var values)) + { + var cookie = values.First(); + if (!string.IsNullOrWhiteSpace(cookie)) + { + return cookie; + } + } + + ArgumentNullException.ThrowIfNull(client.BaseAddress); + var login = new HttpClient { BaseAddress = client.BaseAddress }; + try + { + return await LoginAndGetCookieAsync(login, userName); + } + finally + { + login.Dispose(); + } + } + internal static async Task LoginAndGetCookieAsync(HttpClient client, string userName = TestUserName) { using var first = await client.PostAsJsonAsync( @@ -599,12 +641,12 @@ public class SchoolApiTests(AppHostFixture fixture) { await LoginAsync(client); - for (var pass = 0; pass < 4; pass++) + for (var pass = 0; pass < 8; pass++) { var state = await GetSchoolsAsync(client); if (state.Schools.Count == 0 && state.Others.Count == 0) { - break; + return; } foreach (var school in state.Schools) @@ -615,29 +657,37 @@ public class SchoolApiTests(AppHostFixture fixture) foreach (var other in state.Others) { - using var response = await client.DeleteAsync($"/api/schools/{other.Id}", TestContext.Current.CancellationToken); - if (response.StatusCode == HttpStatusCode.Forbidden) + HttpClient? ownedClient = null; + var deleter = client; + if (!string.IsNullOrWhiteSpace(other.Owner)) { - continue; + ownedClient = await CreateIsolatedClientAsync(client, other.Owner); + deleter = ownedClient; } - response.EnsureSuccessStatusCode(); + try + { + using var response = await deleter.DeleteAsync($"/api/schools/{other.Id}", TestContext.Current.CancellationToken); + if (response.StatusCode == HttpStatusCode.Forbidden) + { + continue; + } + + response.EnsureSuccessStatusCode(); + } + finally + { + ownedClient?.Dispose(); + } } } - var directory = await SavesDirectoryAsync(client); - foreach (var path in Directory.EnumerateFiles(directory)) + var left = await GetSchoolsAsync(client); + if (left.Schools.Count > 0 || left.Others.Count > 0) { - File.Delete(path); + throw new InvalidOperationException( + $"Could not wipe saves: {left.Schools.Count} own and {left.Others.Count} other schools remain."); } - - foreach (var path in Directory.EnumerateDirectories(directory)) - { - Directory.Delete(path, recursive: true); - } - - using var reload = await client.PostAsync("/api/dev/reload-schools", content: null, TestContext.Current.CancellationToken); - reload.EnsureSuccessStatusCode(); } internal static async Task GetSchoolsAsync(HttpClient client) diff --git a/tests/HSchool.AppHost.Tests/SchoolOwnerApiTests.cs b/tests/HSchool.AppHost.Tests/SchoolOwnerApiTests.cs index f1aadcf..0e981e0 100644 --- a/tests/HSchool.AppHost.Tests/SchoolOwnerApiTests.cs +++ b/tests/HSchool.AppHost.Tests/SchoolOwnerApiTests.cs @@ -1,4 +1,6 @@ using System.Net.Http.Json; +using System.Text.Json; +using System.Text.Json.Nodes; namespace HSchool.AppHost.Tests; @@ -37,6 +39,14 @@ public class SchoolOwnerApiTests(AppHostFixture fixture) Assert.Equal(2, aliceList.Others.Count); Assert.All(aliceList.Others, other => Assert.Equal("OwnerBob", other.Owner)); Assert.DoesNotContain(aliceList.Schools, school => school.Name.StartsWith("Bob", StringComparison.Ordinal)); + + foreach (var client in new[] { alice, bob }) + { + client.Dispose(); + } + + using var cleanup = fixture.App.CreateHttpClient("server"); + await SchoolApiTests.WipeAllSavesAsync(cleanup); } [Fact] @@ -99,6 +109,12 @@ public class SchoolOwnerApiTests(AppHostFixture fixture) TestContext.Current.CancellationToken); Assert.Equal(HttpStatusCode.Forbidden, hire.StatusCode); Assert.Equal("not-owner", await SchoolApiTests.ProblemCodeAsync(hire)); + + owner.Dispose(); + guest.Dispose(); + + using var cleanup = fixture.App.CreateHttpClient("server"); + await SchoolApiTests.WipeAllSavesAsync(cleanup); } [Fact] @@ -108,19 +124,41 @@ public class SchoolOwnerApiTests(AppHostFixture fixture) await SchoolApiTests.WipeAllSavesAsync(wipe); var first = await CreateUserAsync("SaveAlpha"); + var created = await SchoolApiTests.CreateAsync(first, "Бесхозная", Start); + var directory = await SavesDirectoryAsync(first); - var golden = Path.Combine(AppContext.BaseDirectory, "golden", "current"); - foreach (var file in Directory.EnumerateFiles(golden)) + var saveFiles = new[] { - File.Copy(file, Path.Combine(directory, Path.GetFileName(file)), overwrite: true); + $"{created.Id}.json", + $"{created.Id}.people.json", + $"{created.Id}.timetable.json", + }; + var copies = saveFiles + .Select(name => Path.Combine(directory, name)) + .Where(File.Exists) + .ToDictionary(path => path, File.ReadAllBytes); + + await SchoolApiTests.WipeAllSavesAsync(first); + + foreach (var (copiedPath, bytes) in copies) + { + File.WriteAllBytes(copiedPath, bytes); } + var schoolSavePath = Path.Combine(directory, $"{created.Id}.json"); + var node = JsonNode.Parse(File.ReadAllText(schoolSavePath)) + ?? throw new InvalidOperationException("School save parsed to nothing."); + node.AsObject().Remove("owner"); + File.WriteAllText( + schoolSavePath, + node.ToJsonString(new JsonSerializerOptions { WriteIndented = true })); + using var reload = await first.PostAsync("/api/dev/reload-schools", content: null, TestContext.Current.CancellationToken); reload.EnsureSuccessStatusCode(); var firstList = await SchoolApiTests.GetSchoolsAsync(first); Assert.Empty(firstList.Schools); - var orphan = Assert.Single(firstList.Others, other => other.Name == "Золотая"); + var orphan = Assert.Single(firstList.Others, other => other.Name == "Бесхозная"); Assert.Null(orphan.Owner); var second = await CreateUserAsync("SaveBeta"); @@ -129,6 +167,12 @@ public class SchoolOwnerApiTests(AppHostFixture fixture) var after = await SchoolApiTests.GetSchoolsAsync(second); Assert.DoesNotContain(after.Others, other => other.Id == orphan.Id); + + first.Dispose(); + second.Dispose(); + + using var cleanup = fixture.App.CreateHttpClient("server"); + await SchoolApiTests.WipeAllSavesAsync(cleanup); } private async Task CreateUserAsync(string userName) => diff --git a/tests/HSchool.AppHost.Tests/SchoolSeedTests.cs b/tests/HSchool.AppHost.Tests/SchoolSeedTests.cs index 1ded5b3..d8cfc6b 100644 --- a/tests/HSchool.AppHost.Tests/SchoolSeedTests.cs +++ b/tests/HSchool.AppHost.Tests/SchoolSeedTests.cs @@ -19,14 +19,21 @@ public class SchoolSeedTests(AppHostFixture fixture) var first = await SchoolApiTests.CreateAsync(client, "Сид одинаковый А", Start, seed: 42); var second = await SchoolApiTests.CreateAsync(client, "Сид одинаковый Б", Start, seed: 42); - var other = await SchoolApiTests.CreateAsync(client, "Сид другой", Start, seed: 7); var firstYearFive = await YearFiveAsync(client, first.Id); var secondYearFive = await YearFiveAsync(client, second.Id); - var otherYearFive = await YearFiveAsync(client, other.Id); Assert.Equal(firstYearFive, secondYearFive); - Assert.NotEqual(firstYearFive, otherYearFive); + + using (var delete = await client.DeleteAsync($"/api/schools/{first.Id}", TestContext.Current.CancellationToken)) + { + delete.EnsureSuccessStatusCode(); + } + + var other = await SchoolApiTests.CreateAsync(client, "Сид другой", Start, seed: 7); + var otherYearFive = await YearFiveAsync(client, other.Id); + + Assert.NotEqual(secondYearFive, otherYearFive); } [Fact] diff --git a/tests/HSchool.AppHost.Tests/ServiceabilityTests.cs b/tests/HSchool.AppHost.Tests/ServiceabilityTests.cs index 2dee6c3..dc90d48 100644 --- a/tests/HSchool.AppHost.Tests/ServiceabilityTests.cs +++ b/tests/HSchool.AppHost.Tests/ServiceabilityTests.cs @@ -26,7 +26,16 @@ public class ServiceabilityTests(AppHostFixture fixture) using var reload = await client.PostAsync("/api/dev/reload-schools", content: null, TestContext.Current.CancellationToken); reload.EnsureSuccessStatusCode(); - Assert.Empty((await SchoolApiTests.GetSchoolsAsync(client)).Schools); + var state = await SchoolApiTests.GetSchoolsAsync(client); + Assert.Empty(state.Schools); + var broken = Assert.Single(state.Others); + Assert.Equal(1, broken.Id); + + using var people = await client.GetAsync( + $"/api/schools/{broken.Id}/people?pageSize=1", + TestContext.Current.CancellationToken); + Assert.Equal(HttpStatusCode.NotFound, people.StatusCode); + Assert.Equal(before, File.ReadAllBytes(path)); } finally @@ -54,7 +63,16 @@ public class ServiceabilityTests(AppHostFixture fixture) using var reload = await client.PostAsync("/api/dev/reload-schools", content: null, TestContext.Current.CancellationToken); reload.EnsureSuccessStatusCode(); - Assert.Empty((await SchoolApiTests.GetSchoolsAsync(client)).Schools); + var state = await SchoolApiTests.GetSchoolsAsync(client); + Assert.Empty(state.Schools); + var broken = Assert.Single(state.Others); + Assert.Equal(1, broken.Id); + + using var people = await client.GetAsync( + $"/api/schools/{broken.Id}/people?pageSize=1", + TestContext.Current.CancellationToken); + Assert.Equal(HttpStatusCode.NotFound, people.StatusCode); + Assert.Equal(before, File.ReadAllBytes(path)); } finally @@ -65,29 +83,6 @@ public class ServiceabilityTests(AppHostFixture fixture) } } - [Fact] - public async Task CurrentFormatSave_LoadsAsBefore() - { - using var client = fixture.App.CreateHttpClient("server"); - await SchoolApiTests.ResetAsync(client); - var directory = await SavesDirectoryAsync(client); - - try - { - InstallGolden(directory, "current"); - using var reload = await client.PostAsync("/api/dev/reload-schools", content: null, TestContext.Current.CancellationToken); - reload.EnsureSuccessStatusCode(); - - var school = Assert.Single((await SchoolApiTests.GetSchoolsAsync(client)).Schools); - Assert.Equal(1, school.Id); - Assert.Equal("Золотая", school.Name); - } - finally - { - await SchoolApiTests.ResetAsync(client); - } - } - [Fact] public async Task Dump_ReturnsPeopleNodesAndTimetable() { diff --git a/tests/HSchool.People.Tests/golden/roster.txt b/tests/HSchool.People.Tests/golden/roster.txt index ed54516..ea87923 100644 --- a/tests/HSchool.People.Tests/golden/roster.txt +++ b/tests/HSchool.People.Tests/golden/roster.txt @@ -3,133 +3,133 @@ class class-classroom-00 year=1 letter=А room=classroom-00 capacity=16 pupils=f class class-classroom-01 year=2 letter=А room=classroom-01 capacity=16 pupils=f0.c1,f12.c0,f12.c1,f12.c2,f13.c1,f13.c2,f14.c2,f20.c0,f23.c1,f26.c0,f27.c0,f29.c0,f31.c0,f4.c0,f7.c0,f9.c0 class class-classroom-02 year=3 letter=А room=classroom-02 capacity=16 pupils=f10.c0,f14.c0,f16.c0,f17.c1,f2.c0,f21.c0,f22.c1,f23.c0,f25.c0,f28.c0,f3.c0,f3.c1,f32.c0,f32.c2,f5.c0,f6.c1 class class-classroom-03 year=4 letter=А room=classroom-03 capacity=16 pupils=f0.c0,f1.c0,f11.c0,f14.c1,f15.c0,f17.c0,f17.c2,f20.c1,f22.c0,f24.c0,f28.c1,f30.c0,f30.c1,f8.c1,f9.c1,f9.c2 -person f0.c0 Андреев Сергей Валериевич female=False roles=student class=class-classroom-03 born=2000-10-23 traits=Kind skills=Agility=33,Biology=57,Communication=47,Endurance=46,Hauling=56,Literature=22,Mathematics=18,PhysicalEducation=73,RussianLanguage=47,Strength=64,UkrainianLanguage=31 items=Underwear:Black:worn,Socks:Gray:worn,Jeans:Black:worn,TShirt:Black:worn,Shoes:Gray:worn,Sweater:Gray:worn,Scarf:Black:worn,Coat:Black:home,PeShirt:Gray:home,PeShorts:Gray:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f0.c1 Андреев Александр Валериевич female=False roles=student class=class-classroom-01 born=2002-11-09 traits=HotTempered,Leader skills=Agility=13,Biology=46,Communication=20,Endurance=52,Hauling=24,Literature=35,Mathematics=49,PhysicalEducation=57,RussianLanguage=47,Strength=45 items=Underwear:Black:worn,Socks:Gray:worn,Jeans:Blue:worn,TShirt:LightBlue:worn,Shoes:Gray:worn,Sweater:Black:worn,Coat:Beige:worn,Scarf:Gray:worn,PeShirt:Gray:home,PeShorts:Black:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f0.p0 Андреев Валерий Николаевич female=False roles=parent class=- born=1971-06-03 traits=Diligent,Quiet skills=Agility=43,Communication=58,Cooking=82,Endurance=61,Hauling=61,Pedagogy=50,RussianLanguage=89,Strength=33 items=Underwear:Black:worn,Socks:White:worn,Jeans:Black:worn,TShirt:LightBlue:worn,WinterBoots:Beige:worn,Jacket:Gray:worn,Bottle:Green:bag -person f0.p1 Андреева Марина Артемовна female=True roles=parent class=- born=1971-09-30 traits=Quiet skills=Agility=25,BelarusianLanguage=26,Communication=38,Endurance=35,Hauling=46,History=61,Mathematics=75,RussianLanguage=65,Strength=40,UkrainianLanguage=23 items=Underwear:Gray:worn,Socks:White:worn,Pants:Beige:worn,Shirt:Blue:worn,WinterBoots:Gray:worn,Sweater:Beige:worn,Jacket:Blue:home,Bottle:White:bag,Phone:Black:bag -person f1.c0 Александрова Людмила Борисовна female=True roles=student class=class-classroom-03 born=2001-01-26 traits=AbsentMinded skills=Agility=72,BelarusianLanguage=36,Biology=67,Communication=37,Endurance=39,Hauling=56,Literature=77,Mathematics=29,PhysicalEducation=54,RussianLanguage=51,Strength=45,UkrainianLanguage=28 items=Underwear:Black:worn,Socks:White:worn,Dress:Black:worn,Shoes:White:worn,Belt:Beige:worn,Jacket:Black:home,PeShirt:Blue:home,PeShorts:Blue:home,Bottle:Blue:bag,Phone:Black:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f1.c1 Александров Евгений Борисович female=False roles=student class=class-classroom-00 born=2004-04-01 traits= skills=Agility=31,BelarusianLanguage=27,Biology=50,Communication=38,Endurance=54,Hauling=58,Literature=65,Mathematics=30,PhysicalEducation=88,RussianLanguage=44,Strength=40 items=Underwear:White:worn,Socks:Black:worn,Jeans:Black:worn,TShirt:Blue:worn,WinterBoots:Black:worn,Hat:Black:worn,Coat:Gray:home,PeShirt:Blue:home,PeShorts:Blue:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f1.c2 Александров Юрий Борисович female=False roles=student class=class-classroom-00 born=2003-10-28 traits= skills=Agility=45,Biology=68,Communication=31,Endurance=30,Hauling=49,Literature=60,Mathematics=29,PhysicalEducation=49,RussianLanguage=53,Strength=74,UkrainianLanguage=23 items=Underwear:Black:worn,Socks:Gray:worn,Pants:Blue:worn,TShirt:LightBlue:worn,Shoes:Black:worn,Sweater:Blue:worn,Scarf:Black:worn,Coat:Beige:home,PeShirt:Red:home,PeShorts:Gray:home,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f1.p0 Александров Борис Ильич female=False roles=parent class=- born=1968-10-25 traits= skills=Agility=45,Communication=74,Endurance=34,Hauling=65,RussianLanguage=53,Strength=53,UkrainianLanguage=14 items=Underwear:Black:worn,Socks:White:worn,Trousers:Gray:worn,TShirt:Blue:worn,WinterBoots:Gray:worn,Hat:Beige:worn,Scarf:Gray:worn,FurCoat:Beige:home -person f1.p1 Александрова Елизавета Романовна female=True roles=parent class=- born=1972-01-25 traits= skills=Administration=71,Agility=45,BelarusianLanguage=26,Communication=76,Cooking=59,Endurance=35,English=58,Hauling=48,Mathematics=30,RussianLanguage=42,Strength=66 items=Underwear:Black:worn,Socks:Black:worn,Skirt:Beige:worn,Shirt:Blue:worn,WinterBoots:Beige:worn,Sweater:Beige:worn,Coat:Beige:home,Bottle:White:bag,Phone:White:bag -person f10.c0 Захаров Пётр Виталиевич female=False roles=student class=class-classroom-02 born=2001-11-08 traits=Diligent skills=Agility=31,BelarusianLanguage=26,Biology=62,Communication=54,Endurance=45,Hauling=56,Literature=25,Mathematics=81,PhysicalEducation=63,RussianLanguage=46,Strength=60 items=Underwear:Black:worn,Socks:Black:worn,Pants:Gray:worn,TShirt:Black:worn,WinterBoots:Beige:worn,Jacket:Gray:home,PeShirt:Red:home,PeShorts:Red:home,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f10.p0 Захаров Виталий Борисович female=False roles=parent class=- born=1976-09-07 traits=HeatLoving,Neat skills=Agility=44,BelarusianLanguage=22,Communication=66,Endurance=82,English=16,Hauling=40,RussianLanguage=69,Strength=55 items=Underwear:Gray:worn,Socks:Black:worn,Trousers:Black:worn,TShirt:White:worn,WinterBoots:Black:worn,FurCoat:Gray:worn,Hat:Blue:worn,Scarf:Blue:worn,Bottle:White:bag,Phone:Black:bag -person f10.p1 Захарова Любовь Валериевна female=True roles=parent class=- born=1978-11-03 traits=AbsentMinded,HotTempered skills=Administration=43,Agility=45,BelarusianLanguage=21,Communication=61,Endurance=53,English=52,Hauling=52,Medicine=23,PhysicalEducation=42,Physics=59,RussianLanguage=66,Strength=28 items=Underwear:Gray:worn,Socks:Gray:worn,Trousers:Blue:worn,Shirt:White:worn,Shoes:Gray:worn,Sweater:Blue:worn,FurCoat:Gray:home,Bottle:White:bag,Phone:Gray:bag -person f11.c0 Андреева Дарья Ивановна female=True roles=student class=class-classroom-03 born=2001-05-20 traits=Lazy skills=Agility=47,BelarusianLanguage=13,Biology=57,Communication=43,Endurance=44,Hauling=50,Literature=59,Mathematics=18,PhysicalEducation=53,RussianLanguage=47,Strength=45,UkrainianLanguage=24 items=Underwear:Gray:worn,Socks:White:worn,Dress:Beige:worn,Shoes:White:worn,Hat:Blue:worn,Coat:Gray:home,PeShirt:Black:home,PeShorts:Black:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f11.p0 Андреев Иван Алексеевич female=False roles=parent class=- born=1976-06-01 traits= skills=Agility=38,BelarusianLanguage=16,Communication=52,Endurance=82,Hauling=33,Medicine=48,Pedagogy=61,PhysicalEducation=54,RussianLanguage=82,Strength=67,UkrainianLanguage=22 items=Underwear:White:worn,Socks:White:worn,Jeans:Black:worn,Shirt:Gray:worn,WinterBoots:Gray:worn,Sweater:Beige:worn,FurCoat:Beige:worn,Phone:Gray:bag -person f11.p1 Андреева София Александровна female=True roles=parent class=- born=1978-04-24 traits=Curious,Diligent skills=Agility=61,Communication=74,Endurance=55,English=61,Hauling=31,History=29,RussianLanguage=54,Strength=43 items=Underwear:Gray:worn,Socks:Black:worn,Trousers:Blue:worn,Shirt:Black:worn,WinterBoots:Black:worn,Coat:Gray:worn,Bottle:White:bag -person f12.c0 Кузьмин Олег Владиславович female=False roles=student class=class-classroom-01 born=2003-03-20 traits=ColdLoving,Lazy skills=Agility=49,Biology=58,Communication=48,Endurance=57,Hauling=54,Literature=54,Mathematics=33,PhysicalEducation=39,RussianLanguage=9,Strength=36,UkrainianLanguage=31 items=Underwear:White:worn,Socks:Gray:worn,Pants:Blue:worn,TShirt:LightBlue:worn,WinterBoots:Black:worn,Hat:Black:worn,Belt:Black:worn,FurCoat:Gray:home,PeShirt:Red:home,PeShorts:Red:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f12.c1 Кузьмин Егор Владиславович female=False roles=student class=class-classroom-01 born=2003-05-31 traits=Lazy,Leader skills=Agility=45,Biology=45,Communication=30,Endurance=59,Hauling=44,Literature=39,Mathematics=30,PhysicalEducation=59,RussianLanguage=49,Strength=35,UkrainianLanguage=23 items=Underwear:Gray:worn,Socks:Gray:worn,Pants:Black:worn,TShirt:Beige:worn,Shoes:Black:worn,Sweater:Black:worn,Coat:Black:worn,Belt:Beige:worn,PeShirt:Black:home,PeShorts:Black:home,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f12.c2 Кузьмина Дарья Владиславовна female=True roles=student class=class-classroom-01 born=2002-10-29 traits=Curious,Leader skills=Agility=42,BelarusianLanguage=20,Biology=56,Communication=39,Endurance=58,Hauling=59,Literature=35,Mathematics=22,PhysicalEducation=81,RussianLanguage=58,Strength=34,UkrainianLanguage=28 items=Underwear:Black:worn,Socks:Gray:worn,Skirt:Blue:worn,Shirt:Black:worn,Shoes:Black:worn,Sweater:Beige:worn,Coat:Beige:worn,PeShirt:Green:home,PeShorts:Black:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f12.p0 Кузьмин Владислав Александрович female=False roles=parent class=- born=1975-01-17 traits= skills=Administration=56,Agility=25,BelarusianLanguage=10,Communication=68,Endurance=35,English=36,Hauling=63,Informatics=56,Medicine=59,RussianLanguage=54,Strength=36 items=Underwear:Gray:worn,Socks:Gray:worn,Jeans:Blue:worn,Shirt:Blue:worn,Shoes:Gray:worn,Jacket:Blue:worn,Bottle:Blue:bag -person f12.p1 Кузьмина Ольга Кирилловна female=True roles=parent class=- born=1979-10-15 traits=AbsentMinded,HotTempered skills=Administration=38,Agility=39,BelarusianLanguage=25,Chemistry=48,Communication=32,Endurance=41,English=42,Geography=41,Hauling=46,RussianLanguage=32,Strength=34,UkrainianLanguage=40 items=Underwear:Gray:worn,Socks:Gray:worn,Pants:Gray:worn,Shirt:Gray:worn,WinterBoots:Gray:worn,Sweater:Gray:worn,FurCoat:Gray:home -person f13.c0 Яковлев Константин Владиславович female=False roles=student class=class-classroom-00 born=2004-06-16 traits= skills=Agility=53,BelarusianLanguage=24,Biology=48,Communication=29,Endurance=37,Hauling=64,Literature=58,Mathematics=22,PhysicalEducation=44,RussianLanguage=31,Strength=37 items=Underwear:Gray:worn,Socks:Black:worn,Trousers:Gray:worn,Shirt:Blue:worn,WinterBoots:Gray:worn,Jacket:Blue:home,PeShirt:Gray:home,PeShorts:Red:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f13.c1 Яковлев Юрий Владиславович female=False roles=student class=class-classroom-01 born=2003-07-23 traits=Diligent,Quiet skills=Agility=36,BelarusianLanguage=22,Biology=56,Communication=43,Endurance=44,Hauling=57,Literature=56,Mathematics=26,PhysicalEducation=51,RussianLanguage=37,Strength=68,UkrainianLanguage=8 items=Underwear:Black:worn,Socks:Gray:worn,Pants:Blue:worn,Shirt:Black:worn,WinterBoots:Gray:worn,Sweater:Gray:worn,FurCoat:Black:worn,PeShirt:Gray:home,PeShorts:Blue:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f13.c2 Яковлева Алина Владиславовна female=True roles=student class=class-classroom-01 born=2003-02-01 traits=Diligent skills=Agility=58,BelarusianLanguage=20,Biology=60,Communication=45,Endurance=54,Hauling=53,Literature=66,Mathematics=34,PhysicalEducation=47,RussianLanguage=59,Strength=45 items=Underwear:White:worn,Socks:White:worn,Trousers:Black:worn,Shirt:Blue:worn,Shoes:Gray:worn,Hat:Gray:worn,Scarf:Blue:worn,Coat:Beige:home,PeShirt:Red:home,PeShorts:Red:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f13.p0 Яковлев Владислав Андреевич female=False roles=parent class=- born=1974-09-23 traits=AbsentMinded,HotTempered skills=Agility=25,BelarusianLanguage=28,Communication=42,Endurance=35,English=26,Hauling=52,RussianLanguage=54,Strength=38 items=Underwear:Black:worn,Socks:White:worn,Trousers:Black:worn,TShirt:White:worn,Shoes:Beige:worn,Hat:Beige:worn,Coat:Beige:home,Bottle:Green:bag,Phone:White:bag -person f14.c0 Александров Степан Денисович female=False roles=student class=class-classroom-02 born=2002-01-03 traits= skills=Agility=57,BelarusianLanguage=17,Biology=53,Communication=18,Endurance=60,Hauling=64,Literature=34,Mathematics=33,PhysicalEducation=55,RussianLanguage=32,Strength=41 items=Underwear:Gray:worn,Socks:Gray:worn,Pants:Blue:worn,TShirt:Gray:worn,Shoes:Black:worn,Sweater:Beige:worn,Jacket:Gray:worn,Hat:Blue:worn,Scarf:Blue:worn,PeShirt:Gray:home,PeShorts:Red:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f14.c1 Александрова Вероника Денисовна female=True roles=student class=class-classroom-03 born=2001-08-11 traits= skills=Agility=45,BelarusianLanguage=15,Biology=52,Communication=38,Endurance=37,Hauling=44,Literature=29,Mathematics=18,PhysicalEducation=65,RussianLanguage=46,Strength=64 items=Underwear:Black:worn,Socks:Gray:worn,Trousers:Gray:worn,TShirt:Black:worn,WinterBoots:Gray:worn,Belt:Gray:worn,FurCoat:Beige:home,PeShirt:Red:home,PeShorts:Gray:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f14.c2 Александров Константин Денисович female=False roles=student class=class-classroom-01 born=2003-08-21 traits= skills=Agility=29,BelarusianLanguage=21,Biology=66,Communication=21,Endurance=51,Hauling=42,Literature=64,Mathematics=27,PhysicalEducation=67,RussianLanguage=20,Strength=45 items=Underwear:White:worn,Socks:Gray:worn,Jeans:Gray:worn,TShirt:Gray:worn,Shoes:Gray:worn,Scarf:Blue:worn,Coat:Black:home,PeShirt:Blue:home,PeShorts:Red:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f14.p1 Александрова Виктория Викторовна female=True roles=parent class=- born=1973-07-23 traits= skills=Agility=45,Communication=72,Cooking=68,Endurance=64,Geography=58,Hauling=44,Physics=38,RussianLanguage=61,Strength=49,UkrainianLanguage=18 items=Underwear:Gray:worn,Socks:Gray:worn,Dress:Gray:worn,WinterBoots:Beige:worn,Hat:Black:worn,Coat:Black:home,Phone:Black:bag -person f15.c0 Воробьёв Игорь Сергеевич female=False roles=student class=class-classroom-03 born=2000-09-20 traits= skills=Agility=45,BelarusianLanguage=24,Biology=33,Communication=61,Endurance=58,Hauling=42,Literature=45,Mathematics=32,PhysicalEducation=40,RussianLanguage=55,Strength=38 items=Underwear:Black:worn,Socks:White:worn,Trousers:Blue:worn,TShirt:Black:worn,Shoes:Beige:worn,Sweater:Black:worn,Hat:Gray:worn,FurCoat:Black:home,PeShirt:Green:home,PeShorts:Blue:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f15.p0 Воробьёв Сергей Никитич female=False roles=parent class=- born=1974-10-22 traits=Neat,Quiet skills=Agility=45,Communication=54,Endurance=45,Hauling=36,Librarianship=67,Pedagogy=38,RussianLanguage=71,Strength=65,UkrainianLanguage=32 items=Underwear:White:worn,Socks:Black:worn,Pants:Blue:worn,TShirt:Gray:worn,Shoes:Black:worn,Sweater:Black:worn,FurCoat:Beige:home,Bottle:Blue:bag -person f15.p1 Воробьёва Вера Виталиевна female=True roles=parent class=- born=1972-09-07 traits=Lazy,Neat skills=Agility=42,BelarusianLanguage=40,Communication=30,Endurance=52,Hauling=46,Pedagogy=55,RussianLanguage=46,Strength=45,UkrainianLanguage=25 items=Underwear:Black:worn,Socks:Gray:worn,Pants:Black:worn,TShirt:Black:worn,WinterBoots:Black:worn,Coat:Gray:worn,Hat:Gray:worn,Scarf:Black:worn,Bottle:Blue:bag,Phone:White:bag -person f16.c0 Фролова Елизавета Игоревна female=True roles=student class=class-classroom-02 born=2002-06-29 traits=Quiet skills=Agility=29,BelarusianLanguage=17,Biology=43,Communication=32,Endurance=19,Hauling=36,Literature=51,Mathematics=51,PhysicalEducation=68,RussianLanguage=24,Strength=45 items=Underwear:Gray:worn,Socks:Black:worn,Jeans:Blue:worn,Shirt:Blue:worn,WinterBoots:Beige:worn,Sweater:Beige:worn,FurCoat:Black:home,PeShirt:Red:home,PeShorts:Gray:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f16.p0 Фролов Игорь Виталиевич female=False roles=parent class=- born=1968-07-25 traits= skills=Administration=51,Agility=57,BelarusianLanguage=25,Communication=58,Endurance=63,Hauling=49,Medicine=53,RussianLanguage=53,Strength=46 items=Underwear:White:worn,Socks:White:worn,Pants:Gray:worn,Shirt:Gray:worn,Shoes:Gray:worn,Hat:Blue:worn,Jacket:Beige:home -person f16.p1 Фролова Татьяна Олеговна female=True roles=parent class=- born=1970-06-17 traits=Leader,Neat skills=Agility=45,BelarusianLanguage=23,Communication=59,Endurance=59,Hauling=62,Literature=51,RussianLanguage=72,Strength=44 items=Underwear:White:worn,Socks:Black:worn,Skirt:Blue:worn,Shirt:Blue:worn,Shoes:Beige:worn,Sweater:Blue:worn,Coat:Beige:home,Bottle:Blue:bag,Phone:Black:bag -person f17.c0 Иванов Андрей Александрович female=False roles=student class=class-classroom-03 born=2001-06-27 traits=Lazy skills=Agility=60,Biology=53,Communication=54,Endurance=35,Hauling=41,Literature=65,Mathematics=32,PhysicalEducation=47,RussianLanguage=35,Strength=45,UkrainianLanguage=13 items=Underwear:White:worn,Socks:Gray:worn,Jeans:Gray:worn,Shirt:White:worn,WinterBoots:Gray:worn,Coat:Black:home,PeShirt:Green:home,PeShorts:Blue:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f17.c1 Иванова Ксения Александровна female=True roles=student class=class-classroom-02 born=2001-09-10 traits=ColdLoving skills=Agility=45,Biology=64,Communication=28,Endurance=64,Hauling=48,Literature=51,Mathematics=37,PhysicalEducation=28,RussianLanguage=43,Strength=45 items=Underwear:White:worn,Socks:Black:worn,Pants:Black:worn,Shirt:Gray:worn,WinterBoots:Gray:worn,FurCoat:Beige:worn,Hat:Black:worn,PeShirt:Blue:home,PeShorts:Green:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f17.c2 Иванова Полина Александровна female=True roles=student class=class-classroom-03 born=2001-05-21 traits=Diligent,HotTempered skills=Agility=36,Biology=43,Communication=45,Endurance=45,Hauling=47,Literature=43,Mathematics=41,PhysicalEducation=58,RussianLanguage=42,Strength=45,UkrainianLanguage=15 items=Underwear:Gray:worn,Socks:Black:worn,Pants:Black:worn,TShirt:Beige:worn,WinterBoots:Gray:worn,Sweater:Gray:worn,Coat:Beige:worn,Belt:Beige:worn,PeShirt:Red:home,PeShorts:Gray:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f17.p0 Иванов Александр Владимирович female=False roles=parent class=- born=1967-12-24 traits=Diligent,HeatLoving skills=Agility=47,Communication=74,Endurance=52,Hauling=55,Physics=39,RussianLanguage=27,Strength=49 items=Underwear:Black:worn,Socks:Gray:worn,Jeans:Blue:worn,TShirt:Blue:worn,WinterBoots:Black:worn,Belt:Beige:worn,FurCoat:Black:home -person f17.p1 Иванова Александра Ивановна female=True roles=parent class=- born=1967-04-25 traits= skills=Agility=40,Communication=69,Endurance=63,Hauling=35,RussianLanguage=56,Strength=68,UkrainianLanguage=19 items=Underwear:White:worn,Socks:White:worn,Pants:Beige:worn,Shirt:Blue:worn,Shoes:Beige:worn,FurCoat:Gray:home,Phone:White:bag -person f18.c0 Лебедев Александр Валериевич female=False roles=student class=class-classroom-00 born=2004-02-16 traits=ColdLoving skills=Agility=26,Biology=50,Communication=9,Endurance=53,Hauling=33,Literature=55,Mathematics=20,PhysicalEducation=56,RussianLanguage=23,Strength=35 items=Underwear:Black:worn,Socks:Black:worn,Pants:Black:worn,Shirt:White:worn,WinterBoots:Beige:worn,Sweater:Beige:worn,Jacket:Blue:worn,Belt:Gray:worn,PeShirt:Blue:home,PeShorts:Green:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f18.p0 Лебедев Валерий Романович female=False roles=parent class=- born=1985-03-29 traits=Leader,Neat skills=Agility=25,Chemistry=56,Communication=59,Endurance=35,English=49,Geography=50,Hauling=35,Pedagogy=51,RussianLanguage=67,Strength=62 items=Underwear:Gray:worn,Socks:Black:worn,Pants:Black:worn,Shirt:Gray:worn,Shoes:Beige:worn,FurCoat:Gray:worn,Belt:Beige:worn,Bottle:Green:bag -person f18.p1 Лебедева Юлия Михаиловна female=True roles=parent class=- born=1981-01-26 traits= skills=Administration=54,Agility=59,BelarusianLanguage=36,Biology=68,Communication=52,Endurance=44,Hauling=67,Mathematics=47,Medicine=37,RussianLanguage=48,Strength=45,UkrainianLanguage=22 items=Underwear:Gray:worn,Socks:White:worn,Dress:Black:worn,Shoes:Gray:worn,Sweater:Gray:worn,Hat:Black:worn,Belt:Black:worn,Jacket:Gray:home,Phone:Black:bag -person f19.c0 Воробьёв Григорий Михаилович female=False roles=student class=class-classroom-00 born=2004-08-29 traits=Quiet skills=Agility=62,BelarusianLanguage=31,Biology=42,Communication=36,Endurance=50,Hauling=39,Literature=34,Mathematics=37,PhysicalEducation=50,RussianLanguage=33,Strength=45,UkrainianLanguage=23 items=Underwear:Black:worn,Socks:White:worn,Pants:Blue:worn,Shirt:Blue:worn,Shoes:Gray:worn,Sweater:Black:worn,Coat:Gray:worn,PeShirt:Blue:home,PeShorts:Black:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f19.p0 Воробьёв Михаил Егорович female=False roles=parent class=- born=1978-11-03 traits=Lazy skills=Administration=53,Agility=40,Communication=56,Endurance=46,Hauling=41,Librarianship=66,Mathematics=36,RussianLanguage=45,Strength=55 items=Underwear:Black:worn,Socks:Black:worn,Pants:Black:worn,Shirt:Black:worn,Shoes:Beige:worn,Hat:Black:worn,Jacket:Green:home,Bottle:Blue:bag -person f19.p1 Воробьёва Вера Алексеевна female=True roles=parent class=- born=1982-08-29 traits=AbsentMinded,Kind skills=Administration=50,Agility=58,BelarusianLanguage=20,Communication=46,Endurance=30,Hauling=50,PhysicalEducation=74,RussianLanguage=49,Strength=40,UkrainianLanguage=25 items=Underwear:Gray:worn,Socks:Gray:worn,Dress:Black:worn,WinterBoots:Black:worn,Jacket:Red:home -person f2.c0 Алексеев Григорий Константинович female=False roles=student class=class-classroom-02 born=2002-07-24 traits=Neat skills=Agility=40,BelarusianLanguage=36,Biology=64,Communication=34,Endurance=57,Hauling=38,Literature=58,Mathematics=41,PhysicalEducation=47,RussianLanguage=28,Strength=45,UkrainianLanguage=23 items=Underwear:White:worn,Socks:Gray:worn,Jeans:Blue:worn,TShirt:Black:worn,Shoes:Black:worn,FurCoat:Gray:home,PeShirt:Red:home,PeShorts:Red:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f2.p0 Алексеев Константин Романович female=False roles=parent class=- born=1974-12-11 traits=Neat skills=Agility=36,BelarusianLanguage=23,Communication=60,Endurance=49,English=34,Hauling=47,Mathematics=63,Medicine=81,Pedagogy=74,RussianLanguage=56,Strength=59,UkrainianLanguage=22 items=Underwear:Gray:worn,Socks:White:worn,Pants:Beige:worn,Shirt:Black:worn,WinterBoots:Beige:worn,Sweater:Black:worn,Hat:Black:worn,FurCoat:Gray:home -person f2.p1 Алексеева Ольга Романовна female=True roles=parent class=- born=1970-07-22 traits=Neat skills=Agility=50,Communication=58,Cooking=51,Endurance=43,English=55,Hauling=54,Literature=35,Medicine=76,PhysicalEducation=32,RussianLanguage=38,Strength=42 items=Underwear:Black:worn,Socks:White:worn,Skirt:Gray:worn,TShirt:Blue:worn,Shoes:Beige:worn,Sweater:Gray:worn,Coat:Beige:home,Bottle:White:bag -person f20.c0 Павлова Елена Игоревна female=True roles=student class=class-classroom-01 born=2002-11-09 traits= skills=Agility=55,Biology=47,Communication=19,Endurance=51,Hauling=38,Literature=41,Mathematics=34,PhysicalEducation=49,RussianLanguage=44,Strength=42,UkrainianLanguage=20 items=Underwear:White:worn,Socks:Gray:worn,Pants:Black:worn,TShirt:Blue:worn,WinterBoots:Beige:worn,Hat:Beige:worn,Belt:Gray:worn,Coat:Gray:home,PeShirt:Red:home,PeShorts:Gray:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f20.c1 Павлова Валентина Игоревна female=True roles=student class=class-classroom-03 born=2001-01-24 traits=HotTempered skills=Agility=68,BelarusianLanguage=12,Biology=41,Communication=31,Endurance=58,Hauling=46,Literature=42,Mathematics=19,PhysicalEducation=50,RussianLanguage=45,Strength=42,UkrainianLanguage=9 items=Underwear:White:worn,Socks:Black:worn,Trousers:Gray:worn,TShirt:Blue:worn,Shoes:Black:worn,Sweater:Blue:worn,FurCoat:Beige:home,PeShirt:Green:home,PeShorts:Blue:home,Bottle:Green:bag,Phone:Black:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f20.p0 Павлов Игорь Никитич female=False roles=parent class=- born=1968-08-01 traits=HeatLoving,Leader skills=Administration=61,Agility=72,Communication=57,Endurance=43,Geography=61,German=73,Hauling=44,Pedagogy=56,RussianLanguage=66,Strength=42,UkrainianLanguage=28 items=Underwear:Black:worn,Socks:White:worn,Trousers:Black:worn,TShirt:Blue:worn,WinterBoots:Gray:worn,FurCoat:Black:home,Bottle:White:bag -person f20.p1 Павлова Людмила Дмитриевна female=True roles=parent class=- born=1970-01-17 traits=ColdLoving,Quiet skills=Agility=46,BelarusianLanguage=26,Communication=71,Endurance=39,English=31,Hauling=36,Physics=42,RussianLanguage=53,Strength=56 items=Underwear:White:worn,Socks:Gray:worn,Dress:Beige:worn,WinterBoots:Beige:worn,Hat:Black:worn,Belt:Beige:worn,Coat:Gray:home,Bottle:White:bag -person f21.c0 Ильин Андрей Константинович female=False roles=student class=class-classroom-02 born=2002-05-12 traits= skills=Agility=54,BelarusianLanguage=15,Biology=72,Communication=22,Endurance=54,Hauling=58,Literature=58,Mathematics=45,PhysicalEducation=52,RussianLanguage=38,Strength=45,UkrainianLanguage=25 items=Underwear:Black:worn,Socks:Black:worn,Jeans:Black:worn,TShirt:White:worn,WinterBoots:Black:worn,Sweater:Black:worn,Jacket:Black:worn,PeShirt:Gray:home,PeShorts:Blue:home,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f21.p0 Ильин Константин Никитич female=False roles=parent class=- born=1971-01-21 traits=Diligent,Quiet skills=Agility=45,Biology=50,Communication=53,Endurance=40,English=46,Hauling=45,History=42,Librarianship=57,RussianLanguage=68,Strength=40,UkrainianLanguage=9 items=Underwear:Gray:worn,Socks:Black:worn,Trousers:Black:worn,Shirt:Black:worn,Shoes:White:worn,Scarf:Black:worn,FurCoat:Beige:home,Bottle:Blue:bag -person f21.p1 Ильина Яна Борисовна female=True roles=parent class=- born=1975-04-20 traits=Kind skills=Agility=74,BelarusianLanguage=26,Biology=41,Communication=54,Endurance=55,English=77,Hauling=35,Librarianship=44,Medicine=57,RussianLanguage=35,Strength=50,UkrainianLanguage=23 items=Underwear:Black:worn,Socks:White:worn,Jeans:Gray:worn,Shirt:Gray:worn,WinterBoots:Gray:worn,Coat:Gray:worn,Bottle:Blue:bag -person f22.c0 Захаров Никита Виталиевич female=False roles=student class=class-classroom-03 born=2000-09-14 traits= skills=Agility=62,BelarusianLanguage=19,Biology=50,Communication=42,Endurance=39,Hauling=47,Literature=68,Mathematics=65,PhysicalEducation=60,RussianLanguage=59,Strength=45,UkrainianLanguage=31 items=Underwear:Gray:worn,Socks:White:worn,Jeans:Gray:worn,TShirt:Beige:worn,Shoes:Beige:worn,FurCoat:Gray:home,PeShirt:Black:home,PeShorts:Blue:home,Bottle:White:bag,Phone:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f22.c1 Захаров Денис Виталиевич female=False roles=student class=class-classroom-02 born=2001-09-11 traits=Kind,Leader skills=Agility=51,Biology=42,Communication=55,Endurance=51,Hauling=69,Literature=53,Mathematics=60,PhysicalEducation=80,RussianLanguage=50,Strength=53,UkrainianLanguage=16 items=Underwear:White:worn,Socks:White:worn,Trousers:Blue:worn,TShirt:Blue:worn,Shoes:White:worn,Hat:Blue:worn,Coat:Beige:home,PeShirt:Red:home,PeShorts:Red:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f22.p0 Захаров Виталий Андреевич female=False roles=parent class=- born=1966-04-08 traits= skills=Agility=29,Communication=62,Endurance=82,Hauling=33,RussianLanguage=65,Strength=58,UkrainianLanguage=18 items=Underwear:Black:worn,Socks:Black:worn,Pants:Beige:worn,TShirt:LightBlue:worn,Shoes:Black:worn,Coat:Black:worn,Hat:Gray:worn,Bottle:White:bag,Phone:Black:bag -person f22.p1 Захарова Елизавета Егоровна female=True roles=parent class=- born=1967-09-05 traits= skills=Administration=37,Agility=42,BelarusianLanguage=28,Communication=82,Cooking=65,Endurance=60,English=52,Hauling=37,Mathematics=67,RussianLanguage=54,Strength=51 items=Underwear:Gray:worn,Socks:White:worn,Dress:Gray:worn,WinterBoots:Gray:worn,FurCoat:Gray:home,Bottle:Green:bag -person f23.c0 Орловская Нина Константиновна female=True roles=student class=class-classroom-02 born=2001-11-01 traits=AbsentMinded,Quiet skills=Agility=44,BelarusianLanguage=13,Biology=42,Communication=9,Endurance=51,Hauling=45,Literature=53,Mathematics=34,PhysicalEducation=70,RussianLanguage=46,Strength=38 items=Underwear:White:worn,Socks:White:worn,Trousers:Blue:worn,TShirt:Blue:worn,Shoes:Beige:worn,Sweater:Blue:worn,Coat:Beige:worn,Hat:Gray:worn,PeShirt:Blue:home,PeShorts:Red:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f23.c1 Орловская Полина Константиновна female=True roles=student class=class-classroom-01 born=2003-03-30 traits=ColdLoving,Quiet skills=Agility=39,BelarusianLanguage=20,Biology=70,Communication=19,Endurance=53,Hauling=54,Literature=73,Mathematics=46,PhysicalEducation=55,RussianLanguage=47,Strength=67 items=Underwear:White:worn,Socks:Black:worn,Dress:Blue:worn,Shoes:Black:worn,Sweater:Beige:worn,Jacket:Red:home,PeShirt:Black:home,PeShorts:Gray:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f23.p0 Орловский Константин Михаилович female=False roles=parent class=- born=1965-04-02 traits=Curious,Diligent skills=Agility=45,BelarusianLanguage=23,Communication=55,Endurance=50,Hauling=50,History=40,Librarianship=56,Physics=67,RussianLanguage=52,Strength=32,UkrainianLanguage=17 items=Underwear:Gray:worn,Socks:White:worn,Jeans:Gray:worn,Shirt:Blue:worn,WinterBoots:Beige:worn,FurCoat:Black:worn,Hat:Beige:worn -person f23.p1 Орловская Яна Степановна female=True roles=parent class=- born=1965-10-22 traits=Lazy skills=Agility=56,BelarusianLanguage=28,Communication=44,Endurance=40,Hauling=81,Pedagogy=45,RussianLanguage=62,Strength=56,UkrainianLanguage=26 items=Underwear:Gray:worn,Socks:Gray:worn,Pants:Gray:worn,TShirt:Blue:worn,Shoes:White:worn,Hat:Blue:worn,Coat:Black:home -person f24.c0 Орлов Фёдор Алексеевич female=False roles=student class=class-classroom-03 born=2000-12-05 traits=Leader skills=Agility=40,BelarusianLanguage=8,Biology=54,Communication=6,Endurance=40,Hauling=45,Literature=41,Mathematics=10,PhysicalEducation=46,RussianLanguage=52,Strength=63 items=Underwear:Black:worn,Socks:Black:worn,Jeans:Gray:worn,Shirt:Black:worn,Shoes:Gray:worn,Sweater:Black:worn,Jacket:Blue:worn,PeShirt:Blue:home,PeShorts:Blue:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f24.c1 Орлова Оксана Алексеевна female=True roles=student class=class-classroom-00 born=2004-03-01 traits=HeatLoving,HotTempered skills=Agility=75,Biology=30,Communication=23,Endurance=54,Hauling=35,Literature=62,Mathematics=49,PhysicalEducation=58,RussianLanguage=26,Strength=41,UkrainianLanguage=31 items=Underwear:Black:worn,Socks:White:worn,Pants:Beige:worn,TShirt:White:worn,WinterBoots:Black:worn,Scarf:Beige:worn,Jacket:Red:home,PeShirt:Black:home,PeShorts:Red:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f24.p0 Орлов Алексей Денисович female=False roles=parent class=- born=1977-04-17 traits= skills=Agility=45,BelarusianLanguage=17,Communication=51,Endurance=18,English=61,Hauling=46,Literature=56,Mathematics=60,RussianLanguage=55,Strength=62,UkrainianLanguage=16 items=Underwear:Black:worn,Socks:Black:worn,Trousers:Gray:worn,Shirt:Gray:worn,WinterBoots:Black:worn,Sweater:Black:worn,Hat:Gray:worn,Jacket:Black:home -person f24.p1 Орлова Надежда Владиславовна female=True roles=parent class=- born=1977-12-04 traits=Kind skills=Agility=25,BelarusianLanguage=25,Communication=52,Endurance=22,English=70,Hauling=52,Librarianship=51,Medicine=10,RussianLanguage=50,Strength=52,UkrainianLanguage=19 items=Underwear:Black:worn,Socks:Gray:worn,Trousers:Blue:worn,TShirt:LightBlue:worn,WinterBoots:Gray:worn,Sweater:Black:worn,FurCoat:Black:worn,Belt:Black:worn,Bottle:Blue:bag,Phone:Black:bag -person f25.c0 Петрова Александра Игоревна female=True roles=student class=class-classroom-02 born=2001-12-19 traits= skills=Agility=57,BelarusianLanguage=29,Biology=64,Communication=32,Endurance=40,Hauling=58,Literature=47,Mathematics=40,PhysicalEducation=79,RussianLanguage=64,Strength=65,UkrainianLanguage=7 items=Underwear:White:worn,Socks:White:worn,Trousers:Black:worn,TShirt:Blue:worn,WinterBoots:Black:worn,Sweater:Gray:worn,Coat:Beige:worn,Hat:Beige:worn,Scarf:Beige:worn,PeShirt:Red:home,PeShorts:Blue:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f25.p1 Петрова Полина Юриевна female=True roles=parent class=- born=1965-12-11 traits=Kind,Neat skills=Agility=53,Communication=50,Endurance=59,English=70,Geography=41,Hauling=56,Medicine=42,RussianLanguage=72,Strength=45 items=Underwear:Gray:worn,Socks:White:worn,Pants:Beige:worn,TShirt:LightBlue:worn,WinterBoots:Beige:worn,Coat:Beige:worn,Hat:Black:worn,Bottle:White:bag -person f26.c0 Яковлев Павел Михаилович female=False roles=student class=class-classroom-01 born=2002-12-29 traits=Kind,Neat skills=Agility=45,BelarusianLanguage=30,Biology=59,Communication=8,Endurance=59,Hauling=53,Literature=60,Mathematics=47,PhysicalEducation=43,RussianLanguage=43,Strength=45,UkrainianLanguage=23 items=Underwear:White:worn,Socks:White:worn,Pants:Beige:worn,TShirt:LightBlue:worn,Shoes:Black:worn,Sweater:Blue:worn,Coat:Beige:worn,PeShirt:Gray:home,PeShorts:Red:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f26.c1 Яковлев Вадим Михаилович female=False roles=student class=class-classroom-00 born=2004-06-28 traits= skills=Agility=40,BelarusianLanguage=24,Biology=36,Communication=14,Endurance=66,Hauling=41,Literature=71,Mathematics=26,PhysicalEducation=55,RussianLanguage=25,Strength=40,UkrainianLanguage=18 items=Underwear:Black:worn,Socks:Black:worn,Jeans:Blue:worn,TShirt:Black:worn,Shoes:Black:worn,FurCoat:Black:home,PeShirt:Blue:home,PeShorts:Red:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f26.p0 Яковлев Михаил Степанович female=False roles=parent class=- born=1975-12-27 traits=HeatLoving,Neat skills=Agility=44,Communication=59,Endurance=66,Hauling=52,Mathematics=44,Medicine=71,PhysicalEducation=53,RussianLanguage=55,Strength=45,UkrainianLanguage=28 items=Underwear:Black:worn,Socks:Black:worn,Jeans:Blue:worn,TShirt:Black:worn,Shoes:Gray:worn,Scarf:Blue:worn,Jacket:Blue:home,Phone:Gray:bag -person f26.p1 Яковлева Марина Кирилловна female=True roles=parent class=- born=1976-12-10 traits=Diligent,Neat skills=Agility=36,BelarusianLanguage=22,Communication=53,Endurance=66,English=40,Hauling=47,History=43,Medicine=66,PhysicalEducation=45,RussianLanguage=65,Strength=43 items=Underwear:White:worn,Socks:White:worn,Jeans:Black:worn,TShirt:Gray:worn,WinterBoots:Black:worn,FurCoat:Gray:worn,Bottle:Blue:bag,Phone:Gray:bag -person f27.c0 Иванов Виктор Константинович female=False roles=student class=class-classroom-01 born=2002-12-27 traits=Bully skills=Agility=29,BelarusianLanguage=19,Biology=56,Communication=26,Endurance=46,Hauling=65,Literature=45,Mathematics=53,PhysicalEducation=58,RussianLanguage=27,Strength=45,UkrainianLanguage=22 items=Underwear:Black:worn,Socks:Gray:worn,Pants:Gray:worn,TShirt:Gray:worn,Shoes:Beige:worn,FurCoat:Black:worn,PeShirt:Gray:home,PeShorts:Red:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f27.c1 Иванов Владислав Константинович female=False roles=student class=class-classroom-00 born=2004-07-04 traits=Diligent,Leader skills=Agility=38,BelarusianLanguage=23,Biology=27,Communication=31,Endurance=53,Hauling=65,Literature=62,Mathematics=33,PhysicalEducation=64,RussianLanguage=49,Strength=53 items=Underwear:White:worn,Socks:White:worn,Trousers:Black:worn,Shirt:Gray:worn,WinterBoots:Black:worn,Sweater:Blue:worn,Jacket:Gray:worn,Hat:Gray:worn,Scarf:Blue:worn,PeShirt:Blue:home,PeShorts:Green:home,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f27.c2 Иванов Александр Константинович female=False roles=student class=class-classroom-00 born=2004-05-25 traits= skills=Agility=58,Biology=51,Communication=37,Endurance=60,Hauling=37,Literature=65,Mathematics=35,PhysicalEducation=33,RussianLanguage=42,Strength=45,UkrainianLanguage=38 items=Underwear:Black:worn,Socks:Black:worn,Pants:Black:worn,TShirt:White:worn,WinterBoots:Beige:worn,Sweater:Black:worn,Hat:Beige:worn,Scarf:Blue:worn,FurCoat:Gray:home,PeShirt:Blue:home,PeShorts:Black:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f27.p0 Иванов Константин Иванович female=False roles=parent class=- born=1974-08-25 traits=Neat skills=Agility=44,BelarusianLanguage=27,Communication=77,Endurance=42,English=53,Hauling=62,Medicine=52,Pedagogy=40,RussianLanguage=32,Strength=28 items=Underwear:Black:worn,Socks:Black:worn,Trousers:Blue:worn,Shirt:Blue:worn,Shoes:Gray:worn,Sweater:Blue:worn,Scarf:Beige:worn,Jacket:Green:home,Bottle:White:bag,Phone:White:bag -person f27.p1 Иванова Марина Константиновна female=True roles=parent class=- born=1969-11-29 traits=Curious skills=Agility=43,BelarusianLanguage=16,Communication=71,Endurance=63,English=50,Hauling=59,RussianLanguage=56,Strength=35,UkrainianLanguage=21 items=Underwear:Black:worn,Socks:Gray:worn,Skirt:Beige:worn,TShirt:Black:worn,WinterBoots:Beige:worn,Sweater:Black:worn,FurCoat:Beige:worn,Bottle:Green:bag,Phone:Black:bag -person f28.c0 Иванов Пётр Петрович female=False roles=student class=class-classroom-02 born=2002-08-02 traits= skills=Agility=43,BelarusianLanguage=34,Biology=36,Communication=42,Endurance=50,Hauling=36,Literature=33,Mathematics=4,PhysicalEducation=78,RussianLanguage=46,Strength=44 items=Underwear:Gray:worn,Socks:White:worn,Pants:Beige:worn,Shirt:Gray:worn,WinterBoots:Black:worn,Sweater:Blue:worn,Jacket:Red:home,PeShirt:Green:home,PeShorts:Green:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f28.c1 Иванова Наталья Петровна female=True roles=student class=class-classroom-03 born=2000-12-07 traits=Quiet skills=Agility=58,BelarusianLanguage=30,Biology=46,Communication=52,Endurance=36,Hauling=58,Literature=38,Mathematics=73,PhysicalEducation=77,RussianLanguage=51,Strength=45,UkrainianLanguage=23 items=Underwear:White:worn,Socks:Gray:worn,Pants:Beige:worn,TShirt:Gray:worn,Shoes:Beige:worn,Hat:Black:worn,Coat:Beige:home,PeShirt:Green:home,PeShorts:Gray:home,Phone:Black:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f28.c2 Иванов Максим Петрович female=False roles=student class=class-classroom-00 born=2004-04-05 traits=Lazy skills=Agility=67,BelarusianLanguage=23,Biology=32,Communication=26,Endurance=54,Hauling=51,Literature=57,Mathematics=10,PhysicalEducation=57,RussianLanguage=47,Strength=75,UkrainianLanguage=35 items=Underwear:Black:worn,Socks:Gray:worn,Pants:Beige:worn,TShirt:Black:worn,Shoes:Gray:worn,Sweater:Gray:worn,Scarf:Black:worn,Coat:Black:home,PeShirt:Black:home,PeShorts:Red:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f28.p0 Иванов Пётр Олегович female=False roles=parent class=- born=1965-06-23 traits=AbsentMinded,HotTempered skills=Agility=75,BelarusianLanguage=24,Biology=51,Communication=60,Cooking=51,Endurance=74,English=57,Geography=66,Hauling=48,RussianLanguage=45,Strength=40,UkrainianLanguage=36 items=Underwear:Black:worn,Socks:Gray:worn,Trousers:Black:worn,Shirt:White:worn,Shoes:White:worn,Sweater:Black:worn,Coat:Gray:worn,Hat:Black:worn,Scarf:Black:worn -person f28.p1 Иванова Полина Алексеевна female=True roles=parent class=- born=1965-11-18 traits=AbsentMinded,Lazy skills=Agility=43,Communication=61,Cooking=49,Endurance=43,Hauling=60,RussianLanguage=46,Strength=45 items=Underwear:White:worn,Socks:White:worn,Dress:Beige:worn,WinterBoots:Gray:worn,Hat:Beige:worn,Belt:Black:worn,FurCoat:Gray:home,Bottle:Green:bag,Phone:Black:bag -person f29.c0 Макарова Ксения Александровна female=True roles=student class=class-classroom-01 born=2002-11-08 traits=Bully,HeatLoving skills=Agility=52,Biology=73,Communication=35,Endurance=53,Hauling=68,Literature=60,Mathematics=34,PhysicalEducation=67,RussianLanguage=18,Strength=45 items=Underwear:Gray:worn,Socks:White:worn,Pants:Blue:worn,TShirt:Gray:worn,WinterBoots:Black:worn,Jacket:Black:worn,Hat:Blue:worn,Belt:Beige:worn,PeShirt:Gray:home,PeShorts:Blue:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f29.p0 Макаров Александр Николаевич female=False roles=parent class=- born=1981-11-08 traits=AbsentMinded skills=Agility=50,BelarusianLanguage=34,Communication=44,Endurance=55,Hauling=42,Informatics=34,PhysicalEducation=74,RussianLanguage=54,Strength=64,UkrainianLanguage=10 items=Underwear:Black:worn,Socks:White:worn,Jeans:Blue:worn,Shirt:Blue:worn,WinterBoots:Black:worn,Sweater:Gray:worn,Coat:Black:worn,Hat:Gray:worn,Scarf:Black:worn,Bottle:Blue:bag,Phone:Black:bag -person f29.p1 Макарова Мария Владиславовна female=True roles=parent class=- born=1977-10-31 traits=ColdLoving,Curious skills=Administration=44,Agility=44,BelarusianLanguage=8,Communication=56,Endurance=62,English=40,Hauling=44,Medicine=15,RussianLanguage=35,Strength=52 items=Underwear:White:worn,Socks:Black:worn,Trousers:Black:worn,Shirt:Blue:worn,Shoes:Beige:worn,Sweater:Black:worn,Hat:Blue:worn,Coat:Black:home,Bottle:White:bag,Phone:White:bag -person f3.c0 Макарова Вероника Петровна female=True roles=student class=class-classroom-02 born=2002-05-15 traits= skills=Agility=68,BelarusianLanguage=24,Biology=34,Communication=45,Endurance=50,Hauling=48,Literature=37,Mathematics=41,PhysicalEducation=50,RussianLanguage=28,Strength=45,UkrainianLanguage=23 items=Underwear:Gray:worn,Socks:Black:worn,Trousers:Blue:worn,Shirt:Gray:worn,WinterBoots:Gray:worn,Jacket:Black:home,PeShirt:Gray:home,PeShorts:Blue:home,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f3.c1 Макаров Роман Петрович female=False roles=student class=class-classroom-02 born=2002-04-16 traits=Kind skills=Agility=50,BelarusianLanguage=22,Biology=59,Communication=22,Endurance=46,Hauling=51,Literature=39,Mathematics=23,PhysicalEducation=45,RussianLanguage=31,Strength=45,UkrainianLanguage=23 items=Underwear:Gray:worn,Socks:Black:worn,Jeans:Blue:worn,Shirt:White:worn,WinterBoots:Black:worn,Hat:Beige:worn,Coat:Black:home,PeShirt:Black:home,PeShorts:Black:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f3.p0 Макаров Пётр Егорович female=False roles=parent class=- born=1977-02-28 traits=Diligent,Neat skills=Agility=67,Communication=28,Endurance=53,English=57,Hauling=43,History=43,Pedagogy=54,RussianLanguage=43,Strength=40 items=Underwear:Black:worn,Socks:White:worn,Pants:Gray:worn,Shirt:Blue:worn,WinterBoots:Black:worn,Sweater:Beige:worn,Hat:Beige:worn,Coat:Black:home -person f3.p1 Макарова Алина Антоновна female=True roles=parent class=- born=1978-04-05 traits=HotTempered,Neat skills=Agility=45,BelarusianLanguage=20,Biology=41,Communication=45,Cooking=68,Endurance=51,English=54,Hauling=52,History=69,Medicine=78,RussianLanguage=63,Strength=44 items=Underwear:Black:worn,Socks:Black:worn,Jeans:Black:worn,TShirt:Gray:worn,WinterBoots:Gray:worn,Sweater:Gray:worn,Hat:Beige:worn,FurCoat:Beige:home,Phone:Black:bag -person f30.c0 Иванова Светлана Владимировна female=True roles=student class=class-classroom-03 born=2000-10-08 traits=Leader skills=Agility=43,Biology=53,Communication=26,Endurance=44,Hauling=49,Literature=68,Mathematics=39,PhysicalEducation=73,RussianLanguage=51,Strength=36,UkrainianLanguage=21 items=Underwear:White:worn,Socks:Black:worn,Pants:Blue:worn,Shirt:Gray:worn,Shoes:Gray:worn,Coat:Black:worn,Hat:Blue:worn,Scarf:Gray:worn,PeShirt:Gray:home,PeShorts:Black:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f30.c1 Иванова Надежда Владимировна female=True roles=student class=class-classroom-03 born=2000-12-10 traits=AbsentMinded,Kind skills=Agility=43,BelarusianLanguage=19,Biology=57,Communication=38,Endurance=66,Hauling=46,Literature=70,Mathematics=23,PhysicalEducation=56,RussianLanguage=41,Strength=40,UkrainianLanguage=10 items=Underwear:White:worn,Socks:Black:worn,Pants:Black:worn,Shirt:White:worn,Shoes:Black:worn,Scarf:Beige:worn,Jacket:Blue:home,PeShirt:Blue:home,PeShorts:Red:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f30.p0 Иванов Владимир Денисович female=False roles=parent class=- born=1974-06-12 traits=Lazy skills=Agility=40,Biology=17,Communication=72,Cooking=61,Endurance=50,Hauling=32,RussianLanguage=58,Strength=40,UkrainianLanguage=26 items=Underwear:Black:worn,Socks:Black:worn,Trousers:Black:worn,Shirt:Gray:worn,WinterBoots:Gray:worn,Coat:Beige:home,Bottle:Blue:bag -person f30.p1 Иванова Мария Андреевна female=True roles=parent class=- born=1973-09-24 traits=Diligent skills=Agility=52,BelarusianLanguage=40,Communication=61,Endurance=62,Hauling=65,Medicine=47,RussianLanguage=43,Strength=39 items=Underwear:White:worn,Socks:Gray:worn,Skirt:Black:worn,Shirt:Blue:worn,Shoes:Gray:worn,Hat:Beige:worn,Jacket:Blue:home,Phone:Gray:bag -person f31.c0 Иванов Николай Вадимович female=False roles=student class=class-classroom-01 born=2003-08-29 traits= skills=Agility=20,BelarusianLanguage=24,Biology=40,Communication=27,Endurance=43,Hauling=46,Literature=36,Mathematics=40,PhysicalEducation=53,RussianLanguage=32,Strength=73,UkrainianLanguage=20 items=Underwear:Gray:worn,Socks:White:worn,Jeans:Black:worn,TShirt:Beige:worn,WinterBoots:Beige:worn,FurCoat:Black:home,PeShirt:Blue:home,PeShorts:Black:home,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f31.p0 Иванов Вадим Андреевич female=False roles=parent class=- born=1985-06-23 traits=Curious,Quiet skills=Administration=28,Agility=45,BelarusianLanguage=32,Communication=54,Endurance=71,English=55,German=47,Hauling=53,PhysicalEducation=44,RussianLanguage=64,Strength=44,UkrainianLanguage=17 items=Underwear:White:worn,Socks:White:worn,Jeans:Black:worn,Shirt:Black:worn,Shoes:Black:worn,Hat:Blue:worn,Coat:Gray:home,Bottle:Blue:bag,Phone:Gray:bag -person f31.p1 Иванова Александра Юриевна female=True roles=parent class=- born=1980-08-20 traits=Curious,Diligent skills=Agility=70,BelarusianLanguage=29,Communication=50,Endurance=41,English=50,Hauling=51,Librarianship=35,PhysicalEducation=73,Physics=60,RussianLanguage=59,Strength=62,UkrainianLanguage=23 items=Underwear:Black:worn,Socks:Black:worn,Pants:Black:worn,TShirt:Gray:worn,Shoes:Black:worn,Jacket:Black:worn,Hat:Black:worn,Scarf:Blue:worn,Phone:Gray:bag -person f32.c0 Петров Артём Евгениевич female=False roles=student class=class-classroom-02 born=2002-07-14 traits=Neat skills=Agility=78,Biology=58,Communication=47,Endurance=32,Hauling=19,Literature=31,Mathematics=29,PhysicalEducation=60,RussianLanguage=65,Strength=18,UkrainianLanguage=18 items=Underwear:Black:worn,Socks:Black:worn,Trousers:Blue:worn,Shirt:Gray:worn,Shoes:Beige:worn,Sweater:Black:worn,FurCoat:Black:worn,PeShirt:Gray:home,PeShorts:Gray:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f32.c1 Петрова Елена Евгениевна female=True roles=student class=class-classroom-00 born=2003-10-27 traits=AbsentMinded,Curious skills=Agility=37,Biology=54,Communication=39,Endurance=60,Hauling=53,Literature=58,Mathematics=33,PhysicalEducation=75,RussianLanguage=34,Strength=42,UkrainianLanguage=30 items=Underwear:Gray:worn,Socks:Black:worn,Pants:Blue:worn,TShirt:White:worn,Shoes:Gray:worn,Coat:Black:worn,Hat:Gray:worn,Belt:Gray:worn,PeShirt:Blue:home,PeShorts:Green:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f32.c2 Петрова Ирина Евгениевна female=True roles=student class=class-classroom-02 born=2001-09-18 traits=HotTempered,Neat skills=Agility=41,BelarusianLanguage=32,Biology=80,Communication=32,Endurance=42,Hauling=67,Literature=51,Mathematics=26,PhysicalEducation=33,RussianLanguage=32,Strength=45 items=Underwear:White:worn,Socks:White:worn,Dress:Beige:worn,WinterBoots:Black:worn,Sweater:Black:worn,Jacket:Beige:worn,Belt:Gray:worn,PeShirt:Black:home,PeShorts:Blue:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f32.p0 Петров Евгений Станиславович female=False roles=parent class=- born=1963-05-02 traits= skills=Agility=45,Communication=66,Endurance=43,English=53,Hauling=42,PhysicalEducation=51,RussianLanguage=62,Strength=52,UkrainianLanguage=26 items=Underwear:Black:worn,Socks:White:worn,Jeans:Black:worn,Shirt:Black:worn,Shoes:White:worn,Sweater:Black:worn,Belt:Beige:worn,Coat:Beige:home,Bottle:Blue:bag -person f33.c0 Морозова Елизавета Григориевна female=True roles=student class=class-classroom-00 born=2003-11-04 traits=AbsentMinded,Kind skills=Agility=59,BelarusianLanguage=25,Biology=55,Communication=45,Endurance=48,Hauling=50,Literature=58,Mathematics=3,PhysicalEducation=62,RussianLanguage=28,Strength=38,UkrainianLanguage=26 items=Underwear:Gray:worn,Socks:Black:worn,Skirt:Gray:worn,TShirt:White:worn,Shoes:Beige:worn,Sweater:Blue:worn,Hat:Blue:worn,Scarf:Gray:worn,Coat:Gray:home,PeShirt:Gray:home,PeShorts:Green:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f33.p0 Морозов Григорий Михаилович female=False roles=parent class=- born=1968-05-01 traits= skills=Agility=28,BelarusianLanguage=8,Communication=68,Endurance=62,Hauling=51,Pedagogy=72,RussianLanguage=56,Strength=46,UkrainianLanguage=22 items=Underwear:Gray:worn,Socks:White:worn,Trousers:Gray:worn,TShirt:Blue:worn,WinterBoots:Beige:worn,Sweater:Blue:worn,Coat:Gray:home,Phone:Black:bag -person f33.p1 Морозова Марина Владимировна female=True roles=parent class=- born=1972-10-24 traits=Leader skills=Administration=54,Agility=41,BelarusianLanguage=28,Communication=42,Endurance=80,English=30,Geography=60,Hauling=41,PhysicalEducation=76,RussianLanguage=74,Strength=37 items=Underwear:Black:worn,Socks:White:worn,Pants:Beige:worn,Shirt:Gray:worn,WinterBoots:Black:worn,Hat:Beige:worn,Belt:Gray:worn,Jacket:Beige:home -person f34.c0 Кузнецова Валентина Ивановна female=True roles=student class=class-classroom-00 born=2004-03-25 traits=Bully,HotTempered skills=Agility=29,Biology=32,Communication=21,Endurance=69,Hauling=54,Literature=54,Mathematics=28,PhysicalEducation=53,RussianLanguage=16,Strength=45,UkrainianLanguage=24 items=Underwear:Black:worn,Socks:Black:worn,Pants:Black:worn,Shirt:White:worn,Shoes:Black:worn,Sweater:Gray:worn,Jacket:Black:worn,PeShirt:Green:home,PeShorts:Red:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f34.p0 Кузнецов Иван Сергеевич female=False roles=parent class=- born=1970-02-02 traits= skills=Agility=44,Communication=43,Endurance=58,English=45,German=55,Hauling=31,Literature=54,RussianLanguage=55,Strength=72 items=Underwear:Black:worn,Socks:Black:worn,Pants:Beige:worn,Shirt:Gray:worn,WinterBoots:Gray:worn,Hat:Black:worn,Scarf:Beige:worn,Coat:Black:home -person f34.p1 Кузнецова Дарья Валериевна female=True roles=parent class=- born=1967-03-25 traits=Diligent skills=Agility=47,BelarusianLanguage=23,Communication=52,Endurance=52,German=38,Hauling=32,Informatics=21,RussianLanguage=62,Strength=67,UkrainianLanguage=15 items=Underwear:Black:worn,Socks:Gray:worn,Jeans:Blue:worn,Shirt:Black:worn,Shoes:Black:worn,Sweater:Gray:worn,Jacket:Blue:home,Bottle:Blue:bag,Phone:Gray:bag -person f4.c0 Васильев Денис Федорович female=False roles=student class=class-classroom-01 born=2003-02-01 traits=Neat skills=Agility=50,BelarusianLanguage=9,Biology=58,Communication=27,Endurance=35,Hauling=25,Literature=52,Mathematics=53,PhysicalEducation=43,RussianLanguage=68,Strength=45,UkrainianLanguage=27 items=Underwear:Gray:worn,Socks:White:worn,Trousers:Black:worn,TShirt:Gray:worn,Shoes:Gray:worn,Sweater:Gray:worn,Jacket:Red:home,PeShirt:Black:home,PeShorts:Black:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f4.p0 Васильев Фёдор Игоревич female=False roles=parent class=- born=1970-05-21 traits=AbsentMinded skills=Agility=66,Communication=49,Endurance=44,English=25,Geography=47,Hauling=45,Medicine=44,RussianLanguage=45,Strength=47,UkrainianLanguage=31 items=Underwear:Black:worn,Socks:Black:worn,Jeans:Gray:worn,TShirt:LightBlue:worn,Shoes:Gray:worn,Coat:Beige:worn,Hat:Black:worn,Belt:Gray:worn,Phone:White:bag -person f4.p1 Васильева Анастасия Федоровна female=True roles=parent class=- born=1975-01-20 traits= skills=Agility=64,BelarusianLanguage=22,Communication=59,Endurance=64,English=43,Hauling=53,Librarianship=49,Medicine=75,RussianLanguage=49,Strength=73,UkrainianLanguage=13 items=Underwear:Gray:worn,Socks:White:worn,Pants:Blue:worn,TShirt:Blue:worn,Shoes:Beige:worn,Sweater:Gray:worn,Hat:Black:worn,Coat:Gray:home,Bottle:White:bag -person f5.c0 Захарова Екатерина Борисовна female=True roles=student class=class-classroom-02 born=2002-07-13 traits=Kind,Lazy skills=Agility=57,BelarusianLanguage=29,Biology=72,Communication=42,Endurance=39,Hauling=29,Literature=19,Mathematics=37,PhysicalEducation=60,RussianLanguage=28,Strength=54 items=Underwear:Gray:worn,Socks:Black:worn,Dress:Black:worn,WinterBoots:Gray:worn,Sweater:Beige:worn,Hat:Black:worn,Coat:Black:home,PeShirt:Blue:home,PeShorts:Black:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f5.p0 Захаров Борис Николаевич female=False roles=parent class=- born=1980-04-15 traits= skills=Agility=45,BelarusianLanguage=29,Communication=37,Cooking=51,Endurance=51,Hauling=32,Pedagogy=36,RussianLanguage=66,Strength=51,UkrainianLanguage=15 items=Underwear:Black:worn,Socks:Gray:worn,Jeans:Blue:worn,Shirt:White:worn,Shoes:Gray:worn,Scarf:Beige:worn,Jacket:Gray:home -person f5.p1 Захарова Вероника Григориевна female=True roles=parent class=- born=1979-06-22 traits= skills=Administration=39,Agility=68,Communication=64,Endurance=40,English=31,Hauling=61,Informatics=35,PhysicalEducation=35,RussianLanguage=55,Strength=65 items=Underwear:Black:worn,Socks:White:worn,Skirt:Beige:worn,Shirt:Blue:worn,WinterBoots:Gray:worn,Sweater:Black:worn,Jacket:Gray:worn,Hat:Beige:worn -person f6.c0 Козловская Юлия Олеговна female=True roles=student class=class-classroom-00 born=2004-08-15 traits= skills=Agility=45,Biology=49,Communication=42,Endurance=61,Hauling=67,Literature=49,Mathematics=54,PhysicalEducation=39,RussianLanguage=23,Strength=58 items=Underwear:Black:worn,Socks:White:worn,Jeans:Black:worn,Shirt:Black:worn,WinterBoots:Black:worn,Sweater:Gray:worn,Coat:Gray:worn,Hat:Blue:worn,Scarf:Black:worn,PeShirt:Gray:home,PeShorts:Green:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f6.c1 Козловская Светлана Олеговна female=True roles=student class=class-classroom-02 born=2001-12-31 traits=HotTempered,Lazy skills=Agility=53,BelarusianLanguage=37,Biology=59,Communication=36,Endurance=40,Hauling=26,Literature=66,Mathematics=28,PhysicalEducation=43,RussianLanguage=53,Strength=47,UkrainianLanguage=10 items=Underwear:White:worn,Socks:Black:worn,Jeans:Gray:worn,Shirt:Black:worn,WinterBoots:Beige:worn,Coat:Black:worn,Hat:Beige:worn,PeShirt:Blue:home,PeShorts:Green:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f6.c2 Козловская Вероника Олеговна female=True roles=student class=class-classroom-00 born=2003-09-29 traits=Curious skills=Agility=55,Biology=77,Communication=27,Endurance=56,Hauling=46,Literature=56,Mathematics=32,PhysicalEducation=46,RussianLanguage=20,Strength=49,UkrainianLanguage=25 items=Underwear:Gray:worn,Socks:Black:worn,Jeans:Black:worn,TShirt:White:worn,Shoes:White:worn,Coat:Gray:home,PeShirt:Blue:home,PeShorts:Red:home,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f6.p0 Козловский Олег Станиславович female=False roles=parent class=- born=1967-12-18 traits=Kind skills=Agility=42,Communication=82,Endurance=64,Hauling=52,Informatics=53,Literature=33,RussianLanguage=58,Strength=21 items=Underwear:Black:worn,Socks:Black:worn,Pants:Beige:worn,TShirt:White:worn,Shoes:White:worn,Sweater:Gray:worn,Coat:Beige:home,Bottle:Blue:bag -person f6.p1 Козловская Ирина Кирилловна female=True roles=parent class=- born=1963-12-28 traits=AbsentMinded,Kind skills=Agility=57,BelarusianLanguage=11,Communication=67,Endurance=42,English=48,Hauling=57,RussianLanguage=79,Strength=45 items=Underwear:Gray:worn,Socks:White:worn,Jeans:Gray:worn,TShirt:Gray:worn,WinterBoots:Beige:worn,Coat:Black:worn,Bottle:White:bag -person f7.c0 Андреев Виктор Степанович female=False roles=student class=class-classroom-01 born=2003-07-19 traits=Curious,Kind skills=Agility=50,BelarusianLanguage=27,Biology=78,Communication=24,Endurance=50,Hauling=73,Literature=51,Mathematics=26,PhysicalEducation=26,RussianLanguage=50,Strength=64,UkrainianLanguage=34 items=Underwear:Gray:worn,Socks:Black:worn,Jeans:Black:worn,Shirt:Black:worn,Shoes:Gray:worn,Belt:Beige:worn,Jacket:Red:home,PeShirt:Blue:home,PeShorts:Green:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f7.p0 Андреев Степан Андреевич female=False roles=parent class=- born=1973-08-21 traits= skills=Agility=64,Communication=76,Endurance=36,English=60,Hauling=46,History=47,Literature=57,RussianLanguage=64,Strength=62,UkrainianLanguage=27 items=Underwear:Black:worn,Socks:White:worn,Jeans:Gray:worn,Shirt:Black:worn,Shoes:White:worn,Sweater:Black:worn,Hat:Beige:worn,FurCoat:Gray:home -person f7.p1 Андреева Мария Алексеевна female=True roles=parent class=- born=1977-07-16 traits=Diligent skills=Agility=35,BelarusianLanguage=18,Communication=56,Cooking=47,Endurance=45,Hauling=56,Medicine=47,RussianLanguage=64,Strength=64 items=Underwear:Gray:worn,Socks:Black:worn,Trousers:Blue:worn,TShirt:Black:worn,Shoes:Gray:worn,Hat:Blue:worn,FurCoat:Beige:home,Bottle:White:bag -person f8.c0 Фролов Евгений Павелович female=False roles=student class=class-classroom-00 born=2003-12-20 traits=AbsentMinded,Kind skills=Agility=69,BelarusianLanguage=26,Biology=50,Communication=28,Endurance=29,Hauling=45,Literature=52,Mathematics=31,PhysicalEducation=44,RussianLanguage=43,Strength=31,UkrainianLanguage=24 items=Underwear:Gray:worn,Socks:Black:worn,Trousers:Black:worn,Shirt:White:worn,WinterBoots:Beige:worn,Sweater:Gray:worn,FurCoat:Beige:worn,PeShirt:Green:home,PeShorts:Black:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f8.c1 Фролов Станислав Павелович female=False roles=student class=class-classroom-03 born=2000-10-11 traits=AbsentMinded,Lazy skills=Agility=57,BelarusianLanguage=20,Biology=56,Communication=57,Endurance=34,Hauling=42,Literature=71,Mathematics=23,PhysicalEducation=25,RussianLanguage=39,Strength=45,UkrainianLanguage=24 items=Underwear:Black:worn,Socks:White:worn,Jeans:Black:worn,TShirt:Black:worn,Shoes:Beige:worn,Jacket:Black:worn,Hat:Black:worn,PeShirt:Red:home,PeShorts:Green:home,Bottle:White:bag,Phone:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f8.p0 Фролов Павел Егорович female=False roles=parent class=- born=1968-06-07 traits=Leader skills=Administration=51,Agility=71,Chemistry=39,Communication=87,Endurance=39,English=42,Hauling=45,Informatics=65,Librarianship=57,RussianLanguage=73,Strength=45,UkrainianLanguage=26 items=Underwear:Gray:worn,Socks:Black:worn,Trousers:Gray:worn,TShirt:White:worn,WinterBoots:Black:worn,Sweater:Gray:worn,Coat:Black:home,Phone:Black:bag -person f8.p1 Фролова Виктория Степановна female=True roles=parent class=- born=1963-10-04 traits=Curious,Lazy skills=Agility=57,Chemistry=51,Communication=77,Endurance=45,English=33,Hauling=52,Librarianship=83,RussianLanguage=58,Strength=55 items=Underwear:White:worn,Socks:White:worn,Skirt:Beige:worn,Shirt:Gray:worn,WinterBoots:Black:worn,Coat:Gray:home,Bottle:Green:bag -person f9.c0 Кузьмина Любовь Александровна female=True roles=student class=class-classroom-01 born=2003-06-13 traits=HotTempered skills=Agility=45,Biology=47,Communication=31,Endurance=32,Hauling=40,Literature=63,Mathematics=46,PhysicalEducation=48,RussianLanguage=41,Strength=44,UkrainianLanguage=5 items=Underwear:Black:worn,Socks:Gray:worn,Skirt:Beige:worn,Shirt:Blue:worn,WinterBoots:Gray:worn,Sweater:Blue:worn,FurCoat:Beige:worn,PeShirt:Gray:home,PeShorts:Green:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f9.c1 Кузьмин Сергей Александрович female=False roles=student class=class-classroom-03 born=2001-08-20 traits=Curious,Quiet skills=Agility=11,Biology=58,Communication=41,Endurance=59,Hauling=40,Literature=39,Mathematics=31,PhysicalEducation=40,RussianLanguage=51,Strength=52 items=Underwear:Gray:worn,Socks:Black:worn,Trousers:Gray:worn,TShirt:Gray:worn,Shoes:Beige:worn,Sweater:Beige:worn,Hat:Beige:worn,Scarf:Beige:worn,Coat:Gray:home,PeShirt:Gray:home,PeShorts:Blue:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f9.c2 Кузьмина Алина Александровна female=True roles=student class=class-classroom-03 born=2001-01-22 traits= skills=Agility=52,Biology=38,Communication=29,Endurance=53,Hauling=34,Literature=63,Mathematics=31,PhysicalEducation=55,RussianLanguage=42,Strength=63,UkrainianLanguage=12 items=Underwear:White:worn,Socks:Gray:worn,Trousers:Blue:worn,Shirt:Blue:worn,WinterBoots:Gray:worn,Jacket:Gray:worn,Scarf:Gray:worn,PeShirt:Blue:home,PeShorts:Black:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool -person f9.p0 Кузьмин Александр Денисович female=False roles=parent class=- born=1981-12-09 traits= skills=Agility=40,Chemistry=55,Communication=58,Endurance=78,English=57,Hauling=43,Pedagogy=52,RussianLanguage=67,Strength=63 items=Underwear:White:worn,Socks:White:worn,Jeans:Gray:worn,Shirt:Gray:worn,Shoes:Beige:worn,FurCoat:Beige:worn,Belt:Beige:worn,Bottle:Green:bag -person f9.p1 Кузьмина Анастасия Сергеевна female=True roles=parent class=- born=1977-01-01 traits=Diligent skills=Agility=62,BelarusianLanguage=27,Communication=72,Endurance=92,Hauling=47,Librarianship=43,Pedagogy=48,RussianLanguage=67,Strength=34,UkrainianLanguage=21 items=Underwear:Black:worn,Socks:White:worn,Skirt:Blue:worn,Shirt:Blue:worn,Shoes:Beige:worn,Hat:Black:worn,Belt:Gray:worn,Jacket:Black:home,Bottle:White:bag +person f0.c0 Андреев Сергей Валериевич female=False roles=student class=class-classroom-03 born=2000-10-23 traits=Lazy skills=Agility=33,Biology=57,Communication=47,Endurance=46,Hauling=56,Literature=22,Mathematics=12,PhysicalEducation=65,RussianLanguage=47,Strength=64,UkrainianLanguage=31 items=Boxers:Black:worn,Socks:Gray:worn,Jeans:Black:worn,TShirt:Black:worn,Shoes:Gray:worn,Sweater:Gray:worn,Scarf:Black:worn,Coat:Black:home,PeShirt:Gray:home,PeShorts:Gray:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f0.c1 Андреев Александр Валериевич female=False roles=student class=class-classroom-01 born=2002-11-09 traits=Kind,Outgoing skills=Agility=13,Biology=46,Communication=20,Endurance=52,Hauling=24,Literature=35,Mathematics=49,PhysicalEducation=57,RussianLanguage=47,Strength=45 items=Briefs:Black:worn,Socks:Gray:worn,Jeans:Blue:worn,TShirt:LightBlue:worn,Shoes:Gray:worn,Sweater:Black:worn,Coat:Beige:worn,Scarf:Gray:worn,PeShirt:Gray:home,PeShorts:Black:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f0.p0 Андреев Валерий Николаевич female=False roles=parent class=- born=1971-06-03 traits=Diligent,Quiet skills=Agility=43,Communication=58,Cooking=82,Endurance=61,Hauling=61,Pedagogy=50,RussianLanguage=89,Strength=33 items=Briefs:Black:worn,Socks:White:worn,Jeans:Black:worn,TShirt:LightBlue:worn,WinterBoots:Beige:worn,Jacket:Gray:worn,Bottle:Green:bag +person f0.p1 Андреева Марина Артемовна female=True roles=parent class=- born=1971-09-30 traits=Quiet skills=Agility=25,BelarusianLanguage=26,Communication=38,Endurance=35,Hauling=46,History=61,Mathematics=75,RussianLanguage=65,Strength=40,UkrainianLanguage=23 items=Panties:Black:worn,Bra:White:worn,Socks:Black:worn,Jeans:Blue:worn,TShirt:LightBlue:worn,Shoes:Black:worn,Sweater:Gray:worn,Belt:Gray:worn,Coat:Beige:home,Bottle:White:bag +person f1.c0 Александрова Людмила Борисовна female=True roles=student class=class-classroom-03 born=2001-01-26 traits=AbsentMinded skills=Agility=72,BelarusianLanguage=36,Biology=67,Communication=37,Endurance=39,Hauling=56,Literature=77,Mathematics=29,PhysicalEducation=54,RussianLanguage=51,Strength=45,UkrainianLanguage=28 items=Panties:Beige:worn,Bra:White:worn,Socks:Gray:worn,Dress:Gray:worn,Shoes:Gray:worn,Coat:Beige:worn,Belt:Beige:worn,PeShirt:Green:home,PeShorts:Blue:home,Bottle:Blue:bag,Phone:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f1.c1 Александров Евгений Борисович female=False roles=student class=class-classroom-00 born=2004-04-01 traits= skills=Agility=31,BelarusianLanguage=27,Biology=50,Communication=38,Endurance=54,Hauling=58,Literature=65,Mathematics=30,PhysicalEducation=88,RussianLanguage=44,Strength=40 items=Boxers:White:worn,Socks:Black:worn,Jeans:Black:worn,TShirt:Blue:worn,WinterBoots:Black:worn,Hat:Black:worn,Coat:Gray:home,PeShirt:Blue:home,PeShorts:Blue:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f1.c2 Александров Юрий Борисович female=False roles=student class=class-classroom-00 born=2003-10-28 traits= skills=Agility=45,Biology=68,Communication=31,Endurance=30,Hauling=49,Literature=60,Mathematics=29,PhysicalEducation=49,RussianLanguage=53,Strength=74,UkrainianLanguage=23 items=Boxers:Black:worn,Socks:Gray:worn,Pants:Blue:worn,TShirt:LightBlue:worn,Shoes:Black:worn,Sweater:Blue:worn,Scarf:Black:worn,Coat:Beige:home,PeShirt:Red:home,PeShorts:Gray:home,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f1.p0 Александров Борис Ильич female=False roles=parent class=- born=1968-10-25 traits= skills=Agility=45,Communication=74,Endurance=34,Hauling=65,RussianLanguage=53,Strength=53,UkrainianLanguage=14 items=Briefs:Black:worn,Socks:White:worn,Trousers:Gray:worn,TShirt:Blue:worn,WinterBoots:Gray:worn,Hat:Beige:worn,Scarf:Gray:worn,FurCoat:Beige:home +person f1.p1 Александрова Елизавета Романовна female=True roles=parent class=- born=1972-01-25 traits= skills=Administration=71,Agility=45,BelarusianLanguage=26,Communication=76,Cooking=59,Endurance=35,English=58,Hauling=48,Mathematics=30,RussianLanguage=42,Strength=66 items=Panties:Black:worn,SportsBra:Blue:worn,Socks:Black:worn,Dress:Black:worn,WinterBoots:Beige:worn,Sweater:Beige:worn,Coat:Beige:home,Bottle:White:bag,Phone:White:bag +person f10.c0 Захаров Пётр Виталиевич female=False roles=student class=class-classroom-02 born=2001-11-08 traits=Gossip skills=Agility=31,BelarusianLanguage=26,Biology=62,Communication=54,Endurance=45,Hauling=56,Literature=25,Mathematics=73,PhysicalEducation=63,RussianLanguage=40,Strength=60 items=Boxers:Black:worn,Socks:Black:worn,Pants:Gray:worn,TShirt:Black:worn,WinterBoots:Beige:worn,Jacket:Gray:home,PeShirt:Red:home,PeShorts:Red:home,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f10.p0 Захаров Виталий Борисович female=False roles=parent class=- born=1976-09-07 traits=HeatLoving,Outgoing skills=Agility=44,BelarusianLanguage=22,Communication=66,Endurance=82,English=16,Hauling=40,RussianLanguage=69,Strength=55 items=Boxers:Gray:worn,Socks:Black:worn,Trousers:Black:worn,TShirt:White:worn,WinterBoots:Black:worn,FurCoat:Gray:worn,Hat:Blue:worn,Scarf:Blue:worn,Bottle:White:bag,Phone:Black:bag +person f10.p1 Захарова Любовь Валериевна female=True roles=parent class=- born=1978-11-03 traits=AbsentMinded,Kind skills=Administration=43,Agility=45,BelarusianLanguage=21,Communication=61,Endurance=53,English=52,Hauling=52,Medicine=23,PhysicalEducation=42,Physics=59,RussianLanguage=66,Strength=28 items=Panties:Black:worn,SoftBra:Gray:worn,Socks:Gray:worn,Jeans:Gray:worn,Shirt:Gray:worn,Shoes:Beige:worn,FurCoat:Gray:home,Bottle:White:bag,Phone:Gray:bag +person f11.c0 Андреева Дарья Ивановна female=True roles=student class=class-classroom-03 born=2001-05-20 traits=Leader skills=Agility=47,BelarusianLanguage=13,Biology=57,Communication=43,Endurance=44,Hauling=50,Literature=59,Mathematics=24,PhysicalEducation=61,RussianLanguage=47,Strength=45,UkrainianLanguage=24 items=Panties:Gray:worn,Socks:White:worn,Dress:Beige:worn,Shoes:White:worn,Hat:Blue:worn,Coat:Gray:home,PeShirt:Black:home,PeShorts:Black:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f11.p0 Андреев Иван Алексеевич female=False roles=parent class=- born=1976-06-01 traits= skills=Agility=38,BelarusianLanguage=16,Communication=52,Endurance=82,Hauling=33,Medicine=48,Pedagogy=61,PhysicalEducation=54,RussianLanguage=82,Strength=67,UkrainianLanguage=22 items=Briefs:White:worn,Socks:White:worn,Jeans:Black:worn,Shirt:Gray:worn,WinterBoots:Gray:worn,Sweater:Beige:worn,FurCoat:Beige:worn,Phone:Gray:bag +person f11.p1 Андреева София Александровна female=True roles=parent class=- born=1978-04-24 traits=Diligent,HeatLoving skills=Agility=61,Communication=74,Endurance=55,English=61,Hauling=31,History=29,RussianLanguage=54,Strength=43 items=Panties:Gray:worn,Bra:Black:worn,Socks:Gray:worn,Jeans:Black:worn,TShirt:Blue:worn,WinterBoots:Beige:worn,Sweater:Blue:worn,Hat:Blue:worn,FurCoat:Gray:home,Bottle:Green:bag +person f12.c0 Кузьмин Олег Владиславович female=False roles=student class=class-classroom-01 born=2003-03-20 traits=ColdLoving,Lazy skills=Agility=49,Biology=58,Communication=48,Endurance=57,Hauling=54,Literature=54,Mathematics=33,PhysicalEducation=39,RussianLanguage=9,Strength=36,UkrainianLanguage=31 items=Briefs:White:worn,Socks:Gray:worn,Pants:Blue:worn,TShirt:LightBlue:worn,WinterBoots:Black:worn,Hat:Black:worn,Belt:Black:worn,FurCoat:Gray:home,PeShirt:Red:home,PeShorts:Red:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f12.c1 Кузьмин Егор Владиславович female=False roles=student class=class-classroom-01 born=2003-05-31 traits=Lazy,Leader skills=Agility=45,Biology=45,Communication=30,Endurance=59,Hauling=44,Literature=39,Mathematics=30,PhysicalEducation=59,RussianLanguage=49,Strength=35,UkrainianLanguage=23 items=Briefs:Gray:worn,Socks:Gray:worn,Pants:Black:worn,TShirt:Beige:worn,Shoes:Black:worn,Sweater:Black:worn,Coat:Black:worn,Belt:Beige:worn,PeShirt:Black:home,PeShorts:Black:home,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f12.c2 Кузьмина Дарья Владиславовна female=True roles=student class=class-classroom-01 born=2002-10-29 traits=Diligent,Neat skills=Agility=42,BelarusianLanguage=20,Biology=56,Communication=39,Endurance=58,Hauling=59,Literature=35,Mathematics=30,PhysicalEducation=81,RussianLanguage=64,Strength=34,UkrainianLanguage=28 items=Panties:Black:worn,Socks:Gray:worn,Skirt:Blue:worn,Shirt:Black:worn,Shoes:Black:worn,Sweater:Beige:worn,Coat:Beige:worn,PeShirt:Green:home,PeShorts:Black:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f12.p0 Кузьмин Владислав Александрович female=False roles=parent class=- born=1975-01-17 traits= skills=Administration=56,Agility=25,BelarusianLanguage=10,Communication=68,Endurance=35,English=36,Hauling=63,Informatics=56,Medicine=59,RussianLanguage=54,Strength=36 items=Boxers:Gray:worn,Socks:Gray:worn,Jeans:Blue:worn,Shirt:Blue:worn,Shoes:Gray:worn,Jacket:Blue:worn,Bottle:Blue:bag +person f12.p1 Кузьмина Ольга Кирилловна female=True roles=parent class=- born=1979-10-15 traits=AbsentMinded,Kind skills=Administration=38,Agility=39,BelarusianLanguage=25,Chemistry=48,Communication=32,Endurance=41,English=42,Geography=41,Hauling=46,RussianLanguage=32,Strength=34,UkrainianLanguage=40 items=Panties:Gray:worn,SoftBra:Gray:worn,Socks:White:worn,Jeans:Blue:worn,TShirt:LightBlue:worn,Shoes:Beige:worn,FurCoat:Gray:home +person f13.c0 Яковлев Константин Владиславович female=False roles=student class=class-classroom-00 born=2004-06-16 traits= skills=Agility=53,BelarusianLanguage=24,Biology=48,Communication=29,Endurance=37,Hauling=64,Literature=58,Mathematics=22,PhysicalEducation=44,RussianLanguage=31,Strength=37 items=Boxers:Gray:worn,Socks:Black:worn,Trousers:Gray:worn,Shirt:Blue:worn,WinterBoots:Gray:worn,Jacket:Blue:home,PeShirt:Gray:home,PeShorts:Red:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f13.c1 Яковлев Юрий Владиславович female=False roles=student class=class-classroom-01 born=2003-07-23 traits=Diligent,Quiet skills=Agility=36,BelarusianLanguage=22,Biology=56,Communication=43,Endurance=44,Hauling=57,Literature=56,Mathematics=26,PhysicalEducation=51,RussianLanguage=37,Strength=68,UkrainianLanguage=8 items=Briefs:Black:worn,Socks:Gray:worn,Pants:Blue:worn,Shirt:Black:worn,WinterBoots:Gray:worn,Sweater:Gray:worn,FurCoat:Black:worn,PeShirt:Gray:home,PeShorts:Blue:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f13.c2 Яковлева Алина Владиславовна female=True roles=student class=class-classroom-01 born=2003-02-01 traits=Diligent skills=Agility=58,BelarusianLanguage=20,Biology=60,Communication=45,Endurance=54,Hauling=53,Literature=66,Mathematics=34,PhysicalEducation=47,RussianLanguage=59,Strength=45 items=Panties:White:worn,Socks:White:worn,Trousers:Black:worn,Shirt:Blue:worn,Shoes:Gray:worn,Hat:Gray:worn,Scarf:Blue:worn,Coat:Beige:home,PeShirt:Red:home,PeShorts:Red:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f13.p0 Яковлев Владислав Андреевич female=False roles=parent class=- born=1974-09-23 traits=AbsentMinded,HotTempered skills=Agility=25,BelarusianLanguage=28,Communication=42,Endurance=35,English=26,Hauling=52,RussianLanguage=54,Strength=38 items=Boxers:Black:worn,Socks:White:worn,Trousers:Black:worn,TShirt:White:worn,Shoes:Beige:worn,Hat:Beige:worn,Coat:Beige:home,Bottle:Green:bag,Phone:White:bag +person f14.c0 Александров Степан Денисович female=False roles=student class=class-classroom-02 born=2002-01-03 traits= skills=Agility=57,BelarusianLanguage=17,Biology=53,Communication=18,Endurance=60,Hauling=64,Literature=34,Mathematics=33,PhysicalEducation=55,RussianLanguage=32,Strength=41 items=Briefs:Gray:worn,Socks:Gray:worn,Pants:Blue:worn,TShirt:Gray:worn,Shoes:Black:worn,Sweater:Beige:worn,Jacket:Gray:worn,Hat:Blue:worn,Scarf:Blue:worn,PeShirt:Gray:home,PeShorts:Red:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f14.c1 Александрова Вероника Денисовна female=True roles=student class=class-classroom-03 born=2001-08-11 traits= skills=Agility=45,BelarusianLanguage=15,Biology=52,Communication=38,Endurance=37,Hauling=44,Literature=29,Mathematics=18,PhysicalEducation=65,RussianLanguage=46,Strength=64 items=Panties:Beige:worn,Socks:Gray:worn,Trousers:Gray:worn,TShirt:Black:worn,WinterBoots:Gray:worn,Belt:Gray:worn,FurCoat:Beige:home,PeShirt:Red:home,PeShorts:Gray:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f14.c2 Александров Константин Денисович female=False roles=student class=class-classroom-01 born=2003-08-21 traits= skills=Agility=29,BelarusianLanguage=21,Biology=66,Communication=21,Endurance=51,Hauling=42,Literature=64,Mathematics=27,PhysicalEducation=67,RussianLanguage=20,Strength=45 items=Boxers:White:worn,Socks:Gray:worn,Jeans:Gray:worn,TShirt:Gray:worn,Shoes:Gray:worn,Scarf:Blue:worn,Coat:Black:home,PeShirt:Blue:home,PeShorts:Red:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f14.p1 Александрова Виктория Викторовна female=True roles=parent class=- born=1973-07-23 traits= skills=Agility=45,Communication=72,Cooking=68,Endurance=64,Geography=58,Hauling=44,Physics=38,RussianLanguage=61,Strength=49,UkrainianLanguage=18 items=Panties:Gray:worn,Bra:Gray:worn,Socks:White:worn,Skirt:Beige:worn,TShirt:White:worn,Shoes:Beige:worn,Hat:Gray:worn,Belt:Gray:worn,FurCoat:Black:home,Bottle:Blue:bag,Phone:White:bag +person f15.c0 Воробьёв Игорь Сергеевич female=False roles=student class=class-classroom-03 born=2000-09-20 traits= skills=Agility=45,BelarusianLanguage=24,Biology=33,Communication=61,Endurance=58,Hauling=42,Literature=45,Mathematics=32,PhysicalEducation=40,RussianLanguage=55,Strength=38 items=Briefs:Black:worn,Socks:White:worn,Trousers:Blue:worn,TShirt:Black:worn,Shoes:Beige:worn,Sweater:Black:worn,Hat:Gray:worn,FurCoat:Black:home,PeShirt:Green:home,PeShorts:Blue:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f15.p0 Воробьёв Сергей Никитич female=False roles=parent class=- born=1974-10-22 traits=Neat,Quiet skills=Agility=45,Communication=54,Endurance=45,Hauling=36,Librarianship=67,Pedagogy=38,RussianLanguage=71,Strength=65,UkrainianLanguage=32 items=Boxers:White:worn,Socks:Black:worn,Pants:Blue:worn,TShirt:Gray:worn,Shoes:Black:worn,Sweater:Black:worn,FurCoat:Beige:home,Bottle:Blue:bag +person f15.p1 Воробьёва Вера Виталиевна female=True roles=parent class=- born=1972-09-07 traits=Leader,Neat skills=Agility=42,BelarusianLanguage=40,Communication=30,Endurance=52,Hauling=46,Pedagogy=55,RussianLanguage=46,Strength=45,UkrainianLanguage=25 items=Panties:Beige:worn,Bra:Black:worn,Socks:Black:worn,Trousers:Black:worn,TShirt:Gray:worn,WinterBoots:Black:worn,Sweater:Beige:worn,Scarf:Black:worn,Coat:Beige:home,Bottle:White:bag,Phone:White:bag +person f16.c0 Фролова Елизавета Игоревна female=True roles=student class=class-classroom-02 born=2002-06-29 traits=Quiet skills=Agility=29,BelarusianLanguage=17,Biology=43,Communication=32,Endurance=19,Hauling=36,Literature=51,Mathematics=51,PhysicalEducation=68,RussianLanguage=24,Strength=45 items=Panties:Gray:worn,Socks:Black:worn,Jeans:Blue:worn,Shirt:Blue:worn,WinterBoots:Beige:worn,Sweater:Beige:worn,FurCoat:Black:home,PeShirt:Red:home,PeShorts:Gray:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f16.p0 Фролов Игорь Виталиевич female=False roles=parent class=- born=1968-07-25 traits= skills=Administration=51,Agility=57,BelarusianLanguage=25,Communication=58,Endurance=63,Hauling=49,Medicine=53,RussianLanguage=53,Strength=46 items=Boxers:White:worn,Socks:White:worn,Pants:Gray:worn,Shirt:Gray:worn,Shoes:Gray:worn,Hat:Blue:worn,Jacket:Beige:home +person f16.p1 Фролова Татьяна Олеговна female=True roles=parent class=- born=1970-06-17 traits=Neat,Outgoing skills=Agility=45,BelarusianLanguage=23,Communication=59,Endurance=59,Hauling=62,Literature=51,RussianLanguage=72,Strength=44 items=Panties:White:worn,Bra:Beige:worn,Socks:Gray:worn,Dress:Black:worn,Shoes:Beige:worn,Sweater:Blue:worn,Coat:Beige:home,Bottle:Blue:bag,Phone:Black:bag +person f17.c0 Иванов Андрей Александрович female=False roles=student class=class-classroom-03 born=2001-06-27 traits=Leader skills=Agility=60,Biology=53,Communication=54,Endurance=35,Hauling=41,Literature=65,Mathematics=38,PhysicalEducation=55,RussianLanguage=35,Strength=45,UkrainianLanguage=13 items=Boxers:White:worn,Socks:Gray:worn,Jeans:Gray:worn,Shirt:White:worn,WinterBoots:Gray:worn,Coat:Black:home,PeShirt:Green:home,PeShorts:Blue:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f17.c1 Иванова Ксения Александровна female=True roles=student class=class-classroom-02 born=2001-09-10 traits=ColdLoving skills=Agility=45,Biology=64,Communication=28,Endurance=64,Hauling=48,Literature=51,Mathematics=37,PhysicalEducation=28,RussianLanguage=43,Strength=45 items=Panties:White:worn,Socks:Black:worn,Pants:Black:worn,Shirt:Gray:worn,WinterBoots:Gray:worn,FurCoat:Beige:worn,Hat:Black:worn,PeShirt:Blue:home,PeShorts:Green:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f17.c2 Иванова Полина Александровна female=True roles=student class=class-classroom-03 born=2001-05-21 traits=Gossip,HotTempered skills=Agility=36,Biology=43,Communication=45,Endurance=45,Hauling=47,Literature=43,Mathematics=33,PhysicalEducation=58,RussianLanguage=36,Strength=45,UkrainianLanguage=15 items=Panties:Black:worn,Socks:Black:worn,Pants:Black:worn,TShirt:Beige:worn,WinterBoots:Gray:worn,Sweater:Gray:worn,Coat:Beige:worn,Belt:Beige:worn,PeShirt:Red:home,PeShorts:Gray:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f17.p0 Иванов Александр Владимирович female=False roles=parent class=- born=1967-12-24 traits=Diligent,HeatLoving skills=Agility=47,Communication=74,Endurance=52,Hauling=55,Physics=39,RussianLanguage=27,Strength=49 items=Briefs:Black:worn,Socks:Gray:worn,Jeans:Blue:worn,TShirt:Blue:worn,WinterBoots:Black:worn,Belt:Beige:worn,FurCoat:Black:home +person f17.p1 Иванова Александра Ивановна female=True roles=parent class=- born=1967-04-25 traits= skills=Agility=40,Communication=69,Endurance=63,Hauling=35,RussianLanguage=56,Strength=68,UkrainianLanguage=19 items=Panties:White:worn,Bra:Gray:worn,Socks:Black:worn,Jeans:Blue:worn,Shirt:Black:worn,WinterBoots:Gray:worn,Jacket:Black:home +person f18.c0 Лебедев Александр Валериевич female=False roles=student class=class-classroom-00 born=2004-02-16 traits=Curious skills=Agility=26,Biology=50,Communication=9,Endurance=53,Hauling=33,Literature=55,Mathematics=20,PhysicalEducation=56,RussianLanguage=23,Strength=35 items=Boxers:Black:worn,Socks:Black:worn,Pants:Black:worn,Shirt:White:worn,WinterBoots:Beige:worn,Sweater:Beige:worn,Jacket:Blue:worn,Belt:Gray:worn,PeShirt:Blue:home,PeShorts:Green:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f18.p0 Лебедев Валерий Романович female=False roles=parent class=- born=1985-03-29 traits=Neat,Quiet skills=Agility=25,Chemistry=56,Communication=59,Endurance=35,English=49,Geography=50,Hauling=35,Pedagogy=51,RussianLanguage=67,Strength=62 items=Briefs:Gray:worn,Socks:Black:worn,Pants:Black:worn,Shirt:Gray:worn,Shoes:Beige:worn,FurCoat:Gray:worn,Belt:Beige:worn,Bottle:Green:bag +person f18.p1 Лебедева Юлия Михаиловна female=True roles=parent class=- born=1981-01-26 traits= skills=Administration=54,Agility=59,BelarusianLanguage=36,Biology=68,Communication=52,Endurance=44,Hauling=67,Mathematics=47,Medicine=37,RussianLanguage=48,Strength=45,UkrainianLanguage=22 items=Panties:Gray:worn,SoftBra:White:worn,Socks:Gray:worn,Dress:Blue:worn,Shoes:Beige:worn,Hat:Black:worn,Belt:Black:worn,Jacket:Gray:home,Phone:Black:bag +person f19.c0 Воробьёв Григорий Михаилович female=False roles=student class=class-classroom-00 born=2004-08-29 traits=Outgoing skills=Agility=62,BelarusianLanguage=31,Biology=42,Communication=36,Endurance=50,Hauling=39,Literature=34,Mathematics=37,PhysicalEducation=50,RussianLanguage=33,Strength=45,UkrainianLanguage=23 items=Briefs:Black:worn,Socks:White:worn,Pants:Blue:worn,Shirt:Blue:worn,Shoes:Gray:worn,Sweater:Black:worn,Coat:Gray:worn,PeShirt:Blue:home,PeShorts:Black:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f19.p0 Воробьёв Михаил Егорович female=False roles=parent class=- born=1978-11-03 traits=Leader skills=Administration=53,Agility=40,Communication=56,Endurance=46,Hauling=41,Librarianship=66,Mathematics=42,RussianLanguage=45,Strength=55 items=Briefs:Black:worn,Socks:Black:worn,Pants:Black:worn,Shirt:Black:worn,Shoes:Beige:worn,Hat:Black:worn,Jacket:Green:home,Bottle:Blue:bag +person f19.p1 Воробьёва Вера Алексеевна female=True roles=parent class=- born=1982-08-29 traits=AbsentMinded,Lazy skills=Administration=50,Agility=58,BelarusianLanguage=20,Communication=46,Endurance=30,Hauling=50,PhysicalEducation=66,RussianLanguage=49,Strength=40,UkrainianLanguage=25 items=Panties:Black:worn,Bra:Gray:worn,Socks:Gray:worn,Skirt:Blue:worn,TShirt:Blue:worn,WinterBoots:Gray:worn,Coat:Gray:home,Bottle:Green:bag +person f2.c0 Алексеев Григорий Константинович female=False roles=student class=class-classroom-02 born=2002-07-24 traits=Neat skills=Agility=40,BelarusianLanguage=36,Biology=64,Communication=34,Endurance=57,Hauling=38,Literature=58,Mathematics=41,PhysicalEducation=47,RussianLanguage=28,Strength=45,UkrainianLanguage=23 items=Briefs:White:worn,Socks:Gray:worn,Jeans:Blue:worn,TShirt:Black:worn,Shoes:Black:worn,FurCoat:Gray:home,PeShirt:Red:home,PeShorts:Red:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f2.p0 Алексеев Константин Романович female=False roles=parent class=- born=1974-12-11 traits=Neat skills=Agility=36,BelarusianLanguage=23,Communication=60,Endurance=49,English=34,Hauling=47,Mathematics=63,Medicine=81,Pedagogy=74,RussianLanguage=56,Strength=59,UkrainianLanguage=22 items=Boxers:Gray:worn,Socks:White:worn,Pants:Beige:worn,Shirt:Black:worn,WinterBoots:Beige:worn,Sweater:Black:worn,Hat:Black:worn,FurCoat:Gray:home +person f2.p1 Алексеева Ольга Романовна female=True roles=parent class=- born=1970-07-22 traits=Outgoing skills=Agility=50,Communication=58,Cooking=51,Endurance=43,English=55,Hauling=54,Literature=35,Medicine=76,PhysicalEducation=32,RussianLanguage=38,Strength=42 items=Panties:Beige:worn,Bra:Gray:worn,Socks:White:worn,Skirt:Black:worn,Shirt:Black:worn,Shoes:Beige:worn,Coat:Beige:home,Bottle:White:bag +person f20.c0 Павлова Елена Игоревна female=True roles=student class=class-classroom-01 born=2002-11-09 traits= skills=Agility=55,Biology=47,Communication=19,Endurance=51,Hauling=38,Literature=41,Mathematics=34,PhysicalEducation=49,RussianLanguage=44,Strength=42,UkrainianLanguage=20 items=Panties:White:worn,Socks:Gray:worn,Pants:Black:worn,TShirt:Blue:worn,WinterBoots:Beige:worn,Hat:Beige:worn,Belt:Gray:worn,Coat:Gray:home,PeShirt:Red:home,PeShorts:Gray:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f20.c1 Павлова Валентина Игоревна female=True roles=student class=class-classroom-03 born=2001-01-24 traits=HotTempered skills=Agility=68,BelarusianLanguage=12,Biology=41,Communication=31,Endurance=58,Hauling=46,Literature=42,Mathematics=19,PhysicalEducation=50,RussianLanguage=45,Strength=42,UkrainianLanguage=9 items=Panties:White:worn,SoftBra:Beige:worn,Socks:White:worn,Trousers:Blue:worn,Shirt:Blue:worn,Shoes:Black:worn,FurCoat:Beige:home,PeShirt:Green:home,PeShorts:Blue:home,Bottle:Green:bag,Phone:Black:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f20.p0 Павлов Игорь Никитич female=False roles=parent class=- born=1968-08-01 traits=HotTempered,Neat skills=Administration=61,Agility=72,Communication=57,Endurance=43,Geography=61,German=73,Hauling=44,Pedagogy=56,RussianLanguage=66,Strength=42,UkrainianLanguage=28 items=Briefs:Black:worn,Socks:White:worn,Trousers:Black:worn,TShirt:Blue:worn,WinterBoots:Gray:worn,FurCoat:Black:home,Bottle:White:bag +person f20.p1 Павлова Людмила Дмитриевна female=True roles=parent class=- born=1970-01-17 traits=ColdLoving,Quiet skills=Agility=46,BelarusianLanguage=26,Communication=71,Endurance=39,English=31,Hauling=36,Physics=42,RussianLanguage=53,Strength=56 items=Panties:White:worn,Bra:Gray:worn,Socks:Black:worn,Pants:Beige:worn,TShirt:Blue:worn,Shoes:White:worn,FurCoat:Beige:worn,Hat:Black:worn +person f21.c0 Ильин Андрей Константинович female=False roles=student class=class-classroom-02 born=2002-05-12 traits= skills=Agility=54,BelarusianLanguage=15,Biology=72,Communication=22,Endurance=54,Hauling=58,Literature=58,Mathematics=45,PhysicalEducation=52,RussianLanguage=38,Strength=45,UkrainianLanguage=25 items=Boxers:Black:worn,Socks:Black:worn,Jeans:Black:worn,TShirt:White:worn,WinterBoots:Black:worn,Sweater:Black:worn,Jacket:Black:worn,PeShirt:Gray:home,PeShorts:Blue:home,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f21.p0 Ильин Константин Никитич female=False roles=parent class=- born=1971-01-21 traits=Diligent,Outgoing skills=Agility=45,Biology=50,Communication=53,Endurance=40,English=46,Hauling=45,History=42,Librarianship=57,RussianLanguage=68,Strength=40,UkrainianLanguage=9 items=Briefs:Gray:worn,Socks:Black:worn,Trousers:Black:worn,Shirt:Black:worn,Shoes:White:worn,Scarf:Black:worn,FurCoat:Beige:home,Bottle:Blue:bag +person f21.p1 Ильина Яна Борисовна female=True roles=parent class=- born=1975-04-20 traits=Lazy skills=Agility=74,BelarusianLanguage=26,Biology=41,Communication=54,Endurance=55,English=77,Hauling=35,Librarianship=44,Medicine=57,RussianLanguage=35,Strength=50,UkrainianLanguage=23 items=Panties:Black:worn,SportsBra:White:worn,Socks:White:worn,Jeans:Gray:worn,TShirt:White:worn,WinterBoots:Beige:worn,Sweater:Black:worn,Hat:Blue:worn,Belt:Gray:worn,Coat:Gray:home +person f22.c0 Захаров Никита Виталиевич female=False roles=student class=class-classroom-03 born=2000-09-14 traits= skills=Agility=62,BelarusianLanguage=19,Biology=50,Communication=42,Endurance=39,Hauling=47,Literature=68,Mathematics=65,PhysicalEducation=60,RussianLanguage=59,Strength=45,UkrainianLanguage=31 items=Briefs:Gray:worn,Socks:White:worn,Jeans:Gray:worn,TShirt:Beige:worn,Shoes:Beige:worn,FurCoat:Gray:home,PeShirt:Black:home,PeShorts:Blue:home,Bottle:White:bag,Phone:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f22.c1 Захаров Денис Виталиевич female=False roles=student class=class-classroom-02 born=2001-09-11 traits=Kind,Leader skills=Agility=51,Biology=42,Communication=55,Endurance=51,Hauling=69,Literature=53,Mathematics=60,PhysicalEducation=80,RussianLanguage=50,Strength=53,UkrainianLanguage=16 items=Boxers:White:worn,Socks:White:worn,Trousers:Blue:worn,TShirt:Blue:worn,Shoes:White:worn,Hat:Blue:worn,Coat:Beige:home,PeShirt:Red:home,PeShorts:Red:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f22.p0 Захаров Виталий Андреевич female=False roles=parent class=- born=1966-04-08 traits= skills=Agility=29,Communication=62,Endurance=82,Hauling=33,RussianLanguage=65,Strength=58,UkrainianLanguage=18 items=Briefs:Black:worn,Socks:Black:worn,Pants:Beige:worn,TShirt:LightBlue:worn,Shoes:Black:worn,Coat:Black:worn,Hat:Gray:worn,Bottle:White:bag,Phone:Black:bag +person f22.p1 Захарова Елизавета Егоровна female=True roles=parent class=- born=1967-09-05 traits= skills=Administration=37,Agility=42,BelarusianLanguage=28,Communication=82,Cooking=65,Endurance=60,English=52,Hauling=37,Mathematics=67,RussianLanguage=54,Strength=51 items=Panties:Gray:worn,SoftBra:White:worn,Socks:White:worn,Trousers:Gray:worn,TShirt:LightBlue:worn,WinterBoots:Gray:worn,Jacket:Gray:home,Phone:White:bag +person f23.c0 Орловская Нина Константиновна female=True roles=student class=class-classroom-02 born=2001-11-01 traits=AbsentMinded,Quiet skills=Agility=44,BelarusianLanguage=13,Biology=42,Communication=9,Endurance=51,Hauling=45,Literature=53,Mathematics=34,PhysicalEducation=70,RussianLanguage=46,Strength=38 items=Panties:White:worn,Socks:White:worn,Trousers:Blue:worn,TShirt:Blue:worn,Shoes:Beige:worn,Sweater:Blue:worn,Coat:Beige:worn,Hat:Gray:worn,PeShirt:Blue:home,PeShorts:Red:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f23.c1 Орловская Полина Константиновна female=True roles=student class=class-classroom-01 born=2003-03-30 traits=ColdLoving,Quiet skills=Agility=39,BelarusianLanguage=20,Biology=70,Communication=19,Endurance=53,Hauling=54,Literature=73,Mathematics=46,PhysicalEducation=55,RussianLanguage=47,Strength=67 items=Panties:White:worn,Socks:Black:worn,Dress:Blue:worn,Shoes:Black:worn,Sweater:Beige:worn,Jacket:Red:home,PeShirt:Black:home,PeShorts:Gray:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f23.p0 Орловский Константин Михаилович female=False roles=parent class=- born=1965-04-02 traits=Diligent,HeatLoving skills=Agility=45,BelarusianLanguage=23,Communication=55,Endurance=50,Hauling=50,History=40,Librarianship=56,Physics=61,RussianLanguage=52,Strength=32,UkrainianLanguage=17 items=Briefs:Gray:worn,Socks:White:worn,Jeans:Gray:worn,Shirt:Blue:worn,WinterBoots:Beige:worn,FurCoat:Black:worn,Hat:Beige:worn +person f23.p1 Орловская Яна Степановна female=True roles=parent class=- born=1965-10-22 traits=Leader skills=Agility=56,BelarusianLanguage=28,Communication=44,Endurance=40,Hauling=81,Pedagogy=45,RussianLanguage=62,Strength=56,UkrainianLanguage=26 items=Panties:Gray:worn,SportsBra:Gray:worn,Socks:White:worn,Pants:Black:worn,Shirt:White:worn,WinterBoots:Black:worn,Sweater:Blue:worn,Hat:Blue:worn,Jacket:Red:home +person f24.c0 Орлов Фёдор Алексеевич female=False roles=student class=class-classroom-03 born=2000-12-05 traits=Neat skills=Agility=40,BelarusianLanguage=8,Biology=54,Communication=6,Endurance=40,Hauling=45,Literature=41,Mathematics=10,PhysicalEducation=46,RussianLanguage=52,Strength=63 items=Briefs:Black:worn,Socks:Black:worn,Jeans:Gray:worn,Shirt:Black:worn,Shoes:Gray:worn,Sweater:Black:worn,Jacket:Blue:worn,PeShirt:Blue:home,PeShorts:Blue:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f24.c1 Орлова Оксана Алексеевна female=True roles=student class=class-classroom-00 born=2004-03-01 traits=HotTempered,Kind skills=Agility=75,Biology=30,Communication=23,Endurance=54,Hauling=35,Literature=62,Mathematics=49,PhysicalEducation=58,RussianLanguage=26,Strength=41,UkrainianLanguage=31 items=Panties:Beige:worn,Socks:White:worn,Pants:Beige:worn,TShirt:White:worn,WinterBoots:Black:worn,Scarf:Beige:worn,Jacket:Red:home,PeShirt:Black:home,PeShorts:Red:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f24.p0 Орлов Алексей Денисович female=False roles=parent class=- born=1977-04-17 traits= skills=Agility=45,BelarusianLanguage=17,Communication=51,Endurance=18,English=61,Hauling=46,Literature=56,Mathematics=60,RussianLanguage=55,Strength=62,UkrainianLanguage=16 items=Boxers:Black:worn,Socks:Black:worn,Trousers:Gray:worn,Shirt:Gray:worn,WinterBoots:Black:worn,Sweater:Black:worn,Hat:Gray:worn,Jacket:Black:home +person f24.p1 Орлова Надежда Владиславовна female=True roles=parent class=- born=1977-12-04 traits=Kind skills=Agility=25,BelarusianLanguage=25,Communication=52,Endurance=22,English=70,Hauling=52,Librarianship=51,Medicine=10,RussianLanguage=50,Strength=52,UkrainianLanguage=19 items=Panties:Beige:worn,SoftBra:Gray:worn,Socks:Gray:worn,Pants:Gray:worn,TShirt:LightBlue:worn,Shoes:Black:worn,Sweater:Blue:worn,FurCoat:Beige:worn,Hat:Black:worn,Belt:Beige:worn +person f25.c0 Петрова Александра Игоревна female=True roles=student class=class-classroom-02 born=2001-12-19 traits= skills=Agility=57,BelarusianLanguage=29,Biology=64,Communication=32,Endurance=40,Hauling=58,Literature=47,Mathematics=40,PhysicalEducation=79,RussianLanguage=64,Strength=65,UkrainianLanguage=7 items=Panties:White:worn,Socks:White:worn,Trousers:Black:worn,TShirt:Blue:worn,WinterBoots:Black:worn,Sweater:Gray:worn,Coat:Beige:worn,Hat:Beige:worn,Scarf:Beige:worn,PeShirt:Red:home,PeShorts:Blue:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f25.p1 Петрова Полина Юриевна female=True roles=parent class=- born=1965-12-11 traits=Kind,Neat skills=Agility=53,Communication=50,Endurance=59,English=70,Geography=41,Hauling=56,Medicine=42,RussianLanguage=72,Strength=45 items=Panties:Gray:worn,SportsBra:White:worn,Socks:Black:worn,Trousers:Gray:worn,TShirt:Black:worn,WinterBoots:Beige:worn,Sweater:Black:worn,Coat:Gray:home +person f26.c0 Яковлев Павел Михаилович female=False roles=student class=class-classroom-01 born=2002-12-29 traits=Kind,Outgoing skills=Agility=45,BelarusianLanguage=30,Biology=59,Communication=8,Endurance=59,Hauling=53,Literature=60,Mathematics=47,PhysicalEducation=43,RussianLanguage=43,Strength=45,UkrainianLanguage=23 items=Boxers:White:worn,Socks:White:worn,Pants:Beige:worn,TShirt:LightBlue:worn,Shoes:Black:worn,Sweater:Blue:worn,Coat:Beige:worn,PeShirt:Gray:home,PeShorts:Red:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f26.c1 Яковлев Вадим Михаилович female=False roles=student class=class-classroom-00 born=2004-06-28 traits= skills=Agility=40,BelarusianLanguage=24,Biology=36,Communication=14,Endurance=66,Hauling=41,Literature=71,Mathematics=26,PhysicalEducation=55,RussianLanguage=25,Strength=40,UkrainianLanguage=18 items=Briefs:Black:worn,Socks:Black:worn,Jeans:Blue:worn,TShirt:Black:worn,Shoes:Black:worn,FurCoat:Black:home,PeShirt:Blue:home,PeShorts:Red:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f26.p0 Яковлев Михаил Степанович female=False roles=parent class=- born=1975-12-27 traits=HeatLoving,Outgoing skills=Agility=44,Communication=59,Endurance=66,Hauling=52,Mathematics=44,Medicine=71,PhysicalEducation=53,RussianLanguage=55,Strength=45,UkrainianLanguage=28 items=Briefs:Black:worn,Socks:Black:worn,Jeans:Blue:worn,TShirt:Black:worn,Shoes:Gray:worn,Scarf:Blue:worn,Jacket:Blue:home,Phone:Gray:bag +person f26.p1 Яковлева Марина Кирилловна female=True roles=parent class=- born=1976-12-10 traits=Diligent,Outgoing skills=Agility=36,BelarusianLanguage=22,Communication=53,Endurance=66,English=40,Hauling=47,History=43,Medicine=66,PhysicalEducation=45,RussianLanguage=65,Strength=43 items=Panties:Gray:worn,SportsBra:White:worn,Socks:Black:worn,Skirt:Blue:worn,TShirt:Blue:worn,WinterBoots:Beige:worn,Coat:Beige:home,Bottle:Green:bag,Phone:Gray:bag +person f27.c0 Иванов Виктор Константинович female=False roles=student class=class-classroom-01 born=2002-12-27 traits=Bully skills=Agility=29,BelarusianLanguage=19,Biology=56,Communication=26,Endurance=46,Hauling=65,Literature=45,Mathematics=53,PhysicalEducation=58,RussianLanguage=27,Strength=45,UkrainianLanguage=22 items=Boxers:Black:worn,Socks:Gray:worn,Pants:Gray:worn,TShirt:Gray:worn,Shoes:Beige:worn,FurCoat:Black:worn,PeShirt:Gray:home,PeShorts:Red:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f27.c1 Иванов Владислав Константинович female=False roles=student class=class-classroom-00 born=2004-07-04 traits=Gossip,Neat skills=Agility=38,BelarusianLanguage=23,Biology=27,Communication=31,Endurance=53,Hauling=65,Literature=62,Mathematics=25,PhysicalEducation=64,RussianLanguage=43,Strength=53 items=Boxers:White:worn,Socks:White:worn,Trousers:Black:worn,Shirt:Gray:worn,WinterBoots:Black:worn,Sweater:Blue:worn,Jacket:Gray:worn,Hat:Gray:worn,Scarf:Blue:worn,PeShirt:Blue:home,PeShorts:Green:home,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f27.c2 Иванов Александр Константинович female=False roles=student class=class-classroom-00 born=2004-05-25 traits= skills=Agility=58,Biology=51,Communication=37,Endurance=60,Hauling=37,Literature=65,Mathematics=35,PhysicalEducation=33,RussianLanguage=42,Strength=45,UkrainianLanguage=38 items=Boxers:Black:worn,Socks:Black:worn,Pants:Black:worn,TShirt:White:worn,WinterBoots:Beige:worn,Sweater:Black:worn,Hat:Beige:worn,Scarf:Blue:worn,FurCoat:Gray:home,PeShirt:Blue:home,PeShorts:Black:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f27.p0 Иванов Константин Иванович female=False roles=parent class=- born=1974-08-25 traits=Outgoing skills=Agility=44,BelarusianLanguage=27,Communication=77,Endurance=42,English=53,Hauling=62,Medicine=52,Pedagogy=40,RussianLanguage=32,Strength=28 items=Boxers:Black:worn,Socks:Black:worn,Trousers:Blue:worn,Shirt:Blue:worn,Shoes:Gray:worn,Sweater:Blue:worn,Scarf:Beige:worn,Jacket:Green:home,Bottle:White:bag,Phone:White:bag +person f27.p1 Иванова Марина Константиновна female=True roles=parent class=- born=1969-11-29 traits=Curious skills=Agility=43,BelarusianLanguage=16,Communication=71,Endurance=63,English=50,Hauling=59,RussianLanguage=56,Strength=35,UkrainianLanguage=21 items=Panties:Beige:worn,SoftBra:Gray:worn,Socks:Black:worn,Skirt:Black:worn,TShirt:Beige:worn,Shoes:Gray:worn,Sweater:Black:worn,FurCoat:Gray:worn,Hat:Black:worn,Belt:Gray:worn,Bottle:Green:bag,Phone:Black:bag +person f28.c0 Иванов Пётр Петрович female=False roles=student class=class-classroom-02 born=2002-08-02 traits= skills=Agility=43,BelarusianLanguage=34,Biology=36,Communication=42,Endurance=50,Hauling=36,Literature=33,Mathematics=4,PhysicalEducation=78,RussianLanguage=46,Strength=44 items=Briefs:Gray:worn,Socks:White:worn,Pants:Beige:worn,Shirt:Gray:worn,WinterBoots:Black:worn,Sweater:Blue:worn,Jacket:Red:home,PeShirt:Green:home,PeShorts:Green:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f28.c1 Иванова Наталья Петровна female=True roles=student class=class-classroom-03 born=2000-12-07 traits=Quiet skills=Agility=58,BelarusianLanguage=30,Biology=46,Communication=52,Endurance=36,Hauling=58,Literature=38,Mathematics=73,PhysicalEducation=77,RussianLanguage=51,Strength=45,UkrainianLanguage=23 items=Panties:Gray:worn,SoftBra:Gray:worn,Socks:Black:worn,Skirt:Blue:worn,Shirt:Black:worn,WinterBoots:Black:worn,Sweater:Black:worn,Hat:Blue:worn,Jacket:Black:home,PeShirt:Blue:home,PeShorts:Black:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f28.c2 Иванов Максим Петрович female=False roles=student class=class-classroom-00 born=2004-04-05 traits=Lazy skills=Agility=67,BelarusianLanguage=23,Biology=32,Communication=26,Endurance=54,Hauling=51,Literature=57,Mathematics=10,PhysicalEducation=57,RussianLanguage=47,Strength=75,UkrainianLanguage=35 items=Boxers:Black:worn,Socks:Gray:worn,Pants:Beige:worn,TShirt:Black:worn,Shoes:Gray:worn,Sweater:Gray:worn,Scarf:Black:worn,Coat:Black:home,PeShirt:Black:home,PeShorts:Red:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f28.p0 Иванов Пётр Олегович female=False roles=parent class=- born=1965-06-23 traits=AbsentMinded,Kind skills=Agility=75,BelarusianLanguage=24,Biology=51,Communication=60,Cooking=51,Endurance=74,English=57,Geography=66,Hauling=48,RussianLanguage=45,Strength=40,UkrainianLanguage=36 items=Boxers:Black:worn,Socks:Gray:worn,Trousers:Black:worn,Shirt:White:worn,Shoes:White:worn,Sweater:Black:worn,Coat:Gray:worn,Hat:Black:worn,Scarf:Black:worn +person f28.p1 Иванова Полина Алексеевна female=True roles=parent class=- born=1965-11-18 traits=AbsentMinded,Leader skills=Agility=43,Communication=61,Cooking=49,Endurance=43,Hauling=60,RussianLanguage=46,Strength=45 items=Panties:White:worn,SoftBra:White:worn,Socks:Black:worn,Skirt:Gray:worn,TShirt:Gray:worn,Shoes:Beige:worn,Sweater:Black:worn,FurCoat:Black:home,Bottle:Blue:bag,Phone:White:bag +person f29.c0 Макарова Ксения Александровна female=True roles=student class=class-classroom-01 born=2002-11-08 traits=Bully,HeatLoving skills=Agility=52,Biology=73,Communication=35,Endurance=53,Hauling=68,Literature=60,Mathematics=34,PhysicalEducation=67,RussianLanguage=18,Strength=45 items=Panties:Black:worn,Socks:White:worn,Pants:Blue:worn,TShirt:Gray:worn,WinterBoots:Black:worn,Jacket:Black:worn,Hat:Blue:worn,Belt:Beige:worn,PeShirt:Gray:home,PeShorts:Blue:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f29.p0 Макаров Александр Николаевич female=False roles=parent class=- born=1981-11-08 traits=AbsentMinded skills=Agility=50,BelarusianLanguage=34,Communication=44,Endurance=55,Hauling=42,Informatics=34,PhysicalEducation=74,RussianLanguage=54,Strength=64,UkrainianLanguage=10 items=Briefs:Black:worn,Socks:White:worn,Jeans:Blue:worn,Shirt:Blue:worn,WinterBoots:Black:worn,Sweater:Gray:worn,Coat:Black:worn,Hat:Gray:worn,Scarf:Black:worn,Bottle:Blue:bag,Phone:Black:bag +person f29.p1 Макарова Мария Владиславовна female=True roles=parent class=- born=1977-10-31 traits=ColdLoving,Curious skills=Administration=44,Agility=44,BelarusianLanguage=8,Communication=56,Endurance=62,English=40,Hauling=44,Medicine=15,RussianLanguage=35,Strength=52 items=Panties:White:worn,Bra:Beige:worn,Socks:Black:worn,Pants:Black:worn,Shirt:Black:worn,Shoes:Black:worn,Sweater:Black:worn,Coat:Black:home,Bottle:White:bag,Phone:White:bag +person f3.c0 Макарова Вероника Петровна female=True roles=student class=class-classroom-02 born=2002-05-15 traits= skills=Agility=68,BelarusianLanguage=24,Biology=34,Communication=45,Endurance=50,Hauling=48,Literature=37,Mathematics=41,PhysicalEducation=50,RussianLanguage=28,Strength=45,UkrainianLanguage=23 items=Panties:Gray:worn,Socks:Black:worn,Trousers:Blue:worn,Shirt:Gray:worn,WinterBoots:Gray:worn,Jacket:Black:home,PeShirt:Gray:home,PeShorts:Blue:home,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f3.c1 Макаров Роман Петрович female=False roles=student class=class-classroom-02 born=2002-04-16 traits=Lazy skills=Agility=50,BelarusianLanguage=22,Biology=59,Communication=22,Endurance=46,Hauling=51,Literature=39,Mathematics=17,PhysicalEducation=37,RussianLanguage=31,Strength=45,UkrainianLanguage=23 items=Boxers:Gray:worn,Socks:Black:worn,Jeans:Blue:worn,Shirt:White:worn,WinterBoots:Black:worn,Hat:Beige:worn,Coat:Black:home,PeShirt:Black:home,PeShorts:Black:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f3.p0 Макаров Пётр Егорович female=False roles=parent class=- born=1977-02-28 traits=Diligent,Outgoing skills=Agility=67,Communication=28,Endurance=53,English=57,Hauling=43,History=43,Pedagogy=54,RussianLanguage=43,Strength=40 items=Briefs:Black:worn,Socks:White:worn,Pants:Gray:worn,Shirt:Blue:worn,WinterBoots:Black:worn,Sweater:Beige:worn,Hat:Beige:worn,Coat:Black:home +person f3.p1 Макарова Алина Антоновна female=True roles=parent class=- born=1978-04-05 traits=HotTempered,Outgoing skills=Agility=45,BelarusianLanguage=20,Biology=41,Communication=45,Cooking=68,Endurance=51,English=54,Hauling=52,History=69,Medicine=78,RussianLanguage=63,Strength=44 items=Panties:Beige:worn,SportsBra:Black:worn,Socks:Black:worn,Pants:Blue:worn,TShirt:LightBlue:worn,Shoes:Black:worn,Hat:Beige:worn,FurCoat:Beige:home,Phone:Black:bag +person f30.c0 Иванова Светлана Владимировна female=True roles=student class=class-classroom-03 born=2000-10-08 traits=Leader skills=Agility=43,Biology=53,Communication=26,Endurance=44,Hauling=49,Literature=68,Mathematics=39,PhysicalEducation=73,RussianLanguage=51,Strength=36,UkrainianLanguage=21 items=Panties:White:worn,SoftBra:Beige:worn,Socks:Gray:worn,Dress:Blue:worn,Shoes:Gray:worn,Coat:Black:worn,Hat:Blue:worn,Scarf:Gray:worn,PeShirt:Gray:home,PeShorts:Black:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f30.c1 Иванова Надежда Владимировна female=True roles=student class=class-classroom-03 born=2000-12-10 traits=AbsentMinded,Kind skills=Agility=43,BelarusianLanguage=19,Biology=57,Communication=38,Endurance=66,Hauling=46,Literature=70,Mathematics=23,PhysicalEducation=56,RussianLanguage=41,Strength=40,UkrainianLanguage=10 items=Panties:White:worn,SoftBra:Beige:worn,Socks:Black:worn,Dress:Gray:worn,Shoes:Black:worn,Scarf:Beige:worn,Jacket:Blue:home,PeShirt:Blue:home,PeShorts:Red:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f30.p0 Иванов Владимир Денисович female=False roles=parent class=- born=1974-06-12 traits=Leader skills=Agility=40,Biology=17,Communication=72,Cooking=61,Endurance=50,Hauling=32,RussianLanguage=58,Strength=40,UkrainianLanguage=26 items=Boxers:Black:worn,Socks:Black:worn,Trousers:Black:worn,Shirt:Gray:worn,WinterBoots:Gray:worn,Coat:Beige:home,Bottle:Blue:bag +person f30.p1 Иванова Мария Андреевна female=True roles=parent class=- born=1973-09-24 traits=Diligent skills=Agility=52,BelarusianLanguage=40,Communication=61,Endurance=62,Hauling=65,Medicine=47,RussianLanguage=43,Strength=39 items=Panties:White:worn,Bra:Gray:worn,Socks:Black:worn,Jeans:Blue:worn,Shirt:Gray:worn,WinterBoots:Black:worn,Sweater:Beige:worn,Jacket:Black:home,Bottle:Blue:bag,Phone:Black:bag +person f31.c0 Иванов Николай Вадимович female=False roles=student class=class-classroom-01 born=2003-08-29 traits= skills=Agility=20,BelarusianLanguage=24,Biology=40,Communication=27,Endurance=43,Hauling=46,Literature=36,Mathematics=40,PhysicalEducation=53,RussianLanguage=32,Strength=73,UkrainianLanguage=20 items=Briefs:Gray:worn,Socks:White:worn,Jeans:Black:worn,TShirt:Beige:worn,WinterBoots:Beige:worn,FurCoat:Black:home,PeShirt:Blue:home,PeShorts:Black:home,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f31.p0 Иванов Вадим Андреевич female=False roles=parent class=- born=1985-06-23 traits=Curious,Quiet skills=Administration=28,Agility=45,BelarusianLanguage=32,Communication=54,Endurance=71,English=55,German=47,Hauling=53,PhysicalEducation=44,RussianLanguage=64,Strength=44,UkrainianLanguage=17 items=Boxers:White:worn,Socks:White:worn,Jeans:Black:worn,Shirt:Black:worn,Shoes:Black:worn,Hat:Blue:worn,Coat:Gray:home,Bottle:Blue:bag,Phone:Gray:bag +person f31.p1 Иванова Александра Юриевна female=True roles=parent class=- born=1980-08-20 traits=Diligent,HeatLoving skills=Agility=70,BelarusianLanguage=29,Communication=50,Endurance=41,English=50,Hauling=51,Librarianship=35,PhysicalEducation=73,Physics=54,RussianLanguage=59,Strength=62,UkrainianLanguage=23 items=Panties:Beige:worn,SportsBra:Blue:worn,Socks:Gray:worn,Trousers:Blue:worn,Shirt:Blue:worn,WinterBoots:Beige:worn,Hat:Black:worn,Scarf:Blue:worn,Jacket:Black:home,Bottle:Green:bag +person f32.c0 Петров Артём Евгениевич female=False roles=student class=class-classroom-02 born=2002-07-14 traits=Neat skills=Agility=78,Biology=58,Communication=47,Endurance=32,Hauling=19,Literature=31,Mathematics=29,PhysicalEducation=60,RussianLanguage=65,Strength=18,UkrainianLanguage=18 items=Boxers:Black:worn,Socks:Black:worn,Trousers:Blue:worn,Shirt:Gray:worn,Shoes:Beige:worn,Sweater:Black:worn,FurCoat:Black:worn,PeShirt:Gray:home,PeShorts:Gray:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f32.c1 Петрова Елена Евгениевна female=True roles=student class=class-classroom-00 born=2003-10-27 traits=Bully,Curious skills=Agility=37,Biology=54,Communication=39,Endurance=60,Hauling=53,Literature=54,Mathematics=39,PhysicalEducation=75,RussianLanguage=34,Strength=45,UkrainianLanguage=30 items=Panties:Gray:worn,Socks:Black:worn,Pants:Blue:worn,TShirt:White:worn,Shoes:Gray:worn,Coat:Black:worn,Hat:Gray:worn,Belt:Gray:worn,PeShirt:Blue:home,PeShorts:Green:home,Bottle:Green:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f32.c2 Петрова Ирина Евгениевна female=True roles=student class=class-classroom-02 born=2001-09-18 traits=Kind,Quiet skills=Agility=41,BelarusianLanguage=32,Biology=80,Communication=32,Endurance=42,Hauling=67,Literature=51,Mathematics=26,PhysicalEducation=33,RussianLanguage=32,Strength=45 items=Panties:White:worn,Socks:White:worn,Dress:Beige:worn,WinterBoots:Black:worn,Sweater:Black:worn,Jacket:Beige:worn,Belt:Gray:worn,PeShirt:Black:home,PeShorts:Blue:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f32.p0 Петров Евгений Станиславович female=False roles=parent class=- born=1963-05-02 traits= skills=Agility=45,Communication=66,Endurance=43,English=53,Hauling=42,PhysicalEducation=51,RussianLanguage=62,Strength=52,UkrainianLanguage=26 items=Briefs:Black:worn,Socks:White:worn,Jeans:Black:worn,Shirt:Black:worn,Shoes:White:worn,Sweater:Black:worn,Belt:Beige:worn,Coat:Beige:home,Bottle:Blue:bag +person f33.c0 Морозова Елизавета Григориевна female=True roles=student class=class-classroom-00 born=2003-11-04 traits=AbsentMinded,Lazy skills=Agility=59,BelarusianLanguage=25,Biology=55,Communication=45,Endurance=48,Hauling=50,Literature=58,Mathematics=0,PhysicalEducation=54,RussianLanguage=28,Strength=38,UkrainianLanguage=26 items=Panties:Gray:worn,Socks:Black:worn,Skirt:Gray:worn,TShirt:White:worn,Shoes:Beige:worn,Sweater:Blue:worn,Hat:Blue:worn,Scarf:Gray:worn,Coat:Gray:home,PeShirt:Gray:home,PeShorts:Green:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f33.p0 Морозов Григорий Михаилович female=False roles=parent class=- born=1968-05-01 traits= skills=Agility=28,BelarusianLanguage=8,Communication=68,Endurance=62,Hauling=51,Pedagogy=72,RussianLanguage=56,Strength=46,UkrainianLanguage=22 items=Boxers:Gray:worn,Socks:White:worn,Trousers:Gray:worn,TShirt:Blue:worn,WinterBoots:Beige:worn,Sweater:Blue:worn,Coat:Gray:home,Phone:Black:bag +person f33.p1 Морозова Марина Владимировна female=True roles=parent class=- born=1972-10-24 traits=Neat skills=Administration=54,Agility=41,BelarusianLanguage=28,Communication=42,Endurance=80,English=30,Geography=60,Hauling=41,PhysicalEducation=76,RussianLanguage=74,Strength=37 items=Panties:Beige:worn,SportsBra:White:worn,Socks:Black:worn,Dress:Blue:worn,WinterBoots:Black:worn,Hat:Beige:worn,Belt:Gray:worn,Jacket:Beige:home +person f34.c0 Кузнецова Валентина Ивановна female=True roles=student class=class-classroom-00 born=2004-03-25 traits=Bully,HotTempered skills=Agility=29,Biology=32,Communication=21,Endurance=69,Hauling=54,Literature=54,Mathematics=28,PhysicalEducation=53,RussianLanguage=16,Strength=45,UkrainianLanguage=24 items=Panties:Beige:worn,Socks:Black:worn,Pants:Black:worn,Shirt:White:worn,Shoes:Black:worn,Sweater:Gray:worn,Jacket:Black:worn,PeShirt:Green:home,PeShorts:Red:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f34.p0 Кузнецов Иван Сергеевич female=False roles=parent class=- born=1970-02-02 traits= skills=Agility=44,Communication=43,Endurance=58,English=45,German=55,Hauling=31,Literature=54,RussianLanguage=55,Strength=72 items=Boxers:Black:worn,Socks:Black:worn,Pants:Beige:worn,Shirt:Gray:worn,WinterBoots:Gray:worn,Hat:Black:worn,Scarf:Beige:worn,Coat:Black:home +person f34.p1 Кузнецова Дарья Валериевна female=True roles=parent class=- born=1967-03-25 traits=HeatLoving skills=Agility=47,BelarusianLanguage=23,Communication=52,Endurance=52,German=38,Hauling=32,Informatics=21,RussianLanguage=56,Strength=67,UkrainianLanguage=15 items=Panties:Beige:worn,Bra:Black:worn,Socks:Gray:worn,Dress:Beige:worn,Shoes:Black:worn,Sweater:Gray:worn,Jacket:Blue:home,Bottle:Blue:bag,Phone:Gray:bag +person f4.c0 Васильев Денис Федорович female=False roles=student class=class-classroom-01 born=2003-02-01 traits=Outgoing skills=Agility=50,BelarusianLanguage=9,Biology=58,Communication=27,Endurance=35,Hauling=25,Literature=52,Mathematics=53,PhysicalEducation=43,RussianLanguage=68,Strength=45,UkrainianLanguage=27 items=Boxers:Gray:worn,Socks:White:worn,Trousers:Black:worn,TShirt:Gray:worn,Shoes:Gray:worn,Sweater:Gray:worn,Jacket:Red:home,PeShirt:Black:home,PeShorts:Black:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f4.p0 Васильев Фёдор Игоревич female=False roles=parent class=- born=1970-05-21 traits=AbsentMinded skills=Agility=66,Communication=49,Endurance=44,English=25,Geography=47,Hauling=45,Medicine=44,RussianLanguage=45,Strength=47,UkrainianLanguage=31 items=Boxers:Black:worn,Socks:Black:worn,Jeans:Gray:worn,TShirt:LightBlue:worn,Shoes:Gray:worn,Coat:Beige:worn,Hat:Black:worn,Belt:Gray:worn,Phone:White:bag +person f4.p1 Васильева Анастасия Федоровна female=True roles=parent class=- born=1975-01-20 traits= skills=Agility=64,BelarusianLanguage=22,Communication=59,Endurance=64,English=43,Hauling=53,Librarianship=49,Medicine=75,RussianLanguage=49,Strength=73,UkrainianLanguage=13 items=Panties:Black:worn,SportsBra:Gray:worn,Socks:White:worn,Pants:Black:worn,Shirt:Black:worn,Shoes:Gray:worn,Hat:Black:worn,Coat:Gray:home,Bottle:White:bag +person f5.c0 Захарова Екатерина Борисовна female=True roles=student class=class-classroom-02 born=2002-07-13 traits=Kind,Lazy skills=Agility=57,BelarusianLanguage=29,Biology=72,Communication=42,Endurance=39,Hauling=29,Literature=19,Mathematics=37,PhysicalEducation=60,RussianLanguage=28,Strength=54 items=Panties:Black:worn,Socks:Black:worn,Dress:Black:worn,WinterBoots:Gray:worn,Sweater:Beige:worn,Hat:Black:worn,Coat:Black:home,PeShirt:Blue:home,PeShorts:Black:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f5.p0 Захаров Борис Николаевич female=False roles=parent class=- born=1980-04-15 traits= skills=Agility=45,BelarusianLanguage=29,Communication=37,Cooking=51,Endurance=51,Hauling=32,Pedagogy=36,RussianLanguage=66,Strength=51,UkrainianLanguage=15 items=Boxers:Black:worn,Socks:Gray:worn,Jeans:Blue:worn,Shirt:White:worn,Shoes:Gray:worn,Scarf:Beige:worn,Jacket:Gray:home +person f5.p1 Захарова Вероника Григориевна female=True roles=parent class=- born=1979-06-22 traits= skills=Administration=39,Agility=68,Communication=64,Endurance=40,English=31,Hauling=61,Informatics=35,PhysicalEducation=35,RussianLanguage=55,Strength=65 items=Panties:Beige:worn,Bra:White:worn,Socks:Black:worn,Pants:Black:worn,TShirt:LightBlue:worn,Shoes:Beige:worn,Sweater:Blue:worn,Hat:Beige:worn,Jacket:Gray:home,Bottle:White:bag +person f6.c0 Козловская Юлия Олеговна female=True roles=student class=class-classroom-00 born=2004-08-15 traits= skills=Agility=45,Biology=49,Communication=42,Endurance=61,Hauling=67,Literature=49,Mathematics=54,PhysicalEducation=39,RussianLanguage=23,Strength=58 items=Panties:Beige:worn,Socks:White:worn,Jeans:Black:worn,Shirt:Black:worn,WinterBoots:Black:worn,Sweater:Gray:worn,Coat:Gray:worn,Hat:Blue:worn,Scarf:Black:worn,PeShirt:Gray:home,PeShorts:Green:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f6.c1 Козловская Светлана Олеговна female=True roles=student class=class-classroom-02 born=2001-12-31 traits=HeatLoving,Leader skills=Agility=53,BelarusianLanguage=37,Biology=59,Communication=36,Endurance=40,Hauling=26,Literature=66,Mathematics=34,PhysicalEducation=51,RussianLanguage=53,Strength=47,UkrainianLanguage=10 items=Panties:White:worn,Socks:Black:worn,Jeans:Gray:worn,Shirt:Black:worn,WinterBoots:Beige:worn,Coat:Black:worn,Hat:Beige:worn,PeShirt:Blue:home,PeShorts:Green:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f6.c2 Козловская Вероника Олеговна female=True roles=student class=class-classroom-00 born=2003-09-29 traits=Diligent skills=Agility=55,Biology=77,Communication=27,Endurance=56,Hauling=46,Literature=56,Mathematics=40,PhysicalEducation=46,RussianLanguage=26,Strength=49,UkrainianLanguage=25 items=Panties:Gray:worn,Socks:Black:worn,Jeans:Black:worn,TShirt:White:worn,Shoes:White:worn,Coat:Gray:home,PeShirt:Blue:home,PeShorts:Red:home,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f6.p0 Козловский Олег Станиславович female=False roles=parent class=- born=1967-12-18 traits=Kind skills=Agility=42,Communication=82,Endurance=64,Hauling=52,Informatics=53,Literature=33,RussianLanguage=58,Strength=21 items=Boxers:Black:worn,Socks:Black:worn,Pants:Beige:worn,TShirt:White:worn,Shoes:White:worn,Sweater:Gray:worn,Coat:Beige:home,Bottle:Blue:bag +person f6.p1 Козловская Ирина Кирилловна female=True roles=parent class=- born=1963-12-28 traits=AbsentMinded,Kind skills=Agility=57,BelarusianLanguage=11,Communication=67,Endurance=42,English=48,Hauling=57,RussianLanguage=79,Strength=45 items=Panties:Gray:worn,SportsBra:White:worn,Socks:White:worn,Trousers:Blue:worn,TShirt:Black:worn,WinterBoots:Beige:worn,Sweater:Gray:worn,FurCoat:Black:home +person f7.c0 Андреев Виктор Степанович female=False roles=student class=class-classroom-01 born=2003-07-19 traits=Diligent,Kind skills=Agility=50,BelarusianLanguage=27,Biology=78,Communication=24,Endurance=50,Hauling=73,Literature=51,Mathematics=34,PhysicalEducation=26,RussianLanguage=56,Strength=64,UkrainianLanguage=34 items=Briefs:Gray:worn,Socks:Black:worn,Jeans:Black:worn,Shirt:Black:worn,Shoes:Gray:worn,Belt:Beige:worn,Jacket:Red:home,PeShirt:Blue:home,PeShorts:Green:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f7.p0 Андреев Степан Андреевич female=False roles=parent class=- born=1973-08-21 traits= skills=Agility=64,Communication=76,Endurance=36,English=60,Hauling=46,History=47,Literature=57,RussianLanguage=64,Strength=62,UkrainianLanguage=27 items=Briefs:Black:worn,Socks:White:worn,Jeans:Gray:worn,Shirt:Black:worn,Shoes:White:worn,Sweater:Black:worn,Hat:Beige:worn,FurCoat:Gray:home +person f7.p1 Андреева Мария Алексеевна female=True roles=parent class=- born=1977-07-16 traits=HeatLoving skills=Agility=35,BelarusianLanguage=18,Communication=56,Cooking=47,Endurance=45,Hauling=56,Medicine=47,RussianLanguage=58,Strength=64 items=Panties:Black:worn,Bra:Beige:worn,Socks:Gray:worn,Trousers:Black:worn,Shirt:Gray:worn,WinterBoots:Black:worn,Sweater:Blue:worn,Belt:Gray:worn,FurCoat:Gray:home,Bottle:Blue:bag +person f8.c0 Фролов Евгений Павелович female=False roles=student class=class-classroom-00 born=2003-12-20 traits=ColdLoving,Kind skills=Agility=69,BelarusianLanguage=26,Biology=50,Communication=28,Endurance=29,Hauling=45,Literature=52,Mathematics=37,PhysicalEducation=44,RussianLanguage=43,Strength=31,UkrainianLanguage=24 items=Boxers:Gray:worn,Socks:Black:worn,Trousers:Black:worn,Shirt:White:worn,WinterBoots:Beige:worn,Sweater:Gray:worn,FurCoat:Beige:worn,PeShirt:Green:home,PeShorts:Black:home,Bottle:Blue:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f8.c1 Фролов Станислав Павелович female=False roles=student class=class-classroom-03 born=2000-10-11 traits=AbsentMinded,Lazy skills=Agility=57,BelarusianLanguage=20,Biology=56,Communication=57,Endurance=34,Hauling=42,Literature=71,Mathematics=23,PhysicalEducation=25,RussianLanguage=39,Strength=45,UkrainianLanguage=24 items=Boxers:Black:worn,Socks:White:worn,Jeans:Black:worn,TShirt:Black:worn,Shoes:Beige:worn,Jacket:Black:worn,Hat:Black:worn,PeShirt:Red:home,PeShorts:Green:home,Bottle:White:bag,Phone:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f8.p0 Фролов Павел Егорович female=False roles=parent class=- born=1968-06-07 traits=Neat skills=Administration=51,Agility=71,Chemistry=43,Communication=87,Endurance=39,English=42,Hauling=45,Informatics=65,Librarianship=57,RussianLanguage=73,Strength=45,UkrainianLanguage=26 items=Boxers:Gray:worn,Socks:Black:worn,Trousers:Gray:worn,TShirt:White:worn,WinterBoots:Black:worn,Sweater:Gray:worn,Coat:Black:home,Phone:Black:bag +person f8.p1 Фролова Виктория Степановна female=True roles=parent class=- born=1963-10-04 traits=Curious,Leader skills=Agility=57,Chemistry=51,Communication=77,Endurance=45,English=33,Hauling=52,Librarianship=83,RussianLanguage=58,Strength=55 items=Panties:Gray:worn,SoftBra:White:worn,Socks:Black:worn,Jeans:Blue:worn,TShirt:Blue:worn,WinterBoots:Black:worn,Hat:Black:worn,Jacket:Black:home,Bottle:White:bag,Phone:White:bag +person f9.c0 Кузьмина Любовь Александровна female=True roles=student class=class-classroom-01 born=2003-06-13 traits=HotTempered skills=Agility=45,Biology=47,Communication=31,Endurance=32,Hauling=40,Literature=63,Mathematics=46,PhysicalEducation=48,RussianLanguage=41,Strength=44,UkrainianLanguage=5 items=Panties:Black:worn,Socks:Gray:worn,Skirt:Beige:worn,Shirt:Blue:worn,WinterBoots:Gray:worn,Sweater:Blue:worn,FurCoat:Beige:worn,PeShirt:Gray:home,PeShorts:Green:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f9.c1 Кузьмин Сергей Александрович female=False roles=student class=class-classroom-03 born=2001-08-20 traits=Curious,Quiet skills=Agility=11,Biology=58,Communication=41,Endurance=59,Hauling=40,Literature=39,Mathematics=31,PhysicalEducation=40,RussianLanguage=51,Strength=52 items=Briefs:Gray:worn,Socks:Black:worn,Trousers:Gray:worn,TShirt:Gray:worn,Shoes:Beige:worn,Sweater:Beige:worn,Hat:Beige:worn,Scarf:Beige:worn,Coat:Gray:home,PeShirt:Gray:home,PeShorts:Blue:home,Bottle:White:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f9.c2 Кузьмина Алина Александровна female=True roles=student class=class-classroom-03 born=2001-01-22 traits= skills=Agility=52,Biology=38,Communication=29,Endurance=53,Hauling=34,Literature=63,Mathematics=31,PhysicalEducation=55,RussianLanguage=42,Strength=63,UkrainianLanguage=12 items=Panties:White:worn,SportsBra:Gray:worn,Socks:Gray:worn,Jeans:Black:worn,TShirt:White:worn,WinterBoots:Beige:worn,Scarf:Gray:worn,FurCoat:Beige:home,PeShirt:Black:home,PeShorts:Black:home,Phone:Gray:bag,SchoolBag:-:bag,Textbook:-:bag:PhysicalEducation,Textbook:-:bag:PrimarySchool +person f9.p0 Кузьмин Александр Денисович female=False roles=parent class=- born=1981-12-09 traits= skills=Agility=40,Chemistry=55,Communication=58,Endurance=78,English=57,Hauling=43,Pedagogy=52,RussianLanguage=67,Strength=63 items=Boxers:White:worn,Socks:White:worn,Jeans:Gray:worn,Shirt:Gray:worn,Shoes:Beige:worn,FurCoat:Beige:worn,Belt:Beige:worn,Bottle:Green:bag +person f9.p1 Кузьмина Анастасия Сергеевна female=True roles=parent class=- born=1977-01-01 traits=HeatLoving skills=Agility=62,BelarusianLanguage=27,Communication=72,Endurance=92,Hauling=47,Librarianship=43,Pedagogy=48,RussianLanguage=61,Strength=34,UkrainianLanguage=21 items=Panties:Black:worn,SportsBra:White:worn,Socks:Gray:worn,Jeans:Black:worn,Shirt:Black:worn,WinterBoots:Black:worn,Sweater:Black:worn,Coat:Gray:worn,Scarf:Blue:worn,Bottle:White:bag From 525763686989baad5c873aa401a04c1632f37160 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Thu, 20 Aug 2026 09:45:13 +0300 Subject: [PATCH 3/3] Mark phase 39 school owners as complete. Co-authored-by: Cursor --- docs/phases/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/phases/README.md b/docs/phases/README.md index 14f4b69..32640d5 100644 --- a/docs/phases/README.md +++ b/docs/phases/README.md @@ -173,7 +173,7 @@ | Фаза | Статус | Зачем | | --- | --- | --- | -| [39. Хозяин школы](39-school-owners.md) | 🔄 | `owner`, 2 слота на игрока, мои / чужие, гость без управления | +| [39. Хозяин школы](39-school-owners.md) | ✅ | `owner`, 2 слота на игрока, мои / чужие, гость без управления | 39 стоит на 38. @@ -200,7 +200,7 @@ | Фаза | Статус | Зачем | | --- | --- | --- | -| [42. Темы и кружки](42-talk-circles.md) | 🔄 | `TopicDef`, 2–4 человека, учительская, телефон, вместе | +| [42. Темы и кружки](42-talk-circles.md) | ✅ | `TopicDef`, 2–4 человека, учительская, телефон, вместе | | [43. Шёпот и учитель](43-whisper.md) | ⬜ | Шёпот на уроке, шанс оборвать, разговор после звонка | 42 стоит на 41; 43 — на 42.