updated level...player can now "complete" (no ui)
All checks were successful
/ build-web (push) Successful in 4m5s
All checks were successful
/ build-web (push) Successful in 4m5s
This commit is contained in:
parent
2f13bb91f7
commit
67aaa9589f
20 changed files with 1914 additions and 199 deletions
8
lib/attributes/resetable.dart
Normal file
8
lib/attributes/resetable.dart
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import "dart:async";
|
||||||
|
|
||||||
|
import "package:meta/meta.dart";
|
||||||
|
|
||||||
|
abstract mixin class Resetable {
|
||||||
|
@mustBeOverridden
|
||||||
|
FutureOr<void> reset();
|
||||||
|
}
|
74
lib/game/components/base.dart
Normal file
74
lib/game/components/base.dart
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:shitman/attributes/resetable.dart';
|
||||||
|
import 'package:shitman/game/shitman_game.dart';
|
||||||
|
import 'package:shitman/services/log_service.dart';
|
||||||
|
|
||||||
|
/// Base class for all components in the Shitman game.
|
||||||
|
/// This class can be extended to create specific game components.
|
||||||
|
abstract class ShitComponent extends PositionComponent
|
||||||
|
with Resetable, HasGameReference<ShitmanGame>, AppLogging {
|
||||||
|
bool get isStationary => false;
|
||||||
|
|
||||||
|
ShitComponent({
|
||||||
|
super.position,
|
||||||
|
super.size,
|
||||||
|
super.scale,
|
||||||
|
super.angle,
|
||||||
|
super.nativeAngle = 0,
|
||||||
|
super.anchor,
|
||||||
|
super.children,
|
||||||
|
super.priority,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
// Additional initialization logic can go here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class DecorativeShit extends ShitComponent {}
|
||||||
|
|
||||||
|
abstract class InteractiveShit extends ShitComponent {}
|
||||||
|
|
||||||
|
mixin Stationary on ShitComponent {
|
||||||
|
/// Whether the item is stationary
|
||||||
|
@override
|
||||||
|
final bool isStationary = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin Ambulatory on ShitComponent {
|
||||||
|
/// Whether the item is stationary
|
||||||
|
@override
|
||||||
|
final bool isStationary = false;
|
||||||
|
|
||||||
|
/// Method to handle ambulatory behavior
|
||||||
|
void handleAmbulatory() {
|
||||||
|
// Logic for ambulatory items, e.g., moving around
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin Collectible on InteractiveShit {
|
||||||
|
bool _isCollected = false;
|
||||||
|
bool _isCollectible = true;
|
||||||
|
|
||||||
|
/// Whether the item is collected
|
||||||
|
bool get isCollected => _isCollected;
|
||||||
|
|
||||||
|
/// Whether the item can be collected
|
||||||
|
bool get isCollectible => _isCollectible;
|
||||||
|
|
||||||
|
/// Set the item as collectible
|
||||||
|
void setCollectible(bool value) {
|
||||||
|
_isCollectible = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Method to collect the item
|
||||||
|
@mustCallSuper
|
||||||
|
void collect() {
|
||||||
|
_isCollected = true;
|
||||||
|
// Additional logic for collecting the item
|
||||||
|
}
|
||||||
|
}
|
149
lib/game/components/house_components.dart
Normal file
149
lib/game/components/house_components.dart
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:shitman/game/components/level_components.dart';
|
||||||
|
import 'package:shitman/attributes/resetable.dart';
|
||||||
|
|
||||||
|
/// Base house component
|
||||||
|
class HouseComponent extends StructureComponent {
|
||||||
|
static const double houseSize = 80.0;
|
||||||
|
|
||||||
|
HouseType houseType;
|
||||||
|
Vector2? doorPosition;
|
||||||
|
Vector2? yardCenter;
|
||||||
|
|
||||||
|
List<Component> securitySystems = [];
|
||||||
|
|
||||||
|
HouseComponent({
|
||||||
|
required super.gridPosition,
|
||||||
|
required this.houseType,
|
||||||
|
}) {
|
||||||
|
size = Vector2.all(houseSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
|
||||||
|
// Calculate door and yard positions
|
||||||
|
doorPosition = position + Vector2(size.x / 2, size.y);
|
||||||
|
yardCenter = position + size / 2;
|
||||||
|
|
||||||
|
appLog.fine('House loaded at $gridPosition (type: $houseType)');
|
||||||
|
}
|
||||||
|
|
||||||
|
Color _getHouseColor() {
|
||||||
|
switch (houseType) {
|
||||||
|
case HouseType.suburban:
|
||||||
|
return const Color(0xFF8B4513); // Brown
|
||||||
|
case HouseType.modern:
|
||||||
|
return const Color(0xFF4682B4); // Blue
|
||||||
|
case HouseType.cottage:
|
||||||
|
return const Color(0xFF228B22); // Green
|
||||||
|
case HouseType.apartment:
|
||||||
|
return const Color(0xFF696969); // Gray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
// Draw house with color based on type
|
||||||
|
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 = 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a security system to this house
|
||||||
|
void addSecuritySystem(Component security) {
|
||||||
|
securitySystems.add(security);
|
||||||
|
add(security);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove all security systems
|
||||||
|
void clearSecuritySystems() {
|
||||||
|
for (final security in securitySystems) {
|
||||||
|
remove(security);
|
||||||
|
}
|
||||||
|
securitySystems.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if player is detected by any security system
|
||||||
|
bool detectsPlayer(Vector2 playerPosition, double playerStealthLevel) {
|
||||||
|
// Basic detection based on distance to house center
|
||||||
|
if (yardCenter == null) return false;
|
||||||
|
final distance = (playerPosition - yardCenter!).length;
|
||||||
|
final detectionRadius = 40.0 * (1.0 - playerStealthLevel);
|
||||||
|
return distance < detectionRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> reset() async {
|
||||||
|
// Reset all security systems
|
||||||
|
for (final security in securitySystems) {
|
||||||
|
if (security is Resetable) {
|
||||||
|
await (security as Resetable).reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
appLog.fine('House reset at $gridPosition');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Target house variant with special highlighting
|
||||||
|
class TargetHouseComponent extends HouseComponent {
|
||||||
|
bool isActiveTarget = false;
|
||||||
|
|
||||||
|
TargetHouseComponent({
|
||||||
|
required super.gridPosition,
|
||||||
|
required super.houseType,
|
||||||
|
});
|
||||||
|
|
||||||
|
void setAsTarget(bool isTarget) {
|
||||||
|
isActiveTarget = isTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
super.render(canvas);
|
||||||
|
|
||||||
|
// Draw target indicator if this is the active target
|
||||||
|
if (isActiveTarget) {
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> reset() async {
|
||||||
|
await super.reset();
|
||||||
|
isActiveTarget = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum HouseType { suburban, modern, cottage, apartment }
|
77
lib/game/components/level_components.dart
Normal file
77
lib/game/components/level_components.dart
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:shitman/game/components/base.dart';
|
||||||
|
|
||||||
|
/// Base class for all level building block components
|
||||||
|
abstract class LevelComponent extends DecorativeShit with Stationary {
|
||||||
|
Vector2 gridPosition;
|
||||||
|
|
||||||
|
LevelComponent({required this.gridPosition}) {
|
||||||
|
size = Vector2.all(80.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert grid position to world position
|
||||||
|
Vector2 getWorldPosition(double cellSize) {
|
||||||
|
return Vector2(gridPosition.x * cellSize, gridPosition.y * cellSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Base class for road components
|
||||||
|
abstract class RoadComponent extends LevelComponent {
|
||||||
|
static const double roadWidth = 60.0;
|
||||||
|
|
||||||
|
RoadComponent({required super.gridPosition});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
final roadPaint = Paint()..color = const Color(0xFF333333);
|
||||||
|
canvas.drawRect(size.toRect(), roadPaint);
|
||||||
|
|
||||||
|
// Draw road markings
|
||||||
|
final markingPaint = Paint()
|
||||||
|
..color = const Color(0xFFFFFFFF)
|
||||||
|
..strokeWidth = 2.0;
|
||||||
|
|
||||||
|
renderRoadMarkings(canvas, markingPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override in subclasses to render specific road markings
|
||||||
|
void renderRoadMarkings(Canvas canvas, Paint paint);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> reset() async {
|
||||||
|
// Default implementation for road components
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Base class for structure components (houses, etc.)
|
||||||
|
abstract class StructureComponent extends LevelComponent {
|
||||||
|
StructureComponent({required super.gridPosition});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> reset() async {
|
||||||
|
// Default implementation for structure components
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Base class for security components
|
||||||
|
abstract class SecurityComponent extends InteractiveShit {
|
||||||
|
double detectionRange;
|
||||||
|
bool isActive;
|
||||||
|
|
||||||
|
SecurityComponent({
|
||||||
|
required Vector2 position,
|
||||||
|
required this.detectionRange,
|
||||||
|
this.isActive = true,
|
||||||
|
}) {
|
||||||
|
this.position = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if player is detected by this security component
|
||||||
|
bool detectsPlayer(Vector2 playerPosition, double playerStealthLevel);
|
||||||
|
|
||||||
|
/// Get the detection radius considering stealth
|
||||||
|
double getEffectiveDetectionRange(double playerStealthLevel) {
|
||||||
|
return detectionRange * (1.0 - playerStealthLevel);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,11 @@
|
||||||
import 'package:flame/components.dart';
|
import 'package:flame/components.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:shitman/game/components/vision_cone.dart';
|
import 'package:shitman/game/components/vision_cone.dart';
|
||||||
import 'package:shitman/game/shitman_game.dart';
|
import 'package:shitman/game/components/base.dart';
|
||||||
|
import 'package:shitman/settings/app_settings.dart';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
class Neighborhood extends Component {
|
class Neighborhood extends DecorativeShit with Stationary, AppSettings {
|
||||||
static const double streetWidth = 60.0;
|
static const double streetWidth = 60.0;
|
||||||
static const double houseSize = 80.0;
|
static const double houseSize = 80.0;
|
||||||
static const double yardSize = 40.0;
|
static const double yardSize = 40.0;
|
||||||
|
@ -15,7 +16,9 @@ class Neighborhood extends Component {
|
||||||
@override
|
@override
|
||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
await super.onLoad();
|
await super.onLoad();
|
||||||
|
await initSettings();
|
||||||
generateNeighborhood();
|
generateNeighborhood();
|
||||||
|
appLog.fine('Neighborhood loaded with ${houses.length} houses');
|
||||||
}
|
}
|
||||||
|
|
||||||
void generateNeighborhood() {
|
void generateNeighborhood() {
|
||||||
|
@ -74,8 +77,6 @@ class Neighborhood extends Component {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void render(Canvas canvas) {
|
void render(Canvas canvas) {
|
||||||
super.render(canvas);
|
|
||||||
|
|
||||||
// Draw streets
|
// Draw streets
|
||||||
final streetPaint = Paint()..color = const Color(0xFF333333);
|
final streetPaint = Paint()..color = const Color(0xFF333333);
|
||||||
|
|
||||||
|
@ -105,9 +106,22 @@ class Neighborhood extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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 RectangleComponent with HasGameReference<ShitmanGame> {
|
class House extends DecorativeShit with Stationary, AppSettings {
|
||||||
bool isTarget;
|
bool isTarget;
|
||||||
int houseType;
|
int houseType;
|
||||||
bool hasLights = false;
|
bool hasLights = false;
|
||||||
|
@ -122,14 +136,15 @@ class House extends RectangleComponent with HasGameReference<ShitmanGame> {
|
||||||
required Vector2 position,
|
required Vector2 position,
|
||||||
required this.isTarget,
|
required this.isTarget,
|
||||||
required this.houseType,
|
required this.houseType,
|
||||||
}) : super(position: position, size: Vector2.all(Neighborhood.houseSize));
|
}) {
|
||||||
|
this.position = position;
|
||||||
|
size = Vector2.all(Neighborhood.houseSize);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
await super.onLoad();
|
await super.onLoad();
|
||||||
|
await initSettings();
|
||||||
// Set house color based on type
|
|
||||||
paint = Paint()..color = _getHouseColor();
|
|
||||||
|
|
||||||
// Calculate door and yard positions
|
// Calculate door and yard positions
|
||||||
doorPosition = position + Vector2(size.x / 2, size.y);
|
doorPosition = position + Vector2(size.x / 2, size.y);
|
||||||
|
@ -145,6 +160,8 @@ class House extends RectangleComponent with HasGameReference<ShitmanGame> {
|
||||||
if (hasSecurityCamera) {
|
if (hasSecurityCamera) {
|
||||||
_createVisionCone();
|
_createVisionCone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appLog.fine('House loaded at $position (type: $houseType, target: $isTarget)');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _createVisionCone() {
|
void _createVisionCone() {
|
||||||
|
@ -192,7 +209,9 @@ class House extends RectangleComponent with HasGameReference<ShitmanGame> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void render(Canvas canvas) {
|
void render(Canvas canvas) {
|
||||||
super.render(canvas);
|
// Draw house with color based on type and target status
|
||||||
|
final housePaint = Paint()..color = _getHouseColor();
|
||||||
|
canvas.drawRect(size.toRect(), housePaint);
|
||||||
|
|
||||||
// Draw door
|
// Draw door
|
||||||
final doorPaint = Paint()..color = const Color(0xFF654321);
|
final doorPaint = Paint()..color = const Color(0xFF654321);
|
||||||
|
@ -202,10 +221,8 @@ class House extends RectangleComponent with HasGameReference<ShitmanGame> {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Draw windows
|
// Draw windows
|
||||||
final windowPaint =
|
final windowPaint = Paint()
|
||||||
Paint()
|
..color = hasLights ? const Color(0xFFFFFF00) : const Color(0xFF87CEEB);
|
||||||
..color =
|
|
||||||
hasLights ? const Color(0xFFFFFF00) : const Color(0xFF87CEEB);
|
|
||||||
|
|
||||||
// Left window
|
// Left window
|
||||||
canvas.drawRect(
|
canvas.drawRect(
|
||||||
|
@ -233,12 +250,11 @@ class House extends RectangleComponent with HasGameReference<ShitmanGame> {
|
||||||
|
|
||||||
// Draw detection radius if setting is enabled
|
// Draw detection radius if setting is enabled
|
||||||
try {
|
try {
|
||||||
if (game.appSettings.getBool('game.show_detection_radius')) {
|
if (appSettings.getBool('game.show_detection_radius')) {
|
||||||
final radiusPaint =
|
final radiusPaint = Paint()
|
||||||
Paint()
|
..color = const Color(0xFFFF9800).withValues(alpha: 0.2)
|
||||||
..color = const Color(0xFFFF9800).withValues(alpha: 0.2)
|
..style = PaintingStyle.stroke
|
||||||
..style = PaintingStyle.stroke
|
..strokeWidth = 2.0;
|
||||||
..strokeWidth = 2.0;
|
|
||||||
canvas.drawCircle(
|
canvas.drawCircle(
|
||||||
Offset(size.x / 2, size.y / 2),
|
Offset(size.x / 2, size.y / 2),
|
||||||
getDetectionRadius(),
|
getDetectionRadius(),
|
||||||
|
@ -251,11 +267,10 @@ class House extends RectangleComponent with HasGameReference<ShitmanGame> {
|
||||||
|
|
||||||
// Draw target indicator
|
// Draw target indicator
|
||||||
if (isTarget) {
|
if (isTarget) {
|
||||||
final targetPaint =
|
final targetPaint = Paint()
|
||||||
Paint()
|
..color = const Color(0xFFFF0000)
|
||||||
..color = const Color(0xFFFF0000)
|
..style = PaintingStyle.stroke
|
||||||
..style = PaintingStyle.stroke
|
..strokeWidth = 3.0;
|
||||||
..strokeWidth = 3.0;
|
|
||||||
canvas.drawCircle(
|
canvas.drawCircle(
|
||||||
Offset(size.x / 2, size.y / 2),
|
Offset(size.x / 2, size.y / 2),
|
||||||
size.x / 2 + 10,
|
size.x / 2 + 10,
|
||||||
|
@ -297,13 +312,39 @@ class House extends RectangleComponent with HasGameReference<ShitmanGame> {
|
||||||
// Update vision cone visibility based on settings
|
// Update vision cone visibility based on settings
|
||||||
if (visionCone != null) {
|
if (visionCone != null) {
|
||||||
try {
|
try {
|
||||||
final showVisionCones = game.appSettings.getBool(
|
final showVisionCones = appSettings.getBool('game.show_vision_cones');
|
||||||
'game.show_vision_cones',
|
|
||||||
);
|
|
||||||
visionCone!.updateOpacity(showVisionCones ? 0.3 : 0.0);
|
visionCone!.updateOpacity(showVisionCones ? 0.3 : 0.0);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
visionCone!.updateOpacity(0.0); // Hide if settings not ready
|
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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,33 +3,40 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:shitman/game/shitman_game.dart';
|
import 'package:shitman/game/shitman_game.dart';
|
||||||
import 'package:shitman/game/components/poop_bag.dart';
|
import 'package:shitman/game/components/poop_bag.dart';
|
||||||
import 'package:shitman/game/components/neighborhood.dart';
|
import 'package:shitman/game/components/house_components.dart';
|
||||||
import 'package:shitman/game/components/vision_cone.dart';
|
import 'package:shitman/game/components/vision_cone.dart';
|
||||||
|
import 'package:shitman/game/components/base.dart';
|
||||||
|
import 'package:shitman/game/levels/operation_shitstorm.dart';
|
||||||
|
import 'package:shitman/settings/app_settings.dart';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
class Player extends RectangleComponent with HasGameReference<ShitmanGame> {
|
class Player extends InteractiveShit with Ambulatory, AppSettings {
|
||||||
static const double speed = 100.0;
|
static const double speed = 100.0;
|
||||||
static const double playerSize = 32.0;
|
static const double playerSize = 32.0;
|
||||||
|
|
||||||
Vector2 velocity = Vector2.zero();
|
Vector2 velocity = Vector2.zero();
|
||||||
bool hasPoopBag = true;
|
bool hasPoopBag = true;
|
||||||
bool isHidden = false;
|
bool isHidden = false;
|
||||||
|
bool isCaught = false;
|
||||||
double stealthLevel = 0.0; // 0.0 = fully visible, 1.0 = completely hidden
|
double stealthLevel = 0.0; // 0.0 = fully visible, 1.0 = completely hidden
|
||||||
PoopBag? placedPoopBag;
|
PoopBag? placedPoopBag;
|
||||||
VisionCone? playerVisionCone;
|
VisionCone? playerVisionCone;
|
||||||
double lastMovementDirection = 0.0;
|
double lastMovementDirection = 0.0;
|
||||||
|
|
||||||
|
// Mission state
|
||||||
|
bool isEscaping = false;
|
||||||
|
double escapeTimeLimit = 30.0; // 30 seconds to escape
|
||||||
|
double escapeTimeRemaining = 0.0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
await super.onLoad();
|
await super.onLoad();
|
||||||
|
await initSettings();
|
||||||
|
|
||||||
// Create a simple colored rectangle as player
|
// Create a simple colored rectangle as player
|
||||||
size = Vector2.all(playerSize);
|
size = Vector2.all(playerSize);
|
||||||
position = Vector2(200, 200); // Start at center intersection
|
position = Vector2(200, 200); // Start at center intersection
|
||||||
|
|
||||||
// Set player color
|
|
||||||
paint = Paint()..color = const Color(0xFF0000FF); // Blue player
|
|
||||||
|
|
||||||
// Create player vision cone
|
// Create player vision cone
|
||||||
playerVisionCone = VisionCone(
|
playerVisionCone = VisionCone(
|
||||||
origin: size / 2, // Relative to player center
|
origin: size / 2, // Relative to player center
|
||||||
|
@ -40,9 +47,17 @@ class Player extends RectangleComponent with HasGameReference<ShitmanGame> {
|
||||||
opacity: 0.0, // Start hidden
|
opacity: 0.0, // Start hidden
|
||||||
);
|
);
|
||||||
add(playerVisionCone!);
|
add(playerVisionCone!);
|
||||||
|
|
||||||
|
appLog.fine('Player loaded at position $position');
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleInput(Set<LogicalKeyboardKey> keysPressed) {
|
void handleInput(Set<LogicalKeyboardKey> keysPressed) {
|
||||||
|
// Don't handle input if caught
|
||||||
|
if (isCaught) {
|
||||||
|
velocity = Vector2.zero();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
velocity = Vector2.zero();
|
velocity = Vector2.zero();
|
||||||
|
|
||||||
// Movement controls
|
// Movement controls
|
||||||
|
@ -65,6 +80,9 @@ class Player extends RectangleComponent with HasGameReference<ShitmanGame> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleAction(LogicalKeyboardKey key) {
|
void handleAction(LogicalKeyboardKey key) {
|
||||||
|
// Don't handle actions if caught
|
||||||
|
if (isCaught) return;
|
||||||
|
|
||||||
// Action controls
|
// Action controls
|
||||||
if (key == LogicalKeyboardKey.space) {
|
if (key == LogicalKeyboardKey.space) {
|
||||||
placePoop();
|
placePoop();
|
||||||
|
@ -89,7 +107,7 @@ class Player extends RectangleComponent with HasGameReference<ShitmanGame> {
|
||||||
void placePoop() {
|
void placePoop() {
|
||||||
if (!hasPoopBag) return;
|
if (!hasPoopBag) return;
|
||||||
|
|
||||||
debugPrint('Placing poop bag at $position');
|
appLog.fine('Placing poop bag at $position');
|
||||||
|
|
||||||
// Create and place the poop bag
|
// Create and place the poop bag
|
||||||
placedPoopBag = PoopBag();
|
placedPoopBag = PoopBag();
|
||||||
|
@ -104,48 +122,62 @@ class Player extends RectangleComponent with HasGameReference<ShitmanGame> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ringDoorbell() {
|
void ringDoorbell() {
|
||||||
debugPrint('Attempting to ring doorbell');
|
appLog.fine('Attempting to ring doorbell');
|
||||||
|
|
||||||
// Check if near target house door
|
// Check if near any target house door
|
||||||
if (game.targetHouse.isPlayerNearTarget(position)) {
|
final currentLevel = game.world.children.whereType<OperationShitstorm>().firstOrNull;
|
||||||
|
if (currentLevel != null && currentLevel.isPlayerNearTarget(position)) {
|
||||||
if (placedPoopBag != null) {
|
if (placedPoopBag != null) {
|
||||||
// Light the poop bag on fire
|
// Light the poop bag on fire
|
||||||
placedPoopBag!.lightOnFire();
|
placedPoopBag!.lightOnFire();
|
||||||
debugPrint('Ding dong! Poop bag is lit! RUN!');
|
appLog.info('Ding dong! Poop bag is lit! RUN!');
|
||||||
|
|
||||||
// Start escape timer - player has limited time to escape
|
// Start escape timer - player has limited time to escape
|
||||||
startEscapeSequence();
|
startEscapeSequence();
|
||||||
} else {
|
} else {
|
||||||
debugPrint('Need to place poop bag first!');
|
appLog.fine('Need to place poop bag first!');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debugPrint('Not near target house door');
|
appLog.fine('Not near target house door');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void startEscapeSequence() {
|
void startEscapeSequence() {
|
||||||
// TODO: Implement escape mechanics
|
appLog.info('Escape sequence started! Get to the edge of the map!');
|
||||||
// For now, automatically complete mission after a delay
|
isEscaping = true;
|
||||||
Future.delayed(Duration(seconds: 3), () {
|
escapeTimeRemaining = escapeTimeLimit;
|
||||||
if (game.gameState == GameState.playing) {
|
|
||||||
game.completeCurrentMission();
|
// TODO: Show escape timer UI
|
||||||
}
|
// TODO: Highlight escape routes on the map
|
||||||
});
|
}
|
||||||
|
|
||||||
|
/// Check if player is at an escape point (edge of the map)
|
||||||
|
bool isAtEscapePoint() {
|
||||||
|
const double escapeZoneThreshold = 50.0;
|
||||||
|
|
||||||
|
// Check if near any edge of the level
|
||||||
|
return position.x < escapeZoneThreshold ||
|
||||||
|
position.y < escapeZoneThreshold ||
|
||||||
|
position.x > (5 * 100) - escapeZoneThreshold || // 5x5 grid * 100 cell size
|
||||||
|
position.y > (5 * 100) - escapeZoneThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
void checkMissionProgress() {
|
void checkMissionProgress() {
|
||||||
// Check if near target house and has placed poop bag
|
// Check if near target house and has placed poop bag
|
||||||
final targetPos = game.targetHouse.getTargetPosition();
|
final currentLevel = game.world.children.whereType<OperationShitstorm>().firstOrNull;
|
||||||
|
final targetPos = currentLevel?.getTargetPosition();
|
||||||
if (targetPos != null && placedPoopBag != null) {
|
if (targetPos != null && placedPoopBag != null) {
|
||||||
final distance = (position - targetPos).length;
|
final distance = (position - targetPos).length;
|
||||||
if (distance < 80) {
|
if (distance < 80) {
|
||||||
debugPrint('Near target house with poop bag placed!');
|
appLog.fine('Near target house with poop bag placed!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void getDetected() {
|
void getDetected() {
|
||||||
debugPrint('Player detected! Mission failed!');
|
appLog.warning('Player detected! Mission failed!');
|
||||||
|
isCaught = true;
|
||||||
|
velocity = Vector2.zero(); // Stop movement immediately
|
||||||
game.failMission();
|
game.failMission();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,8 +201,30 @@ class Player extends RectangleComponent with HasGameReference<ShitmanGame> {
|
||||||
// Update stealth level based on environment
|
// Update stealth level based on environment
|
||||||
updateStealthLevel(dt);
|
updateStealthLevel(dt);
|
||||||
|
|
||||||
// Check for detection by houses
|
// Handle escape sequence
|
||||||
checkForDetection();
|
if (isEscaping) {
|
||||||
|
escapeTimeRemaining -= dt;
|
||||||
|
|
||||||
|
// Check if time ran out
|
||||||
|
if (escapeTimeRemaining <= 0) {
|
||||||
|
appLog.warning('Time ran out! Mission failed!');
|
||||||
|
getDetected();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if player reached escape point (edge of map)
|
||||||
|
if (isAtEscapePoint()) {
|
||||||
|
appLog.info('Successfully escaped! Mission complete!');
|
||||||
|
isEscaping = false;
|
||||||
|
game.completeCurrentMission();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for detection by houses (only if not already caught)
|
||||||
|
if (!isCaught) {
|
||||||
|
checkForDetection();
|
||||||
|
}
|
||||||
|
|
||||||
// Update player vision cone
|
// Update player vision cone
|
||||||
if (playerVisionCone != null) {
|
if (playerVisionCone != null) {
|
||||||
|
@ -178,9 +232,7 @@ class Player extends RectangleComponent with HasGameReference<ShitmanGame> {
|
||||||
|
|
||||||
// Update vision cone visibility based on settings
|
// Update vision cone visibility based on settings
|
||||||
try {
|
try {
|
||||||
final showVisionCones = game.appSettings.getBool(
|
final showVisionCones = appSettings.getBool('game.show_vision_cones');
|
||||||
'game.show_vision_cones',
|
|
||||||
);
|
|
||||||
playerVisionCone!.updateOpacity(showVisionCones ? 0.2 : 0.0);
|
playerVisionCone!.updateOpacity(showVisionCones ? 0.2 : 0.0);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
playerVisionCone!.updateOpacity(0.0); // Hide if settings not ready
|
playerVisionCone!.updateOpacity(0.0); // Hide if settings not ready
|
||||||
|
@ -189,12 +241,13 @@ class Player extends RectangleComponent with HasGameReference<ShitmanGame> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void checkForDetection() {
|
void checkForDetection() {
|
||||||
final neighborhood =
|
// Check for detection from houses in any active level
|
||||||
game.world.children.whereType<Neighborhood>().firstOrNull;
|
final houses = game.world.children
|
||||||
if (neighborhood == null) return;
|
.expand((component) => component.children)
|
||||||
|
.whereType<HouseComponent>();
|
||||||
for (final house in neighborhood.houses) {
|
|
||||||
if (house.canDetectPlayer(position, stealthLevel)) {
|
for (final house in houses) {
|
||||||
|
if (house.detectsPlayer(position, stealthLevel)) {
|
||||||
getDetected();
|
getDetected();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -203,30 +256,50 @@ class Player extends RectangleComponent with HasGameReference<ShitmanGame> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void render(Canvas canvas) {
|
void render(Canvas canvas) {
|
||||||
// Update paint color based on stealth level
|
// Draw player as rectangle with color based on stealth level
|
||||||
paint =
|
final playerPaint = Paint()
|
||||||
Paint()
|
..color = isHidden
|
||||||
..color =
|
? const Color(0xFF00FF00).withValues(alpha: 0.7) // Green when hidden
|
||||||
isHidden
|
: const Color(0xFF0000FF).withValues(alpha: 0.9); // Blue when visible
|
||||||
? const Color(0xFF00FF00).withValues(alpha: 0.7)
|
|
||||||
: // Green when hidden
|
|
||||||
const Color(
|
|
||||||
0xFF0000FF,
|
|
||||||
).withValues(alpha: 0.9); // Blue when visible
|
|
||||||
|
|
||||||
super.render(canvas);
|
canvas.drawRect(size.toRect(), playerPaint);
|
||||||
|
|
||||||
// Draw stealth indicator in debug mode
|
// Draw stealth indicator in debug mode
|
||||||
if (game.debugMode) {
|
try {
|
||||||
final stealthPaint =
|
final debugMode = appSettings.getBool('game.debug_mode');
|
||||||
Paint()
|
if (debugMode) {
|
||||||
..color =
|
final stealthPaint = Paint()
|
||||||
Color.lerp(
|
..color = Color.lerp(
|
||||||
const Color(0xFFFF0000),
|
const Color(0xFFFF0000),
|
||||||
const Color(0xFF00FF00),
|
const Color(0xFF00FF00),
|
||||||
stealthLevel,
|
stealthLevel,
|
||||||
)!;
|
)!;
|
||||||
canvas.drawRect(Rect.fromLTWH(-5, -10, size.x + 10, 5), stealthPaint);
|
canvas.drawRect(Rect.fromLTWH(-5, -10, size.x + 10, 5), stealthPaint);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Continue without debug rendering if settings not available
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> reset() async {
|
||||||
|
hasPoopBag = true;
|
||||||
|
isHidden = false;
|
||||||
|
isCaught = false;
|
||||||
|
isEscaping = false;
|
||||||
|
escapeTimeRemaining = 0.0;
|
||||||
|
stealthLevel = 0.0;
|
||||||
|
velocity = Vector2.zero();
|
||||||
|
lastMovementDirection = 0.0;
|
||||||
|
placedPoopBag = null;
|
||||||
|
position = Vector2(200, 200); // Reset to starting position
|
||||||
|
|
||||||
|
// Reset vision cone
|
||||||
|
if (playerVisionCone != null) {
|
||||||
|
await playerVisionCone!.reset();
|
||||||
|
playerVisionCone!.updatePosition(size / 2, lastMovementDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
appLog.fine('Player reset to initial state');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,23 @@
|
||||||
import 'package:flame/components.dart';
|
import 'package:flame/components.dart';
|
||||||
import 'package:flame/effects.dart';
|
import 'package:flame/effects.dart';
|
||||||
|
import 'package:flame/particles.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:shitman/game/components/base.dart';
|
||||||
|
|
||||||
enum PoopBagState { placed, lit, burning, extinguished }
|
enum PoopBagState { placed, lit, burning, extinguished }
|
||||||
|
|
||||||
class PoopBag extends CircleComponent {
|
class PoopBag extends ShitComponent {
|
||||||
PoopBagState state = PoopBagState.placed;
|
PoopBagState state = PoopBagState.placed;
|
||||||
double burnTimer = 0.0;
|
double burnTimer = 0.0;
|
||||||
static const double burnDuration = 3.0; // seconds to burn
|
static const double burnDuration = 3.0; // seconds to burn
|
||||||
static const double bagSize = 16.0;
|
static const double bagSize = 16.0;
|
||||||
|
double radius = bagSize / 2;
|
||||||
|
late Paint paint;
|
||||||
|
|
||||||
late Vector2 smokeOffset;
|
late Vector2 smokeOffset;
|
||||||
List<SmokeParticle> smokeParticles = [];
|
List<Particle> smokeParticles = [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
|
@ -37,7 +42,7 @@ class PoopBag extends CircleComponent {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
debugPrint('Poop bag is now on fire!');
|
appLog.finest('Poop bag is now on fire!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +81,15 @@ class PoopBag extends CircleComponent {
|
||||||
smokeOffset +
|
smokeOffset +
|
||||||
Vector2(random.nextDouble() * 10 - 5, random.nextDouble() * 5),
|
Vector2(random.nextDouble() * 10 - 5, random.nextDouble() * 5),
|
||||||
);
|
);
|
||||||
smokeParticles.add(particle);
|
smokeParticles.add(
|
||||||
|
particle.accelerated(
|
||||||
|
acceleration: Vector2(
|
||||||
|
Random().nextDouble() * 20 - 10,
|
||||||
|
-Random().nextDouble() * 30 - 20,
|
||||||
|
),
|
||||||
|
position: particle.position,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void extinguish() {
|
void extinguish() {
|
||||||
|
@ -86,13 +99,14 @@ class PoopBag extends CircleComponent {
|
||||||
// Change to burnt color
|
// Change to burnt color
|
||||||
paint = Paint()..color = const Color(0xFF2F2F2F);
|
paint = Paint()..color = const Color(0xFF2F2F2F);
|
||||||
|
|
||||||
debugPrint('Poop bag has burned out');
|
appLog.finest('Poop bag has burned out');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void render(Canvas canvas) {
|
void render(Canvas canvas) {
|
||||||
super.render(canvas);
|
super.render(canvas);
|
||||||
|
// Draw the poop bag
|
||||||
|
canvas.drawCircle(Offset(0, 0), radius, paint);
|
||||||
// Draw flame effect when lit
|
// Draw flame effect when lit
|
||||||
if (state == PoopBagState.lit) {
|
if (state == PoopBagState.lit) {
|
||||||
final flamePaint =
|
final flamePaint =
|
||||||
|
@ -121,42 +135,30 @@ class PoopBag extends CircleComponent {
|
||||||
bool isNearPosition(Vector2 targetPosition, {double threshold = 30.0}) {
|
bool isNearPosition(Vector2 targetPosition, {double threshold = 30.0}) {
|
||||||
return (position - targetPosition).length < threshold;
|
return (position - targetPosition).length < threshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> reset() async {
|
||||||
|
state = PoopBagState.placed;
|
||||||
|
burnTimer = 0.0;
|
||||||
|
smokeParticles.clear();
|
||||||
|
paint = Paint()..color = const Color(0xFF8B4513);
|
||||||
|
removeAll(children.whereType<Effect>());
|
||||||
|
appLog.finest('Poop bag reset to placed state');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SmokeParticle {
|
class SmokeParticle extends Particle {
|
||||||
Vector2 position;
|
Vector2 position;
|
||||||
Vector2 velocity;
|
|
||||||
double life;
|
|
||||||
double maxLife;
|
|
||||||
bool shouldRemove = false;
|
|
||||||
|
|
||||||
SmokeParticle({required this.position})
|
SmokeParticle({required this.position}) : super(lifespan: 2.0);
|
||||||
: velocity = Vector2(
|
|
||||||
Random().nextDouble() * 20 - 10,
|
|
||||||
-Random().nextDouble() * 30 - 20,
|
|
||||||
),
|
|
||||||
life = 2.0,
|
|
||||||
maxLife = 2.0;
|
|
||||||
|
|
||||||
void update(double dt) {
|
|
||||||
position += velocity * dt;
|
|
||||||
velocity *= 0.98; // Slight air resistance
|
|
||||||
life -= dt;
|
|
||||||
|
|
||||||
if (life <= 0) {
|
|
||||||
shouldRemove = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@override
|
||||||
void render(Canvas canvas) {
|
void render(Canvas canvas) {
|
||||||
final alpha = (life / maxLife).clamp(0.0, 1.0);
|
final paint =
|
||||||
final smokePaint =
|
Paint()
|
||||||
Paint()..color = Color(0xFF666666).withValues(alpha: alpha * 0.3);
|
..color = const Color(0xFF808080).withValues(alpha: 0.5)
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
|
||||||
canvas.drawCircle(
|
canvas.drawCircle(Offset(position.x, position.y), 3.0, paint);
|
||||||
Offset(position.x, position.y),
|
|
||||||
6.0 * (1.0 - life / maxLife),
|
|
||||||
smokePaint,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
239
lib/game/components/road_components.dart
Normal file
239
lib/game/components/road_components.dart
Normal file
|
@ -0,0 +1,239 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:shitman/game/components/level_components.dart';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
/// Horizontal road segment
|
||||||
|
class HorizontalRoad extends RoadComponent {
|
||||||
|
HorizontalRoad({required super.gridPosition});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void renderRoadMarkings(Canvas canvas, Paint paint) {
|
||||||
|
// Draw center line
|
||||||
|
final centerY = size.y / 2;
|
||||||
|
for (double x = 5; x < size.x; x += 15) {
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(x, centerY - 1),
|
||||||
|
Offset(x + 8, centerY - 1),
|
||||||
|
paint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> reset() async {
|
||||||
|
// Nothing to reset for road components
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Vertical road segment
|
||||||
|
class VerticalRoad extends RoadComponent {
|
||||||
|
VerticalRoad({required super.gridPosition});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void renderRoadMarkings(Canvas canvas, Paint paint) {
|
||||||
|
// Draw center line
|
||||||
|
final centerX = size.x / 2;
|
||||||
|
for (double y = 5; y < size.y; y += 15) {
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(centerX - 1, y),
|
||||||
|
Offset(centerX - 1, y + 8),
|
||||||
|
paint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> reset() async {
|
||||||
|
// Nothing to reset for road components
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Road intersection (crossroads)
|
||||||
|
class IntersectionRoad extends RoadComponent {
|
||||||
|
IntersectionRoad({required super.gridPosition});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void renderRoadMarkings(Canvas canvas, Paint paint) {
|
||||||
|
// Draw intersection markings - just corner lines
|
||||||
|
const offset = 10.0;
|
||||||
|
|
||||||
|
// Top-left corner
|
||||||
|
canvas.drawLine(
|
||||||
|
const Offset(offset, offset),
|
||||||
|
Offset(offset + 15, offset),
|
||||||
|
paint,
|
||||||
|
);
|
||||||
|
canvas.drawLine(
|
||||||
|
const Offset(offset, offset),
|
||||||
|
Offset(offset, offset + 15),
|
||||||
|
paint,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Top-right corner
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(size.x - offset - 15, offset),
|
||||||
|
Offset(size.x - offset, offset),
|
||||||
|
paint,
|
||||||
|
);
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(size.x - offset, offset),
|
||||||
|
Offset(size.x - offset, offset + 15),
|
||||||
|
paint,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Bottom-left corner
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(offset, size.y - offset - 15),
|
||||||
|
Offset(offset, size.y - offset),
|
||||||
|
paint,
|
||||||
|
);
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(offset, size.y - offset),
|
||||||
|
Offset(offset + 15, size.y - offset),
|
||||||
|
paint,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Bottom-right corner
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(size.x - offset, size.y - offset - 15),
|
||||||
|
Offset(size.x - offset, size.y - offset),
|
||||||
|
paint,
|
||||||
|
);
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(size.x - offset - 15, size.y - offset),
|
||||||
|
Offset(size.x - offset, size.y - offset),
|
||||||
|
paint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> reset() async {
|
||||||
|
// Nothing to reset for road components
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Corner road (L-shaped)
|
||||||
|
class CornerRoad extends RoadComponent {
|
||||||
|
final CornerDirection direction;
|
||||||
|
|
||||||
|
CornerRoad({required super.gridPosition, required this.direction});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void renderRoadMarkings(Canvas canvas, Paint paint) {
|
||||||
|
const curveRadius = 10.0;
|
||||||
|
|
||||||
|
switch (direction) {
|
||||||
|
case CornerDirection.topLeft:
|
||||||
|
// Draw curve from top to left
|
||||||
|
canvas.drawArc(
|
||||||
|
Rect.fromLTWH(0, 0, curveRadius * 2, curveRadius * 2),
|
||||||
|
0,
|
||||||
|
pi / 2,
|
||||||
|
false,
|
||||||
|
paint..style = PaintingStyle.stroke,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case CornerDirection.topRight:
|
||||||
|
// Draw curve from top to right
|
||||||
|
canvas.drawArc(
|
||||||
|
Rect.fromLTWH(
|
||||||
|
size.x - curveRadius * 2,
|
||||||
|
0,
|
||||||
|
curveRadius * 2,
|
||||||
|
curveRadius * 2,
|
||||||
|
),
|
||||||
|
pi / 2,
|
||||||
|
pi / 2,
|
||||||
|
false,
|
||||||
|
paint..style = PaintingStyle.stroke,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case CornerDirection.bottomLeft:
|
||||||
|
// Draw curve from bottom to left
|
||||||
|
canvas.drawArc(
|
||||||
|
Rect.fromLTWH(
|
||||||
|
0,
|
||||||
|
size.y - curveRadius * 2,
|
||||||
|
curveRadius * 2,
|
||||||
|
curveRadius * 2,
|
||||||
|
),
|
||||||
|
3 * pi / 2,
|
||||||
|
pi / 2,
|
||||||
|
false,
|
||||||
|
paint..style = PaintingStyle.stroke,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case CornerDirection.bottomRight:
|
||||||
|
// Draw curve from bottom to right
|
||||||
|
canvas.drawArc(
|
||||||
|
Rect.fromLTWH(
|
||||||
|
size.x - curveRadius * 2,
|
||||||
|
size.y - curveRadius * 2,
|
||||||
|
curveRadius * 2,
|
||||||
|
curveRadius * 2,
|
||||||
|
),
|
||||||
|
pi,
|
||||||
|
pi / 2,
|
||||||
|
false,
|
||||||
|
paint..style = PaintingStyle.stroke,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> reset() async {
|
||||||
|
// Nothing to reset for road components
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dead end road
|
||||||
|
class DeadEndRoad extends RoadComponent {
|
||||||
|
final DeadEndDirection direction;
|
||||||
|
|
||||||
|
DeadEndRoad({required super.gridPosition, required this.direction});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void renderRoadMarkings(Canvas canvas, Paint paint) {
|
||||||
|
// Draw a line across the dead end
|
||||||
|
switch (direction) {
|
||||||
|
case DeadEndDirection.north:
|
||||||
|
canvas.drawLine(
|
||||||
|
const Offset(10, 10),
|
||||||
|
Offset(size.x - 10, 10),
|
||||||
|
paint..strokeWidth = 4.0,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case DeadEndDirection.south:
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(10, size.y - 10),
|
||||||
|
Offset(size.x - 10, size.y - 10),
|
||||||
|
paint..strokeWidth = 4.0,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case DeadEndDirection.east:
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(size.x - 10, 10),
|
||||||
|
Offset(size.x - 10, size.y - 10),
|
||||||
|
paint..strokeWidth = 4.0,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case DeadEndDirection.west:
|
||||||
|
canvas.drawLine(
|
||||||
|
const Offset(10, 10),
|
||||||
|
Offset(10, size.y - 10),
|
||||||
|
paint..strokeWidth = 4.0,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> reset() async {
|
||||||
|
// Nothing to reset for road components
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CornerDirection { topLeft, topRight, bottomLeft, bottomRight }
|
||||||
|
|
||||||
|
enum DeadEndDirection { north, south, east, west }
|
297
lib/game/components/security_components.dart
Normal file
297
lib/game/components/security_components.dart
Normal file
|
@ -0,0 +1,297 @@
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:shitman/game/components/level_components.dart';
|
||||||
|
import 'package:shitman/game/components/vision_cone.dart';
|
||||||
|
import 'package:shitman/settings/app_settings.dart';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
/// PIR sensor triggered light
|
||||||
|
class PIRSensorComponent extends SecurityComponent with AppSettings {
|
||||||
|
bool isTriggered = false;
|
||||||
|
bool lightsOn = false;
|
||||||
|
bool _showDetectionRadius = true;
|
||||||
|
|
||||||
|
PIRSensorComponent({
|
||||||
|
required super.position,
|
||||||
|
super.detectionRange = 40.0,
|
||||||
|
}) {
|
||||||
|
size = Vector2(8, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
await initSettings();
|
||||||
|
|
||||||
|
// Initialize detection radius visibility from settings
|
||||||
|
try {
|
||||||
|
_showDetectionRadius = appSettings.getBool('game.show_detection_radius');
|
||||||
|
} catch (e) {
|
||||||
|
_showDetectionRadius = true; // Default to visible
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool detectsPlayer(Vector2 playerPosition, double playerStealthLevel) {
|
||||||
|
final distance = (playerPosition - position).length;
|
||||||
|
final effectiveRange = getEffectiveDetectionRange(playerStealthLevel);
|
||||||
|
|
||||||
|
bool detected = distance < effectiveRange;
|
||||||
|
|
||||||
|
if (detected && !isTriggered) {
|
||||||
|
isTriggered = true;
|
||||||
|
lightsOn = true;
|
||||||
|
appLog.fine('PIR sensor triggered at $position');
|
||||||
|
} else if (!detected && isTriggered) {
|
||||||
|
// Cool down period before turning off
|
||||||
|
Future.delayed(const Duration(seconds: 5), () {
|
||||||
|
isTriggered = false;
|
||||||
|
lightsOn = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return detected;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
// Draw detection radius if enabled
|
||||||
|
if (_showDetectionRadius) {
|
||||||
|
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),
|
||||||
|
detectionRange,
|
||||||
|
radiusPaint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw PIR sensor as small circle
|
||||||
|
final sensorPaint = Paint()
|
||||||
|
..color = isTriggered ? const Color(0xFFFF0000) : const Color(0xFF000000);
|
||||||
|
canvas.drawCircle(Offset(size.x / 2, size.y / 2), 4, sensorPaint);
|
||||||
|
|
||||||
|
// Draw light effect if triggered
|
||||||
|
if (lightsOn) {
|
||||||
|
final lightPaint = Paint()
|
||||||
|
..color = const Color(0xFFFFFF00).withValues(alpha: 0.3);
|
||||||
|
canvas.drawCircle(
|
||||||
|
Offset(size.x / 2, size.y / 2),
|
||||||
|
detectionRange,
|
||||||
|
lightPaint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update detection radius visibility (call when settings change)
|
||||||
|
void updateDetectionRadiusVisibility(bool visible) {
|
||||||
|
_showDetectionRadius = visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refresh visibility from current settings
|
||||||
|
void refreshVisibilityFromSettings() {
|
||||||
|
try {
|
||||||
|
final newVisibility = appSettings.getBool('game.show_detection_radius');
|
||||||
|
updateDetectionRadiusVisibility(newVisibility);
|
||||||
|
} catch (e) {
|
||||||
|
// Settings not ready, keep current state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> reset() async {
|
||||||
|
isTriggered = false;
|
||||||
|
lightsOn = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Security camera with vision cone
|
||||||
|
class SecurityCameraComponent extends SecurityComponent with AppSettings {
|
||||||
|
late VisionCone visionCone;
|
||||||
|
double direction;
|
||||||
|
bool _currentVisionConeVisibility = true;
|
||||||
|
|
||||||
|
SecurityCameraComponent({
|
||||||
|
required super.position,
|
||||||
|
required this.direction,
|
||||||
|
super.detectionRange = 120.0,
|
||||||
|
}) {
|
||||||
|
size = Vector2(8, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
await initSettings();
|
||||||
|
|
||||||
|
// Create vision cone with initial visibility based on settings
|
||||||
|
try {
|
||||||
|
_currentVisionConeVisibility = appSettings.getBool('game.show_vision_cones');
|
||||||
|
} catch (e) {
|
||||||
|
_currentVisionConeVisibility = true; // Default to visible
|
||||||
|
}
|
||||||
|
|
||||||
|
visionCone = VisionCone(
|
||||||
|
origin: Vector2.zero(),
|
||||||
|
direction: direction,
|
||||||
|
range: detectionRange,
|
||||||
|
fov: pi / 2, // 90 degrees
|
||||||
|
color: Colors.red,
|
||||||
|
opacity: _currentVisionConeVisibility ? 0.3 : 0.0,
|
||||||
|
);
|
||||||
|
add(visionCone);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool detectsPlayer(Vector2 playerPosition, double playerStealthLevel) {
|
||||||
|
return visionCone.canSee(playerPosition) &&
|
||||||
|
visionCone.hasLineOfSight(playerPosition, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
// Draw camera as black circle
|
||||||
|
final cameraPaint = Paint()..color = const Color(0xFF000000);
|
||||||
|
canvas.drawCircle(Offset(size.x / 2, size.y / 2), 4, cameraPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call this method when settings change to update vision cone visibility
|
||||||
|
void updateVisionConeVisibility(bool visible) {
|
||||||
|
if (_currentVisionConeVisibility != visible) {
|
||||||
|
_currentVisionConeVisibility = visible;
|
||||||
|
visionCone.updateOpacity(visible ? 0.3 : 0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refresh visibility from current settings (call when settings might have changed)
|
||||||
|
void refreshVisibilityFromSettings() {
|
||||||
|
try {
|
||||||
|
final newVisibility = appSettings.getBool('game.show_vision_cones');
|
||||||
|
updateVisionConeVisibility(newVisibility);
|
||||||
|
} catch (e) {
|
||||||
|
// Settings not ready, keep current state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> reset() async {
|
||||||
|
await visionCone.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Guard dog with patrol area
|
||||||
|
class GuardDogComponent extends SecurityComponent with AppSettings {
|
||||||
|
Vector2 patrolCenter;
|
||||||
|
double patrolRadius;
|
||||||
|
double currentAngle = 0;
|
||||||
|
bool isPatrolling = true;
|
||||||
|
bool _showDetectionRadius = true;
|
||||||
|
|
||||||
|
GuardDogComponent({
|
||||||
|
required super.position,
|
||||||
|
required this.patrolCenter,
|
||||||
|
this.patrolRadius = 30.0,
|
||||||
|
super.detectionRange = 50.0,
|
||||||
|
}) {
|
||||||
|
size = Vector2(12, 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
await initSettings();
|
||||||
|
|
||||||
|
// Initialize detection radius visibility from settings
|
||||||
|
try {
|
||||||
|
_showDetectionRadius = appSettings.getBool('game.show_detection_radius');
|
||||||
|
} catch (e) {
|
||||||
|
_showDetectionRadius = true; // Default to visible
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool detectsPlayer(Vector2 playerPosition, double playerStealthLevel) {
|
||||||
|
final distance = (playerPosition - position).length;
|
||||||
|
final effectiveRange = getEffectiveDetectionRange(playerStealthLevel);
|
||||||
|
|
||||||
|
return distance < effectiveRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
super.update(dt);
|
||||||
|
|
||||||
|
if (isPatrolling) {
|
||||||
|
// Simple circular patrol
|
||||||
|
currentAngle += dt * 0.5; // Rotation speed
|
||||||
|
final offset = Vector2(
|
||||||
|
cos(currentAngle) * patrolRadius,
|
||||||
|
sin(currentAngle) * patrolRadius,
|
||||||
|
);
|
||||||
|
position = patrolCenter + offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
// Draw detection radius if enabled
|
||||||
|
if (_showDetectionRadius) {
|
||||||
|
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),
|
||||||
|
detectionRange,
|
||||||
|
radiusPaint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw dog house
|
||||||
|
final dogHousePaint = Paint()..color = const Color(0xFF8B4513);
|
||||||
|
canvas.drawRect(
|
||||||
|
Rect.fromLTWH(0, 0, size.x, size.y),
|
||||||
|
dogHousePaint,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Draw dog (simple circle)
|
||||||
|
final dogPaint = Paint()..color = const Color(0xFF654321);
|
||||||
|
canvas.drawCircle(
|
||||||
|
Offset(size.x / 2, size.y / 2),
|
||||||
|
4,
|
||||||
|
dogPaint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopPatrol() {
|
||||||
|
isPatrolling = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void startPatrol() {
|
||||||
|
isPatrolling = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update detection radius visibility (call when settings change)
|
||||||
|
void updateDetectionRadiusVisibility(bool visible) {
|
||||||
|
_showDetectionRadius = visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refresh visibility from current settings
|
||||||
|
void refreshVisibilityFromSettings() {
|
||||||
|
try {
|
||||||
|
final newVisibility = appSettings.getBool('game.show_detection_radius');
|
||||||
|
updateDetectionRadiusVisibility(newVisibility);
|
||||||
|
} catch (e) {
|
||||||
|
// Settings not ready, keep current state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> reset() async {
|
||||||
|
currentAngle = 0;
|
||||||
|
isPatrolling = true;
|
||||||
|
position = patrolCenter;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
import 'package:flame/components.dart';
|
import 'package:flame/components.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:shitman/game/components/base.dart';
|
||||||
import 'package:shitman/game/components/neighborhood.dart';
|
import 'package:shitman/game/components/neighborhood.dart';
|
||||||
|
|
||||||
class TargetHouse extends Component {
|
class TargetHouse extends ShitComponent {
|
||||||
House? currentTarget;
|
House? currentTarget;
|
||||||
bool missionActive = false;
|
bool missionActive = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
await super.onLoad();
|
await super.onLoad();
|
||||||
|
@ -27,7 +27,7 @@ class TargetHouse extends Component {
|
||||||
if (currentTarget != null) {
|
if (currentTarget != null) {
|
||||||
currentTarget!.isTarget = true;
|
currentTarget!.isTarget = true;
|
||||||
missionActive = true;
|
missionActive = true;
|
||||||
debugPrint('New target selected at ${currentTarget!.position}');
|
appLog.finest('New target selected at ${currentTarget!.position}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ class TargetHouse extends Component {
|
||||||
|
|
||||||
bool isPlayerNearTarget(Vector2 playerPosition, {double threshold = 50.0}) {
|
bool isPlayerNearTarget(Vector2 playerPosition, {double threshold = 50.0}) {
|
||||||
if (currentTarget?.doorPosition == null) return false;
|
if (currentTarget?.doorPosition == null) return false;
|
||||||
|
|
||||||
final distance = (playerPosition - currentTarget!.doorPosition!).length;
|
final distance = (playerPosition - currentTarget!.doorPosition!).length;
|
||||||
return distance < threshold;
|
return distance < threshold;
|
||||||
}
|
}
|
||||||
|
@ -49,4 +49,13 @@ class TargetHouse extends Component {
|
||||||
Vector2? getTargetPosition() {
|
Vector2? getTargetPosition() {
|
||||||
return currentTarget?.doorPosition;
|
return currentTarget?.doorPosition;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@override
|
||||||
|
Future<void> reset() async {
|
||||||
|
// Reset target house state
|
||||||
|
currentTarget?.isTarget = false;
|
||||||
|
currentTarget = null;
|
||||||
|
missionActive = false;
|
||||||
|
appLog.finest('Target house reset');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,17 +2,19 @@ import 'package:flame/components.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
class VisionCone extends PositionComponent {
|
import 'package:shitman/game/components/base.dart';
|
||||||
|
|
||||||
|
class VisionCone extends ShitComponent {
|
||||||
double direction; // Angle in radians
|
double direction; // Angle in radians
|
||||||
final double range;
|
final double range;
|
||||||
final double fov; // Field of view angle in radians
|
final double fov; // Field of view angle in radians
|
||||||
final Color color;
|
final Color color;
|
||||||
double opacity;
|
double opacity;
|
||||||
|
|
||||||
List<Vector2> visionVertices = [];
|
List<Vector2> visionVertices = [];
|
||||||
late Paint fillPaint;
|
late Paint fillPaint;
|
||||||
late Paint strokePaint;
|
late Paint strokePaint;
|
||||||
|
|
||||||
VisionCone({
|
VisionCone({
|
||||||
required Vector2 origin,
|
required Vector2 origin,
|
||||||
required this.direction,
|
required this.direction,
|
||||||
|
@ -25,35 +27,37 @@ class VisionCone extends PositionComponent {
|
||||||
@override
|
@override
|
||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
await super.onLoad();
|
await super.onLoad();
|
||||||
|
|
||||||
fillPaint = Paint()
|
fillPaint =
|
||||||
..color = color.withValues(alpha: opacity)
|
Paint()
|
||||||
..style = PaintingStyle.fill;
|
..color = color.withValues(alpha: opacity)
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
strokePaint = Paint()
|
|
||||||
..color = color.withValues(alpha: opacity + 0.2)
|
strokePaint =
|
||||||
..style = PaintingStyle.stroke
|
Paint()
|
||||||
..strokeWidth = 2.0;
|
..color = color.withValues(alpha: opacity + 0.2)
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 2.0;
|
||||||
|
|
||||||
_calculateVisionCone();
|
_calculateVisionCone();
|
||||||
}
|
}
|
||||||
|
|
||||||
void updatePosition(Vector2 newOrigin, double newDirection) {
|
void updatePosition(Vector2 newOrigin, double newDirection) {
|
||||||
position.setFrom(newOrigin);
|
position.setFrom(newOrigin);
|
||||||
direction = newDirection;
|
direction = newDirection;
|
||||||
_calculateVisionCone();
|
_calculateVisionCone();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _calculateVisionCone() {
|
void _calculateVisionCone() {
|
||||||
visionVertices.clear();
|
visionVertices.clear();
|
||||||
|
|
||||||
// Start from the origin (0,0 relative to this component)
|
// Start from the origin (0,0 relative to this component)
|
||||||
visionVertices.add(Vector2.zero());
|
visionVertices.add(Vector2.zero());
|
||||||
|
|
||||||
// Calculate the two edge rays of the vision cone
|
// Calculate the two edge rays of the vision cone
|
||||||
final leftAngle = direction - fov / 2;
|
final leftAngle = direction - fov / 2;
|
||||||
final rightAngle = direction + fov / 2;
|
final rightAngle = direction + fov / 2;
|
||||||
|
|
||||||
// Create points along the vision cone arc
|
// Create points along the vision cone arc
|
||||||
const int arcPoints = 10;
|
const int arcPoints = 10;
|
||||||
for (int i = 0; i <= arcPoints; i++) {
|
for (int i = 0; i <= arcPoints; i++) {
|
||||||
|
@ -61,30 +65,30 @@ class VisionCone extends PositionComponent {
|
||||||
final point = Vector2(cos(angle), sin(angle)) * range;
|
final point = Vector2(cos(angle), sin(angle)) * range;
|
||||||
visionVertices.add(point);
|
visionVertices.add(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the cone back to origin
|
// Close the cone back to origin
|
||||||
visionVertices.add(Vector2.zero());
|
visionVertices.add(Vector2.zero());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a point is within the vision cone
|
/// Check if a point is within the vision cone
|
||||||
bool canSee(Vector2 point) {
|
bool canSee(Vector2 point) {
|
||||||
// Get the world position of this vision cone by adding parent position
|
// Get the world position of this vision cone by adding parent position
|
||||||
final parentPosition = (parent as PositionComponent?)?.position ?? Vector2.zero();
|
final parentPosition =
|
||||||
|
(parent as PositionComponent?)?.position ?? Vector2.zero();
|
||||||
final worldPosition = parentPosition + position;
|
final worldPosition = parentPosition + position;
|
||||||
final toPoint = point - worldPosition;
|
final toPoint = point - worldPosition;
|
||||||
final distance = toPoint.length;
|
final distance = toPoint.length;
|
||||||
|
|
||||||
|
|
||||||
// Check if within range
|
// Check if within range
|
||||||
if (distance > range) return false;
|
if (distance > range) return false;
|
||||||
|
|
||||||
// Check if within angle
|
// Check if within angle
|
||||||
final angleToPoint = atan2(toPoint.y, toPoint.x);
|
final angleToPoint = atan2(toPoint.y, toPoint.x);
|
||||||
final angleDiff = _normalizeAngle(angleToPoint - direction);
|
final angleDiff = _normalizeAngle(angleToPoint - direction);
|
||||||
|
|
||||||
return angleDiff.abs() <= fov / 2;
|
return angleDiff.abs() <= fov / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Normalize angle to [-pi, pi]
|
/// Normalize angle to [-pi, pi]
|
||||||
double _normalizeAngle(double angle) {
|
double _normalizeAngle(double angle) {
|
||||||
while (angle > pi) {
|
while (angle > pi) {
|
||||||
|
@ -95,21 +99,22 @@ class VisionCone extends PositionComponent {
|
||||||
}
|
}
|
||||||
return angle;
|
return angle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Raycast from origin to target, checking for obstructions
|
/// Raycast from origin to target, checking for obstructions
|
||||||
bool hasLineOfSight(Vector2 target, List<Component> obstacles) {
|
bool hasLineOfSight(Vector2 target, List<Component> obstacles) {
|
||||||
// Simple line-of-sight check - can be enhanced with proper raycasting
|
// Simple line-of-sight check - can be enhanced with proper raycasting
|
||||||
final parentPosition = (parent as PositionComponent?)?.position ?? Vector2.zero();
|
final parentPosition =
|
||||||
|
(parent as PositionComponent?)?.position ?? Vector2.zero();
|
||||||
final worldPosition = parentPosition + position;
|
final worldPosition = parentPosition + position;
|
||||||
final rayDirection = target - worldPosition;
|
final rayDirection = target - worldPosition;
|
||||||
final distance = rayDirection.length;
|
final distance = rayDirection.length;
|
||||||
final normalizedDir = rayDirection.normalized();
|
final normalizedDir = rayDirection.normalized();
|
||||||
|
|
||||||
// Check points along the ray for obstacles
|
// Check points along the ray for obstacles
|
||||||
const double stepSize = 5.0;
|
const double stepSize = 5.0;
|
||||||
for (double step = stepSize; step < distance; step += stepSize) {
|
for (double step = stepSize; step < distance; step += stepSize) {
|
||||||
final checkPoint = worldPosition + normalizedDir * step;
|
final checkPoint = worldPosition + normalizedDir * step;
|
||||||
|
|
||||||
// Check if this point intersects with any obstacles
|
// Check if this point intersects with any obstacles
|
||||||
for (final obstacle in obstacles) {
|
for (final obstacle in obstacles) {
|
||||||
if (obstacle is RectangleComponent) {
|
if (obstacle is RectangleComponent) {
|
||||||
|
@ -119,40 +124,51 @@ class VisionCone extends PositionComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true; // Clear line of sight
|
return true; // Clear line of sight
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateOpacity(double newOpacity) {
|
void updateOpacity(double newOpacity) {
|
||||||
opacity = newOpacity;
|
opacity = newOpacity;
|
||||||
fillPaint = Paint()
|
fillPaint =
|
||||||
..color = color.withValues(alpha: opacity)
|
Paint()
|
||||||
..style = PaintingStyle.fill;
|
..color = color.withValues(alpha: opacity)
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
strokePaint = Paint()
|
|
||||||
..color = color.withValues(alpha: opacity + 0.2)
|
strokePaint =
|
||||||
..style = PaintingStyle.stroke
|
Paint()
|
||||||
..strokeWidth = 2.0;
|
..color = color.withValues(alpha: opacity + 0.2)
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 2.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void render(Canvas canvas) {
|
void render(Canvas canvas) {
|
||||||
if (visionVertices.length < 3 || opacity <= 0.0) return;
|
if (visionVertices.length < 3 || opacity <= 0.0) return;
|
||||||
|
|
||||||
// Create path for vision cone
|
// Create path for vision cone
|
||||||
final path = Path();
|
final path = Path();
|
||||||
path.moveTo(visionVertices[0].x, visionVertices[0].y);
|
path.moveTo(visionVertices[0].x, visionVertices[0].y);
|
||||||
|
|
||||||
for (int i = 1; i < visionVertices.length; i++) {
|
for (int i = 1; i < visionVertices.length; i++) {
|
||||||
path.lineTo(visionVertices[i].x, visionVertices[i].y);
|
path.lineTo(visionVertices[i].x, visionVertices[i].y);
|
||||||
}
|
}
|
||||||
path.close();
|
path.close();
|
||||||
|
|
||||||
// Draw filled vision cone
|
// Draw filled vision cone
|
||||||
canvas.drawPath(path, fillPaint);
|
canvas.drawPath(path, fillPaint);
|
||||||
|
|
||||||
// Draw vision cone outline
|
// Draw vision cone outline
|
||||||
canvas.drawPath(path, strokePaint);
|
canvas.drawPath(path, strokePaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
@override
|
||||||
|
Future<void> reset() async {
|
||||||
|
// Reset vision cone state
|
||||||
|
direction = 0.0;
|
||||||
|
opacity = 0.3;
|
||||||
|
visionVertices.clear();
|
||||||
|
_calculateVisionCone();
|
||||||
|
appLog.finest('Vision cone reset');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
226
lib/game/levels/operation_shitstorm.dart
Normal file
226
lib/game/levels/operation_shitstorm.dart
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:shitman/game/levels/shit_level.dart';
|
||||||
|
import 'package:shitman/game/components/level_components.dart';
|
||||||
|
import 'package:shitman/game/components/road_components.dart';
|
||||||
|
import 'package:shitman/game/components/house_components.dart';
|
||||||
|
import 'package:shitman/game/components/security_components.dart';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
/// Level 1: Operation Shitstorm
|
||||||
|
/// A grid-based neighborhood with various house types and security systems
|
||||||
|
class OperationShitstorm extends ShitLevel {
|
||||||
|
static const double cellSize = 100.0;
|
||||||
|
static const int gridWidth = 5;
|
||||||
|
static const int gridHeight = 5;
|
||||||
|
|
||||||
|
List<List<LevelComponent?>> levelGrid = [];
|
||||||
|
List<HouseComponent> houses = [];
|
||||||
|
TargetHouseComponent? currentTarget;
|
||||||
|
|
||||||
|
OperationShitstorm() : super(levelName: "Operation: Shitstorm", difficulty: 1);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> initializeLevelComponents() async {
|
||||||
|
appLog.fine('Initializing Operation Shitstorm level layout');
|
||||||
|
|
||||||
|
// Initialize the grid
|
||||||
|
levelGrid = List.generate(
|
||||||
|
gridHeight,
|
||||||
|
(row) => List.generate(gridWidth, (col) => null),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create the level layout
|
||||||
|
await createLevelLayout();
|
||||||
|
|
||||||
|
// Add all components to the level
|
||||||
|
await addComponentsToLevel();
|
||||||
|
|
||||||
|
// Initialize core components (player, etc.)
|
||||||
|
await super.initializeLevelComponents();
|
||||||
|
|
||||||
|
// Select a random target house
|
||||||
|
selectRandomTarget();
|
||||||
|
|
||||||
|
appLog.fine('Operation Shitstorm level initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> createLevelLayout() async {
|
||||||
|
// Create a simple grid layout:
|
||||||
|
// H = House, R = Road (various types), I = Intersection
|
||||||
|
// Layout pattern:
|
||||||
|
// H R H R H
|
||||||
|
// R I R I R
|
||||||
|
// H R H R H
|
||||||
|
// R I R I R
|
||||||
|
// H R H R H
|
||||||
|
|
||||||
|
for (int row = 0; row < gridHeight; row++) {
|
||||||
|
for (int col = 0; col < gridWidth; col++) {
|
||||||
|
final gridPos = Vector2(col.toDouble(), row.toDouble());
|
||||||
|
|
||||||
|
if (row % 2 == 0) { // Even rows: Houses and vertical roads
|
||||||
|
if (col % 2 == 0) {
|
||||||
|
// House position
|
||||||
|
levelGrid[row][col] = createRandomHouse(gridPos);
|
||||||
|
} else {
|
||||||
|
// Vertical road
|
||||||
|
levelGrid[row][col] = VerticalRoad(gridPosition: gridPos);
|
||||||
|
}
|
||||||
|
} else { // Odd rows: Horizontal roads and intersections
|
||||||
|
if (col % 2 == 0) {
|
||||||
|
// Horizontal road
|
||||||
|
levelGrid[row][col] = HorizontalRoad(gridPosition: gridPos);
|
||||||
|
} else {
|
||||||
|
// Intersection
|
||||||
|
levelGrid[row][col] = IntersectionRoad(gridPosition: gridPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HouseComponent createRandomHouse(Vector2 gridPos) {
|
||||||
|
final random = Random();
|
||||||
|
final houseTypes = HouseType.values;
|
||||||
|
final selectedType = houseTypes[random.nextInt(houseTypes.length)];
|
||||||
|
|
||||||
|
final house = HouseComponent(
|
||||||
|
gridPosition: gridPos,
|
||||||
|
houseType: selectedType,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Randomly add security systems to some houses
|
||||||
|
if (random.nextDouble() < 0.4) { // 40% chance of security
|
||||||
|
addRandomSecurityToHouse(house, random);
|
||||||
|
}
|
||||||
|
|
||||||
|
houses.add(house);
|
||||||
|
return house;
|
||||||
|
}
|
||||||
|
|
||||||
|
void addRandomSecurityToHouse(HouseComponent house, Random random) {
|
||||||
|
final securityTypes = random.nextInt(3); // 0-2 different security types
|
||||||
|
|
||||||
|
switch (securityTypes) {
|
||||||
|
case 0:
|
||||||
|
// PIR sensor
|
||||||
|
final pirSensor = PIRSensorComponent(
|
||||||
|
position: Vector2(house.size.x * 0.9, house.size.y * 0.1),
|
||||||
|
);
|
||||||
|
house.addSecuritySystem(pirSensor);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
// Security camera
|
||||||
|
final camera = SecurityCameraComponent(
|
||||||
|
position: Vector2(house.size.x * 0.9, house.size.y * 0.1),
|
||||||
|
direction: _getOptimalCameraDirection(house),
|
||||||
|
);
|
||||||
|
house.addSecuritySystem(camera);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
// Guard dog
|
||||||
|
final dog = GuardDogComponent(
|
||||||
|
position: Vector2(-20, house.size.y + 10),
|
||||||
|
patrolCenter: Vector2(-20, house.size.y + 10),
|
||||||
|
);
|
||||||
|
house.addSecuritySystem(dog);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double _getOptimalCameraDirection(HouseComponent house) {
|
||||||
|
// Point camera towards center of level
|
||||||
|
final levelCenter = Vector2(gridWidth * cellSize / 2, gridHeight * cellSize / 2);
|
||||||
|
final houseCenter = house.getWorldPosition(cellSize) + house.size / 2;
|
||||||
|
final toCenter = levelCenter - houseCenter;
|
||||||
|
return atan2(toCenter.y, toCenter.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addComponentsToLevel() async {
|
||||||
|
for (int row = 0; row < gridHeight; row++) {
|
||||||
|
for (int col = 0; col < gridWidth; col++) {
|
||||||
|
final component = levelGrid[row][col];
|
||||||
|
if (component != null) {
|
||||||
|
// Set world position based on grid position
|
||||||
|
component.position = component.getWorldPosition(cellSize);
|
||||||
|
add(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectRandomTarget() {
|
||||||
|
if (houses.isEmpty) return;
|
||||||
|
|
||||||
|
// Clear previous target
|
||||||
|
currentTarget?.setAsTarget(false);
|
||||||
|
|
||||||
|
// Convert a random house to a target house
|
||||||
|
final random = Random();
|
||||||
|
final randomHouse = houses[random.nextInt(houses.length)];
|
||||||
|
|
||||||
|
// Remove the old house and create a new target house at the same position
|
||||||
|
remove(randomHouse);
|
||||||
|
houses.remove(randomHouse);
|
||||||
|
|
||||||
|
currentTarget = TargetHouseComponent(
|
||||||
|
gridPosition: randomHouse.gridPosition,
|
||||||
|
houseType: randomHouse.houseType,
|
||||||
|
);
|
||||||
|
currentTarget!.position = randomHouse.position;
|
||||||
|
currentTarget!.setAsTarget(true);
|
||||||
|
|
||||||
|
// Copy security systems
|
||||||
|
for (final security in randomHouse.securitySystems) {
|
||||||
|
currentTarget!.addSecuritySystem(security);
|
||||||
|
}
|
||||||
|
|
||||||
|
add(currentTarget!);
|
||||||
|
houses.add(currentTarget!);
|
||||||
|
|
||||||
|
appLog.info('Target selected at grid position ${currentTarget!.gridPosition}');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLevelStart() async {
|
||||||
|
appLog.info('Starting Operation: Shitstorm');
|
||||||
|
// Level-specific start logic can be added here
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLevelEnd() async {
|
||||||
|
appLog.info('Operation: Shitstorm completed');
|
||||||
|
// Level-specific end logic can be added here
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> reset() async {
|
||||||
|
await super.reset();
|
||||||
|
|
||||||
|
// Clear level-specific data
|
||||||
|
levelGrid.clear();
|
||||||
|
houses.clear();
|
||||||
|
currentTarget = null;
|
||||||
|
|
||||||
|
// Recreate the level
|
||||||
|
await createLevelLayout();
|
||||||
|
await addComponentsToLevel();
|
||||||
|
selectRandomTarget();
|
||||||
|
|
||||||
|
appLog.fine('Operation Shitstorm level reset');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current target house position
|
||||||
|
Vector2? getTargetPosition() {
|
||||||
|
return currentTarget?.doorPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if player is near the target
|
||||||
|
bool isPlayerNearTarget(Vector2 playerPosition, {double threshold = 50.0}) {
|
||||||
|
final targetPos = getTargetPosition();
|
||||||
|
if (targetPos == null) return false;
|
||||||
|
|
||||||
|
final distance = (playerPosition - targetPos).length;
|
||||||
|
return distance < threshold;
|
||||||
|
}
|
||||||
|
}
|
124
lib/game/levels/shit_level.dart
Normal file
124
lib/game/levels/shit_level.dart
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:shitman/attributes/resetable.dart';
|
||||||
|
import 'package:shitman/game/shitman_game.dart';
|
||||||
|
import 'package:shitman/game/components/base.dart';
|
||||||
|
import 'package:shitman/game/components/player.dart';
|
||||||
|
import 'package:shitman/services/log_service.dart';
|
||||||
|
import 'package:shitman/settings/app_settings.dart';
|
||||||
|
|
||||||
|
class ShitLevel extends PositionComponent
|
||||||
|
with HasGameReference<ShitmanGame>, Resetable, AppLogging, AppSettings {
|
||||||
|
/// Base class for game levels.
|
||||||
|
/// This can be extended to create specific levels with unique layouts
|
||||||
|
|
||||||
|
String levelName;
|
||||||
|
int difficulty;
|
||||||
|
bool _isActive = false;
|
||||||
|
|
||||||
|
// Core game components
|
||||||
|
late Player player;
|
||||||
|
|
||||||
|
ShitLevel({required this.levelName, this.difficulty = 1});
|
||||||
|
|
||||||
|
/// Whether the level is currently active
|
||||||
|
bool get isActive => _isActive;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
await initSettings();
|
||||||
|
|
||||||
|
// Initialize level-specific components
|
||||||
|
await initializeLevelComponents();
|
||||||
|
|
||||||
|
appLog.fine('Level loaded: $levelName (difficulty: $difficulty)');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize the core components for this level
|
||||||
|
Future<void> initializeLevelComponents() async {
|
||||||
|
appLog.fine('Initializing level components for $levelName');
|
||||||
|
|
||||||
|
// Create player
|
||||||
|
player = Player();
|
||||||
|
add(player);
|
||||||
|
|
||||||
|
// Setup camera to follow player if game reference is available
|
||||||
|
try {
|
||||||
|
game.camera.follow(player);
|
||||||
|
} catch (e) {
|
||||||
|
appLog.warning('Unable to setup camera following: $e');
|
||||||
|
}
|
||||||
|
|
||||||
|
appLog.fine('Level components initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start the level gameplay
|
||||||
|
Future<void> startLevel() async {
|
||||||
|
if (_isActive) return;
|
||||||
|
|
||||||
|
_isActive = true;
|
||||||
|
appLog.info('Starting level: $levelName');
|
||||||
|
|
||||||
|
// Any level-specific start logic can be added here
|
||||||
|
await onLevelStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// End the level gameplay
|
||||||
|
Future<void> endLevel() async {
|
||||||
|
if (!_isActive) return;
|
||||||
|
|
||||||
|
_isActive = false;
|
||||||
|
appLog.info('Ending level: $levelName');
|
||||||
|
|
||||||
|
// Any level-specific end logic can be added here
|
||||||
|
await onLevelEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override this method in subclasses for custom start logic
|
||||||
|
Future<void> onLevelStart() async {
|
||||||
|
// Default implementation does nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override this method in subclasses for custom end logic
|
||||||
|
Future<void> onLevelEnd() async {
|
||||||
|
// Default implementation does nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all components in this level that implement Resetable
|
||||||
|
List<Resetable> getResetableComponents() {
|
||||||
|
return children.whereType<Resetable>().toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all ShitComponents in this level
|
||||||
|
List<ShitComponent> getAllShitComponents() {
|
||||||
|
return children.whereType<ShitComponent>().toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset all components in this level
|
||||||
|
Future<void> resetAllComponents() async {
|
||||||
|
appLog.fine('Resetting all components in level: $levelName');
|
||||||
|
|
||||||
|
final resetableComponents = getResetableComponents();
|
||||||
|
for (final component in resetableComponents) {
|
||||||
|
await component.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> reset() async {
|
||||||
|
appLog.fine('Resetting level: $levelName');
|
||||||
|
|
||||||
|
_isActive = false;
|
||||||
|
|
||||||
|
// Reset all child components first
|
||||||
|
await resetAllComponents();
|
||||||
|
|
||||||
|
// Override in subclasses for additional reset logic
|
||||||
|
await onLevelReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Override this method in subclasses for custom reset logic
|
||||||
|
Future<void> onLevelReset() async {
|
||||||
|
// Default implementation does nothing
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,8 +4,8 @@ import 'package:flame/events.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:shitman/game/components/player.dart';
|
import 'package:shitman/game/components/player.dart';
|
||||||
import 'package:shitman/game/components/neighborhood.dart';
|
import 'package:shitman/game/levels/operation_shitstorm.dart';
|
||||||
import 'package:shitman/game/components/target_house.dart';
|
import 'package:shitman/game/shitman_world.dart';
|
||||||
import 'package:shitman/settings/app_settings.dart';
|
import 'package:shitman/settings/app_settings.dart';
|
||||||
|
|
||||||
/// Shitman Game
|
/// Shitman Game
|
||||||
|
@ -18,8 +18,7 @@ enum GameState { mainMenu, playing, paused, gameOver, missionComplete }
|
||||||
class ShitmanGame extends FlameGame
|
class ShitmanGame extends FlameGame
|
||||||
with HasKeyboardHandlerComponents, HasCollisionDetection, AppSettings {
|
with HasKeyboardHandlerComponents, HasCollisionDetection, AppSettings {
|
||||||
late Player player;
|
late Player player;
|
||||||
late Neighborhood neighborhood;
|
late OperationShitstorm currentLevel;
|
||||||
late TargetHouse targetHouse;
|
|
||||||
late CameraComponent gameCamera;
|
late CameraComponent gameCamera;
|
||||||
|
|
||||||
GameState gameState = GameState.mainMenu;
|
GameState gameState = GameState.mainMenu;
|
||||||
|
@ -31,6 +30,7 @@ class ShitmanGame extends FlameGame
|
||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
await super.onLoad();
|
await super.onLoad();
|
||||||
await initSettings();
|
await initSettings();
|
||||||
|
world = ShitmanWorld();
|
||||||
|
|
||||||
// Setup camera
|
// Setup camera
|
||||||
gameCamera = CameraComponent.withFixedResolution(
|
gameCamera = CameraComponent.withFixedResolution(
|
||||||
|
@ -49,16 +49,16 @@ class ShitmanGame extends FlameGame
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void startGame() {
|
Future<void> startGame() async {
|
||||||
gameState = GameState.playing;
|
gameState = GameState.playing;
|
||||||
initializeLevel();
|
await initializeLevel();
|
||||||
}
|
}
|
||||||
|
|
||||||
void startInfiniteMode() {
|
Future<void> startInfiniteMode() async {
|
||||||
infiniteMode = true;
|
infiniteMode = true;
|
||||||
overlays.remove('MainMenu');
|
overlays.remove('MainMenu');
|
||||||
overlays.add('InGameUI');
|
overlays.add('InGameUI');
|
||||||
startGame();
|
await startGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
void stopGame() {
|
void stopGame() {
|
||||||
|
@ -75,21 +75,21 @@ class ShitmanGame extends FlameGame
|
||||||
gameState = GameState.playing;
|
gameState = GameState.playing;
|
||||||
}
|
}
|
||||||
|
|
||||||
void initializeLevel() {
|
Future<void> initializeLevel() async {
|
||||||
// Clear previous level
|
// Clear previous level
|
||||||
world.removeAll(world.children);
|
world.removeAll(world.children);
|
||||||
|
|
||||||
// Create neighborhood
|
// Create the Operation Shitstorm level
|
||||||
neighborhood = Neighborhood();
|
currentLevel = OperationShitstorm();
|
||||||
world.add(neighborhood);
|
|
||||||
|
// Add and wait for the level to load
|
||||||
|
await world.add(currentLevel);
|
||||||
|
|
||||||
// Create target house
|
// Start the level
|
||||||
targetHouse = TargetHouse();
|
await currentLevel.startLevel();
|
||||||
world.add(targetHouse);
|
|
||||||
|
|
||||||
// Create player
|
// Get the player from the level
|
||||||
player = Player();
|
player = currentLevel.player;
|
||||||
world.add(player);
|
|
||||||
|
|
||||||
// Setup camera to follow player
|
// Setup camera to follow player
|
||||||
gameCamera.follow(player);
|
gameCamera.follow(player);
|
||||||
|
@ -101,9 +101,9 @@ class ShitmanGame extends FlameGame
|
||||||
totalMissions++;
|
totalMissions++;
|
||||||
|
|
||||||
if (infiniteMode) {
|
if (infiniteMode) {
|
||||||
// Generate new mission after delay
|
// Generate new mission after delay, but keep allowing input
|
||||||
Future.delayed(Duration(seconds: 2), () {
|
Future.delayed(Duration(seconds: 2), () {
|
||||||
initializeLevel();
|
currentLevel.selectRandomTarget();
|
||||||
gameState = GameState.playing;
|
gameState = GameState.playing;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -120,7 +120,11 @@ class ShitmanGame extends FlameGame
|
||||||
Set<LogicalKeyboardKey> keysPressed,
|
Set<LogicalKeyboardKey> keysPressed,
|
||||||
) {
|
) {
|
||||||
super.onKeyEvent(event, keysPressed);
|
super.onKeyEvent(event, keysPressed);
|
||||||
if (gameState != GameState.playing) return KeyEventResult.ignored;
|
if (gameState != GameState.playing &&
|
||||||
|
gameState != GameState.missionComplete &&
|
||||||
|
gameState != GameState.gameOver) {
|
||||||
|
return KeyEventResult.ignored;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle pause
|
// Handle pause
|
||||||
if (keysPressed.contains(LogicalKeyboardKey.escape)) {
|
if (keysPressed.contains(LogicalKeyboardKey.escape)) {
|
||||||
|
@ -137,6 +141,9 @@ class ShitmanGame extends FlameGame
|
||||||
player.handleAction(event.logicalKey);
|
player.handleAction(event.logicalKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mission completion is now handled by the player's escape sequence
|
||||||
|
// No automatic completion when near target
|
||||||
|
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
105
lib/game/shitman_world.dart
Normal file
105
lib/game/shitman_world.dart
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:shitman/attributes/resetable.dart';
|
||||||
|
import 'package:shitman/game/levels/shit_level.dart';
|
||||||
|
import 'package:shitman/game/components/base.dart';
|
||||||
|
import 'package:shitman/services/log_service.dart';
|
||||||
|
import 'package:shitman/settings/app_settings.dart';
|
||||||
|
|
||||||
|
class ShitmanWorld extends World with Resetable, AppLogging, AppSettings {
|
||||||
|
ShitLevel? _currentLevel;
|
||||||
|
bool _isInitialized = false;
|
||||||
|
|
||||||
|
/// Get the current level
|
||||||
|
ShitLevel? get currentLevel => _currentLevel;
|
||||||
|
|
||||||
|
/// Check if world is initialized
|
||||||
|
bool get isInitialized => _isInitialized;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
await initSettings();
|
||||||
|
_isInitialized = true;
|
||||||
|
appLog.fine('ShitmanWorld initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load a specific level
|
||||||
|
Future<void> loadLevel(ShitLevel level) async {
|
||||||
|
appLog.fine('Loading level: ${level.levelName}');
|
||||||
|
|
||||||
|
// Reset current level if one exists
|
||||||
|
if (_currentLevel != null) {
|
||||||
|
await _currentLevel!.reset();
|
||||||
|
_currentLevel!.removeFromParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set and load new level
|
||||||
|
_currentLevel = level;
|
||||||
|
add(_currentLevel!);
|
||||||
|
await _currentLevel!.onLoad();
|
||||||
|
_currentLevel!.startLevel();
|
||||||
|
|
||||||
|
appLog.info('Level loaded: ${level.levelName}');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unload the current level
|
||||||
|
Future<void> unloadLevel() async {
|
||||||
|
if (_currentLevel == null) return;
|
||||||
|
|
||||||
|
appLog.fine('Unloading level: ${_currentLevel!.levelName}');
|
||||||
|
|
||||||
|
_currentLevel!.endLevel();
|
||||||
|
await _currentLevel!.reset();
|
||||||
|
_currentLevel!.removeFromParent();
|
||||||
|
_currentLevel = null;
|
||||||
|
|
||||||
|
appLog.fine('Level unloaded');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset all components in the world that implement Resetable
|
||||||
|
Future<void> resetAllComponents() async {
|
||||||
|
appLog.fine('Resetting all world components');
|
||||||
|
|
||||||
|
final resetableComponents = children.whereType<Resetable>().toList();
|
||||||
|
for (final component in resetableComponents) {
|
||||||
|
await component.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also reset the current level
|
||||||
|
if (_currentLevel != null) {
|
||||||
|
await _currentLevel!.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
appLog.fine('All components reset');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all components of a specific type
|
||||||
|
List<T> getComponentsOfType<T extends Component>() {
|
||||||
|
return children.whereType<T>().toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all ShitComponents
|
||||||
|
List<ShitComponent> getAllShitComponents() {
|
||||||
|
return children.whereType<ShitComponent>().toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear all components from the world
|
||||||
|
Future<void> clearWorld() async {
|
||||||
|
appLog.fine('Clearing world');
|
||||||
|
|
||||||
|
// Reset all resetable components before removing
|
||||||
|
await resetAllComponents();
|
||||||
|
|
||||||
|
// Remove all children
|
||||||
|
removeAll(children);
|
||||||
|
_currentLevel = null;
|
||||||
|
|
||||||
|
appLog.fine('World cleared');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> reset() async {
|
||||||
|
appLog.fine('Resetting ShitmanWorld');
|
||||||
|
await clearWorld();
|
||||||
|
}
|
||||||
|
}
|
258
lib/services/log_service.dart
Normal file
258
lib/services/log_service.dart
Normal file
|
@ -0,0 +1,258 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:collection';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:isolate';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
|
mixin class AppLogging {
|
||||||
|
final LogService appLog = LogService();
|
||||||
|
}
|
||||||
|
|
||||||
|
class LogEntry {
|
||||||
|
final DateTime timestamp;
|
||||||
|
final Level level;
|
||||||
|
final String message;
|
||||||
|
final String? logger;
|
||||||
|
final Object? error;
|
||||||
|
final StackTrace? stackTrace;
|
||||||
|
|
||||||
|
LogEntry({
|
||||||
|
required this.timestamp,
|
||||||
|
required this.level,
|
||||||
|
required this.message,
|
||||||
|
this.logger,
|
||||||
|
this.error,
|
||||||
|
this.stackTrace,
|
||||||
|
});
|
||||||
|
|
||||||
|
String format() {
|
||||||
|
final buffer = StringBuffer();
|
||||||
|
buffer.write('${timestamp.toIso8601String()} ');
|
||||||
|
buffer.write('[${level.name.toUpperCase().padRight(8)}] ');
|
||||||
|
if (logger != null) buffer.write('$logger: ');
|
||||||
|
buffer.write(message);
|
||||||
|
if (error != null) buffer.write(' | Error: $error');
|
||||||
|
if (stackTrace != null) buffer.write('\n$stackTrace');
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LogService {
|
||||||
|
static final LogService _instance = LogService._internal();
|
||||||
|
static LogService get instance => _instance;
|
||||||
|
static const String defaultLoggerName = 'ShitMan-Game';
|
||||||
|
|
||||||
|
final Queue<LogEntry> _buffer = Queue<LogEntry>();
|
||||||
|
final StreamController<LogEntry> _controller =
|
||||||
|
StreamController<LogEntry>.broadcast();
|
||||||
|
|
||||||
|
Timer? _flushTimer;
|
||||||
|
Isolate? _fileIsolate;
|
||||||
|
SendPort? _fileSendPort;
|
||||||
|
|
||||||
|
bool _isFileLoggingEnabled = false;
|
||||||
|
String? _logFilePath;
|
||||||
|
Level _minLevel = Level.INFO;
|
||||||
|
|
||||||
|
static const int _bufferSize = 100;
|
||||||
|
static const Duration _flushInterval = Duration(seconds: 1);
|
||||||
|
|
||||||
|
factory LogService() => _instance;
|
||||||
|
|
||||||
|
LogService._internal() {
|
||||||
|
_initializeLogger();
|
||||||
|
_setupPeriodicFlush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _initializeLogger() {
|
||||||
|
Logger.root.level = Level.ALL;
|
||||||
|
Logger.root.onRecord.listen((record) {
|
||||||
|
final level = record.level;
|
||||||
|
if (level >= _minLevel) {
|
||||||
|
_addEntry(
|
||||||
|
LogEntry(
|
||||||
|
timestamp: record.time,
|
||||||
|
level: level,
|
||||||
|
message: record.message,
|
||||||
|
logger: record.loggerName,
|
||||||
|
error: record.error,
|
||||||
|
stackTrace: record.stackTrace,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setupPeriodicFlush() {
|
||||||
|
_flushTimer = Timer.periodic(_flushInterval, (_) => _flushBuffer());
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addEntry(LogEntry entry) {
|
||||||
|
_buffer.add(entry);
|
||||||
|
_controller.add(entry);
|
||||||
|
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint(entry.format());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_buffer.length >= _bufferSize) {
|
||||||
|
_flushBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _flushBuffer() {
|
||||||
|
if (_buffer.isEmpty || !_isFileLoggingEnabled || _fileSendPort == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final entries = List<LogEntry>.from(_buffer);
|
||||||
|
_buffer.clear();
|
||||||
|
|
||||||
|
_fileSendPort!.send(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> enableFileLogging({String? customPath}) async {
|
||||||
|
if (_isFileLoggingEnabled) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
_logFilePath = customPath ?? 'logs/shitman.log';
|
||||||
|
|
||||||
|
final receivePort = ReceivePort();
|
||||||
|
_fileIsolate = await Isolate.spawn(_fileLoggingIsolate, [
|
||||||
|
receivePort.sendPort,
|
||||||
|
_logFilePath!,
|
||||||
|
]);
|
||||||
|
|
||||||
|
_fileSendPort = await receivePort.first as SendPort;
|
||||||
|
_isFileLoggingEnabled = true;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Failed to enable file logging: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _fileLoggingIsolate(List<dynamic> args) async {
|
||||||
|
final SendPort mainSendPort = args[0];
|
||||||
|
final String logFilePath = args[1];
|
||||||
|
|
||||||
|
final receivePort = ReceivePort();
|
||||||
|
mainSendPort.send(receivePort.sendPort);
|
||||||
|
|
||||||
|
IOSink? logFile;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final file = File(logFilePath);
|
||||||
|
await file.parent.create(recursive: true);
|
||||||
|
logFile = file.openWrite(mode: FileMode.append);
|
||||||
|
|
||||||
|
await for (final data in receivePort) {
|
||||||
|
if (data is List<LogEntry>) {
|
||||||
|
for (final entry in data) {
|
||||||
|
logFile.writeln(entry.format());
|
||||||
|
}
|
||||||
|
await logFile.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('File logging isolate error: $e');
|
||||||
|
} finally {
|
||||||
|
await logFile?.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setMinLevel(Level level) {
|
||||||
|
_minLevel = level;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<LogEntry> get logStream => _controller.stream;
|
||||||
|
|
||||||
|
void log(
|
||||||
|
String message, {
|
||||||
|
Level level = Level.INFO,
|
||||||
|
String? logger,
|
||||||
|
Object? error,
|
||||||
|
StackTrace? stackTrace,
|
||||||
|
}) {
|
||||||
|
Logger(logger ?? defaultLoggerName).log(level, message, error, stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
void finest(
|
||||||
|
String message, {
|
||||||
|
String? logger,
|
||||||
|
Object? error,
|
||||||
|
StackTrace? stackTrace,
|
||||||
|
}) {
|
||||||
|
Logger(logger ?? defaultLoggerName).finest(message, error, stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
void finer(
|
||||||
|
String message, {
|
||||||
|
String? logger,
|
||||||
|
Object? error,
|
||||||
|
StackTrace? stackTrace,
|
||||||
|
}) {
|
||||||
|
Logger(logger ?? defaultLoggerName).finer(message, error, stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fine(
|
||||||
|
String message, {
|
||||||
|
String? logger,
|
||||||
|
Object? error,
|
||||||
|
StackTrace? stackTrace,
|
||||||
|
}) {
|
||||||
|
Logger(logger ?? defaultLoggerName).fine(message, error, stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
void config(
|
||||||
|
String message, {
|
||||||
|
String? logger,
|
||||||
|
Object? error,
|
||||||
|
StackTrace? stackTrace,
|
||||||
|
}) {
|
||||||
|
Logger(logger ?? defaultLoggerName).config(message, error, stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
void info(
|
||||||
|
String message, {
|
||||||
|
String? logger,
|
||||||
|
Object? error,
|
||||||
|
StackTrace? stackTrace,
|
||||||
|
}) {
|
||||||
|
Logger(logger ?? defaultLoggerName).info(message, error, stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
void warning(
|
||||||
|
String message, {
|
||||||
|
String? logger,
|
||||||
|
Object? error,
|
||||||
|
StackTrace? stackTrace,
|
||||||
|
}) {
|
||||||
|
Logger(logger ?? defaultLoggerName).warning(message, error, stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
void severe(
|
||||||
|
String message, {
|
||||||
|
String? logger,
|
||||||
|
Object? error,
|
||||||
|
StackTrace? stackTrace,
|
||||||
|
}) {
|
||||||
|
Logger(logger ?? defaultLoggerName).severe(message, error, stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
void shout(
|
||||||
|
String message, {
|
||||||
|
String? logger,
|
||||||
|
Object? error,
|
||||||
|
StackTrace? stackTrace,
|
||||||
|
}) {
|
||||||
|
Logger(logger ?? defaultLoggerName).shout(message, error, stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> dispose() async {
|
||||||
|
_flushTimer?.cancel();
|
||||||
|
_flushBuffer();
|
||||||
|
await _controller.close();
|
||||||
|
_fileIsolate?.kill();
|
||||||
|
}
|
||||||
|
}
|
|
@ -116,10 +116,10 @@ class MainMenuUI extends StatelessWidget with AppSettings {
|
||||||
children: [
|
children: [
|
||||||
NesButton(
|
NesButton(
|
||||||
type: NesButtonType.primary,
|
type: NesButtonType.primary,
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
game.overlays.remove(MainMenuUI.overlayID);
|
game.overlays.remove(MainMenuUI.overlayID);
|
||||||
game.overlays.add(InGameUI.overlayID);
|
game.overlays.add(InGameUI.overlayID);
|
||||||
game.startGame();
|
await game.startGame();
|
||||||
},
|
},
|
||||||
child: Text('menu.start_mission'.tr()),
|
child: Text('menu.start_mission'.tr()),
|
||||||
),
|
),
|
||||||
|
@ -135,7 +135,7 @@ class MainMenuUI extends StatelessWidget with AppSettings {
|
||||||
SizedBox(height: 16),
|
SizedBox(height: 16),
|
||||||
NesButton(
|
NesButton(
|
||||||
type: NesButtonType.normal,
|
type: NesButtonType.normal,
|
||||||
onPressed: () => game.startInfiniteMode(),
|
onPressed: () async => await game.startInfiniteMode(),
|
||||||
child: Text('menu.infinite_mode'.tr()),
|
child: Text('menu.infinite_mode'.tr()),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
10
pubspec.lock
10
pubspec.lock
|
@ -269,6 +269,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.1.1"
|
version: "5.1.1"
|
||||||
|
logging:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -286,7 +294,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.11.1"
|
version: "0.11.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
name: shitman
|
name: shitman
|
||||||
description: "Hitman, but with shit."
|
description: "Hitman, but shit."
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
@ -16,6 +16,8 @@ dependencies:
|
||||||
google_fonts: ^6.2.1
|
google_fonts: ^6.2.1
|
||||||
easy_localization: ^3.0.7+1
|
easy_localization: ^3.0.7+1
|
||||||
shared_preferences: ^2.2.2
|
shared_preferences: ^2.2.2
|
||||||
|
meta: ^1.16.0
|
||||||
|
logging: ^1.3.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#200E29",
|
"background_color": "#200E29",
|
||||||
"theme_color": "#ab519f",
|
"theme_color": "#ab519f",
|
||||||
"description": "Hitman, but with shit.",
|
"description": "Hitman, but shit.",
|
||||||
"orientation": "portrait-primary",
|
"orientation": "portrait-primary",
|
||||||
"prefer_related_applications": false,
|
"prefer_related_applications": false,
|
||||||
"icons": [
|
"icons": [
|
||||||
|
@ -32,4 +32,4 @@
|
||||||
"purpose": "maskable"
|
"purpose": "maskable"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue