309 lines
8.1 KiB
Dart
309 lines
8.1 KiB
Dart
import 'package:flame/components.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:shitman/game/components/vision_cone.dart';
|
|
import 'package:shitman/game/shitman_game.dart';
|
|
import 'dart:math';
|
|
|
|
class Neighborhood extends Component {
|
|
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();
|
|
generateNeighborhood();
|
|
}
|
|
|
|
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) {
|
|
super.render(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,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
class House extends RectangleComponent with HasGameReference<ShitmanGame> {
|
|
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,
|
|
}) : super(position: position, size: Vector2.all(Neighborhood.houseSize));
|
|
|
|
@override
|
|
Future<void> onLoad() async {
|
|
await super.onLoad();
|
|
|
|
// Set house color based on type
|
|
paint = Paint()..color = _getHouseColor();
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
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) {
|
|
super.render(canvas);
|
|
|
|
// 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 (game.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 = game.appSettings.getBool(
|
|
'game.show_vision_cones',
|
|
);
|
|
visionCone!.updateOpacity(showVisionCones ? 0.3 : 0.0);
|
|
} catch (e) {
|
|
visionCone!.updateOpacity(0.0); // Hide if settings not ready
|
|
}
|
|
}
|
|
}
|
|
}
|