feat(phase2): aircraft market, fleet management, finances, loans

- market.php: Buy new aircraft (6 types: Dash8 to A380)
- aircraft.php: Fleet management, crew assignment, route planning
- finances.php: Account balance, transactions, loans
- Database.php: Extended with loan/transaction/aircraft methods
- schema.sql: Aircraft types table, 10M starting balance, INSERT OR IGNORE
- dashboard.php: Updated with fleet/financial overview
- Fixed: duplicate function declarations in Database.php
- Fixed: initializeSchema() with idempotent error handling
This commit is contained in:
2026-04-24 17:27:02 +02:00
parent ab5f97acbf
commit 12ddf5a185
9 changed files with 2052 additions and 36 deletions

View File

@@ -8,7 +8,7 @@ CREATE TABLE IF NOT EXISTS users (
email VARCHAR(100) UNIQUE NOT NULL, email VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL, password_hash VARCHAR(255) NOT NULL,
airline_name VARCHAR(100) DEFAULT '', airline_name VARCHAR(100) DEFAULT '',
balance DECIMAL(15,2) DEFAULT 500000.00, balance DECIMAL(15,2) DEFAULT 10000000.00,
level INTEGER DEFAULT 1, level INTEGER DEFAULT 1,
experience INTEGER DEFAULT 0, experience INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP, created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
@@ -92,10 +92,55 @@ CREATE TABLE IF NOT EXISTS daily_stats (
total_revenue DECIMAL(15,2) DEFAULT 0.00, total_revenue DECIMAL(15,2) DEFAULT 0.00,
total_profit DECIMAL(15,2) DEFAULT 0.00, total_profit DECIMAL(15,2) DEFAULT 0.00,
fuel_cost DECIMAL(15,2) DEFAULT 0.00, fuel_cost DECIMAL(15,2) DEFAULT 0.00,
maintenance_cost DECIMAL(15,2) DEFAULT 0.00,
UNIQUE(user_id, date), UNIQUE(user_id, date),
FOREIGN KEY (user_id) REFERENCES users(id) FOREIGN KEY (user_id) REFERENCES users(id)
); );
-- Kredite
CREATE TABLE IF NOT EXISTS loans (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
principal DECIMAL(15,2) NOT NULL,
current_amount DECIMAL(15,2) NOT NULL,
interest_rate DECIMAL(5,4) DEFAULT 0.02,
daily_interest DECIMAL(15,2) DEFAULT 0.00,
days_outstanding INTEGER DEFAULT 0,
status VARCHAR(20) DEFAULT 'active',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
due_date DATE,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- Transaktionen (alle Buchungen)
CREATE TABLE IF NOT EXISTS transactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
type VARCHAR(50) NOT NULL,
category VARCHAR(50) NOT NULL,
amount DECIMAL(15,2) NOT NULL,
balance_after DECIMAL(15,2) NOT NULL,
description TEXT DEFAULT '',
reference_id INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- Wartungshistorie
CREATE TABLE IF NOT EXISTS maintenance_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
aircraft_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
maintenance_type VARCHAR(50) DEFAULT 'scheduled',
cost DECIMAL(15,2) NOT NULL,
condition_before DECIMAL(5,2) DEFAULT 100.00,
condition_after DECIMAL(5,2) DEFAULT 100.00,
notes TEXT DEFAULT '',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (aircraft_id) REFERENCES aircraft(id),
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- Events (Werbeaktionen, Wettbewerbe) -- Events (Werbeaktionen, Wettbewerbe)
CREATE TABLE IF NOT EXISTS events ( CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -114,3 +159,27 @@ CREATE INDEX IF NOT EXISTS idx_flights_user ON flights(user_id);
CREATE INDEX IF NOT EXISTS idx_flights_route ON flights(route_id); CREATE INDEX IF NOT EXISTS idx_flights_route ON flights(route_id);
CREATE INDEX IF NOT EXISTS idx_aircraft_user ON aircraft(user_id); CREATE INDEX IF NOT EXISTS idx_aircraft_user ON aircraft(user_id);
CREATE INDEX IF NOT EXISTS idx_airports_iata ON airports(iata_code); CREATE INDEX IF NOT EXISTS idx_airports_iata ON airports(iata_code);
-- Flugzeugtypen (Referenztabelle)
CREATE TABLE IF NOT EXISTS aircraft_types (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code VARCHAR(20) UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
manufacturer VARCHAR(50) NOT NULL,
range_km INTEGER NOT NULL,
capacity INTEGER NOT NULL,
buy_price DECIMAL(15,2) NOT NULL,
crew_cost_per_tick DECIMAL(10,2) NOT NULL,
fuel_per_km DECIMAL(6,4) DEFAULT 0.1500,
maintenance_interval INTEGER DEFAULT 50,
description TEXT DEFAULT '',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
INSERT OR IGNORE INTO aircraft_types (code, name, manufacturer, range_km, capacity, buy_price, crew_cost_per_tick, fuel_per_km, maintenance_interval, description) VALUES
('dash8-100', 'Dash 8-100', 'Bombardier', 1500, 37, 500000.00, 200.00, 0.12, 50, 'Kleiner RegionalturboProp mit 37 Sitzen'),
('atr72-500', 'ATR 72-500', 'ATR', 1650, 70, 1500000.00, 300.00, 0.15, 50, 'Mittlerer RegionalturboProp mit 70 Sitzen'),
('a320-200', 'A320-200', 'Airbus', 5700, 150, 8000000.00, 600.00, 0.25, 50, 'Kurz-/Mittelstrecken-Jet mit 150 Sitzen'),
('b737-800', 'B737-800', 'Boeing', 5700, 162, 9500000.00, 650.00, 0.26, 50, 'Kurz-/Mittelstrecken-Jet mit 162 Sitzen'),
('b777-200', 'B777-200', 'Boeing', 12000, 300, 35000000.00, 1200.00, 0.55, 50, 'Langstrecken-Widebody mit 300 Sitzen'),
('a380-800', 'A380-800', 'Airbus', 15200, 525, 85000000.00, 2000.00, 0.80, 50, 'Doppelstöckiger Langstrecken-Airliner mit 525 Sitzen');

371
www/public/aircraft.php Normal file
View File

@@ -0,0 +1,371 @@
<?php
/**
* Airline Tycoon - Flotten-Übersicht
* Zeigt alle Flugzeuge des Spielers mit Zustand und Wartungsstatus
*/
require_once __DIR__ . '/../src/bootstrap.php';
// Auth Check
if (!Session::isLoggedIn()) {
header('Location: login.php');
exit;
}
$userId = Session::getUserId();
$user = $db->getUserById($userId);
$aircraft = $db->getUserAircraft($userId);
$config = $db->getConfig();
// Wartungsstatus prüfen
$maintenanceDue = $db->checkMaintenanceDue($userId);
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flotte - <?= htmlspecialchars($user['airline_name']) ?> - <?= $config['app']['name'] ?></title>
<link rel="stylesheet" href="<?= $basePath ?>assets/css/style.css">
<link href="https://fonts.googleapis.com/css2?family=Bangers&family=Patrick+Hand&family=Nunito:wght@400;600;700&display=swap" rel="stylesheet">
</head>
<body data-theme="<?= $theme ?? 'light' ?>">
<!-- Navbar -->
<nav class="navbar">
<div class="navbar-brand">
<a href="<?= $basePath ?>dashboard.php" class="navbar-logo">✈️ <?= $config['app']['name'] ?></a>
</div>
<button class="navbar-toggle" aria-label="Menü">☰</button>
<div class="navbar-menu">
<a href="<?= $basePath ?>dashboard.php" class="navbar-item">Dashboard</a>
<a href="<?= $basePath ?>airports.php" class="navbar-item">Flughäfen</a>
<a href="<?= $basePath ?>aircraft.php" class="navbar-item active">Flotte</a>
<a href="<?= $basePath ?>finances.php" class="navbar-item">Finanzen</a>
<a href="<?= $basePath ?>market.php" class="navbar-item">Markt</a>
<button class="theme-toggle" id="themeToggle" title="Theme wechseln">🌙</button>
</div>
</nav>
<!-- Main Content -->
<main class="container">
<!-- Header -->
<section class="hero hero-sm">
<div class="hero-content">
<h1 class="hero-title">🛫 Deine Flotte</h1>
<p class="hero-subtitle">Verwalte deine Flugzeuge, prüfe den Zustand und buche Wartungen</p>
</div>
</section>
<!-- Wartungshinweis -->
<?php if (!empty($maintenanceDue)): ?>
<div class="alert alert-warning mb-lg">
<div class="alert-icon">⚠️</div>
<div class="alert-content">
<strong>Wartung fällig!</strong>
<p>Bei <?= count($maintenanceDue) ?> Flugzeug(en) ist die Wartung überfällig.</p>
</div>
</div>
<?php endif; ?>
<!-- Statistik-Grid -->
<div class="stats-grid mt-lg">
<div class="stat-card">
<div class="stat-icon">✈️</div>
<div class="stat-value"><?= count($aircraft) ?></div>
<div class="stat-label">Flugzeuge</div>
</div>
<div class="stat-card">
<div class="stat-icon">💺</div>
<div class="stat-value"><?= number_format(array_sum(array_column($aircraft, 'seat_capacity')), 0, ',', '.') ?></div>
<div class="stat-label">Gesamtkapazität</div>
</div>
<div class="stat-card">
<div class="stat-icon">🛡️</div>
<div class="stat-value"><?= count($maintenanceDue) ?></div>
<div class="stat-label">Wartung fällig</div>
</div>
<div class="stat-card">
<div class="stat-icon">📅</div>
<div class="stat-value"><?= $config['game']['maintenance_interval'] ?></div>
<div class="stat-label">Flüge bis Wartung</div>
</div>
</div>
<!-- Flugzeugliste -->
<section class="panel mt-xl">
<div class="panel-header">
<h2 class="panel-title">🛩️ Alle Flugzeuge</h2>
<a href="<?= $basePath ?>market.php" class="btn btn-primary">
Flugzeug kaufen
</a>
</div>
<?php if (empty($aircraft)): ?>
<div class="empty-state">
<div class="empty-icon">✈️</div>
<h3>Keine Flugzeuge vorhanden</h3>
<p>Du besitzt noch keine Flugzeuge. Kaufe dein erstes Flugzeug im Markt!</p>
<a href="<?= $basePath ?>market.php" class="btn btn-primary btn-lg mt-md">Zum Markt</a>
</div>
<?php else: ?>
<div class="table-container">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Typ</th>
<th>Zustand</th>
<th>Flüge</th>
<th>Nächste Wartung</th>
<th>Reichweite</th>
<th>Kapazität</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<?php foreach ($aircraft as $plane): ?>
<?php
$conditionClass = $plane['condition'] >= 80 ? 'text-success' : ($plane['condition'] >= 50 ? 'text-warning' : 'text-danger');
$maintenanceProgress = min(100, ($plane['flights_count'] / $config['game']['maintenance_interval']) * 100);
$needsMaintenance = $plane['flights_count'] >= $config['game']['maintenance_interval'];
?>
<tr>
<td>
<strong><?= htmlspecialchars($plane['name']) ?></strong>
</td>
<td>
<span class="badge"><?= htmlspecialchars($plane['aircraft_type']) ?></span>
</td>
<td>
<div class="d-flex flex-col align-start gap-sm">
<span class="<?= $conditionClass ?>">
<?= number_format($plane['condition'], 1, ',', '.') ?>%
</span>
<div class="progress" style="width: 80px;">
<div class="progress-bar" style="width: <?= $plane['condition'] ?>%"></div>
</div>
</div>
</td>
<td><?= number_format($plane['flights_count'], 0, ',', '.') ?></td>
<td>
<?php if ($needsMaintenance): ?>
<span class="badge badge-danger">⚠️ Jetzt!</span>
<?php else: ?>
<span class="text-secondary">
<?= number_format($config['game']['maintenance_interval'] - $plane['flights_count'], 0, ',', '.') ?> Flüge
</span>
<?php endif; ?>
</td>
<td><?= number_format($plane['range_km'], 0, ',', '.') ?> km</td>
<td><?= number_format($plane['seat_capacity'], 0, ',', '.') ?> Pax</td>
<td>
<div class="d-flex gap-sm">
<?php if ($needsMaintenance): ?>
<button class="btn btn-sm btn-warning"
onclick="performMaintenance(<?= $plane['id'] ?>)"
data-modal="maintenanceModal">
🔧 Wartung
</button>
<?php endif; ?>
<button class="btn btn-sm btn-secondary"
onclick="showAircraftDetails(<?= $plane['id'] ?>)">
📋 Details
</button>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</section>
<!-- Flugzeug-Typen Übersicht -->
<section class="panel mt-xl">
<h2 class="panel-title">📊 Flugzeugtypen</h2>
<div class="aircraft-types-grid mt-md">
<?php foreach ($config['aircraft_types'] as $type => $specs): ?>
<div class="aircraft-type-card">
<h3 class="aircraft-type-name"><?= htmlspecialchars($type) ?></h3>
<div class="aircraft-type-stats">
<div class="aircraft-type-stat">
<span class="stat-label">Preis:</span>
<span class="stat-value"><?= number_format($specs['purchase_price'], 0, ',', '.') ?> €</span>
</div>
<div class="aircraft-type-stat">
<span class="stat-label">Reichweite:</span>
<span class="stat-value"><?= number_format($specs['range_km'], 0, ',', '.') ?> km</span>
</div>
<div class="aircraft-type-stat">
<span class="stat-label">Kapazität:</span>
<span class="stat-value"><?= $specs['seat_capacity'] ?> Pax</span>
</div>
<div class="aircraft-type-stat">
<span class="stat-label">Crew-Kosten:</span>
<span class="stat-value"><?= $specs['crew_cost_per_tick'] ?? 500 ?> €/Tick</span>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</section>
</main>
<!-- Wartungs-Modal -->
<div class="modal-overlay" id="maintenanceModal">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title">🔧 Wartung durchführen</h3>
<button class="modal-close" onclick="closeModal('maintenanceModal')">×</button>
</div>
<div class="modal-body">
<div id="maintenanceInfo">
<p>Flugzeug: <strong id="maintenanceAircraftName"></strong></p>
<p>Zustand: <span id="maintenanceCondition"></span></p>
<p>Geschätzte Kosten: <strong id="maintenanceCost"></strong></p>
</div>
<div class="alert alert-info mt-md">
<p>Nach der Wartung wird der Zustand auf 100% zurückgesetzt und der Flugzähler zurückgesetzt.</p>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal('maintenanceModal')">Abbrechen</button>
<button class="btn btn-warning" id="confirmMaintenanceBtn">Wartung durchführen</button>
</div>
</div>
</div>
<!-- Details-Modal -->
<div class="modal-overlay" id="detailsModal">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title">📋 Flugzeug-Details</h3>
<button class="modal-close" onclick="closeModal('detailsModal')">×</button>
</div>
<div class="modal-body" id="aircraftDetails">
<!-- Dynamisch geladen -->
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="closeModal('detailsModal')">Schließen</button>
</div>
</div>
</div>
<!-- Toast Container -->
<div id="toastContainer" class="toast-container"></div>
<script src="<?= $basePath ?>assets/js/main.js"></script>
<script>
let selectedAircraftId = null;
async function performMaintenance(aircraftId) {
selectedAircraftId = aircraftId;
try {
const response = await fetch(`api/game.php?action=aircraft_details&id=${aircraftId}`);
const data = await response.json();
if (data.success) {
document.getElementById('maintenanceAircraftName').textContent = data.aircraft.name;
document.getElementById('maintenanceCondition').textContent = data.aircraft.condition.toFixed(1) + '%';
document.getElementById('maintenanceCost').textContent = formatMoney(data.maintenance_cost);
openModal('maintenanceModal');
}
} catch (error) {
showToast('Fehler beim Laden der Daten', 'error');
}
}
document.getElementById('confirmMaintenanceBtn').addEventListener('click', async () => {
if (!selectedAircraftId) return;
try {
const formData = new FormData();
formData.append('action', 'maintenance');
formData.append('aircraft_id', selectedAircraftId);
const response = await fetch('api/game.php', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
showToast('Wartung erfolgreich durchgeführt!', 'success');
closeModal('maintenanceModal');
setTimeout(() => location.reload(), 1000);
} else {
showToast(data.error || 'Wartung fehlgeschlagen', 'error');
}
} catch (error) {
showToast('Netzwerkfehler', 'error');
}
});
async function showAircraftDetails(aircraftId) {
try {
const response = await fetch(`api/game.php?action=aircraft_details&id=${aircraftId}`);
const data = await response.json();
if (data.success) {
const aircraft = data.aircraft;
const history = data.maintenance_history || [];
let html = `
<div class="aircraft-detail-header">
<h4>${aircraft.name}</h4>
<span class="badge">${aircraft.aircraft_type}</span>
</div>
<div class="aircraft-detail-grid">
<div class="detail-item">
<span class="detail-label">Zustand:</span>
<span class="detail-value ${aircraft.condition >= 80 ? 'text-success' : aircraft.condition >= 50 ? 'text-warning' : 'text-danger'}">
${aircraft.condition.toFixed(1)}%
</span>
</div>
<div class="detail-item">
<span class="detail-label">Flüge gesamt:</span>
<span class="detail-value">${aircraft.flights_count}</span>
</div>
<div class="detail-item">
<span class="detail-label">Reichweite:</span>
<span class="detail-value">${aircraft.range_km.toLocaleString()} km</span>
</div>
<div class="detail-item">
<span class="detail-label">Kapazität:</span>
<span class="detail-value">${aircraft.seat_capacity} Pax</span>
</div>
<div class="detail-item">
<span class="detail-label">Kaufpreis:</span>
<span class="detail-value">${formatMoney(aircraft.purchase_price)}</span>
</div>
<div class="detail-item">
<span class="detail-label">Nächste Wartung in:</span>
<span class="detail-value">${50 - (aircraft.flights_count % 50)} Flügen</span>
</div>
</div>
`;
if (history.length > 0) {
html += `<h4 class="mt-lg">Wartungshistorie</h4><ul class="maintenance-history">`;
history.forEach(h => {
html += `<li>${h.created_at}: ${h.maintenance_type} - ${formatMoney(h.cost)} (${h.condition_before}% → ${h.condition_after}%)</li>`;
});
html += `</ul>`;
}
document.getElementById('aircraftDetails').innerHTML = html;
openModal('detailsModal');
}
} catch (error) {
showToast('Fehler beim Laden der Details', 'error');
}
}
function formatMoney(amount) {
return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(amount);
}
</script>
</body>
</html>

View File

@@ -49,6 +49,37 @@ switch ($action) {
handleLeaderboard(); handleLeaderboard();
break; break;
// NEU: Kredite
case 'take_loan':
handleTakeLoan();
break;
case 'repay_loan':
handleRepayLoan();
break;
case 'loans':
handleLoans();
break;
// NEU: Wartung
case 'maintenance':
handleMaintenance();
break;
case 'aircraft_details':
handleAircraftDetails();
break;
// NEU: Finanzen
case 'transactions':
handleTransactions();
break;
case 'finance_summary':
handleFinanceSummary();
break;
default: default:
jsonResponse(['error' => 'Unbekannte Aktion'], 400); jsonResponse(['error' => 'Unbekannte Aktion'], 400);
} }
@@ -329,3 +360,216 @@ function handleLeaderboard() {
'count' => count($leaders) 'count' => count($leaders)
]); ]);
} }
// ============================================
// KREDIT HANDLERS
// ============================================
function handleTakeLoan() {
global $db, $config;
$userId = Session::getUserId();
// CSRF Check
if (!Session::validateCsrfToken($_POST['csrf_token'] ?? '')) {
jsonResponse(['error' => 'Ungültiges CSRF-Token'], 403);
}
$amount = (float) ($_POST['amount'] ?? 0);
if ($amount <= 0) {
jsonResponse(['error' => 'Ungültiger Betrag'], 400);
}
try {
$loanId = $db->createLoan($userId, $amount);
// Update user balance
$user = $db->getUserById($userId);
jsonResponse([
'success' => true,
'message' => 'Kredit über ' . number_format($amount, 0, ',', '.') . ' € aufgenommen!',
'loan_id' => $loanId,
'amount' => $amount,
'new_balance' => $user['balance'] + $amount,
'interest_rate' => $config['loans']['interest_rate'],
'daily_interest' => $amount * $config['loans']['interest_rate']
]);
} catch (Exception $e) {
jsonResponse(['error' => $e->getMessage()], 400);
}
}
function handleRepayLoan() {
global $db;
$userId = Session::getUserId();
// CSRF Check
if (!Session::validateCsrfToken($_POST['csrf_token'] ?? '')) {
jsonResponse(['error' => 'Ungültiges CSRF-Token'], 403);
}
$loanId = (int) ($_POST['loan_id'] ?? 0);
$amount = (float) ($_POST['amount'] ?? 0);
if ($loanId <= 0 || $amount <= 0) {
jsonResponse(['error' => 'Ungültige Parameter'], 400);
}
try {
$result = $db->repayLoan($userId, $loanId, $amount);
jsonResponse([
'success' => true,
'message' => number_format($result['repaid'], 2, ',', '.') . ' € zurückgezahlt!',
'repaid' => $result['repaid'],
'remaining' => $result['remaining'],
'new_balance' => $result['new_balance']
]);
} catch (Exception $e) {
jsonResponse(['error' => $e->getMessage()], 400);
}
}
function handleLoans() {
global $db;
$userId = Session::getUserId();
$loans = $db->getUserLoans($userId);
$totalDebt = $db->getTotalDebt($userId);
$totalDebtWithInterest = $db->getTotalDebtWithInterest($userId);
jsonResponse([
'success' => true,
'loans' => $loans,
'total_debt' => $totalDebt,
'total_debt_with_interest' => $totalDebtWithInterest,
'count' => count($loans)
]);
}
// ============================================
// WARTUNG HANDLERS
// ============================================
function handleMaintenance() {
global $db, $config;
$userId = Session::getUserId();
// CSRF Check
if (!Session::validateCsrfToken($_POST['csrf_token'] ?? '')) {
jsonResponse(['error' => 'Ungültiges CSRF-Token'], 403);
}
$aircraftId = (int) ($_POST['aircraft_id'] ?? 0);
if ($aircraftId <= 0) {
jsonResponse(['error' => 'Flugzeug-ID erforderlich'], 400);
}
try {
$result = $db->performMaintenance($userId, $aircraftId);
jsonResponse([
'success' => true,
'message' => 'Wartung erfolgreich durchgeführt!',
'cost' => $result['cost'],
'condition_before' => $result['condition_before'],
'condition_after' => $result['condition_after'],
'new_balance' => $result['new_balance']
]);
} catch (Exception $e) {
jsonResponse(['error' => $e->getMessage()], 400);
}
}
function handleAircraftDetails() {
global $db;
$userId = Session::getUserId();
$aircraftId = (int) ($_GET['id'] ?? 0);
if ($aircraftId <= 0) {
jsonResponse(['error' => 'Flugzeug-ID erforderlich'], 400);
}
$aircraft = $db->selectOne(
"SELECT * FROM aircraft WHERE id = ? AND user_id = ?",
[$aircraftId, $userId]
);
if (!$aircraft) {
jsonResponse(['error' => 'Flugzeug nicht gefunden'], 404);
}
// Get maintenance history for this aircraft
$maintenanceHistory = $db->select(
"SELECT * FROM maintenance_history WHERE aircraft_id = ? ORDER BY created_at DESC LIMIT 10",
[$aircraftId]
);
// Get aircraft config for maintenance cost estimate
$config = $db->getConfig();
$aircraftConfig = $config['aircraft_types'][$aircraft['aircraft_type']] ?? [];
$maintenanceCost = $aircraftConfig['maintenance_base_cost'] ?? 50000;
// Adjust for condition
$conditionLoss = 100 - $aircraft['condition'];
$estimatedCost = $maintenanceCost + ($conditionLoss * 0.01 * $maintenanceCost);
jsonResponse([
'success' => true,
'aircraft' => $aircraft,
'maintenance_history' => $maintenanceHistory,
'maintenance_cost' => $estimatedCost,
'maintenance_interval' => $config['game']['maintenance_interval']
]);
}
// ============================================
// FINANZEN HANDLERS
// ============================================
function handleTransactions() {
global $db;
$userId = Session::getUserId();
$limit = min(100, (int) ($_GET['limit'] ?? 50));
$transactions = $db->getUserTransactions($userId, $limit);
jsonResponse([
'success' => true,
'transactions' => $transactions,
'count' => count($transactions)
]);
}
function handleFinanceSummary() {
global $db;
$userId = Session::getUserId();
$user = $db->getUserById($userId);
$today = date('Y-m-d');
$weekAgo = date('Y-m-d', strtotime('-7 days'));
$monthAgo = date('Y-m-d', strtotime('-30 days'));
$todayStats = $db->getTransactionSummary($userId, $today, $today);
$weekStats = $db->getTransactionSummary($userId, $weekAgo, $today);
$monthStats = $db->getTransactionSummary($userId, $monthAgo, $today);
$loans = $db->getUserLoans($userId);
$totalDebt = $db->getTotalDebtWithInterest($userId);
jsonResponse([
'success' => true,
'balance' => $user['balance'],
'net_worth' => $user['balance'] - $totalDebt,
'total_debt' => $totalDebt,
'today' => $todayStats,
'week' => $weekStats,
'month' => $monthStats,
'active_loans' => count($loans)
]);
}

View File

@@ -1048,3 +1048,373 @@ a:hover {
height: 48px; height: 48px;
} }
} }
/* ============================================
PHASE 2: AIRCRAFT, MARKET & FINANCES STYLES
============================================ */
/* Aircraft Market Grid */
.aircraft-market-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: var(--space-lg);
}
.market-aircraft-card {
background: var(--bg-secondary);
border: 3px solid var(--text-primary);
border-radius: var(--radius-lg);
padding: var(--space-lg);
box-shadow: var(--shadow-md);
transition: transform var(--transition-fast);
}
.market-aircraft-card:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-lg);
}
.market-aircraft-card.disabled {
opacity: 0.6;
filter: grayscale(30%);
}
.market-aircraft-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--space-md);
padding-bottom: var(--space-md);
border-bottom: 2px dashed var(--border-color);
}
.market-aircraft-name {
font-family: var(--font-primary);
font-size: 1.25rem;
color: var(--text-primary);
}
.market-aircraft-price {
font-family: var(--font-primary);
font-size: 1.5rem;
font-weight: bold;
}
.market-aircraft-specs {
display: flex;
flex-direction: column;
gap: var(--space-sm);
}
.spec-row {
display: flex;
justify-content: space-between;
padding: var(--space-xs) 0;
}
.spec-label {
color: var(--text-secondary);
font-size: 0.875rem;
}
.spec-value {
font-weight: 600;
color: var(--text-primary);
}
.cannot-afford {
text-align: center;
padding: var(--space-md);
background: var(--bg-tertiary);
border-radius: var(--radius-md);
}
/* Aircraft Types Grid */
.aircraft-types-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: var(--space-md);
}
.aircraft-type-card {
background: var(--bg-tertiary);
border: 2px solid var(--border-color);
border-radius: var(--radius-md);
padding: var(--space-md);
}
.aircraft-type-name {
font-family: var(--font-primary);
font-size: 1.1rem;
margin-bottom: var(--space-sm);
padding-bottom: var(--space-sm);
border-bottom: 1px dashed var(--border-color);
}
.aircraft-type-stats {
display: flex;
flex-direction: column;
gap: var(--space-xs);
}
.aircraft-type-stat {
display: flex;
justify-content: space-between;
font-size: 0.875rem;
}
/* Balance Banner */
.balance-banner {
display: flex;
justify-content: space-between;
align-items: center;
background: var(--accent-gradient);
color: white;
padding: var(--space-lg);
}
.balance-info {
display: flex;
flex-direction: column;
gap: var(--space-xs);
}
.balance-label {
font-size: 0.875rem;
opacity: 0.9;
}
.balance-value {
font-family: var(--font-primary);
font-size: 2rem;
font-weight: bold;
}
/* Period Stats */
.period-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--space-md);
}
.period-card {
background: var(--bg-tertiary);
border: 2px solid var(--border-color);
border-radius: var(--radius-md);
padding: var(--space-md);
}
.period-card h3 {
font-family: var(--font-primary);
font-size: 1rem;
margin-bottom: var(--space-md);
padding-bottom: var(--space-sm);
border-bottom: 1px solid var(--border-color);
}
.period-amounts {
display: flex;
flex-direction: column;
gap: var(--space-sm);
}
.amount-row {
display: flex;
justify-content: space-between;
font-size: 0.875rem;
}
.amount-row.total {
font-weight: bold;
padding-top: var(--space-sm);
border-top: 1px dashed var(--border-color);
margin-top: var(--space-sm);
}
/* Loan Info */
.loan-info {
background: var(--info-bg);
border: 2px solid var(--accent-color);
border-radius: var(--radius-md);
padding: var(--space-md);
}
.loan-info h3 {
font-family: var(--font-primary);
margin-bottom: var(--space-md);
}
.loan-info ul {
list-style: none;
padding: 0;
margin: 0;
}
.loan-info li {
padding: var(--space-xs) 0;
border-bottom: 1px dashed var(--border-color);
}
.loan-info li:last-child {
border-bottom: none;
}
/* Aircraft Detail Modal */
.aircraft-detail-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--space-lg);
padding-bottom: var(--space-md);
border-bottom: 2px dashed var(--border-color);
}
.aircraft-detail-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--space-md);
}
.detail-item {
display: flex;
flex-direction: column;
gap: var(--space-xs);
}
.detail-label {
font-size: 0.75rem;
color: var(--text-secondary);
text-transform: uppercase;
}
.detail-value {
font-weight: 600;
font-size: 1rem;
}
.maintenance-history {
list-style: none;
padding: 0;
margin: var(--space-md) 0 0 0;
}
.maintenance-history li {
padding: var(--space-sm);
background: var(--bg-tertiary);
border-radius: var(--radius-sm);
margin-bottom: var(--space-sm);
font-size: 0.875rem;
}
/* Info Grid */
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: var(--space-lg);
}
.info-card {
background: var(--bg-secondary);
border: 2px solid var(--border-color);
border-radius: var(--radius-md);
padding: var(--space-md);
}
.info-card h3 {
font-family: var(--font-primary);
font-size: 1rem;
margin-bottom: var(--space-md);
}
.info-list {
list-style: disc;
padding-left: var(--space-lg);
}
.info-list li {
padding: var(--space-xs) 0;
font-size: 0.875rem;
color: var(--text-secondary);
}
/* Maintenance Progress */
.maintenance-progress {
display: flex;
align-items: center;
gap: var(--space-sm);
}
.maintenance-bar {
flex: 1;
height: 8px;
background: var(--bg-tertiary);
border-radius: 4px;
overflow: hidden;
}
.maintenance-bar-fill {
height: 100%;
background: var(--comic-green);
transition: width var(--transition-normal);
}
.maintenance-bar-fill.warning {
background: var(--comic-orange);
}
.maintenance-bar-fill.danger {
background: var(--comic-red);
}
/* D-flex utilities */
.d-flex {
display: flex;
}
.flex-col {
flex-direction: column;
}
.align-start {
align-items: flex-start;
}
.gap-sm {
gap: var(--space-sm);
}
.gap-md {
gap: var(--space-md);
}
.w-100 {
width: 100%;
}
/* Responsive table container */
.table-container {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* Aircraft condition colors */
.condition-excellent {
color: var(--comic-green);
}
.condition-good {
color: var(--accent-color);
}
.condition-warning {
color: var(--comic-orange);
}
.condition-danger {
color: var(--comic-red);
}
/* Aircraft table */
.aircraft-table td {
vertical-align: middle;
}

View File

@@ -24,6 +24,10 @@ $todayStats = $db->getTodayStats($userId);
$aircraft = $db->getUserAircraft($userId); $aircraft = $db->getUserAircraft($userId);
$routes = $db->getUserRoutes($userId); $routes = $db->getUserRoutes($userId);
// Kredite und Finanzen
$totalDebt = $db->getTotalDebtWithInterest($userId);
$maintenanceDue = $db->checkMaintenanceDue($userId);
// Level-Fortschritt berechnen // Level-Fortschritt berechnen
$expForNextLevel = $config['game']['level_up_threshold'] * pow($config['game']['level_multiplier'], $user['level'] - 1); $expForNextLevel = $config['game']['level_up_threshold'] * pow($config['game']['level_multiplier'], $user['level'] - 1);
$levelProgress = ($user['experience'] / $expForNextLevel) * 100; $levelProgress = ($user['experience'] / $expForNextLevel) * 100;
@@ -47,9 +51,10 @@ $levelProgress = ($user['experience'] / $expForNextLevel) * 100;
<button class="navbar-toggle" aria-label="Menü">☰</button> <button class="navbar-toggle" aria-label="Menü">☰</button>
<ul class="navbar-menu"> <ul class="navbar-menu">
<li><a href="<?= BASE_PATH ?>dashboard.php" class="navbar-item active">📊 Dashboard</a></li> <li><a href="<?= BASE_PATH ?>dashboard.php" class="navbar-item active">📊 Dashboard</a></li>
<li><a href="<?= BASE_PATH ?>airports.php" class="navbar-item">🌍 Weltkarte</a></li> <li><a href="<?= BASE_PATH ?>airports.php" class="navbar-item">🌍 Flughäfen</a></li>
<li><a href="<?= BASE_PATH ?>airports.php?action=fleet" class="navbar-item">✈️ Flotte</a></li> <li><a href="<?= BASE_PATH ?>aircraft.php" class="navbar-item">✈️ Flotte</a></li>
<li><a href="<?= BASE_PATH ?>airports.php?action=routes" class="navbar-item">🛫 Routen</a></li> <li><a href="<?= BASE_PATH ?>finances.php" class="navbar-item">💰 Finanzen</a></li>
<li><a href="<?= BASE_PATH ?>market.php" class="navbar-item">🛒 Markt</a></li>
<li> <li>
<span class="navbar-item"> <span class="navbar-item">
💰 <?= formatMoney($user['balance']) ?> 💰 <?= formatMoney($user['balance']) ?>
@@ -124,6 +129,29 @@ $levelProgress = ($user['experience'] / $expForNextLevel) * 100;
<div class="stat-value"><?= formatNumber($stats['total_flights'] ?? 0) ?></div> <div class="stat-value"><?= formatNumber($stats['total_flights'] ?? 0) ?></div>
<div class="stat-label">Flüge (gesamt)</div> <div class="stat-label">Flüge (gesamt)</div>
</div> </div>
<div class="stat-card">
<div class="stat-icon">🏦</div>
<div class="stat-value <?= $totalDebt > 0 ? 'text-warning' : '' ?>">
<?= formatMoney($totalDebt) ?>
</div>
<div class="stat-label">Schulden</div>
</div>
<div class="stat-card">
<div class="stat-icon">🔧</div>
<div class="stat-value <?= count($maintenanceDue) > 0 ? 'text-danger' : '' ?>">
<?= count($maintenanceDue) ?>
</div>
<div class="stat-label">Wartung fällig</div>
</div>
<?php if (!empty($maintenanceDue)): ?>
<div class="alert alert-warning mt-md mb-none">
⚠️ Bei <?= count($maintenanceDue) ?> Flugzeug(en) ist die Wartung fällig!
<a href="<?= BASE_PATH ?>aircraft.php" class="btn btn-sm btn-warning ml-md">Jetzt warten</a>
</div>
<?php endif; ?>
</div> </div>
<!-- Quick Actions --> <!-- Quick Actions -->

439
www/public/finances.php Normal file
View File

@@ -0,0 +1,439 @@
<?php
/**
* Airline Tycoon - Finanz-Übersicht
* Zeigt alle Finanzen, Transaktionen, Kredite und Bilanzen
*/
require_once __DIR__ . '/../src/bootstrap.php';
// Auth Check
if (!Session::isLoggedIn()) {
header('Location: login.php');
exit;
}
$userId = Session::getUserId();
$user = $db->getUserById($userId);
$config = $db->getConfig();
$csrfToken = Session::generateCsrfToken();
// Kredite holen
$loans = $db->getUserLoans($userId);
$totalDebt = $db->getTotalDebt($userId);
$totalDebtWithInterest = $db->getTotalDebtWithInterest($userId);
// Transaktionen holen
$transactions = $db->getUserTransactions($userId, 50);
// Heutige Statistik
$today = date('Y-m-d');
$todayStats = $db->getTransactionSummary($userId, $today, $today);
// Letzte 7 Tage
$weekAgo = date('Y-m-d', strtotime('-7 days'));
$weekStats = $db->getTransactionSummary($userId, $weekAgo, $today);
// Letzte 30 Tage
$monthAgo = date('Y-m-d', strtotime('-30 days'));
$monthStats = $db->getTransactionSummary($userId, $monthAgo, $today);
// Wartungshistorie
$maintenanceHistory = $db->getMaintenanceHistory($userId, 10);
// Net Worth (Balance - Schulden)
$netWorth = $user['balance'] - $totalDebtWithInterest;
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Finanzen - <?= htmlspecialchars($user['airline_name']) ?> - <?= $config['app']['name'] ?></title>
<link rel="stylesheet" href="<?= $basePath ?>assets/css/style.css">
<link href="https://fonts.googleapis.com/css2?family=Bangers&family=Patrick+Hand&family=Nunito:wght@400;600;700&display=swap" rel="stylesheet">
</head>
<body data-theme="<?= $theme ?? 'light' ?>">
<!-- Navbar -->
<nav class="navbar">
<div class="navbar-brand">
<a href="<?= $basePath ?>dashboard.php" class="navbar-logo">✈️ <?= $config['app']['name'] ?></a>
</div>
<button class="navbar-toggle" aria-label="Menü">☰</button>
<div class="navbar-menu">
<a href="<?= $basePath ?>dashboard.php" class="navbar-item">Dashboard</a>
<a href="<?= $basePath ?>airports.php" class="navbar-item">Flughäfen</a>
<a href="<?= $basePath ?>aircraft.php" class="navbar-item">Flotte</a>
<a href="<?= $basePath ?>finances.php" class="navbar-item active">Finanzen</a>
<a href="<?= $basePath ?>market.php" class="navbar-item">Markt</a>
<button class="theme-toggle" id="themeToggle" title="Theme wechseln">🌙</button>
</div>
</nav>
<!-- Main Content -->
<main class="container">
<!-- Header -->
<section class="hero hero-sm">
<div class="hero-content">
<h1 class="hero-title">💰 Finanzen</h1>
<p class="hero-subtitle">Deine Einnahmen, Ausgaben und Kredite im Überblick</p>
</div>
</section>
<!-- Kontostand & Net Worth -->
<div class="stats-grid mt-lg">
<div class="stat-card">
<div class="stat-icon">💵</div>
<div class="stat-value <?= $user['balance'] >= 0 ? 'text-success' : 'text-danger' ?>">
<?= number_format($user['balance'], 0, ',', '.') ?> €
</div>
<div class="stat-label">Kontostand</div>
</div>
<div class="stat-card">
<div class="stat-icon">📊</div>
<div class="stat-value <?= $netWorth >= 0 ? 'text-success' : 'text-danger' ?>">
<?= number_format($netWorth, 0, ',', '.') ?> €
</div>
<div class="stat-label">Net Worth</div>
</div>
<div class="stat-card">
<div class="stat-icon">🏦</div>
<div class="stat-value <?= $totalDebt > 0 ? 'text-warning' : 'text-success' ?>">
<?= number_format($totalDebtWithInterest, 0, ',', '.') ?> €
</div>
<div class="stat-label">Kredit-Schuld</div>
</div>
<div class="stat-card">
<div class="stat-icon">📈</div>
<div class="stat-value">
<?= $config['loans']['interest_rate'] * 100 ?>%
</div>
<div class="stat-label">Kredit-Zins/Ticket</div>
</div>
</div>
<!-- Zeitraum-Statistiken -->
<section class="panel mt-xl">
<h2 class="panel-title">📅 Bilanz nach Zeitraum</h2>
<div class="period-stats mt-md">
<div class="period-card">
<h3>Heute</h3>
<div class="period-amounts">
<div class="amount-row">
<span>Einnahmen:</span>
<span class="text-success">+<?= number_format($todayStats['total_income'] ?? 0, 2, ',', '.') ?> €</span>
</div>
<div class="amount-row">
<span>Ausgaben:</span>
<span class="text-danger">-<?= number_format($todayStats['total_expenses'] ?? 0, 2, ',', '.') ?> €</span>
</div>
<div class="amount-row total">
<span>Saldo:</span>
<span class="<?= ($todayStats['total_income'] - $todayStats['total_expenses']) >= 0 ? 'text-success' : 'text-danger' ?>">
<?= number_format(($todayStats['total_income'] ?? 0) - ($todayStats['total_expenses'] ?? 0), 2, ',', '.') ?> €
</span>
</div>
</div>
</div>
<div class="period-card">
<h3>Letzte 7 Tage</h3>
<div class="period-amounts">
<div class="amount-row">
<span>Einnahmen:</span>
<span class="text-success">+<?= number_format($weekStats['total_income'] ?? 0, 2, ',', '.') ?> €</span>
</div>
<div class="amount-row">
<span>Ausgaben:</span>
<span class="text-danger">-<?= number_format($weekStats['total_expenses'] ?? 0, 2, ',', '.') ?> €</span>
</div>
<div class="amount-row total">
<span>Saldo:</span>
<span class="<?= ($weekStats['total_income'] - $weekStats['total_expenses']) >= 0 ? 'text-success' : 'text-danger' ?>">
<?= number_format(($weekStats['total_income'] ?? 0) - ($weekStats['total_expenses'] ?? 0), 2, ',', '.') ?> €
</span>
</div>
</div>
</div>
<div class="period-card">
<h3>Letzte 30 Tage</h3>
<div class="period-amounts">
<div class="amount-row">
<span>Einnahmen:</span>
<span class="text-success">+<?= number_format($monthStats['total_income'] ?? 0, 2, ',', '.') ?> €</span>
</div>
<div class="amount-row">
<span>Ausgaben:</span>
<span class="text-danger">-<?= number_format($monthStats['total_expenses'] ?? 0, 2, ',', '.') ?> €</span>
</div>
<div class="amount-row total">
<span>Saldo:</span>
<span class="<?= ($monthStats['total_income'] - $monthStats['total_expenses']) >= 0 ? 'text-success' : 'text-danger' ?>">
<?= number_format(($monthStats['total_income'] ?? 0) - ($monthStats['total_expenses'] ?? 0), 2, ',', '.') ?> €
</span>
</div>
</div>
</div>
</div>
</section>
<!-- Kredite -->
<section class="panel mt-xl">
<div class="panel-header">
<h2 class="panel-title">🏦 Kredite</h2>
<button class="btn btn-primary" onclick="openModal('takeLoanModal')">
Kredit aufnehmen
</button>
</div>
<?php if (empty($loans)): ?>
<div class="empty-state">
<div class="empty-icon">🏦</div>
<h3>Keine Kredite</h3>
<p>Du hast keine Kredite offen. Nimm einen Kredit auf, um dein Geschäft zu finanzieren!</p>
</div>
<?php else: ?>
<div class="table-container mt-md">
<table class="table">
<thead>
<tr>
<th>Datum</th>
<th>Originalbetrag</th>
<th>Aktueller Betrag</th>
<th>Tageszins</th>
<th>Tage offen</th>
<th>Gesamtzins</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
<?php foreach ($loans as $loan):
$totalInterest = $loan['daily_interest'] * $loan['days_outstanding'];
$totalOwed = $loan['current_amount'] + $totalInterest;
?>
<tr>
<td><?= date('d.m.Y', strtotime($loan['created_at'])) ?></td>
<td><?= number_format($loan['principal'], 2, ',', '.') ?> €</td>
<td><?= number_format($loan['current_amount'], 2, ',', '.') ?> €</td>
<td><?= number_format($loan['daily_interest'], 2, ',', '.') ?> €</td>
<td><?= $loan['days_outstanding'] ?></td>
<td><?= number_format($totalInterest, 2, ',', '.') ?> €</td>
<td>
<button class="btn btn-sm btn-warning"
onclick="repayLoan(<?= $loan['id'] ?>, <?= $totalOwed ?>)">
💵 Zurückzahlen
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<div class="loan-info panel mt-lg">
<h3>📖 Kredit-Informationen</h3>
<ul>
<li>Maximaler Kreditbetrag: <?= number_format($config['loans']['max_amount'], 0, ',', '.') ?> €</li>
<li>Zinssatz: <?= $config['loans']['interest_rate'] * 100 ?>% pro Spieltag</li>
<li>Mindestkreditbetrag: <?= number_format($config['loans']['min_amount'], 0, ',', '.') ?> €</li>
</ul>
</div>
</section>
<!-- Letzte Transaktionen -->
<section class="panel mt-xl">
<h2 class="panel-title">📜 Letzte Transaktionen</h2>
<?php if (empty($transactions)): ?>
<div class="empty-state">
<div class="empty-icon">📋</div>
<h3>Keine Transaktionen</h3>
<p>Es gibt noch keine Transaktionen.</p>
</div>
<?php else: ?>
<div class="table-container mt-md">
<table class="table">
<thead>
<tr>
<th>Datum</th>
<th>Typ</th>
<th>Beschreibung</th>
<th>Betrag</th>
<th>Saldo</th>
</tr>
</thead>
<tbody>
<?php foreach ($transactions as $tx): ?>
<tr>
<td><?= date('d.m.Y H:i', strtotime($tx['created_at'])) ?></td>
<td>
<?php
$typeColors = [
'flight' => 'badge-primary',
'fuel' => 'badge-warning',
'maintenance' => 'badge-danger',
'aircraft_purchase' => 'badge-info',
'loan' => 'badge-secondary',
'loan_repayment' => 'badge-warning'
];
$badgeClass = $typeColors[$tx['type']] ?? 'badge-secondary';
?>
<span class="badge <?= $badgeClass ?>"><?= htmlspecialchars($tx['type']) ?></span>
</td>
<td><?= htmlspecialchars($tx['description']) ?></td>
<td class="<?= $tx['amount'] >= 0 ? 'text-success' : 'text-danger' ?>">
<?= number_format($tx['amount'], 2, ',', '.') ?> €
</td>
<td><?= number_format($tx['balance_after'], 2, ',', '.') ?> €</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</section>
<!-- Wartungshistorie -->
<?php if (!empty($maintenanceHistory)): ?>
<section class="panel mt-xl">
<h2 class="panel-title">🔧 Wartungshistorie</h2>
<div class="table-container mt-md">
<table class="table">
<thead>
<tr>
<th>Datum</th>
<th>Flugzeug</th>
<th>Typ</th>
<th>Zustand vorher</th>
<th>Zustand nachher</th>
<th>Kosten</th>
</tr>
</thead>
<tbody>
<?php foreach ($maintenanceHistory as $m): ?>
<tr>
<td><?= date('d.m.Y', strtotime($m['created_at'])) ?></td>
<td><?= htmlspecialchars($m['aircraft_name']) ?></td>
<td><span class="badge"><?= htmlspecialchars($m['maintenance_type']) ?></span></td>
<td><?= number_format($m['condition_before'], 1, ',', '.') ?>%</td>
<td><?= number_format($m['condition_after'], 1, ',', '.') ?>%</td>
<td class="text-danger">-<?= number_format($m['cost'], 2, ',', '.') ?> €</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</section>
<?php endif; ?>
</main>
<!-- Kredit aufnehmen Modal -->
<div class="modal-overlay" id="takeLoanModal">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title">🏦 Kredit aufnehmen</h3>
<button class="modal-close" onclick="closeModal('takeLoanModal')">×</button>
</div>
<form action="api/game.php" method="POST" data-ajax>
<input type="hidden" name="action" value="take_loan">
<input type="hidden" name="csrf_token" value="<?= $csrfToken ?>">
<div class="modal-body">
<div class="form-group">
<label class="form-label">Kreditbetrag (€)</label>
<input type="number" name="amount" class="form-input"
min="<?= $config['loans']['min_amount'] ?>"
max="<?= $config['loans']['max_amount'] - $totalDebt ?>"
step="10000"
placeholder="z.B. 1000000" required>
<small class="form-hint">
Min: <?= number_format($config['loans']['min_amount'], 0, ',', '.') ?> € |
Max: <?= number_format($config['loans']['max_amount'] - $totalDebt, 0, ',', '.') ?> € verfügbar
</small>
</div>
<div class="alert alert-info mt-md">
<p><strong>Zinssatz:</strong> <?= $config['loans']['interest_rate'] * 100 ?>% pro Spieltag</p>
<p><strong>Tageszins für max. Kredit:</strong> <?= number_format(($config['loans']['max_amount'] - $totalDebt) * $config['loans']['interest_rate'], 2, ',', '.') ?> €</p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeModal('takeLoanModal')">Abbrechen</button>
<button type="submit" class="btn btn-primary">Kredit aufnehmen</button>
</div>
</form>
</div>
</div>
<!-- Kredit zurückzahlen Modal -->
<div class="modal-overlay" id="repayLoanModal">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title">💵 Kredit zurückzahlen</h3>
<button class="modal-close" onclick="closeModal('repayLoanModal')">×</button>
</div>
<form action="api/game.php" method="POST" data-ajax>
<input type="hidden" name="action" value="repay_loan">
<input type="hidden" name="csrf_token" value="<?= $csrfToken ?>">
<input type="hidden" name="loan_id" id="repayLoanId">
<div class="modal-body">
<p>Gesamtbetrag inkl. Zinsen: <strong id="repayTotalAmount"></strong></p>
<div class="form-group mt-md">
<label class="form-label">Zurückzahlungsbetrag (€)</label>
<input type="number" name="amount" class="form-input"
min="100" step="1000"
id="repayAmountInput" required>
<small class="form-hint">Du kannst auch nur einen Teil zurückzahlen.</small>
</div>
<div class="alert alert-warning mt-md">
<p>💡 Du kannst den Kredit jederzeit (teilweise oder vollständig) zurückzahlen.</p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeModal('repayLoanModal')">Abbrechen</button>
<button type="submit" class="btn btn-warning">Zurückzahlen</button>
</div>
</form>
</div>
</div>
<!-- Toast Container -->
<div id="toastContainer" class="toast-container"></div>
<script src="<?= $basePath ?>assets/js/main.js"></script>
<script>
let currentLoanId = null;
let currentTotalOwed = 0;
function repayLoan(loanId, totalOwed) {
currentLoanId = loanId;
currentTotalOwed = totalOwed;
document.getElementById('repayLoanId').value = loanId;
document.getElementById('repayTotalAmount').textContent =
new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(totalOwed);
document.getElementById('repayAmountInput').max = totalOwed;
document.getElementById('repayAmountInput').value = totalOwed;
openModal('repayLoanModal');
}
// Erfolg-Callbacks
window.loanSuccess = function(data) {
showToast('Kredit über ' + formatMoney(data.amount) + ' aufgenommen!', 'success');
setTimeout(() => location.reload(), 1500);
};
window.repaySuccess = function(data) {
showToast('Kredit zurückgezahlt: ' + formatMoney(data.repaid), 'success');
setTimeout(() => location.reload(), 1500);
};
function formatMoney(amount) {
return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(amount);
}
</script>
</body>
</html>

198
www/public/market.php Normal file
View File

@@ -0,0 +1,198 @@
<?php
/**
* Airline Tycoon - Flugzeug-Markt
* Kauf von neuen Flugzeugen
*/
require_once __DIR__ . '/../src/bootstrap.php';
// Auth Check
if (!Session::isLoggedIn()) {
header('Location: login.php');
exit;
}
$userId = Session::getUserId();
$user = $db->getUserById($userId);
$config = $db->getConfig();
$csrfToken = Session::generateCsrfToken();
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Markt - <?= htmlspecialchars($user['airline_name']) ?> - <?= $config['app']['name'] ?></title>
<link rel="stylesheet" href="<?= $basePath ?>assets/css/style.css">
<link href="https://fonts.googleapis.com/css2?family=Bangers&family=Patrick+Hand&family=Nunito:wght@400;600;700&display=swap" rel="stylesheet">
</head>
<body data-theme="<?= $theme ?? 'light' ?>">
<!-- Navbar -->
<nav class="navbar">
<div class="navbar-brand">
<a href="<?= $basePath ?>dashboard.php" class="navbar-logo">✈️ <?= $config['app']['name'] ?></a>
</div>
<button class="navbar-toggle" aria-label="Menü">☰</button>
<div class="navbar-menu">
<a href="<?= $basePath ?>dashboard.php" class="navbar-item">Dashboard</a>
<a href="<?= $basePath ?>airports.php" class="navbar-item">Flughäfen</a>
<a href="<?= $basePath ?>aircraft.php" class="navbar-item">Flotte</a>
<a href="<?= $basePath ?>finances.php" class="navbar-item">Finanzen</a>
<a href="<?= $basePath ?>market.php" class="navbar-item active">Markt</a>
<button class="theme-toggle" id="themeToggle" title="Theme wechseln">🌙</button>
</div>
</nav>
<!-- Main Content -->
<main class="container">
<!-- Header -->
<section class="hero hero-sm">
<div class="hero-content">
<h1 class="hero-title">🛒 Flugzeug-Markt</h1>
<p class="hero-subtitle">Kaufe neue Flugzeuge für deine Airline</p>
</div>
</section>
<!-- Kontostand Hinweis -->
<div class="balance-banner panel mt-lg">
<div class="balance-info">
<span class="balance-label">Dein Kontostand:</span>
<span class="balance-value" id="currentBalance"><?= number_format($user['balance'], 2, ',', '.') ?> €</span>
</div>
<div class="balance-actions">
<a href="<?= $basePath ?>finances.php" class="btn btn-sm btn-secondary">
💰 Kontoauszug
</a>
</div>
</div>
<!-- Flugzeugtypen -->
<section class="panel mt-xl">
<h2 class="panel-title">🛩️ Verfügbare Flugzeuge</h2>
<p class="text-secondary mb-lg">Wähle einen Flugzeugtyp und gib ihm einen Namen.</p>
<div class="aircraft-market-grid">
<?php foreach ($config['aircraft_types'] as $type => $specs): ?>
<?php
$canAfford = $user['balance'] >= $specs['purchase_price'];
$fuelEff = $specs['fuel_efficiency'] ?? 1.0;
?>
<div class="market-aircraft-card <?= $canAfford ? '' : 'disabled' ?>">
<div class="market-aircraft-header">
<h3 class="market-aircraft-name"><?= htmlspecialchars($type) ?></h3>
<span class="market-aircraft-price <?= $canAfford ? 'text-success' : 'text-danger' ?>">
<?= number_format($specs['purchase_price'], 0, ',', '.') ?> €
</span>
</div>
<div class="market-aircraft-specs">
<div class="spec-row">
<span class="spec-label">✈️ Sitzplätze:</span>
<span class="spec-value"><?= $specs['seat_capacity'] ?></span>
</div>
<div class="spec-row">
<span class="spec-label">🌍 Reichweite:</span>
<span class="spec-value"><?= number_format($specs['range_km'], 0, ',', '.') ?> km</span>
</div>
<div class="spec-row">
<span class="spec-label">⛽ Effizienz:</span>
<span class="spec-value">
<?php if ($fuelEff < 0.9): ?>
🟢 Sehr gut
<?php elseif ($fuelEff < 1.0): ?>
🟡 Gut
<?php else: ?>
🟠 Normal
<?php endif; ?>
</span>
</div>
<div class="spec-row">
<span class="spec-label">💨 Geschwindigkeit:</span>
<span class="spec-value"><?= $specs['speed_kmh'] ?? 800 ?> km/h</span>
</div>
<div class="spec-row">
<span class="spec-label">👨‍✈️ Crew-Kosten:</span>
<span class="spec-value"><?= $specs['crew_cost_per_tick'] ?? 500 ?> €/Tick</span>
</div>
<div class="spec-row">
<span class="spec-label">🔧 Wartung:</span>
<span class="spec-value"><?= number_format($specs['maintenance_base_cost'] ?? 50000, 0, ',', '.') ?> €</span>
</div>
</div>
<?php if ($canAfford): ?>
<form class="purchase-form mt-md" data-ajax>
<input type="hidden" name="action" value="purchase">
<input type="hidden" name="csrf_token" value="<?= $csrfToken ?>">
<input type="hidden" name="aircraft_type" value="<?= htmlspecialchars($type) ?>">
<div class="form-group">
<input type="text" name="name" class="form-input"
placeholder="Flugzeugname (z.B. 'Hans')"
maxlength="30" required>
</div>
<button type="submit" class="btn btn-primary btn-lg w-100">
✈️ Kaufen für <?= number_format($specs['purchase_price'], 0, ',', '.') ?> €
</button>
</form>
<?php else: ?>
<div class="cannot-afford mt-md">
<span class="badge badge-danger">Nicht genug Guthaben</span>
<p class="text-secondary mt-sm">
Du brauchst noch <?= number_format($specs['purchase_price'] - $user['balance'], 0, ',', '.') ?> €
</p>
</div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</section>
<!-- Informationen -->
<section class="panel mt-xl">
<h2 class="panel-title">📖 Informationen</h2>
<div class="info-grid mt-md">
<div class="info-card">
<h3>💡 Tipps zum Flugzeugkauf</h3>
<ul class="info-list">
<li>Propeller-Flugzeuge sind günstig und gut für kurze Strecken</li>
<li>Schmale Rumpf-Flugzeuge (Narrowbody) sind vielseitig einsetzbar</li>
<li>Widebody-Flugzeuge sind für Langstrecken gedacht</li>
<li>Der A380 ist das größte Passagierflugzeug der Welt</li>
</ul>
</div>
<div class="info-card">
<h3>🔧 Wartung</h3>
<ul class="info-list">
<li>Alle 50 Flüge muss ein Flugzeug zur Wartung</li>
<li>Die Wartung stellt den Zustand auf 100% zurück</li>
<li>Vernachlässigte Wartung erhöht die Kosten</li>
<li>Ein Flugzeug mit weniger als 20% Zustand kann nicht mehr fliegen</li>
</ul>
</div>
<div class="info-card">
<h3>⛽ Treibstoffkosten</h3>
<ul class="info-list">
<li>Treibstoff kostet <?= $config['game']['fuel_cost_per_km'] ?> € pro km</li>
<li>Effizientere Flugzeuge verbrauchen weniger</li>
<li>Die Treibstoffkosten werden pro Flug vom Gewinn abgezogen</li>
</ul>
</div>
</div>
</section>
</main>
<!-- Toast Container -->
<div id="toastContainer" class="toast-container"></div>
<script src="<?= $basePath ?>assets/js/main.js"></script>
<script>
// Erfolg beim Kauf
window.purchaseSuccess = function(data) {
document.getElementById('currentBalance').textContent =
new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(data.new_balance);
};
</script>
</body>
</html>

View File

@@ -46,7 +46,15 @@ class Database {
if (file_exists($schemaPath)) { if (file_exists($schemaPath)) {
$schema = file_get_contents($schemaPath); $schema = file_get_contents($schemaPath);
try {
$this->connection->exec($schema); $this->connection->exec($schema);
} catch (\PDOException $e) {
// Schema already exists (tables/data already present) - ignore
if (strpos($e->getMessage(), 'UNIQUE constraint failed') === false &&
strpos($e->getMessage(), 'table already exists') === false) {
throw $e;
}
}
} }
} }
@@ -187,15 +195,22 @@ class Database {
throw new Exception("Unbekannter Flugzeugtyp: {$type}"); throw new Exception("Unbekannter Flugzeugtyp: {$type}");
} }
return $this->insert('aircraft', [ $aircraftId = $this->insert('aircraft', [
'user_id' => $userId, 'user_id' => $userId,
'aircraft_type' => $type, 'aircraft_type' => $type,
'name' => $name ?: $type, 'name' => $name ?: $type,
'purchase_price' => $price, 'purchase_price' => $price,
'seat_capacity' => $config['seat_capacity'], 'seat_capacity' => $config['seat_capacity'],
'range_km' => $config['range_km'], 'range_km' => $config['range_km'],
'fuel_efficiency' => $config['fuel_efficiency'] 'fuel_efficiency' => $config['fuel_efficiency'],
'condition' => 100.00
]); ]);
// Log transaction
$this->logTransaction($userId, 'aircraft_purchase', 'expense', -$price,
"Flugzeug {$type} \"{$name}\" gekauft", $aircraftId);
return $aircraftId;
} }
// Route Methoden // Route Methoden
@@ -286,12 +301,10 @@ class Database {
]); ]);
// Balance aktualisieren // Balance aktualisieren
$this->update('users', ['balance' => $this->config['game']['start_balance']],
'id = ?', [$userId]); // Placeholder, wird unten korrekt gemacht
$user = $this->getUserById($userId); $user = $this->getUserById($userId);
$newBalance = $user['balance'] + $calc['profit'];
$this->update('users', [ $this->update('users', [
'balance' => $user['balance'] + $calc['profit'], 'balance' => $newBalance,
'experience' => $user['experience'] + $this->config['game']['experience_per_flight'] + 'experience' => $user['experience'] + $this->config['game']['experience_per_flight'] +
($calc['passengers'] * $this->config['game']['experience_per_passenger']) ($calc['passengers'] * $this->config['game']['experience_per_passenger'])
], 'id = ?', [$userId]); ], 'id = ?', [$userId]);
@@ -302,6 +315,12 @@ class Database {
'flights_count' => $aircraft['flights_count'] + 1 'flights_count' => $aircraft['flights_count'] + 1
], 'id = ?', [$aircraftId]); ], 'id = ?', [$aircraftId]);
// Transaktionen loggen
$this->logTransaction($userId, 'flight', 'income', $calc['revenue'],
"Flug mit {$aircraft['name']}: {$calc['passengers']} Passagiere", $flightId);
$this->logTransaction($userId, 'fuel', 'expense', -$calc['fuel_cost'],
"Treibstoff für Flug", $flightId);
// Tagesstatistik aktualisieren // Tagesstatistik aktualisieren
$today = date('Y-m-d'); $today = date('Y-m-d');
$existing = $this->selectOne( $existing = $this->selectOne(
@@ -329,7 +348,7 @@ class Database {
]); ]);
} }
return array_merge($calc, ['flight_id' => $flightId]); return array_merge($calc, ['flight_id' => $flightId, 'new_balance' => $newBalance]);
} }
// Statistik Methoden // Statistik Methoden
@@ -370,4 +389,254 @@ class Database {
public function getConfig(): array { public function getConfig(): array {
return $this->config; return $this->config;
} }
// ============================================
// LOAN METHODS
// ============================================
public function getTotalDebt(int $userId): float {
$result = $this->selectOne(
"SELECT COALESCE(SUM(current_amount), 0) as total FROM loans WHERE user_id = ? AND status = 'active'",
[$userId]
);
return (float) $result['total'];
}
public function getTotalDebtWithInterest(int $userId): float {
$result = $this->selectOne(
"SELECT COALESCE(SUM(current_amount + daily_interest * days_outstanding), 0) as total
FROM loans WHERE user_id = ? AND status = 'active'",
[$userId]
);
return (float) $result['total'];
}
public function createLoan(int $userId, float $amount): int {
$config = $this->config['loans'];
if ($amount < $config['min_amount']) {
throw new Exception("Mindestkreditbetrag: " . number_format($config['min_amount'], 0, ',', '.') . "");
}
$maxAmount = $config['max_amount'];
$currentDebt = $this->getTotalDebt($userId);
if ($currentDebt + $amount > $maxAmount) {
throw new Exception("Kreditlimit überschritten! Maximal verfügbar: " .
number_format($maxAmount - $currentDebt, 0, ',', '.') . "");
}
$user = $this->getUserById($userId);
if (!$user) {
throw new Exception("Benutzer nicht gefunden");
}
$interest = $amount * $config['interest_rate'];
return $this->insert('loans', [
'user_id' => $userId,
'principal' => $amount,
'current_amount' => $amount,
'interest_rate' => $config['interest_rate'],
'daily_interest' => $interest,
'days_outstanding' => 0,
'status' => 'active',
'created_at' => date('Y-m-d H:i:s'),
'due_date' => date('Y-m-d', strtotime('+365 days'))
]);
}
public function repayLoan(int $userId, int $loanId, float $amount): array {
$loan = $this->selectOne("SELECT * FROM loans WHERE id = ? AND user_id = ? AND status = 'active'",
[$loanId, $userId]);
if (!$loan) {
throw new Exception("Kredit nicht gefunden");
}
$user = $this->getUserById($userId);
$repayAmount = min($amount, $loan['current_amount'] + ($loan['daily_interest'] * $loan['days_outstanding']));
if ($repayAmount > $user['balance']) {
throw new Exception("Nicht genug Guthaben für Rückzahlung");
}
$totalOwed = $loan['current_amount'] + ($loan['daily_interest'] * $loan['days_outstanding']);
$remaining = $totalOwed - $repayAmount;
if ($remaining <= 0) {
// Loan fully paid
$this->update('loans', ['status' => 'paid'], 'id = ?', [$loanId]);
$newBalance = $user['balance'] - $repayAmount;
} else {
// Partial repayment
$this->update('loans', [
'current_amount' => $remaining,
'daily_interest' => $remaining * $loan['interest_rate']
], 'id = ?', [$loanId]);
$newBalance = $user['balance'] - $repayAmount;
}
$this->update('users', ['balance' => $newBalance], 'id = ?', [$userId]);
$this->logTransaction($userId, 'loan_repayment', 'expense', -$repayAmount,
"Rückzahlung Kredit #{$loanId}", $loanId);
return [
'repaid' => $repayAmount,
'remaining' => $remaining > 0 ? $remaining : 0,
'new_balance' => $newBalance
];
}
public function processDailyLoanInterest(int $userId): float {
$loans = $this->getUserLoans($userId);
$totalInterest = 0;
foreach ($loans as $loan) {
$totalInterest += $loan['daily_interest'];
$this->update('loans', [
'days_outstanding' => $loan['days_outstanding'] + 1
], 'id = ?', [$loanId ?? $loan['id']]);
}
return $totalInterest;
}
// ============================================
// MAINTENANCE METHODS
// ============================================
public function performMaintenance(int $userId, int $aircraftId, string $type = 'scheduled'): array {
$aircraft = $this->selectOne("SELECT * FROM aircraft WHERE id = ? AND user_id = ?",
[$aircraftId, $userId]);
if (!$aircraft) {
throw new Exception("Flugzeug nicht gefunden");
}
$config = $this->config['aircraft_types'][$aircraft['aircraft_type']];
$maintenanceCost = $config['maintenance_base_cost'] ?? 50000;
// Cost increases if condition is low
$conditionLoss = 100 - $aircraft['condition'];
$totalCost = $maintenanceCost + ($conditionLoss * 0.01 * $maintenanceCost);
$user = $this->getUserById($userId);
if ($user['balance'] < $totalCost) {
throw new Exception("Nicht genug Guthaben für Wartung!");
}
// Restore condition
$newCondition = min(100, $aircraft['condition'] + $this->config['game']['maintenance_condition_restore']);
// Update aircraft
$this->update('aircraft', [
'condition' => $newCondition,
'flights_count' => 0 // Reset flight counter after maintenance
], 'id = ?', [$aircraftId]);
// Deduct cost
$newBalance = $user['balance'] - $totalCost;
$this->update('users', ['balance' => $newBalance], 'id = ?', [$userId]);
// Log transaction
$this->logTransaction($userId, 'maintenance', 'expense', -$totalCost,
"Wartung {$aircraft['name']} ({$type})", $aircraftId);
// Maintenance history
$this->insert('maintenance_history', [
'aircraft_id' => $aircraftId,
'user_id' => $userId,
'maintenance_type' => $type,
'cost' => $totalCost,
'condition_before' => $aircraft['condition'],
'condition_after' => $newCondition,
'created_at' => date('Y-m-d H:i:s')
]);
return [
'cost' => $totalCost,
'condition_before' => $aircraft['condition'],
'condition_after' => $newCondition,
'new_balance' => $newBalance
];
}
public function getMaintenanceHistory(int $userId, int $limit = 20): array {
return $this->select(
"SELECT mh.*, a.name as aircraft_name, a.aircraft_type
FROM maintenance_history mh
JOIN aircraft a ON mh.aircraft_id = a.id
WHERE mh.user_id = ?
ORDER BY mh.created_at DESC
LIMIT ?",
[$userId, $limit]
);
}
public function checkMaintenanceDue(int $userId): array {
$aircraft = $this->getUserAircraft($userId);
$due = [];
foreach ($aircraft as $plane) {
if ($plane['flights_count'] >= $this->config['game']['maintenance_interval']) {
$due[] = $plane;
}
}
return $due;
}
// ============================================
// TRANSACTION METHODS
// ============================================
public function logTransaction(int $userId, string $type, string $category, float $amount, string $description = '', ?int $referenceId = null): int {
$user = $this->getUserById($userId);
return $this->insert('transactions', [
'user_id' => $userId,
'type' => $type,
'category' => $category,
'amount' => $amount,
'description' => $description,
'reference_id' => $referenceId,
'balance_after' => $user['balance'],
'created_at' => date('Y-m-d H:i:s')
]);
}
public function getUserTransactions(int $userId, int $limit = 50): array {
return $this->select(
"SELECT * FROM transactions WHERE user_id = ? ORDER BY created_at DESC LIMIT ?",
[$userId, $limit]
);
}
public function getTransactionSummary(int $userId, string $startDate, string $endDate): array {
$result = $this->selectOne(
"SELECT
COALESCE(SUM(CASE WHEN amount > 0 THEN amount ELSE 0 END), 0) as total_income,
COALESCE(SUM(CASE WHEN amount < 0 THEN ABS(amount) ELSE 0 END), 0) as total_expenses
FROM transactions
WHERE user_id = ? AND DATE(created_at) BETWEEN ? AND ?",
[$userId, $startDate, $endDate]
);
return [
'total_income' => (float) $result['total_income'],
'total_expenses' => (float) $result['total_expenses']
];
}
// ============================================
// LOAN METHODS
// ============================================
public function getUserLoans(int $userId): array {
return $this->select(
"SELECT * FROM loans WHERE user_id = ? AND status = 'active' ORDER BY created_at DESC",
[$userId]
);
}
public function getLoanById(int $loanId): ?array {
return $this->selectOne("SELECT * FROM loans WHERE id = ?", [$loanId]);
}
} }

View File

@@ -23,52 +23,80 @@ return [
// Spiel // Spiel
'game' => [ 'game' => [
'start_balance' => 500000.00, 'start_balance' => 10000000.00,
'start_aircraft' => 'Dash 8-100', 'start_aircraft' => 'Dash 8-100',
'fuel_cost_per_km' => 0.15, 'fuel_cost_per_km' => 0.15,
'maintenance_cost_rate' => 0.001, 'maintenance_cost_rate' => 0.001,
'maintenance_interval' => 50,
'maintenance_condition_restore' => 10.00,
'experience_per_flight' => 10, 'experience_per_flight' => 10,
'experience_per_passenger' => 1, 'experience_per_passenger' => 1,
'level_up_threshold' => 1000, 'level_up_threshold' => 1000,
'level_multiplier' => 1.5 'level_multiplier' => 1.5
], ],
// Flugzeugtypen // Kredite
'loans' => [
'max_amount' => 50000000.00,
'interest_rate' => 0.02,
'min_amount' => 100000.00
],
// Flugzeugtypen (6 Typen)
'aircraft_types' => [ 'aircraft_types' => [
'Dash 8-100' => [ 'Dash 8-100' => [
'seat_capacity' => 35, 'seat_capacity' => 37,
'range_km' => 1500, 'range_km' => 1500,
'purchase_price' => 8500000, 'purchase_price' => 500000,
'fuel_efficiency' => 1.2, 'fuel_efficiency' => 1.2,
'speed_kmh' => 500 'speed_kmh' => 500,
'crew_cost_per_tick' => 200,
'maintenance_base_cost' => 10000
], ],
'Boeing 737-300' => [ 'ATR 72-500' => [
'seat_capacity' => 126, 'seat_capacity' => 70,
'range_km' => 4200, 'range_km' => 1650,
'purchase_price' => 45000000, 'purchase_price' => 1500000,
'fuel_efficiency' => 1.0, 'fuel_efficiency' => 1.15,
'speed_kmh' => 830 'speed_kmh' => 510,
'crew_cost_per_tick' => 300,
'maintenance_base_cost' => 15000
], ],
'Airbus A320' => [ 'A320-200' => [
'seat_capacity' => 150, 'seat_capacity' => 150,
'range_km' => 5700, 'range_km' => 5700,
'purchase_price' => 65000000, 'purchase_price' => 8000000,
'fuel_efficiency' => 0.95, 'fuel_efficiency' => 1.0,
'speed_kmh' => 840 'speed_kmh' => 840,
'crew_cost_per_tick' => 600,
'maintenance_base_cost' => 50000
], ],
'Boeing 747-400' => [ 'B737-800' => [
'seat_capacity' => 416, 'seat_capacity' => 162,
'range_km' => 13400, 'range_km' => 5700,
'purchase_price' => 220000000, 'purchase_price' => 9500000,
'fuel_efficiency' => 0.8, 'fuel_efficiency' => 0.98,
'speed_kmh' => 910 'speed_kmh' => 850,
'crew_cost_per_tick' => 650,
'maintenance_base_cost' => 55000
], ],
'Airbus A380' => [ 'B777-200' => [
'seat_capacity' => 300,
'range_km' => 12000,
'purchase_price' => 35000000,
'fuel_efficiency' => 0.85,
'speed_kmh' => 905,
'crew_cost_per_tick' => 1200,
'maintenance_base_cost' => 150000
],
'A380-800' => [
'seat_capacity' => 525, 'seat_capacity' => 525,
'range_km' => 15200, 'range_km' => 15200,
'purchase_price' => 350000000, 'purchase_price' => 85000000,
'fuel_efficiency' => 0.75, 'fuel_efficiency' => 0.75,
'speed_kmh' => 900 'speed_kmh' => 900,
'crew_cost_per_tick' => 2000,
'maintenance_base_cost' => 350000
] ]
], ],