Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Mount & Blade: German Wars 1400</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
.map-container { | |
background-image: url('https://upload.wikimedia.org/wikipedia/commons/thumb/5/5f/Holy_Roman_Empire_1400.svg/1200px-Holy_Roman_Empire_1400.svg.png'); | |
background-size: contain; | |
background-repeat: no-repeat; | |
background-position: center; | |
position: relative; | |
height: 70vh; | |
} | |
.settlement { | |
position: absolute; | |
width: 20px; | |
height: 20px; | |
border-radius: 50%; | |
cursor: pointer; | |
transition: all 0.3s; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
font-weight: bold; | |
color: white; | |
text-shadow: 1px 1px 2px black; | |
} | |
.settlement:hover { | |
transform: scale(1.5); | |
z-index: 10; | |
} | |
.castle { | |
background-color: #8B4513; | |
} | |
.town { | |
background-color: #4682B4; | |
} | |
.village { | |
background-color: #228B22; | |
} | |
.battle-animation { | |
animation: pulse 1s infinite; | |
} | |
@keyframes pulse { | |
0% { transform: scale(1); } | |
50% { transform: scale(1.2); } | |
100% { transform: scale(1); } | |
} | |
.faction-bavaria { background-color: #000080; } | |
.faction-swabia { background-color: #FF0000; } | |
.faction-franconia { background-color: #FFD700; } | |
.faction-saxony { background-color: #008000; } | |
.faction-lorraine { background-color: #FFFFFF; border: 1px solid #000; } | |
.faction-player { background-color: #800080; } | |
.tooltip { | |
position: absolute; | |
background-color: rgba(0, 0, 0, 0.8); | |
color: white; | |
padding: 5px 10px; | |
border-radius: 5px; | |
pointer-events: none; | |
z-index: 100; | |
font-size: 14px; | |
max-width: 200px; | |
} | |
.combat-log { | |
height: 200px; | |
overflow-y: auto; | |
background-color: #1a1a1a; | |
color: #e0e0e0; | |
padding: 10px; | |
border-radius: 5px; | |
font-family: monospace; | |
} | |
.combat-entry { | |
margin-bottom: 5px; | |
border-bottom: 1px solid #333; | |
padding-bottom: 3px; | |
} | |
.victory { color: #4CAF50; } | |
.defeat { color: #F44336; } | |
.neutral { color: #FFC107; } | |
</style> | |
</head> | |
<body class="bg-gray-900 text-white"> | |
<div class="container mx-auto px-4 py-8"> | |
<header class="mb-8 text-center"> | |
<h1 class="text-4xl font-bold mb-2 text-yellow-400">Mount & Blade: German Wars 1400</h1> | |
<p class="text-lg text-gray-300">Lead your faction to dominance in the fractured Holy Roman Empire</p> | |
</header> | |
<div id="game-container"> | |
<!-- Character Creation Screen --> | |
<div id="character-creation" class="bg-gray-800 p-6 rounded-lg shadow-lg max-w-2xl mx-auto"> | |
<h2 class="text-2xl font-bold mb-6 text-center text-yellow-400">Create Your Character</h2> | |
<div class="grid grid-cols-1 md:grid-cols-2 gap-6"> | |
<div> | |
<div class="mb-4"> | |
<label class="block text-gray-300 mb-2">Character Name</label> | |
<input type="text" id="char-name" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded" value="Sir Ulrich"> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-300 mb-2">Faction</label> | |
<select id="faction-select" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded"> | |
<option value="bavaria">Duchy of Bavaria (Blue)</option> | |
<option value="swabia">Duchy of Swabia (Red)</option> | |
<option value="franconia">Duchy of Franconia (Gold)</option> | |
<option value="saxony">Duchy of Saxony (Green)</option> | |
<option value="lorraine">Duchy of Lorraine (White)</option> | |
</select> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-300 mb-2">Background</label> | |
<select id="background-select" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded"> | |
<option value="noble">Noble Son (+Leadership, +Relations with Nobles)</option> | |
<option value="merchant">Merchant (+Trade, +Gold)</option> | |
<option value="mercenary">Mercenary (+Combat Skills, -Relations with Nobles)</option> | |
<option value="peasant">Peasant (+Recruitment, -Starting Gold)</option> | |
</select> | |
</div> | |
</div> | |
<div> | |
<div class="mb-4"> | |
<label class="block text-gray-300 mb-2">Starting Location</label> | |
<select id="start-location" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded"> | |
<option value="munich">Munich (Bavaria)</option> | |
<option value="augsburg">Augsburg (Swabia)</option> | |
<option value="wurzburg">Würzburg (Franconia)</option> | |
<option value="leipzig">Leipzig (Saxony)</option> | |
<option value="trier">Trier (Lorraine)</option> | |
</select> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-gray-300 mb-2">Difficulty</label> | |
<select id="difficulty" class="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded"> | |
<option value="easy">Easy (More gold, weaker enemies)</option> | |
<option value="normal" selected>Normal (Balanced experience)</option> | |
<option value="hard">Hard (Less gold, stronger enemies)</option> | |
</select> | |
</div> | |
</div> | |
</div> | |
<div class="mt-6 text-center"> | |
<button id="start-game" class="px-6 py-3 bg-yellow-600 hover:bg-yellow-700 rounded-lg font-bold transition">Begin Your Adventure</button> | |
</div> | |
</div> | |
<!-- Main Game Screen (initially hidden) --> | |
<div id="game-screen" class="hidden"> | |
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6 mb-6"> | |
<!-- Player Stats Panel --> | |
<div class="bg-gray-800 p-4 rounded-lg shadow"> | |
<h3 class="text-xl font-bold mb-4 border-b border-gray-700 pb-2">Player Info</h3> | |
<div class="space-y-3"> | |
<div> | |
<span class="font-semibold">Name:</span> <span id="player-name">Sir Ulrich</span> | |
</div> | |
<div> | |
<span class="font-semibold">Faction:</span> <span id="player-faction">Bavaria</span> | |
</div> | |
<div> | |
<span class="font-semibold">Gold:</span> <span id="player-gold">1000</span> <i class="fas fa-coins text-yellow-400"></i> | |
</div> | |
<div> | |
<span class="font-semibold">Renown:</span> <span id="player-renown">0</span> | |
</div> | |
<div> | |
<span class="font-semibold">Troops:</span> <span id="player-troops">0/50</span> | |
</div> | |
<div class="pt-2"> | |
<div class="w-full bg-gray-700 rounded-full h-2.5"> | |
<div id="health-bar" class="bg-red-600 h-2.5 rounded-full" style="width: 100%"></div> | |
</div> | |
<span class="text-sm">Health: <span id="health-text">100</span>/100</span> | |
</div> | |
</div> | |
</div> | |
<!-- Current Location Panel --> | |
<div class="bg-gray-800 p-4 rounded-lg shadow"> | |
<h3 class="text-xl font-bold mb-4 border-b border-gray-700 pb-2">Current Location</h3> | |
<div id="location-info" class="space-y-3"> | |
<div class="text-center py-8 text-gray-500"> | |
Select a settlement on the map | |
</div> | |
</div> | |
</div> | |
<!-- Actions Panel --> | |
<div class="bg-gray-800 p-4 rounded-lg shadow"> | |
<h3 class="text-xl font-bold mb-4 border-b border-gray-700 pb-2">Actions</h3> | |
<div id="action-buttons" class="space-y-2"> | |
<button class="action-btn w-full px-4 py-2 bg-blue-700 hover:bg-blue-800 rounded disabled:opacity-50 disabled:cursor-not-allowed" disabled> | |
<i class="fas fa-swords mr-2"></i> Attack | |
</button> | |
<button class="action-btn w-full px-4 py-2 bg-green-700 hover:bg-green-800 rounded disabled:opacity-50 disabled:cursor-not-allowed" disabled> | |
<i class="fas fa-handshake mr-2"></i> Negotiate | |
</button> | |
<button class="action-btn w-full px-4 py-2 bg-yellow-700 hover:bg-yellow-800 rounded disabled:opacity-50 disabled:cursor-not-allowed" disabled> | |
<i class="fas fa-users mr-2"></i> Recruit | |
</button> | |
<button class="action-btn w-full px-4 py-2 bg-purple-700 hover:bg-purple-800 rounded disabled:opacity-50 disabled:cursor-not-allowed" disabled> | |
<i class="fas fa-store mr-2"></i> Trade | |
</button> | |
<button class="action-btn w-full px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded disabled:opacity-50 disabled:cursor-not-allowed" disabled> | |
<i class="fas fa-route mr-2"></i> Travel | |
</button> | |
</div> | |
</div> | |
<!-- Game Log --> | |
<div class="bg-gray-800 p-4 rounded-lg shadow"> | |
<h3 class="text-xl font-bold mb-4 border-b border-gray-700 pb-2">Game Log</h3> | |
<div id="game-log" class="h-48 overflow-y-auto text-sm space-y-1"> | |
<div class="text-gray-400 italic">Game begins...</div> | |
</div> | |
</div> | |
</div> | |
<!-- Map and Combat Area --> | |
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
<!-- Map Container --> | |
<div class="lg:col-span-2 bg-gray-800 p-4 rounded-lg shadow"> | |
<div class="flex justify-between items-center mb-4"> | |
<h3 class="text-xl font-bold">Holy Roman Empire - 1400 AD</h3> | |
<div class="flex space-x-2"> | |
<button id="end-turn" class="px-4 py-2 bg-red-700 hover:bg-red-800 rounded"> | |
<i class="fas fa-hourglass-end mr-2"></i> End Turn | |
</button> | |
</div> | |
</div> | |
<div class="map-container rounded-lg border border-gray-700 relative" id="game-map"> | |
<!-- Settlements will be added here by JavaScript --> | |
<div class="tooltip hidden" id="map-tooltip"></div> | |
</div> | |
<div class="mt-4 grid grid-cols-2 md:grid-cols-5 gap-2"> | |
<div class="flex items-center"> | |
<div class="w-4 h-4 rounded-full bg-blue-500 mr-2"></div> | |
<span>Bavaria</span> | |
</div> | |
<div class="flex items-center"> | |
<div class="w-4 h-4 rounded-full bg-red-500 mr-2"></div> | |
<span>Swabia</span> | |
</div> | |
<div class="flex items-center"> | |
<div class="w-4 h-4 rounded-full bg-yellow-500 mr-2"></div> | |
<span>Franconia</span> | |
</div> | |
<div class="flex items-center"> | |
<div class="w-4 h-4 rounded-full bg-green-500 mr-2"></div> | |
<span>Saxony</span> | |
</div> | |
<div class="flex items-center"> | |
<div class="w-4 h-4 rounded-full bg-white border border-gray-500 mr-2"></div> | |
<span>Lorraine</span> | |
</div> | |
</div> | |
</div> | |
<!-- Combat/Event Panel --> | |
<div class="bg-gray-800 p-4 rounded-lg shadow"> | |
<h3 class="text-xl font-bold mb-4 border-b border-gray-700 pb-2" id="combat-header">Events</h3> | |
<div id="combat-screen" class="hidden"> | |
<div class="flex justify-between items-center mb-4"> | |
<div> | |
<span class="font-semibold">Player:</span> <span id="combat-player">Sir Ulrich</span> | |
</div> | |
<div> | |
<span class="font-semibold">vs</span> | |
</div> | |
<div> | |
<span class="font-semibold">Enemy:</span> <span id="combat-enemy">Bandits</span> | |
</div> | |
</div> | |
<div class="grid grid-cols-2 gap-4 mb-4"> | |
<div> | |
<div class="text-center font-semibold mb-1">Player Forces</div> | |
<div id="player-forces" class="bg-gray-700 p-2 rounded"> | |
<!-- Will be filled by JS --> | |
</div> | |
</div> | |
<div> | |
<div class="text-center font-semibold mb-1">Enemy Forces</div> | |
<div id="enemy-forces" class="bg-gray-700 p-2 rounded"> | |
<!-- Will be filled by JS --> | |
</div> | |
</div> | |
</div> | |
<div class="mb-4"> | |
<div class="combat-log" id="combat-log"> | |
<!-- Combat messages will appear here --> | |
</div> | |
</div> | |
<div class="flex space-x-2"> | |
<button id="attack-btn" class="flex-1 px-4 py-2 bg-red-700 hover:bg-red-800 rounded"> | |
<i class="fas fa-swords mr-2"></i> Attack | |
</button> | |
<button id="retreat-btn" class="flex-1 px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded"> | |
<i class="fas fa-running mr-2"></i> Retreat | |
</button> | |
</div> | |
</div> | |
<div id="event-screen"> | |
<div class="text-center py-8 text-gray-500"> | |
No current events | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
// Game State | |
const gameState = { | |
player: { | |
name: "Sir Ulrich", | |
faction: "bavaria", | |
gold: 1000, | |
renown: 0, | |
maxTroops: 50, | |
currentTroops: 0, | |
health: 100, | |
maxHealth: 100, | |
location: null, | |
background: "noble", | |
relations: { | |
bavaria: 50, | |
swabia: 0, | |
franconia: 0, | |
saxony: 0, | |
lorraine: 0 | |
}, | |
inventory: { | |
food: 50, | |
weapons: 20, | |
armor: 10 | |
} | |
}, | |
factions: { | |
bavaria: { name: "Bavaria", color: "blue", strength: 70, relations: {} }, | |
swabia: { name: "Swabia", color: "red", strength: 60, relations: {} }, | |
franconia: { name: "Franconia", color: "yellow", strength: 55, relations: {} }, | |
saxony: { name: "Saxony", color: "green", strength: 65, relations: {} }, | |
lorraine: { name: "Lorraine", color: "white", strength: 50, relations: {} } | |
}, | |
settlements: [ | |
{ id: "munich", name: "Munich", type: "town", faction: "bavaria", x: 75, y: 85, garrison: 150, wealth: 800 }, | |
{ id: "augsburg", name: "Augsburg", type: "town", faction: "swabia", x: 60, y: 70, garrison: 120, wealth: 700 }, | |
{ id: "nuremberg", name: "Nuremberg", type: "town", faction: "franconia", x: 50, y: 60, garrison: 100, wealth: 750 }, | |
{ id: "wurzburg", name: "Würzburg", type: "town", faction: "franconia", x: 40, y: 55, garrison: 90, wealth: 600 }, | |
{ id: "frankfurt", name: "Frankfurt", type: "town", faction: "franconia", x: 30, y: 50, garrison: 110, wealth: 850 }, | |
{ id: "leipzig", name: "Leipzig", type: "town", faction: "saxony", x: 65, y: 40, garrison: 95, wealth: 650 }, | |
{ id: "trier", name: "Trier", type: "town", faction: "lorraine", x: 15, y: 60, garrison: 80, wealth: 550 }, | |
{ id: "regensburg", name: "Regensburg", type: "castle", faction: "bavaria", x: 80, y: 75, garrison: 70, wealth: 400 }, | |
{ id: "ulm", name: "Ulm", type: "castle", faction: "swabia", x: 55, y: 65, garrison: 60, wealth: 350 }, | |
{ id: "bamberg", name: "Bamberg", type: "castle", faction: "franconia", x: 45, y: 58, garrison: 50, wealth: 300 }, | |
{ id: "erfurt", name: "Erfurt", type: "castle", faction: "saxony", x: 55, y: 45, garrison: 55, wealth: 320 }, | |
{ id: "mainz", name: "Mainz", type: "castle", faction: "lorraine", x: 25, y: 55, garrison: 45, wealth: 280 }, | |
{ id: "village1", name: "Dorf an der Donau", type: "village", faction: "bavaria", x: 70, y: 80, garrison: 10, wealth: 150 }, | |
{ id: "village2", name: "Schwäbisches Dorf", type: "village", faction: "swabia", x: 58, y: 68, garrison: 8, wealth: 120 }, | |
{ id: "village3", name: "Fränkisches Dorf", type: "village", faction: "franconia", x: 48, y: 62, garrison: 7, wealth: 110 }, | |
{ id: "village4", name: "Sächsisches Dorf", type: "village", faction: "saxony", x: 60, y: 42, garrison: 9, wealth: 130 }, | |
{ id: "village5", name: "Lothringisches Dorf", type: "village", faction: "lorraine", x: 20, y: 58, garrison: 6, wealth: 100 } | |
], | |
currentTurn: 1, | |
inCombat: false, | |
combatEnemy: null, | |
selectedSettlement: null, | |
gameLog: [] | |
}; | |
// DOM Elements | |
const characterCreation = document.getElementById('character-creation'); | |
const gameScreen = document.getElementById('game-screen'); | |
const startGameBtn = document.getElementById('start-game'); | |
const playerNameEl = document.getElementById('player-name'); | |
const playerFactionEl = document.getElementById('player-faction'); | |
const playerGoldEl = document.getElementById('player-gold'); | |
const playerRenownEl = document.getElementById('player-renown'); | |
const playerTroopsEl = document.getElementById('player-troops'); | |
const healthBarEl = document.getElementById('health-bar'); | |
const healthTextEl = document.getElementById('health-text'); | |
const gameMap = document.getElementById('game-map'); | |
const locationInfo = document.getElementById('location-info'); | |
const actionButtons = document.querySelectorAll('.action-btn'); | |
const gameLog = document.getElementById('game-log'); | |
const combatScreen = document.getElementById('combat-screen'); | |
const combatHeader = document.getElementById('combat-header'); | |
const combatPlayerEl = document.getElementById('combat-player'); | |
const combatEnemyEl = document.getElementById('combat-enemy'); | |
const playerForcesEl = document.getElementById('player-forces'); | |
const enemyForcesEl = document.getElementById('enemy-forces'); | |
const combatLogEl = document.getElementById('combat-log'); | |
const attackBtn = document.getElementById('attack-btn'); | |
const retreatBtn = document.getElementById('retreat-btn'); | |
const endTurnBtn = document.getElementById('end-turn'); | |
const mapTooltip = document.getElementById('map-tooltip'); | |
// Initialize game | |
function initGame() { | |
// Set up event listeners | |
startGameBtn.addEventListener('click', startGame); | |
endTurnBtn.addEventListener('click', endTurn); | |
attackBtn.addEventListener('click', combatAttack); | |
retreatBtn.addEventListener('click', combatRetreat); | |
// Create settlements on map | |
renderSettlements(); | |
// Set up settlement click handlers | |
document.querySelectorAll('.settlement').forEach(settlement => { | |
settlement.addEventListener('click', () => selectSettlement(settlement.dataset.id)); | |
settlement.addEventListener('mouseenter', showSettlementTooltip); | |
settlement.addEventListener('mouseleave', hideSettlementTooltip); | |
}); | |
// Set up action buttons | |
document.querySelectorAll('.action-btn').forEach((btn, index) => { | |
btn.addEventListener('click', () => handleAction(index)); | |
}); | |
} | |
// Start the game | |
function startGame() { | |
// Get character creation values | |
gameState.player.name = document.getElementById('char-name').value || "Sir Ulrich"; | |
gameState.player.faction = document.getElementById('faction-select').value; | |
gameState.player.background = document.getElementById('background-select').value; | |
const startLocation = document.getElementById('start-location').value; | |
const difficulty = document.getElementById('difficulty').value; | |
// Apply background bonuses | |
applyBackgroundBonuses(); | |
// Apply difficulty settings | |
applyDifficultySettings(difficulty); | |
// Set starting location | |
const startingSettlement = gameState.settlements.find(s => s.id === startLocation); | |
gameState.player.location = startingSettlement.id; | |
// Update UI | |
updatePlayerInfo(); | |
selectSettlement(startLocation); | |
// Switch to game screen | |
characterCreation.classList.add('hidden'); | |
gameScreen.classList.remove('hidden'); | |
// Add starting troops | |
recruitTroops(10); | |
// Add initial log entry | |
addGameLog(`You begin your journey in ${startingSettlement.name} as a noble of ${gameState.factions[gameState.player.faction].name}.`); | |
} | |
function applyBackgroundBonuses() { | |
switch(gameState.player.background) { | |
case 'noble': | |
gameState.player.renown = 20; | |
for (const faction in gameState.player.relations) { | |
if (faction !== gameState.player.faction) { | |
gameState.player.relations[faction] = -10; | |
} else { | |
gameState.player.relations[faction] = 50; | |
} | |
} | |
break; | |
case 'merchant': | |
gameState.player.gold = 1500; | |
gameState.player.inventory.food = 100; | |
break; | |
case 'mercenary': | |
gameState.player.maxTroops = 70; | |
gameState.player.currentTroops = 20; | |
for (const faction in gameState.player.relations) { | |
gameState.player.relations[faction] = -20; | |
} | |
break; | |
case 'peasant': | |
gameState.player.gold = 500; | |
gameState.player.maxTroops = 80; | |
gameState.player.currentTroops = 15; | |
gameState.player.relations[gameState.player.faction] = 30; | |
break; | |
} | |
} | |
function applyDifficultySettings(difficulty) { | |
switch(difficulty) { | |
case 'easy': | |
gameState.player.gold *= 1.5; | |
gameState.player.maxHealth *= 1.2; | |
gameState.player.health = gameState.player.maxHealth; | |
break; | |
case 'hard': | |
gameState.player.gold *= 0.7; | |
gameState.player.maxHealth *= 0.8; | |
gameState.player.health = gameState.player.maxHealth; | |
break; | |
} | |
} | |
// Render settlements on the map | |
function renderSettlements() { | |
gameState.settlements.forEach(settlement => { | |
const settlementEl = document.createElement('div'); | |
settlementEl.className = `settlement ${settlement.type} faction-${settlement.faction}`; | |
settlementEl.dataset.id = settlement.id; | |
settlementEl.style.left = `${settlement.x}%`; | |
settlementEl.style.top = `${settlement.y}%`; | |
// Add first letter of settlement name as marker | |
const letter = document.createElement('span'); | |
letter.textContent = settlement.name.charAt(0); | |
settlementEl.appendChild(letter); | |
gameMap.appendChild(settlementEl); | |
}); | |
} | |
// Show settlement tooltip | |
function showSettlementTooltip(e) { | |
const settlementId = e.currentTarget.dataset.id; | |
const settlement = gameState.settlements.find(s => s.id === settlementId); | |
mapTooltip.innerHTML = ` | |
<div class="font-bold">${settlement.name}</div> | |
<div>Type: ${settlement.type.charAt(0).toUpperCase() + settlement.type.slice(1)}</div> | |
<div>Faction: ${gameState.factions[settlement.faction].name}</div> | |
<div>Garrison: ${settlement.garrison}</div> | |
<div>Wealth: ${settlement.wealth}</div> | |
`; | |
mapTooltip.style.left = `${e.pageX + 10}px`; | |
mapTooltip.style.top = `${e.pageY + 10}px`; | |
mapTooltip.classList.remove('hidden'); | |
} | |
// Hide settlement tooltip | |
function hideSettlementTooltip() { | |
mapTooltip.classList.add('hidden'); | |
} | |
// Select a settlement | |
function selectSettlement(settlementId) { | |
const settlement = gameState.settlements.find(s => s.id === settlementId); | |
gameState.selectedSettlement = settlement; | |
// Update location info | |
locationInfo.innerHTML = ` | |
<div class="text-center font-bold text-lg mb-2">${settlement.name}</div> | |
<div class="grid grid-cols-2 gap-2 mb-3"> | |
<div><span class="font-semibold">Type:</span> ${settlement.type.charAt(0).toUpperCase() + settlement.type.slice(1)}</div> | |
<div><span class="font-semibold">Faction:</span> ${gameState.factions[settlement.faction].name}</div> | |
<div><span class="font-semibold">Garrison:</span> ${settlement.garrison}</div> | |
<div><span class="font-semibold">Wealth:</span> ${settlement.wealth}</div> | |
</div> | |
<div class="text-sm text-gray-400 italic"> | |
${getSettlementDescription(settlement)} | |
</div> | |
`; | |
// Enable/disable action buttons based on settlement | |
actionButtons.forEach(btn => btn.disabled = false); | |
// Special cases | |
if (settlement.faction === gameState.player.faction) { | |
document.querySelectorAll('.action-btn')[1].disabled = true; // Disable negotiate with own faction | |
} | |
if (settlement.id === gameState.player.location) { | |
document.querySelectorAll('.action-btn')[4].disabled = true; // Disable travel to current location | |
} | |
// Highlight selected settlement on map | |
document.querySelectorAll('.settlement').forEach(el => { | |
el.classList.remove('border-2', 'border-white'); | |
}); | |
document.querySelector(`.settlement[data-id="${settlementId}"]`).classList.add('border-2', 'border-white'); | |
} | |
function getSettlementDescription(settlement) { | |
const faction = gameState.factions[settlement.faction]; | |
const playerFaction = gameState.factions[gameState.player.faction]; | |
if (settlement.type === 'town') { | |
return `A bustling ${faction.name} town with merchants, craftsmen, and a strong garrison.`; | |
} else if (settlement.type === 'castle') { | |
return `A formidable ${faction.name} castle, home to local nobility and their retinue.`; | |
} else { | |
return `A quiet ${faction.name} village, where peasants work the land.`; | |
} | |
} | |
// Handle action button clicks | |
function handleAction(actionIndex) { | |
const settlement = gameState.selectedSettlement; | |
switch(actionIndex) { | |
case 0: // Attack | |
if (settlement.faction === gameState.player.faction) { | |
addGameLog("You cannot attack your own faction's settlements!"); | |
return; | |
} | |
if (gameState.player.currentTroops < 5) { | |
addGameLog("You need at least 5 troops to attack!"); | |
return; | |
} | |
startCombat(settlement); | |
break; | |
case 1: // Negotiate | |
if (settlement.faction === gameState.player.faction) { | |
return; | |
} | |
negotiate(settlement); | |
break; | |
case 2: // Recruit | |
recruitTroops(5); | |
break; | |
case 3: // Trade | |
trade(); | |
break; | |
case 4: // Travel | |
travelTo(settlement); | |
break; | |
} | |
} | |
// Start combat with a settlement | |
function startCombat(settlement) { | |
gameState.inCombat = true; | |
gameState.combatEnemy = { | |
name: `${settlement.name} Garrison`, | |
type: 'garrison', | |
troops: Math.floor(settlement.garrison * 0.7), // Only part of garrison fights | |
health: 100 | |
}; | |
// Update combat UI | |
combatHeader.textContent = "Battle"; | |
combatPlayerEl.textContent = gameState.player.name; | |
combatEnemyEl.textContent = gameState.combatEnemy.name; | |
// Show forces | |
updateCombatForces(); | |
// Clear combat log | |
combatLogEl.innerHTML = ''; | |
// Show combat screen | |
combatScreen.classList.remove('hidden'); | |
// Add initial log | |
addCombatLog(`You engage the ${settlement.name} garrison in battle!`, 'neutral'); | |
} | |
// Update combat forces display | |
function updateCombatForces() { | |
playerForcesEl.innerHTML = ` | |
<div class="mb-2"> | |
<div class="flex justify-between mb-1"> | |
<span>Troops:</span> | |
<span>${gameState.player.currentTroops}</span> | |
</div> | |
<div class="w-full bg-gray-600 rounded-full h-2"> | |
<div class="bg-blue-600 h-2 rounded-full" style="width: ${gameState.player.health}%"></div> | |
</div> | |
</div> | |
`; | |
enemyForcesEl.innerHTML = ` | |
<div class="mb-2"> | |
<div class="flex justify-between mb-1"> | |
<span>Troops:</span> | |
<span>${gameState.combatEnemy.troops}</span> | |
</div> | |
<div class="w-full bg-gray-600 rounded-full h-2"> | |
<div class="bg-red-600 h-2 rounded-full" style="width: ${gameState.combatEnemy.health}%"></div> | |
</div> | |
</div> | |
`; | |
} | |
// Handle combat attack | |
function combatAttack() { | |
if (!gameState.inCombat) return; | |
// Player attacks | |
const playerDamage = Math.floor(Math.random() * 20) + 10 + gameState.player.currentTroops; | |
const enemyDamage = Math.floor(Math.random() * 15) + 5 + gameState.combatEnemy.troops; | |
// Apply damage | |
gameState.combatEnemy.health -= playerDamage / 2; | |
gameState.player.health -= enemyDamage / 2; | |
// Update troop numbers based on damage | |
const playerTroopsLost = Math.floor(enemyDamage / 10); | |
const enemyTroopsLost = Math.floor(playerDamage / 10); | |
gameState.player.currentTroops = Math.max(0, gameState.player.currentTroops - playerTroopsLost); | |
gameState.combatEnemy.troops = Math.max(0, gameState.combatEnemy.troops - enemyTroopsLost); | |
// Update UI | |
updateCombatForces(); | |
updatePlayerInfo(); | |
// Add combat log | |
addCombatLog(`Your forces attack! You lose ${playerTroopsLost} troops and deal ${enemyTroopsLost} casualties.`, 'neutral'); | |
// Check for combat end | |
if (gameState.player.health <= 0 || gameState.player.currentTroops <= 0) { | |
endCombat(false); | |
return; | |
} | |
if (gameState.combatEnemy.health <= 0 || gameState.combatEnemy.troops <= 0) { | |
endCombat(true); | |
return; | |
} | |
} | |
// Handle combat retreat | |
function combatRetreat() { | |
if (!gameState.inCombat) return; | |
// Lose some troops during retreat | |
const troopsLost = Math.floor(gameState.player.currentTroops * 0.3); | |
gameState.player.currentTroops -= troopsLost; | |
// Update UI | |
updatePlayerInfo(); | |
// Add combat log | |
addCombatLog(`You retreat from battle, losing ${troopsLost} troops in the process.`, 'defeat'); | |
// End combat | |
endCombat(false); | |
} | |
// End combat | |
function endCombat(victory) { | |
gameState.inCombat = false; | |
if (victory) { | |
// Calculate loot | |
const loot = Math.floor(gameState.selectedSettlement.wealth * 0.3); | |
gameState.player.gold += loot; | |
gameState.player.renown += 5; | |
// Update settlement | |
gameState.selectedSettlement.garrison = Math.floor(gameState.selectedSettlement.garrison * 0.5); | |
// Update UI | |
updatePlayerInfo(); | |
addCombatLog(`You are victorious! You gain ${loot} gold and 5 renown.`, 'victory'); | |
addGameLog(`You defeated the ${gameState.selectedSettlement.name} garrison and gained ${loot} gold!`); | |
} else { | |
addCombatLog("You were defeated in battle.", 'defeat'); | |
addGameLog("You were defeated in battle."); | |
// If player has no troops left, they're captured | |
if (gameState.player.currentTroops <= 0) { | |
gameState.player.health = 50; | |
gameState.player.gold = Math.floor(gameState.player.gold * 0.5); | |
addGameLog("You've been captured! You lose half your gold and are returned to your starting location."); | |
// Return to starting location | |
const startingSettlement = gameState.settlements.find(s => s.id === gameState.player.location); | |
selectSettlement(startingSettlement.id); | |
} | |
} | |
// Hide combat screen after delay | |
setTimeout(() => { | |
combatScreen.classList.add('hidden'); | |
}, 3000); | |
} | |
// Add message to combat log | |
function addCombatLog(message, type) { | |
const entry = document.createElement('div'); | |
entry.className = `combat-entry ${type}`; | |
entry.textContent = message; | |
combatLogEl.appendChild(entry); | |
combatLogEl.scrollTop = combatLogEl.scrollHeight; | |
} | |
// Negotiate with settlement | |
function negotiate(settlement) { | |
const faction = settlement.faction; | |
const currentRelation = gameState.player.relations[faction] || 0; | |
// Can't negotiate if relation is too low | |
if (currentRelation < -20) { | |
addGameLog(`The ${gameState.factions[faction].name} refuse to negotiate with you due to poor relations.`); | |
return; | |
} | |
// Chance of success based on relation | |
const successChance = 50 + currentRelation; | |
const isSuccess = Math.random() * 100 < successChance; | |
if (isSuccess) { | |
// Improve relations | |
gameState.player.relations[faction] = (gameState.player.relations[faction] || 0) + 5; | |
// Possible tribute | |
if (Math.random() > 0.7) { | |
const tribute = Math.floor(settlement.wealth * 0.1); | |
gameState.player.gold += tribute; | |
addGameLog(`Negotiation successful! ${settlement.name} agrees to pay you ${tribute} gold in tribute. Relations with ${gameState.factions[faction].name} improved.`); | |
} else { | |
addGameLog(`Negotiation successful! Relations with ${gameState.factions[faction].name} improved.`); | |
} | |
} else { | |
// Small relation penalty for failed negotiation | |
gameState.player.relations[faction] = (gameState.player.relations[faction] || 0) - 2; | |
addGameLog(`Negotiation failed. The ${gameState.factions[faction].name} are not interested in your proposals.`); | |
} | |
updatePlayerInfo(); | |
} | |
// Recruit troops | |
function recruitTroops(amount) { | |
if (gameState.selectedSettlement.faction !== gameState.player.faction) { | |
addGameLog("You can only recruit troops in your own faction's settlements!"); | |
return; | |
} | |
const costPerTroop = 10; | |
const totalCost = amount * costPerTroop; | |
if (gameState.player.gold < totalCost) { | |
addGameLog(`You need ${totalCost} gold to recruit ${amount} troops!`); | |
return; | |
} | |
if (gameState.player.currentTroops + amount > gameState.player.maxTroops) { | |
amount = gameState.player.maxTroops - gameState.player.currentTroops; | |
if (amount <= 0) { | |
addGameLog("You've reached your maximum troop capacity!"); | |
return; | |
} | |
} | |
gameState.player.gold -= amount * costPerTroop; | |
gameState.player.currentTroops += amount; | |
addGameLog(`You recruited ${amount} troops for ${amount * costPerTroop} gold.`); | |
updatePlayerInfo(); | |
} | |
// Trade goods | |
function trade() { | |
const settlement = gameState.selectedSettlement; | |
const tradeAmount = 50; | |
const profit = Math.floor(Math.random() * 30) + 10; | |
// Can't trade in enemy settlements | |
if (settlement.faction !== gameState.player.faction) { | |
addGameLog("You can only trade in your own faction's settlements!"); | |
return; | |
} | |
// Need some food to trade | |
if (gameState.player.inventory.food < tradeAmount) { | |
addGameLog(`You need at least ${tradeAmount} food to trade!`); | |
return; | |
} | |
gameState.player.inventory.food -= tradeAmount; | |
gameState.player.gold += profit; | |
addGameLog(`You traded ${tradeAmount} food and made ${profit} gold profit.`); | |
updatePlayerInfo(); | |
} | |
// Travel to a settlement | |
function travelTo(settlement) { | |
if (settlement.id === gameState.player.location) { | |
return; | |
} | |
// Random events can happen during travel | |
const eventRoll = Math.random(); | |
if (eventRoll < 0.3) { | |
// No event | |
addGameLog(`You travel safely to ${settlement.name}.`); | |
} else if (eventRoll < 0.6) { | |
// Find some loot | |
const loot = Math.floor(Math.random() * 50) + 20; | |
gameState.player.gold += loot; | |
addGameLog(`On the way to ${settlement.name}, you find ${loot} gold!`); | |
} else if (eventRoll < 0.9) { | |
// Bandit attack | |
startRandomCombat(); | |
return; // Don't change location if combat happens | |
} else { | |
// Noble encounter | |
const nobleGift = Math.floor(Math.random() * 100) + 50; | |
gameState.player.gold += nobleGift; | |
addGameLog(`You meet a friendly noble on the road who gifts you ${nobleGift} gold!`); | |
} | |
// Update location | |
gameState.player.location = settlement.id; | |
updatePlayerInfo(); | |
addGameLog(`You arrive at ${settlement.name}.`); | |
} | |
// Start random combat (bandits) | |
function startRandomCombat() { | |
gameState.inCombat = true; | |
const banditStrength = Math.floor(gameState.player.currentTroops * (0.5 + Math.random() * 0.5)); | |
gameState.combatEnemy = { | |
name: "Bandit Group", | |
type: 'bandits', | |
troops: banditStrength, | |
health: 100 | |
}; | |
// Update combat UI | |
combatHeader.textContent = "Ambush!"; | |
combatPlayerEl.textContent = gameState.player.name; | |
combatEnemyEl.textContent = gameState.combatEnemy.name; | |
// Show forces | |
updateCombatForces(); | |
// Clear combat log | |
combatLogEl.innerHTML = ''; | |
// Show combat screen | |
combatScreen.classList.remove('hidden'); | |
// Add initial log | |
addCombatLog(`You've been ambushed by bandits!`, 'neutral'); | |
addGameLog("Your party has been ambushed by bandits on the road!"); | |
} | |
// End turn | |
function endTurn() { | |
gameState.currentTurn++; | |
// Pay troop wages | |
const wages = gameState.player.currentTroops * 2; | |
gameState.player.gold -= wages; | |
// Consume food | |
const foodConsumed = gameState.player.currentTroops; | |
gameState.player.inventory.food -= foodConsumed; | |
// Handle negative food (troops desert) | |
if (gameState.player.inventory.food < 0) { | |
const deserters = Math.floor(gameState.player.currentTroops * 0.2); | |
gameState.player.currentTroops -= deserters; | |
gameState.player.inventory.food = 0; | |
addGameLog(`${deserters} troops deserted due to lack of food!`); | |
} | |
// Handle negative gold (troops desert) | |
if (gameState.player.gold < 0) { | |
const deserters = Math.floor(gameState.player.currentTroops * 0.3); | |
gameState.player.currentTroops -= deserters; | |
gameState.player.gold = 0; | |
addGameLog(`${deserters} troops deserted due to lack of pay!`); | |
} | |
// Random events | |
const eventRoll = Math.random(); | |
if (eventRoll < 0.2) { | |
// Positive event | |
const positiveEvents = [ | |
{ message: "A group of mercenaries joins your cause!", effect: () => { gameState.player.currentTroops += 5; } }, | |
{ message: "You find a chest of gold on the road!", effect: () => { gameState.player.gold += 100; } }, | |
{ message: "Your reputation grows, attracting more troops.", effect: () => { gameState.player.renown += 3; } } | |
]; | |
const event = positiveEvents[Math.floor(Math.random() * positiveEvents.length)]; | |
event.effect(); | |
addGameLog(event.message); | |
} else if (eventRoll < 0.4) { | |
// Negative event | |
const negativeEvents = [ | |
{ message: "A disease sweeps through your camp.", effect: () => { gameState.player.currentTroops = Math.floor(gameState.player.currentTroops * 0.9); } }, | |
{ message: "Thieves steal some of your gold!", effect: () => { gameState.player.gold = Math.floor(gameState.player.gold * 0.8); } }, | |
{ message: "A rival spreads rumors about you.", effect: () => { gameState.player.renown -= 3; } } | |
]; | |
const event = negativeEvents[Math.floor(Math.random() * negativeEvents.length)]; | |
event.effect(); | |
addGameLog(event.message); | |
} | |
// Update UI | |
updatePlayerInfo(); | |
addGameLog(`Turn ${gameState.currentTurn} completed. You paid ${wages} gold in wages and consumed ${foodConsumed} food.`); | |
} | |
// Update player info display | |
function updatePlayerInfo() { | |
playerNameEl.textContent = gameState.player.name; | |
playerFactionEl.textContent = gameState.factions[gameState.player.faction].name; | |
playerGoldEl.textContent = gameState.player.gold; | |
playerRenownEl.textContent = gameState.player.renown; | |
playerTroopsEl.textContent = `${gameState.player.currentTroops}/${gameState.player.maxTroops}`; | |
healthTextEl.textContent = `${gameState.player.health}`; | |
healthBarEl.style.width = `${gameState.player.health}%`; | |
} | |
// Add message to game log | |
function addGameLog(message) { | |
const entry = document.createElement('div'); | |
entry.className = 'border-b border-gray-700 pb-1'; | |
entry.textContent = `[Turn ${gameState.currentTurn}] ${message}`; | |
gameLog.appendChild(entry); | |
gameLog.scrollTop = gameLog.scrollHeight; | |
// Keep log to 50 entries max | |
if (gameLog.children.length > 50) { | |
gameLog.removeChild(gameLog.children[0]); | |
} | |
// Add to game state log | |
gameState.gameLog.push(message); | |
} | |
// Initialize the game when page loads | |
window.addEventListener('DOMContentLoaded', initGame); | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=yazdaabdul/mount-and-blade" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |