Upload files to "/"
This commit is contained in:
parent
e30b33a688
commit
2c6cfccc40
381
index.html
381
index.html
|
|
@ -7914,8 +7914,16 @@ Gamestate.openDiplomacy = function (targetName) {
|
||||||
// STAGE 1: SELECTING THE ATTACKING TERRITORY
|
// STAGE 1: SELECTING THE ATTACKING TERRITORY
|
||||||
if (!this.prevCountry) {
|
if (!this.prevCountry) {
|
||||||
if (country.owner === this.player.name && country.army > 1) {
|
if (country.owner === this.player.name && country.army > 1) {
|
||||||
|
// NEW: Block attacking if exploring
|
||||||
|
if (country.isExploring) {
|
||||||
|
if (Gamestate.showToast) Gamestate.showToast("Cannot attack: Troops are currently exploring.", "red");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
this.prevCountry = country; // Source selected
|
this.prevCountry = country; // Source selected
|
||||||
e.target.classList.add('flash');
|
e.target.classList.add('flash');
|
||||||
|
|
||||||
this.prevTarget = e.target;
|
this.prevTarget = e.target;
|
||||||
if (turnInfoMessage) turnInfoMessage.textContent = "Now, select an adjacent enemy territory to ATTACK.";
|
if (turnInfoMessage) turnInfoMessage.textContent = "Now, select an adjacent enemy territory to ATTACK.";
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -7994,7 +8002,15 @@ Gamestate.openDiplomacy = function (targetName) {
|
||||||
// WHEN YOU CLICK YOUR FIRST TERRITORY (Selecting the attacker)
|
// WHEN YOU CLICK YOUR FIRST TERRITORY (Selecting the attacker)
|
||||||
if (!this.prevCountry) {
|
if (!this.prevCountry) {
|
||||||
if (country.owner === this.player.name) {
|
if (country.owner === this.player.name) {
|
||||||
|
// NEW: Block attacking if exploring
|
||||||
|
if (country.isExploring) {
|
||||||
|
if (Gamestate.showToast) Gamestate.showToast("Cannot attack: Troops are currently exploring.", "red");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// --- INTERNAL PURGE LOGIC ---
|
// --- INTERNAL PURGE LOGIC ---
|
||||||
|
|
||||||
let trespasser = this.players.find(p => p !== this.player && p.alive && !p.isNeutral && p.commander && p.commander.hp > 0 && p.commander.loc === country.name);
|
let trespasser = this.players.find(p => p !== this.player && p.alive && !p.isNeutral && p.commander && p.commander.hp > 0 && p.commander.loc === country.name);
|
||||||
if (this.commandersEnabled && trespasser) {
|
if (this.commandersEnabled && trespasser) {
|
||||||
if (country.army <= 1) {
|
if (country.army <= 1) {
|
||||||
|
|
@ -8173,7 +8189,14 @@ Gamestate.openDiplomacy = function (targetName) {
|
||||||
if (this.showToast) this.showToast("Maneuver blocked: Territory is under Elder's Edict lockdown.", "red");
|
if (this.showToast) this.showToast("Maneuver blocked: Territory is under Elder's Edict lockdown.", "red");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// NEW: Block maneuvering if exploring
|
||||||
|
if (country.isExploring) {
|
||||||
|
if (Gamestate.showToast) Gamestate.showToast("Maneuver blocked: Troops are currently exploring.", "red");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (country.army <= 1) {
|
if (country.army <= 1) {
|
||||||
|
|
||||||
if (this.showToast) this.showToast("Maneuver blocked: Must have more than 1 troop to move.", "red");
|
if (this.showToast) this.showToast("Maneuver blocked: Must have more than 1 troop to move.", "red");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -8593,10 +8616,23 @@ Gamestate.openDiplomacy = function (targetName) {
|
||||||
if (i === this.players.length) {
|
if (i === this.players.length) {
|
||||||
if (this.player.areas.length === 0 || (this.commandersEnabled && this.player.commander && this.player.commander.hp <= 0)) {
|
if (this.player.areas.length === 0 || (this.commandersEnabled && this.player.commander && this.player.commander.hp <= 0)) {
|
||||||
this.player.alive = false;
|
this.player.alive = false;
|
||||||
setTimeout(() => this.aiMove(), 100);
|
this.checkWinCondition(); // This triggers the defeat modal instantly
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- NEW: Process Exploration Countdowns FIRST ---
|
||||||
|
for (let c of this.countries) {
|
||||||
|
if (c.owner === this.player.name && c.isExploring) {
|
||||||
|
c.exploreTurnsLeft--;
|
||||||
|
if (c.exploreTurnsLeft <= 0) {
|
||||||
|
await this.resolveExplorationOutcome(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.bobbleheads) {
|
if (this.bobbleheads) {
|
||||||
|
|
||||||
this.bobbleheads.forEach(b => {
|
this.bobbleheads.forEach(b => {
|
||||||
if (b.active) {
|
if (b.active) {
|
||||||
b.active = false;
|
b.active = false;
|
||||||
|
|
@ -9000,7 +9036,11 @@ Gamestate.openDiplomacy = function (targetName) {
|
||||||
let country = this.countries.find(c => c.name === area);
|
let country = this.countries.find(c => c.name === area);
|
||||||
if (country && country.army > 1 && country.owner === this.players[i].name && (!this.nukesEnabled || !country.isSilo || Math.random() < 0.2)) {
|
if (country && country.army > 1 && country.owner === this.players[i].name && (!this.nukesEnabled || !country.isSilo || Math.random() < 0.2)) {
|
||||||
await this.aiAttack(country, i, turbo);
|
await this.aiAttack(country, i, turbo);
|
||||||
|
// NEW: If this attack ended the game, freeze the AI instantly!
|
||||||
|
if (this.gameOver) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
this.aiManeuver(i);
|
this.aiManeuver(i);
|
||||||
if (this.players[i].conqueredThisTurn) {
|
if (this.players[i].conqueredThisTurn) {
|
||||||
|
|
@ -9641,11 +9681,31 @@ Gamestate.openDiplomacy = function (targetName) {
|
||||||
isVictory = true;
|
isVictory = true;
|
||||||
player.conqueredThisTurn = true;
|
player.conqueredThisTurn = true;
|
||||||
|
|
||||||
// --- THIS IS THE NEW ENCOUNTER TRIGGER ---
|
// --- NEW: EXPEDITION WIPED OUT CHECK ---
|
||||||
await this.triggerEncounterCheck('post_conquest');
|
if (opponent.isExploring) {
|
||||||
|
// Only show the modal if the HUMAN player's expedition was destroyed
|
||||||
|
if (originalOwner === this.player.name) {
|
||||||
|
const title = "Signal Lost";
|
||||||
|
const msg = `>>> CONNECTION SEVERED <<<<br><br>The expedition force searching the ${opponent.explorePOI || 'area'} at ${formatTerritoryName(opponent.name)} has been completely wiped out by an attack from ${player.name}.<br><br>All hands and recovered assets are lost.`;
|
||||||
|
const choices = [{ id: "acknowledge", text: "[Acknowledge] A tragic loss." }];
|
||||||
|
|
||||||
|
this.modalIsOpen = true;
|
||||||
|
await this.showEncounterModal(title, msg, choices, () => {
|
||||||
|
this.logAction(`[ TRAGEDY ] The expedition at ${formatTerritoryName(opponent.name)} was slaughtered by ${player.name}.`, true);
|
||||||
|
return null; // Skip outcome delay screen
|
||||||
|
});
|
||||||
|
this.modalIsOpen = false;
|
||||||
|
}
|
||||||
|
// Clear the exploring flags so the new owner doesn't inherit them
|
||||||
|
opponent.isExploring = false;
|
||||||
|
opponent.exploreTurnsLeft = 0;
|
||||||
|
opponent.exploreType = null;
|
||||||
|
opponent.explorePOI = null;
|
||||||
|
}
|
||||||
|
|
||||||
flavor = (Math.random() < 0.10) ? (" " + combatFlavors[Math.floor(Math.random() * combatFlavors.length)]) : "!";
|
flavor = (Math.random() < 0.10) ? (" " + combatFlavors[Math.floor(Math.random() * combatFlavors.length)]) : "!";
|
||||||
this.players.forEach(p => {
|
this.players.forEach(p => {
|
||||||
|
|
||||||
if (p.name === opponent.owner) {
|
if (p.name === opponent.owner) {
|
||||||
let index = p.areas.indexOf(opponent.name);
|
let index = p.areas.indexOf(opponent.name);
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
|
|
@ -9677,7 +9737,13 @@ Gamestate.openDiplomacy = function (targetName) {
|
||||||
country.army -= movedTroops;
|
country.army -= movedTroops;
|
||||||
if (defender) defender.style.fill = opponent.color;
|
if (defender) defender.style.fill = opponent.color;
|
||||||
|
|
||||||
|
// --- NEW ENCOUNTER TRIGGER IS NOW HERE (After troops have arrived!) ---
|
||||||
|
if (player.isPlayer) {
|
||||||
|
await this.triggerEncounterCheck('post_conquest', opponent.name);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.perksEnabled && player.perk) {
|
if (this.perksEnabled && player.perk) {
|
||||||
|
|
||||||
if (player.perk.id === 'fev_infection') {
|
if (player.perk.id === 'fev_infection') {
|
||||||
// (Bonus Fix: Made the victory FEV math match the repulse FEV math!)
|
// (Bonus Fix: Made the victory FEV math match the repulse FEV math!)
|
||||||
const converted = Math.max(1, Math.floor(originalDefenderArmy * 0.25));
|
const converted = Math.max(1, Math.floor(originalDefenderArmy * 0.25));
|
||||||
|
|
@ -9866,6 +9932,39 @@ Gamestate.openDiplomacy = function (targetName) {
|
||||||
Gamestate.checkWinCondition = function () {
|
Gamestate.checkWinCondition = function () {
|
||||||
if (this.gameOver) return;
|
if (this.gameOver) return;
|
||||||
|
|
||||||
|
let winModal = document.querySelector('#win-modal');
|
||||||
|
let winMessage = document.querySelector('.win-message');
|
||||||
|
if (!winModal || !winMessage) return;
|
||||||
|
|
||||||
|
// --- CHECK 1: PLAYER DEFEAT ---
|
||||||
|
let playerDead = false;
|
||||||
|
if (this.commandersEnabled && (!this.player.commander || this.player.commander.hp <= 0)) {
|
||||||
|
playerDead = true;
|
||||||
|
} else if (this.player.areas.length === 0) {
|
||||||
|
playerDead = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerDead) {
|
||||||
|
this.gameOver = true;
|
||||||
|
this.player.alive = false;
|
||||||
|
|
||||||
|
let defeatFlavors = [
|
||||||
|
"Your faction is nothing but a footnote in the wasteland's bloody history.",
|
||||||
|
"Your forces have been scattered, your settlements burned. The wastes claim another victim.",
|
||||||
|
"War never changes. And this time, you were on the losing side."
|
||||||
|
];
|
||||||
|
let cmdrFlavor = "Your Commander has fallen. Without leadership, your forces scattered into the irradiated winds.";
|
||||||
|
|
||||||
|
winMessage.textContent = "YOU DIED.";
|
||||||
|
winMessage.style.color = "#ff3333";
|
||||||
|
let subMsg = winMessage.nextElementSibling;
|
||||||
|
if (subMsg && subMsg.tagName === 'P') {
|
||||||
|
subMsg.textContent = this.commandersEnabled ? cmdrFlavor : defeatFlavors[Math.floor(Math.random() * defeatFlavors.length)];
|
||||||
|
}
|
||||||
|
winModal.style.display = "block";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let totalPlayableLand = this.countries.filter(c => !c.isCrater).length;
|
let totalPlayableLand = this.countries.filter(c => !c.isCrater).length;
|
||||||
let playerLand = this.countries.filter(c => c.owner === this.player.name).length;
|
let playerLand = this.countries.filter(c => c.owner === this.player.name).length;
|
||||||
let ownsAllLand = (playerLand >= totalPlayableLand);
|
let ownsAllLand = (playerLand >= totalPlayableLand);
|
||||||
|
|
@ -9879,48 +9978,52 @@ Gamestate.openDiplomacy = function (targetName) {
|
||||||
if (livingRivals.length > 0) allRivalsDead = false;
|
if (livingRivals.length > 0) allRivalsDead = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- CHECK 1: TOTAL DOMINATION ---
|
// --- CHECK 2: TOTAL DOMINATION ---
|
||||||
if (ownsAllLand && allRivalsDead) {
|
if (ownsAllLand && allRivalsDead) {
|
||||||
this.gameOver = true;
|
this.gameOver = true;
|
||||||
let winModal = document.querySelector('#win-modal');
|
let domFlavors = [
|
||||||
let winMessage = document.querySelector('.win-message');
|
"You have conquered the wastes. All who opposed you are dust in the wind.",
|
||||||
|
"From the ashes of the old world, your empire rises absolute.",
|
||||||
if (winMessage) {
|
"No rivals remain. The wasteland belongs to you alone."
|
||||||
winMessage.textContent = "TOTAL DOMINATION!";
|
];
|
||||||
winMessage.style.color = "var(--pip-color)";
|
winMessage.textContent = "TOTAL DOMINATION!";
|
||||||
let subMsg = winMessage.nextElementSibling;
|
winMessage.style.color = "var(--pip-color)";
|
||||||
if (subMsg && subMsg.tagName === 'P') {
|
let subMsg = winMessage.nextElementSibling;
|
||||||
if (this.commandersEnabled) {
|
if (subMsg && subMsg.tagName === 'P') {
|
||||||
subMsg.textContent = "All rival commanders have been executed and the wasteland is entirely under your control.";
|
subMsg.textContent = domFlavors[Math.floor(Math.random() * domFlavors.length)];
|
||||||
} else {
|
|
||||||
subMsg.textContent = "You have conquered all territories in the wasteland.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (winModal) winModal.style.display = "block";
|
winModal.style.display = "block";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- CHECK 2: SHARED ALLIED VICTORY ---
|
// --- CHECK 3: SHARED ALLIED VICTORY ---
|
||||||
let livingFactions = this.players.filter(p => p.alive && !p.isNeutral && p.name !== this.player.name);
|
let livingFactions = this.players.filter(p => p.alive && !p.isNeutral && p.name !== this.player.name);
|
||||||
|
|
||||||
if (livingFactions.length > 0) {
|
if (livingFactions.length > 0) {
|
||||||
let allLivingAreIdolized = livingFactions.every(p => this.diplomacy.reputation[p.name] && this.diplomacy.reputation[p.name][this.player.name] >= 35);
|
let allLivingAreAllies = false;
|
||||||
|
|
||||||
|
if (this.isAllianceMode) {
|
||||||
|
// NEW: In Alliance mode, check if everyone alive shares your Team ID
|
||||||
|
allLivingAreAllies = livingFactions.every(p => p.team === this.player.team);
|
||||||
|
} else {
|
||||||
|
// Classic Mode: Require Idolized diplomacy reputation
|
||||||
|
allLivingAreAllies = livingFactions.every(p => this.diplomacy.reputation[p.name] && this.diplomacy.reputation[p.name][this.player.name] >= 35);
|
||||||
|
}
|
||||||
|
|
||||||
if (allLivingAreIdolized) {
|
if (allLivingAreAllies) {
|
||||||
this.gameOver = true;
|
this.gameOver = true;
|
||||||
let winModal = document.querySelector('#win-modal');
|
let allyFlavors = [
|
||||||
let winMessage = document.querySelector('.win-message');
|
"Through diplomacy and shared blood, you and your allies have united the wastes.",
|
||||||
|
"A coalition of power now rules the wasteland. Together, you brought order from chaos.",
|
||||||
if (winMessage) {
|
"The wars are over. Your alliance stands victorious over the irradiated ashes."
|
||||||
winMessage.textContent = "ALLIED VICTORY!";
|
];
|
||||||
winMessage.style.color = "#0088ff"; // A distinct "peace" color
|
winMessage.textContent = "ALLIED VICTORY!";
|
||||||
let subMsg = winMessage.nextElementSibling;
|
winMessage.style.color = "#39ff14"; // Distinct green/alliance color
|
||||||
if (subMsg && subMsg.tagName === 'P') {
|
let subMsg = winMessage.nextElementSibling;
|
||||||
subMsg.textContent = "Through masterful diplomacy and shared trust, the remaining factions have united under your leadership.";
|
if (subMsg && subMsg.tagName === 'P') {
|
||||||
}
|
subMsg.textContent = allyFlavors[Math.floor(Math.random() * allyFlavors.length)];
|
||||||
}
|
}
|
||||||
if (winModal) winModal.style.display = "block";
|
winModal.style.display = "block";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -9945,21 +10048,30 @@ Gamestate.openDiplomacy = function (targetName) {
|
||||||
// If an onChoice function is provided, execute it and get the result message
|
// If an onChoice function is provided, execute it and get the result message
|
||||||
if (onChoice) {
|
if (onChoice) {
|
||||||
const resultMessage = await onChoice(choice.id);
|
const resultMessage = await onChoice(choice.id);
|
||||||
titleEl.textContent = "Outcome";
|
|
||||||
messageEl.innerHTML = resultMessage; // Display the result
|
// NEW: If the event returned text, show the 2.5s delay screen.
|
||||||
choicesContainer.innerHTML = ''; // Clear the buttons
|
// If it returned null, close the modal instantly!
|
||||||
|
if (resultMessage) {
|
||||||
|
titleEl.textContent = "Outcome";
|
||||||
|
messageEl.innerHTML = resultMessage; // Display the result
|
||||||
|
choicesContainer.innerHTML = ''; // Clear the buttons
|
||||||
|
|
||||||
// Wait 2.5 seconds before closing the modal
|
// Wait 2.5 seconds before closing the modal
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
modal.style.display = 'none';
|
||||||
|
resolve(choice.id);
|
||||||
|
}, 2500);
|
||||||
|
} else {
|
||||||
modal.style.display = 'none';
|
modal.style.display = 'none';
|
||||||
resolve(choice.id);
|
resolve(choice.id);
|
||||||
}, 2500);
|
}
|
||||||
} else {
|
} else {
|
||||||
// Original behavior if no onChoice is provided
|
// Original behavior if no onChoice is provided
|
||||||
modal.style.display = 'none';
|
modal.style.display = 'none';
|
||||||
resolve(choice.id);
|
resolve(choice.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
choicesContainer.appendChild(button);
|
choicesContainer.appendChild(button);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -10050,7 +10162,7 @@ Gamestate.openDiplomacy = function (targetName) {
|
||||||
const army = territory.army;
|
const army = territory.army;
|
||||||
const threat = creature.threat;
|
const threat = creature.threat;
|
||||||
const tName = formatTerritoryName(territory.name);
|
const tName = formatTerritoryName(territory.name);
|
||||||
const cName = `<strong>${creature.name}</strong>`;
|
const cName = `${creature.name}`;
|
||||||
|
|
||||||
// Calculate Risk Assessment
|
// Calculate Risk Assessment
|
||||||
let riskText, riskColor;
|
let riskText, riskColor;
|
||||||
|
|
@ -10163,15 +10275,20 @@ Gamestate.openDiplomacy = function (targetName) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}; // <-- This closes the callback properly!
|
||||||
|
|
||||||
|
// These lines properly close the Creature Encounter function!
|
||||||
|
await this.showEncounterModal(title, message, choices, onChoiceCallback);
|
||||||
|
this.modalIsOpen = false;
|
||||||
|
this.updateInfo();
|
||||||
|
this.drawMapText();
|
||||||
|
};
|
||||||
|
|
||||||
Gamestate.resolveRadioEncounter = async function () {
|
Gamestate.resolveRadioEncounter = async function () {
|
||||||
if (this.player.areas.length === 0 || this.modalIsOpen) return;
|
if (this.player.areas.length === 0 || this.modalIsOpen) return;
|
||||||
|
|
||||||
// Find a valid territory: not already exploring, army > 1
|
// Find a valid territory: not already exploring, army > 1
|
||||||
const validTerritories = this.player.areas.map(a => this.countries.find(c => c.name === a)).filter(c => c && c.army > 1 && !c.isExploring);
|
const validTerritories = this.player.areas.map(a => this.countries.find(c => c.name === a)).filter(c => c && c.army > 1 && !c.isExploring);
|
||||||
if (validTerritories.length === 0) return;
|
if (validTerritories.length === 0) return;
|
||||||
|
|
||||||
const territory = validTerritories[Math.floor(Math.random() * validTerritories.length)];
|
const territory = validTerritories[Math.floor(Math.random() * validTerritories.length)];
|
||||||
|
|
||||||
// Pick an encounter type
|
// Pick an encounter type
|
||||||
|
|
@ -10193,7 +10310,7 @@ Gamestate.openDiplomacy = function (targetName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const title = "Radio Transmission";
|
const title = "Radio Transmission";
|
||||||
const message = `>>> INCOMING TRANSMISSION <<<<br><br>Garrison forces at <strong>${formatTerritoryName(territory.name)}</strong> report they have discovered <strong>${poiName}</strong>.<br><br>Do you want to send a detachment to ${actionVerb}? This will lock the territory down for 2 turns and reduce their defenses by 15%.`;
|
const message = `>>> INCOMING TRANSMISSION <<<<br><br>Garrison forces at ${formatTerritoryName(territory.name)} report they have discovered ${poiName}.<br><br>Do you want to send a detachment to ${actionVerb}? This will lock the territory down for 2 turns and reduce their defenses by 15%.`;
|
||||||
|
|
||||||
const choices = [
|
const choices = [
|
||||||
{ id: "explore", text: "[Investigate] Send the detachment." },
|
{ id: "explore", text: "[Investigate] Send the detachment." },
|
||||||
|
|
@ -10211,10 +10328,10 @@ Gamestate.openDiplomacy = function (targetName) {
|
||||||
territory.explorePOI = poiName;
|
territory.explorePOI = poiName;
|
||||||
|
|
||||||
this.logAction(`[ EXPEDITION ] Troops at ${formatTerritoryName(territory.name)} have begun investigating ${poiName}. They will report back in 2 turns.`, true);
|
this.logAction(`[ EXPEDITION ] Troops at ${formatTerritoryName(territory.name)} have begun investigating ${poiName}. They will report back in 2 turns.`, true);
|
||||||
return `Expedition launched. The garrison is now vulnerable.`;
|
return null; // Skip the outcome screen
|
||||||
} else {
|
} else {
|
||||||
this.logAction(`[ EXPEDITION ] You ordered the garrison at ${formatTerritoryName(territory.name)} to hold their position and ignore the ${poiName}.`);
|
this.logAction(`[ EXPEDITION ] You ordered the garrison at ${formatTerritoryName(territory.name)} to hold their position and ignore the ${poiName}.`);
|
||||||
return `Transmission ended. The garrison will maintain their post.`;
|
return null; // Skip the outcome screen
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -10224,6 +10341,128 @@ Gamestate.openDiplomacy = function (targetName) {
|
||||||
this.drawMapText();
|
this.drawMapText();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Gamestate.resolveExplorationOutcome = async function (territory) {
|
||||||
|
if (this.modalIsOpen) return;
|
||||||
|
this.modalIsOpen = true;
|
||||||
|
|
||||||
|
territory.isExploring = false; // Remove the searching status
|
||||||
|
|
||||||
|
const title = "Expedition Returned";
|
||||||
|
let message = `Your detachment has returned from exploring the ${territory.explorePOI} at ${formatTerritoryName(territory.name)}.<br><br>`;
|
||||||
|
let logMsg = "";
|
||||||
|
|
||||||
|
const roll = Math.random();
|
||||||
|
|
||||||
|
// 30% Chance for High Value Loot (Caps or Units)
|
||||||
|
if (roll < 0.30) {
|
||||||
|
let capsFound = Math.floor(Math.random() * 15) + 10;
|
||||||
|
if (this.wastelandEconomyActive) this.player.caps += capsFound;
|
||||||
|
message += `They hit the jackpot! Your troops recovered <strong style="color:#ffcc00">${capsFound} Caps</strong> from a pre-war stash.`;
|
||||||
|
logMsg = `[ EXPEDITION ] Success at ${formatTerritoryName(territory.name)}. Found ${capsFound} Caps!`;
|
||||||
|
}
|
||||||
|
// 20% Chance for Stimpak
|
||||||
|
else if (roll < 0.50 && this.commandersEnabled && this.player.commander && this.player.commander.stimpaks < 3) {
|
||||||
|
this.player.commander.stimpaks++;
|
||||||
|
let navInv = document.getElementById('nav-inv');
|
||||||
|
if (navInv) navInv.classList.add('inv-pulse');
|
||||||
|
message += `They discovered a sealed medical kit containing a rare <strong style="color:var(--pip-color)">Stimpak</strong>!`;
|
||||||
|
logMsg = `[ EXPEDITION ] Success at ${formatTerritoryName(territory.name)}. Recovered a Stimpak!`;
|
||||||
|
}
|
||||||
|
// 25% Chance for an Ambush / Trap (Loss of units)
|
||||||
|
else if (roll < 0.75) {
|
||||||
|
let casualties = Math.floor(territory.army * 0.20) + 1; // 20% casualties
|
||||||
|
if (territory.army - casualties < 1) casualties = territory.army - 1; // Never wipe out
|
||||||
|
|
||||||
|
if (casualties > 0) {
|
||||||
|
territory.army -= casualties;
|
||||||
|
this.player.army -= casualties;
|
||||||
|
message += `It was a trap! The detachment was ambushed, suffering <strong style="color:#ff3333">${casualties} casualties</strong> before retreating.`;
|
||||||
|
logMsg = `[ EXPEDITION ] Disaster at ${formatTerritoryName(territory.name)}. Lost ${casualties} troops to a trap.`;
|
||||||
|
} else {
|
||||||
|
message += `They triggered an ancient security system but narrowly escaped without casualties.`;
|
||||||
|
logMsg = `[ EXPEDITION ] Troops at ${formatTerritoryName(territory.name)} triggered a trap but escaped unharmed.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 25% Chance for Nothing
|
||||||
|
else {
|
||||||
|
message += `The area had already been stripped clean by scavengers. They returned empty-handed.`;
|
||||||
|
logMsg = `[ EXPEDITION ] The search at ${formatTerritoryName(territory.name)} turned up nothing.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const choices = [{ id: "acknowledge", text: "[Acknowledge] Resume normal patrols." }];
|
||||||
|
|
||||||
|
const onChoiceCallback = () => {
|
||||||
|
this.logAction(logMsg, true);
|
||||||
|
return null; // Skip the outcome screen
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.showEncounterModal(title, message, choices, onChoiceCallback);
|
||||||
|
this.modalIsOpen = false;
|
||||||
|
this.updateInfo();
|
||||||
|
this.drawMapText();
|
||||||
|
};
|
||||||
|
|
||||||
|
Gamestate.resolveBattleEncounter = async function (territoryName) {
|
||||||
|
// Force unlock the modal state (in case the Move Troops slider confused it)
|
||||||
|
this.modalIsOpen = false;
|
||||||
|
|
||||||
|
const territory = this.countries.find(c => c.name === territoryName);
|
||||||
|
if (!territory || territory.army < 1) return; // FIX: Even 1 troop can discover a vault after a battle
|
||||||
|
|
||||||
|
// First, check if this territory has a designated Vault
|
||||||
|
|
||||||
|
let poiName = "";
|
||||||
|
let type = "";
|
||||||
|
let actionVerb = "investigate the ruins";
|
||||||
|
let isVault = false;
|
||||||
|
|
||||||
|
const possibleVaults = encounterData.vaults.filter(v => v.territory === territoryName);
|
||||||
|
|
||||||
|
if (possibleVaults.length > 0) {
|
||||||
|
// Pick a vault if there are multiple in this territory
|
||||||
|
poiName = possibleVaults[Math.floor(Math.random() * possibleVaults.length)].name;
|
||||||
|
type = "vault";
|
||||||
|
actionVerb = "breach the vault door";
|
||||||
|
isVault = true;
|
||||||
|
} else {
|
||||||
|
// If no vault, pick a generic location or container
|
||||||
|
const types = ["location", "container"];
|
||||||
|
type = types[Math.floor(Math.random() * types.length)];
|
||||||
|
|
||||||
|
if (type === "location") {
|
||||||
|
poiName = encounterData.genericLocations[Math.floor(Math.random() * encounterData.genericLocations.length)];
|
||||||
|
actionVerb = "secure the area";
|
||||||
|
} else {
|
||||||
|
poiName = encounterData.containers[Math.floor(Math.random() * encounterData.containers.length)];
|
||||||
|
actionVerb = "attempt to crack it open";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = isVault ? "Vault Discovered" : "Post-Battle Discovery";
|
||||||
|
const message = `>>> SECTOR SECURED <<<<br><br>While clearing out the last of the enemy resistance at ${formatTerritoryName(territory.name)}, your troops uncovered ${poiName}.<br><br>Do you want to order the garrison to ${actionVerb}? This will lock the territory down for 2 turns and reduce their defenses by 15%.`;
|
||||||
|
|
||||||
|
const choices = [
|
||||||
|
{ id: "explore", text: "[Investigate] Send the detachment." },
|
||||||
|
{ id: "ignore", text: "[Ignore] Focus on fortifications." }
|
||||||
|
];
|
||||||
|
|
||||||
|
this.modalIsOpen = true;
|
||||||
|
|
||||||
|
const onChoiceCallback = (decision) => {
|
||||||
|
if (decision === 'explore') {
|
||||||
|
territory.isExploring = true;
|
||||||
|
territory.exploreTurnsLeft = 2;
|
||||||
|
territory.exploreType = type;
|
||||||
|
territory.explorePOI = poiName;
|
||||||
|
|
||||||
|
this.logAction(`[ EXPEDITION ] Conquerors at ${formatTerritoryName(territory.name)} are breaching ${poiName}. Reports expected in 2 turns.`, true);
|
||||||
|
return null; // Skip the outcome screen
|
||||||
|
} else {
|
||||||
|
this.logAction(`[ TACTICAL ] The garrison at ${formatTerritoryName(territory.name)} ignored ${poiName} to focus on defense.`);
|
||||||
|
return null; // Skip the outcome screen
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
await this.showEncounterModal(title, message, choices, onChoiceCallback);
|
await this.showEncounterModal(title, message, choices, onChoiceCallback);
|
||||||
this.modalIsOpen = false;
|
this.modalIsOpen = false;
|
||||||
|
|
@ -10232,27 +10471,69 @@ Gamestate.openDiplomacy = function (targetName) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
Gamestate.triggerEncounterCheck = async function (triggerType) {
|
Gamestate.triggerEncounterCheck = async function (triggerType, territoryName = null) {
|
||||||
// If encounters are disabled in the game settings, do nothing.
|
// If encounters are disabled in the game settings, do nothing.
|
||||||
if (!this.encountersEnabled) return;
|
if (!this.encountersEnabled) return;
|
||||||
|
|
||||||
|
// 1. EXPLORATION LOCKOUT: Cannot trigger new events if already exploring
|
||||||
|
let isCurrentlyExploring = this.countries.some(c => c.owner === this.player.name && c.isExploring);
|
||||||
|
if (isCurrentlyExploring) return;
|
||||||
|
|
||||||
|
// Initialize trackers if they don't exist
|
||||||
|
if (this.turnsSinceLastEncounter === undefined) this.turnsSinceLastEncounter = 0;
|
||||||
|
if (this.encounterCooldown === undefined) this.encounterCooldown = 0;
|
||||||
|
|
||||||
let chance = 0;
|
let chance = 0;
|
||||||
|
|
||||||
if (triggerType === 'start_of_turn') {
|
if (triggerType === 'start_of_turn') {
|
||||||
chance = 1.0; // TEST MODE: 100% chance to trigger!
|
// Update timers at the start of the turn
|
||||||
|
if (this.encounterCooldown > 0) this.encounterCooldown--;
|
||||||
|
this.turnsSinceLastEncounter++;
|
||||||
|
|
||||||
|
// 2. THE 3-TURN COOLDOWN: 0% chance while resting
|
||||||
|
if (this.encounterCooldown > 0) {
|
||||||
|
chance = 0;
|
||||||
|
}
|
||||||
|
// 3. THE 9-TURN PITY TIMER: 100% chance if starved
|
||||||
|
else if (this.turnsSinceLastEncounter >= 9) {
|
||||||
|
chance = 1.0;
|
||||||
|
}
|
||||||
|
// STANDARD RATE
|
||||||
|
else {
|
||||||
|
chance = 0.08; // 8% chance per turn
|
||||||
|
}
|
||||||
} else if (triggerType === 'post_conquest') {
|
} else if (triggerType === 'post_conquest') {
|
||||||
chance = 1.0; // TEST MODE: 100% chance to trigger!
|
// If on cooldown, no post-battle discoveries allowed
|
||||||
|
if (this.encounterCooldown > 0) {
|
||||||
|
chance = 0;
|
||||||
|
} else {
|
||||||
|
chance = 0.15; // 15% chance per conquest
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Roll the dice
|
||||||
if (Math.random() < chance) {
|
if (Math.random() < chance) {
|
||||||
if (triggerType === 'start_of_turn') {
|
if (triggerType === 'start_of_turn') {
|
||||||
|
// Reset timers!
|
||||||
|
this.turnsSinceLastEncounter = 0;
|
||||||
|
this.encounterCooldown = 3; // Enforce 3-turn break
|
||||||
|
|
||||||
await this.resolveRadioEncounter();
|
await this.resolveRadioEncounter();
|
||||||
|
} else if (triggerType === 'post_conquest' && territoryName) {
|
||||||
|
// Reset timers!
|
||||||
|
this.turnsSinceLastEncounter = 0;
|
||||||
|
this.encounterCooldown = 3; // Enforce 3-turn break
|
||||||
|
|
||||||
|
await this.resolveBattleEncounter(territoryName);
|
||||||
}
|
}
|
||||||
// Post-conquest will go here shortly!
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the initial reputation values between all players based on their faction affinities.
|
* Sets the initial reputation values between all players based on their faction affinities.
|
||||||
* This function should be called once at the start of a new game.
|
* This function should be called once at the start of a new game.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue