shitman/lib/game/components/player.dart
zeyus f7a08a5099
All checks were successful
/ build-web (push) Successful in 3m24s
Added touch controls.
2025-08-04 11:50:47 +02:00

329 lines
9.3 KiB
Dart

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<void> 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<LogicalKeyboardKey> 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<OperationShitstorm>().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<OperationShitstorm>().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<HouseComponent>();
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<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');
}
}