import 'package:flame/components.dart'; import 'package:flutter/services.dart'; import 'package:flutter/material.dart'; import 'package:shitman/game/components/poop_bag.dart'; import 'package:shitman/game/components/house_components.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/game/input_manager.dart'; import 'package:shitman/settings/app_settings.dart'; import 'dart:math'; class Player extends InteractiveShit with Ambulatory, AppSettings { static const double speed = 100.0; static const double playerSize = 32.0; Vector2 velocity = Vector2.zero(); bool hasPoopBag = true; bool isHidden = false; bool isCaught = false; double stealthLevel = 0.0; // 0.0 = fully visible, 1.0 = completely hidden PoopBag? placedPoopBag; VisionCone? playerVisionCone; double lastMovementDirection = 0.0; // Mission state bool isEscaping = false; double escapeTimeLimit = 30.0; // 30 seconds to escape double escapeTimeRemaining = 0.0; // Input manager for handling both keyboard and touch input final InputManager inputManager = InputManager(); @override Future onLoad() async { await super.onLoad(); await initSettings(); // Create a simple colored rectangle as player size = Vector2.all(playerSize); position = Vector2(200, 200); // Start at center intersection // Create player vision cone playerVisionCone = VisionCone( origin: size / 2, // Relative to player center direction: lastMovementDirection, range: 80.0, fov: pi / 2, // 90 degrees color: Colors.blue, opacity: 0.0, // Start hidden ); add(playerVisionCone!); appLog.fine('Player loaded at position $position'); } void handleInput(Set keysPressed) { // Don't handle input if caught if (isCaught) { velocity = Vector2.zero(); inputManager.reset(); return; } // Update input manager with keyboard input inputManager.setKeyboardMovement(keysPressed); // Apply movement from input manager final movement = inputManager.movementInput; velocity = movement * speed; } void handleAction(LogicalKeyboardKey key) { // Don't handle actions if caught if (isCaught) return; // Action controls if (key == LogicalKeyboardKey.space) { placePoop(); } if (key == LogicalKeyboardKey.keyE) { ringDoorbell(); } } /// Handle touch input from virtual controls void handleTouchMovement(Vector2 input) { if (isCaught) return; inputManager.setTouchMovement(input); // Apply movement from input manager final movement = inputManager.movementInput; velocity = movement * speed; } /// Handle touch actions void handleTouchPoopBag() { if (isCaught) return; placePoop(); } void handleTouchDoorbell() { if (isCaught) return; ringDoorbell(); } void updateStealthLevel(double dt) { // Simple stealth calculation - can be enhanced later // For now, player is more hidden when moving slowly or not at all if (velocity.length < 50) { stealthLevel = (stealthLevel + dt * 0.5).clamp(0.0, 1.0); } else { stealthLevel = (stealthLevel - dt * 1.5).clamp(0.0, 1.0); } isHidden = stealthLevel > 0.7; } void placePoop() { if (!hasPoopBag) return; appLog.fine('Placing poop bag at $position'); // Create and place the poop bag placedPoopBag = PoopBag(); placedPoopBag!.position = position + Vector2(playerSize / 2, playerSize + 10); game.world.add(placedPoopBag!); hasPoopBag = false; // Check if near target house checkMissionProgress(); } void ringDoorbell() { appLog.fine('Attempting to ring doorbell'); // Check if near any target house door final currentLevel = game.world.children.whereType().firstOrNull; if (currentLevel != null && currentLevel.isPlayerNearTarget(position)) { if (placedPoopBag != null) { // Light the poop bag on fire placedPoopBag!.lightOnFire(); appLog.info('Ding dong! Poop bag is lit! RUN!'); // Start escape timer - player has limited time to escape startEscapeSequence(); } else { appLog.fine('Need to place poop bag first!'); } } else { appLog.fine('Not near target house door'); } } void startEscapeSequence() { appLog.info('Escape sequence started! Get to the edge of the map!'); isEscaping = true; escapeTimeRemaining = escapeTimeLimit; // 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() { // Check if near target house and has placed poop bag final currentLevel = game.world.children.whereType().firstOrNull; final targetPos = currentLevel?.getTargetPosition(); if (targetPos != null && placedPoopBag != null) { final distance = (position - targetPos).length; if (distance < 80) { appLog.fine('Near target house with poop bag placed!'); } } } void getDetected() { appLog.warning('Player detected! Mission failed!'); isCaught = true; velocity = Vector2.zero(); // Stop movement immediately game.failMission(); } @override void update(double dt) { super.update(dt); // Apply movement if (velocity.length > 0) { velocity = velocity.normalized() * speed; position += velocity * dt; // Update movement direction for vision cone lastMovementDirection = atan2(velocity.y, velocity.x); // Keep player on screen (basic bounds checking) position.x = position.x.clamp(0, 800 - size.x); position.y = position.y.clamp(0, 600 - size.y); } // Update stealth level based on environment updateStealthLevel(dt); // Handle escape sequence 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 if (playerVisionCone != null) { playerVisionCone!.updatePosition(size / 2, lastMovementDirection); // Update vision cone visibility based on settings try { final showVisionCones = appSettings.getBool('game.show_vision_cones'); playerVisionCone!.updateOpacity(showVisionCones ? 0.2 : 0.0); } catch (e) { playerVisionCone!.updateOpacity(0.0); // Hide if settings not ready } } } void checkForDetection() { // Check for detection from houses in any active level final houses = game.world.children .expand((component) => component.children) .whereType(); for (final house in houses) { if (house.detectsPlayer(position, stealthLevel)) { getDetected(); break; } } } @override void render(Canvas canvas) { // Draw player as rectangle with color based on stealth level final playerPaint = Paint() ..color = isHidden ? const Color(0xFF00FF00).withValues( alpha: 0.7, ) // Green when hidden : const Color( 0xFF0000FF, ).withValues(alpha: 0.9); // Blue when visible canvas.drawRect(size.toRect(), playerPaint); // Draw stealth indicator in debug mode try { final debugMode = appSettings.getBool('game.debug_mode'); if (debugMode) { final stealthPaint = Paint() ..color = Color.lerp( const Color(0xFFFF0000), const Color(0xFF00FF00), stealthLevel, )!; canvas.drawRect(Rect.fromLTWH(-5, -10, size.x + 10, 5), stealthPaint); } } catch (e) { // Continue without debug rendering if settings not available } } @override Future 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'); } }