'city10'], [ 'name' => 'City10', // Performance-tuned size: 72 blocks (9 × 8) after the // 40-unit road grid and its outer margin are derived from // these bounds (layout −160..200 × −160..160). 'bounds_min_x' => -120, 'bounds_max_x' => 160, 'bounds_min_z' => -120, 'bounds_max_z' => 120, 'is_active' => true, 'expansions' => 0, ], ); // --- Control tower agent (airport clearances) --------------------- // Positioned at the north airport tower; model comes from // AI_ATC_MODEL (poolside/laguna-xs-2.1:free by default) and is // re-validated against the allow-list before use. $tower = Airports::geometry($city)['sites'][0]['tower']['center']; $atcModel = (string) config('ai.atc_model'); if (! in_array($atcModel, config('ai.allowed_models', []), true)) { $atcModel = 'poolside/laguna-xs-2.1:free'; } AiAgent::updateOrCreate( ['name' => 'Torre de Controle City10'], [ 'type' => 'atc', 'model' => $atcModel, 'system_prompt' => 'Você é a torre de controle aéreo do Aeroporto City10 Norte. ' .'Autorize decolagens e pousos com linguagem formal, curta e clássica de controle ' .'de tráfego aéreo, sempre em português, citando o voo, a companhia e a pista.', 'current_goal' => 'Gerenciar decolagens, pousos e o tráfego dos dois aeroportos com segurança', 'status' => 'active', 'pos_x' => (float) $tower[0], 'pos_y' => 0, 'pos_z' => (float) $tower[1], ], ); $bakery = Store::where('type', 'bakery')->orderBy('id')->first(); $market = Store::where('type', 'market')->orderBy('id')->first(); Job::updateOrCreate( ['city_id' => $city->id, 'title' => 'Padeiro'], ['store_id' => $bakery?->id, 'salary' => 12, 'requires_education' => 0], ); Job::updateOrCreate( ['city_id' => $city->id, 'title' => 'Operador de mercado'], ['store_id' => $market?->id, 'salary' => 10, 'requires_education' => 0], ); Job::updateOrCreate( ['city_id' => $city->id, 'title' => 'Servidor público'], ['salary' => 15, 'requires_education' => 30], ); Job::updateOrCreate( ['city_id' => $city->id, 'title' => 'Professor'], ['salary' => 14, 'requires_education' => 60], ); // City hospital: accident victims are admitted here for one game hour. Building::updateOrCreate( [ 'city_id' => $city->id, 'type' => Building::TYPE_HOSPITAL, ], [ 'owner_character_id' => null, 'name' => 'Hospital Municipal', 'pos_x' => 60, 'pos_z' => -60, 'width' => 14, 'depth' => 12, 'height' => 8, 'is_complete' => true, 'build_progress' => 100, ], ); // Civic buildings: school and city hall. Building::updateOrCreate( ['city_id' => $city->id, 'type' => 'school'], [ 'owner_character_id' => null, 'name' => 'Escola Municipal', 'pos_x' => -20, 'pos_z' => 60, 'width' => 16, 'depth' => 12, 'height' => 7, 'is_complete' => true, 'build_progress' => 100, ], ); Building::updateOrCreate( ['city_id' => $city->id, 'type' => 'city_hall'], [ 'owner_character_id' => null, 'name' => 'Prefeitura', 'pos_x' => 20, 'pos_z' => 60, 'width' => 18, 'depth' => 14, 'height' => 10, 'is_complete' => true, 'build_progress' => 100, ], ); // More civic landmarks (rendered with luminous letreiros). Building::updateOrCreate( ['city_id' => $city->id, 'type' => 'university'], [ 'owner_character_id' => null, 'name' => 'Universidade City10', 'pos_x' => 100, 'pos_z' => 140, 'width' => 26, 'depth' => 18, 'height' => 12, 'is_complete' => true, 'build_progress' => 100, ], ); Building::updateOrCreate( ['city_id' => $city->id, 'type' => 'fire_station'], [ 'owner_character_id' => null, 'name' => 'Corpo de Bombeiros', 'pos_x' => -100, 'pos_z' => -20, 'width' => 20, 'depth' => 14, 'height' => 9, 'is_complete' => true, 'build_progress' => 100, ], ); // ---- City places (all get facade + front board + blade letreiro) // Each sits on its own free block zone (not on a plaza row), so // the plaza pattern and the AABB audit both stay clean. $places = [ ['Supermercado Bom Preço', 'supermarket', 100, -20, 22, 9, 16, 'Grande sortimento, hortifrúti e padaria interna.'], ['Drogaria Saúde Total', 'pharmacy', 60, 20, 14, 7, 12, 'Remédios, cosméticos e atendimento 24 horas.'], ['Restaurante Sabor da City', 'restaurant', 100, 100, 16, 8, 14, 'Comida caseira, churrasco e culinária regional.'], ['Cabeleireiro Estilo', 'salon', -60, 20, 12, 6, 10, 'Cortes, colorações e escovas com hora marcada.'], ['Banco Central City10', 'bank', 140, 20, 16, 10, 14, 'Conta corrente, crédito e cooperativa da cidade.'], ['Hotel City10 Palace', 'hotel', 60, 100, 20, 14, 16, 'Suítes com vista para a praça e Wi-Fi gratuito.'], ['Shopping City10', 'mall', -60, 100, 26, 16, 22, 'Lojas, praça de alimentação e cinema.'], ]; foreach ($places as [$name, $type, $px, $pz, $width, $height, $depth, $description]) { Store::updateOrCreate( ['name' => $name], [ 'description' => $description, 'type' => $type, 'pos_x' => $px, 'pos_y' => 0, 'pos_z' => $pz, 'width' => $width, 'height' => $height, 'depth' => $depth, 'is_open' => true, ], ); } Mission::updateOrCreate( ['code' => 'exercise-5'], [ 'city_id' => $city->id, 'title' => 'Shape em dia', 'description' => 'Complete 5 séries de ginástica nas praças.', 'metric' => 'exercise_count', 'target' => 5, 'reward' => 25, 'is_active' => true, ], ); Mission::updateOrCreate( ['code' => 'build-home'], [ 'city_id' => $city->id, 'title' => 'Lar doce lar', 'description' => 'Construa a sua primeira casa.', 'metric' => 'buildings', 'target' => 1, 'reward' => 50, 'is_active' => true, ], ); Mission::updateOrCreate( ['code' => 'learn-20'], [ 'city_id' => $city->id, 'title' => 'Estudioso', 'description' => 'Alcance 20% de educação.', 'metric' => 'education', 'target' => 20, 'reward' => 30, 'is_active' => true, ], ); User::query()->where('is_active', true)->where('status', '!=', User::STATUS_ACTIVE) ->update(['status' => User::STATUS_ACTIVE]); User::query()->where('is_active', false)->where('status', User::STATUS_PENDING) ->update(['status' => User::STATUS_BANNED]); User::query()->where('status', User::STATUS_ACTIVE)->each(function (User $user) use ($city): void { Character::ensureForUser($user, $city); }); // Backfill fitness columns for characters created before the migration. Character::query()->whereNull('health')->update(['health' => 100]); Character::query()->whereNull('exercise_count')->update(['exercise_count' => 0]); } }