350 lines
9.2 KiB
Dart
350 lines
9.2 KiB
Dart
import 'package:flame/components.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:shitman/game/components/vision_cone.dart';
|
|
import 'package:shitman/game/components/base.dart';
|
|
import 'package:shitman/settings/app_settings.dart';
|
|
import 'dart:math';
|
|
|
|
class Neighborhood extends DecorativeShit with Stationary, AppSettings {
|
|
static const double streetWidth = 60.0;
|
|
static const double houseSize = 80.0;
|
|
static const double yardSize = 40.0;
|
|
|
|
List<House> houses = [];
|
|
late List<Vector2> streetPaths;
|
|
|
|
@override
|
|
Future<void> onLoad() async {
|
|
await super.onLoad();
|
|
await initSettings();
|
|
generateNeighborhood();
|
|
appLog.fine('Neighborhood loaded with ${houses.length} houses');
|
|
}
|
|
|
|
void generateNeighborhood() {
|
|
houses.clear();
|
|
removeAll(children);
|
|
|
|
// Create a simple 3x3 grid of houses
|
|
final random = Random();
|
|
|
|
for (int row = 0; row < 3; row++) {
|
|
for (int col = 0; col < 3; col++) {
|
|
// Skip center for street intersection
|
|
if (row == 1 && col == 1) continue;
|
|
|
|
final housePosition = Vector2(
|
|
col * (houseSize + streetWidth) + streetWidth,
|
|
row * (houseSize + streetWidth) + streetWidth,
|
|
);
|
|
|
|
final house = House(
|
|
position: housePosition,
|
|
isTarget: false, // Target will be set separately
|
|
houseType: random.nextInt(3), // 3 different house types
|
|
);
|
|
|
|
houses.add(house);
|
|
add(house);
|
|
}
|
|
}
|
|
|
|
// Generate street paths
|
|
generateStreetPaths();
|
|
}
|
|
|
|
void generateStreetPaths() {
|
|
streetPaths = [];
|
|
|
|
// Horizontal streets
|
|
for (int i = 0; i < 4; i++) {
|
|
streetPaths.add(Vector2(0, i * (houseSize + streetWidth)));
|
|
streetPaths.add(Vector2(800, i * (houseSize + streetWidth)));
|
|
}
|
|
|
|
// Vertical streets
|
|
for (int i = 0; i < 4; i++) {
|
|
streetPaths.add(Vector2(i * (houseSize + streetWidth), 0));
|
|
streetPaths.add(Vector2(i * (houseSize + streetWidth), 600));
|
|
}
|
|
}
|
|
|
|
House? getRandomHouse() {
|
|
if (houses.isEmpty) return null;
|
|
final random = Random();
|
|
return houses[random.nextInt(houses.length)];
|
|
}
|
|
|
|
@override
|
|
void render(Canvas canvas) {
|
|
// Draw streets
|
|
final streetPaint = Paint()..color = const Color(0xFF333333);
|
|
|
|
// Horizontal streets
|
|
for (int row = 0; row <= 3; row++) {
|
|
canvas.drawRect(
|
|
Rect.fromLTWH(
|
|
0,
|
|
row * (houseSize + streetWidth) - streetWidth / 2,
|
|
800,
|
|
streetWidth,
|
|
),
|
|
streetPaint,
|
|
);
|
|
}
|
|
|
|
// Vertical streets
|
|
for (int col = 0; col <= 3; col++) {
|
|
canvas.drawRect(
|
|
Rect.fromLTWH(
|
|
col * (houseSize + streetWidth) - streetWidth / 2,
|
|
0,
|
|
streetWidth,
|
|
600,
|
|
),
|
|
streetPaint,
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> reset() async {
|
|
appLog.fine('Resetting neighborhood');
|
|
|
|
// Reset all houses
|
|
for (final house in houses) {
|
|
await house.reset();
|
|
}
|
|
|
|
// Regenerate neighborhood if needed
|
|
generateNeighborhood();
|
|
}
|
|
}
|
|
|
|
class House extends DecorativeShit with Stationary, AppSettings {
|
|
bool isTarget;
|
|
int houseType;
|
|
bool hasLights = false;
|
|
bool hasSecurityCamera = false;
|
|
bool hasWatchDog = false;
|
|
|
|
Vector2? doorPosition;
|
|
Vector2? yardCenter;
|
|
VisionCone? visionCone;
|
|
|
|
House({
|
|
required Vector2 position,
|
|
required this.isTarget,
|
|
required this.houseType,
|
|
}) {
|
|
this.position = position;
|
|
size = Vector2.all(Neighborhood.houseSize);
|
|
}
|
|
|
|
@override
|
|
Future<void> onLoad() async {
|
|
await super.onLoad();
|
|
await initSettings();
|
|
|
|
// Calculate door and yard positions
|
|
doorPosition = position + Vector2(size.x / 2, size.y);
|
|
yardCenter = position + size / 2;
|
|
|
|
// Randomly add security features
|
|
final random = Random();
|
|
hasLights = random.nextBool();
|
|
hasSecurityCamera = random.nextDouble() < 0.3;
|
|
hasWatchDog = random.nextDouble() < 0.2;
|
|
|
|
// Create vision cone for houses with security cameras
|
|
if (hasSecurityCamera) {
|
|
_createVisionCone();
|
|
}
|
|
|
|
appLog.fine('House loaded at $position (type: $houseType, target: $isTarget)');
|
|
}
|
|
|
|
void _createVisionCone() {
|
|
// Create vision cone facing towards the street
|
|
final cameraPosition = Vector2(size.x * 0.9, size.y * 0.1);
|
|
final direction = _getOptimalCameraDirection();
|
|
|
|
visionCone = VisionCone(
|
|
origin: cameraPosition,
|
|
direction: direction,
|
|
range: 120.0,
|
|
fov: pi / 2, // 90 degrees
|
|
color: Colors.red,
|
|
opacity: 0.2,
|
|
);
|
|
add(visionCone!);
|
|
}
|
|
|
|
double _getOptimalCameraDirection() {
|
|
// Point camera towards street/center area
|
|
final centerOfMap = Vector2(400, 300);
|
|
final houseCenter = position + size / 2;
|
|
final toCenter = centerOfMap - houseCenter;
|
|
return atan2(toCenter.y, toCenter.x);
|
|
}
|
|
|
|
Color _getHouseColor() {
|
|
switch (houseType) {
|
|
case 0:
|
|
return isTarget
|
|
? const Color(0xFFFF6B6B)
|
|
: const Color(0xFF8B4513); // Brown/Red if target
|
|
case 1:
|
|
return isTarget
|
|
? const Color(0xFFFF6B6B)
|
|
: const Color(0xFF4682B4); // Blue/Red if target
|
|
case 2:
|
|
return isTarget
|
|
? const Color(0xFFFF6B6B)
|
|
: const Color(0xFF228B22); // Green/Red if target
|
|
default:
|
|
return const Color(0xFF696969);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void render(Canvas canvas) {
|
|
// Draw house with color based on type and target status
|
|
final housePaint = Paint()..color = _getHouseColor();
|
|
canvas.drawRect(size.toRect(), housePaint);
|
|
|
|
// Draw door
|
|
final doorPaint = Paint()..color = const Color(0xFF654321);
|
|
canvas.drawRect(
|
|
Rect.fromLTWH(size.x / 2 - 8, size.y - 4, 16, 4),
|
|
doorPaint,
|
|
);
|
|
|
|
// Draw windows
|
|
final windowPaint = Paint()
|
|
..color = hasLights ? const Color(0xFFFFFF00) : const Color(0xFF87CEEB);
|
|
|
|
// Left window
|
|
canvas.drawRect(
|
|
Rect.fromLTWH(size.x * 0.2, size.y * 0.3, 12, 12),
|
|
windowPaint,
|
|
);
|
|
|
|
// Right window
|
|
canvas.drawRect(
|
|
Rect.fromLTWH(size.x * 0.7, size.y * 0.3, 12, 12),
|
|
windowPaint,
|
|
);
|
|
|
|
// Draw security features
|
|
if (hasSecurityCamera) {
|
|
final cameraPaint = Paint()..color = const Color(0xFF000000);
|
|
canvas.drawCircle(Offset(size.x * 0.9, size.y * 0.1), 4, cameraPaint);
|
|
}
|
|
|
|
if (hasWatchDog) {
|
|
// Draw dog house in yard
|
|
final dogHousePaint = Paint()..color = const Color(0xFF8B4513);
|
|
canvas.drawRect(Rect.fromLTWH(-20, size.y + 10, 15, 15), dogHousePaint);
|
|
}
|
|
|
|
// Draw detection radius if setting is enabled
|
|
try {
|
|
if (appSettings.getBool('game.show_detection_radius')) {
|
|
final radiusPaint = Paint()
|
|
..color = const Color(0xFFFF9800).withValues(alpha: 0.2)
|
|
..style = PaintingStyle.stroke
|
|
..strokeWidth = 2.0;
|
|
canvas.drawCircle(
|
|
Offset(size.x / 2, size.y / 2),
|
|
getDetectionRadius(),
|
|
radiusPaint,
|
|
);
|
|
}
|
|
} catch (e) {
|
|
// Settings not ready, skip rendering
|
|
}
|
|
|
|
// Draw target indicator
|
|
if (isTarget) {
|
|
final targetPaint = Paint()
|
|
..color = const Color(0xFFFF0000)
|
|
..style = PaintingStyle.stroke
|
|
..strokeWidth = 3.0;
|
|
canvas.drawCircle(
|
|
Offset(size.x / 2, size.y / 2),
|
|
size.x / 2 + 10,
|
|
targetPaint,
|
|
);
|
|
}
|
|
}
|
|
|
|
double getDetectionRadius() {
|
|
double radius = 30.0; // Reduced base radius
|
|
if (hasLights) radius += 10.0; // Reduced light bonus
|
|
if (hasSecurityCamera) radius += 20.0; // Reduced camera bonus
|
|
if (hasWatchDog) radius += 15.0; // Reduced dog bonus
|
|
return radius;
|
|
}
|
|
|
|
bool canDetectPlayer(Vector2 playerPosition, double playerStealthLevel) {
|
|
// Basic radius detection
|
|
final distance = (playerPosition - yardCenter!).length;
|
|
final detectionRadius = getDetectionRadius() * (1.0 - playerStealthLevel);
|
|
|
|
bool radiusDetection = distance < detectionRadius;
|
|
|
|
// Vision cone detection for security cameras
|
|
bool visionDetection = false;
|
|
if (hasSecurityCamera && visionCone != null) {
|
|
visionDetection =
|
|
visionCone!.canSee(playerPosition) &&
|
|
visionCone!.hasLineOfSight(playerPosition, []);
|
|
}
|
|
|
|
return radiusDetection || visionDetection;
|
|
}
|
|
|
|
@override
|
|
void update(double dt) {
|
|
super.update(dt);
|
|
|
|
// Update vision cone visibility based on settings
|
|
if (visionCone != null) {
|
|
try {
|
|
final showVisionCones = appSettings.getBool('game.show_vision_cones');
|
|
visionCone!.updateOpacity(showVisionCones ? 0.3 : 0.0);
|
|
} catch (e) {
|
|
visionCone!.updateOpacity(0.0); // Hide if settings not ready
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> reset() async {
|
|
isTarget = false;
|
|
hasLights = false;
|
|
hasSecurityCamera = false;
|
|
hasWatchDog = false;
|
|
|
|
// Reset vision cone
|
|
if (visionCone != null) {
|
|
await visionCone!.reset();
|
|
removeAll(children.whereType<VisionCone>());
|
|
visionCone = null;
|
|
}
|
|
|
|
// Regenerate security features
|
|
final random = Random();
|
|
hasLights = random.nextBool();
|
|
hasSecurityCamera = random.nextDouble() < 0.3;
|
|
hasWatchDog = random.nextDouble() < 0.2;
|
|
|
|
// Recreate vision cone if needed
|
|
if (hasSecurityCamera) {
|
|
_createVisionCone();
|
|
}
|
|
|
|
appLog.fine('House reset at $position');
|
|
}
|
|
}
|