mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-04 01:25:22 +00:00
Nations now gang up on players and prioritize traitors/AFK players 🤖 (+ other improvements) (#2730)
## Description: - **Nations now specifically target AFK players, traitors, and players who are already being attacked (they gang up on them).** Depends on the difficulty. - Added ally assistance directly to the attack order (instead of calling it separately beforehand). - Added ally assistance to `findBestNukeTarget`. - Removed some checks from `findBestNukeTarget` (not necessary; better checks will follow in a dedicated nuking PR). - Relation updates on attack now depend on difficulty (makes Easy & Medium nations a bit easier). - On betrayal, every nation in the game previously received a –40 relation penalty toward the betrayer. That was too extreme, so now this only applies only to neighbors. - Nations send fewer alliance requests now; it felt like too many before. - In team games, nations may now reject alliance requests more often (depending on difficulty). - To ensure there are enough non-friendly players to stop the crown with nukes, nations may now reject alliance requests if the other player has too many alliances (on Hard and Impossible difficulty). - Rebalanced nation emoji usage a bit. - Nations may now send an emoji when they get MIRVed. ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin --------- Co-authored-by: iamlewis <lewismmmm@gmail.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
Difficulty,
|
||||
Game,
|
||||
GameMode,
|
||||
Player,
|
||||
PlayerType,
|
||||
Relation,
|
||||
@@ -59,7 +60,7 @@ export class NationAllianceBehavior {
|
||||
|
||||
for (const enemy of borderingEnemies) {
|
||||
if (
|
||||
this.random.chance(20) &&
|
||||
this.random.chance(30) &&
|
||||
isAcceptablePlayerType(enemy) &&
|
||||
this.player.canSendAllianceRequest(enemy) &&
|
||||
this.getAllianceDecision(enemy, false)
|
||||
@@ -86,18 +87,27 @@ export class NationAllianceBehavior {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Reject if otherPlayer has allied with 50% or more of all players (Hard and Impossible only)
|
||||
// To make sure there are enough non-friendly players in the game to stop the crown with nukes
|
||||
if (this.hasTooManyAlliances(otherPlayer)) {
|
||||
return false;
|
||||
}
|
||||
// Before caring about the relation, first check if the otherPlayer is a threat
|
||||
// Easy (dumb) nations are blinded by hatred, they don't care about threats, they care about the relation
|
||||
// Impossible (smart) nations on the other hand are analyzing the facts
|
||||
if (this.isAlliancePartnerThreat(otherPlayer)) {
|
||||
if (!isResponse && this.random.chance(3)) {
|
||||
if (!isResponse && this.random.chance(6)) {
|
||||
this.emojiBehavior.sendEmoji(otherPlayer, EMOJI_SCARED_OF_THREAT);
|
||||
}
|
||||
if (isResponse && this.random.chance(3)) {
|
||||
if (isResponse && this.random.chance(6)) {
|
||||
this.emojiBehavior.sendEmoji(otherPlayer, EMOJI_LOVE);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Maybe reject if we are in a team game (allying makes less sense there)
|
||||
if (this.shouldRejectInTeamGame()) {
|
||||
return false;
|
||||
}
|
||||
// Reject if relation is bad
|
||||
if (this.player.relation(otherPlayer) < Relation.Neutral) {
|
||||
if (isResponse && this.random.chance(3)) {
|
||||
@@ -124,6 +134,21 @@ export class NationAllianceBehavior {
|
||||
return this.isAlliancePartnerSimilarlyStrong(otherPlayer);
|
||||
}
|
||||
|
||||
private hasTooManyAlliances(otherPlayer: Player): boolean {
|
||||
const { difficulty } = this.game.config().gameConfig();
|
||||
if (
|
||||
difficulty !== Difficulty.Hard &&
|
||||
difficulty !== Difficulty.Impossible
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const totalPlayers = this.game.players().length;
|
||||
const otherPlayerAlliances = otherPlayer.alliances().length;
|
||||
|
||||
return otherPlayerAlliances >= totalPlayers * 0.5;
|
||||
}
|
||||
|
||||
private isConfused(): boolean {
|
||||
const { difficulty } = this.game.config().gameConfig();
|
||||
switch (difficulty) {
|
||||
@@ -207,6 +232,26 @@ export class NationAllianceBehavior {
|
||||
}
|
||||
}
|
||||
|
||||
private shouldRejectInTeamGame(): boolean {
|
||||
if (this.game.config().gameConfig().gameMode !== GameMode.Team) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { difficulty } = this.game.config().gameConfig();
|
||||
switch (difficulty) {
|
||||
case Difficulty.Easy:
|
||||
return false; // 0% chance to reject on easy
|
||||
case Difficulty.Medium:
|
||||
return this.random.nextInt(0, 100) < 20; // 20% chance to reject on medium
|
||||
case Difficulty.Hard:
|
||||
return this.random.nextInt(0, 100) < 40; // 40% chance to reject on hard
|
||||
case Difficulty.Impossible:
|
||||
return this.random.nextInt(0, 100) < 60; // 60% chance to reject on impossible
|
||||
default:
|
||||
assertNever(difficulty);
|
||||
}
|
||||
}
|
||||
|
||||
private checkAlreadyEnoughAlliances(otherPlayer: Player): boolean {
|
||||
const { difficulty } = this.game.config().gameConfig();
|
||||
switch (difficulty) {
|
||||
|
||||
@@ -15,9 +15,7 @@ import { EmojiExecution } from "../EmojiExecution";
|
||||
|
||||
const emojiId = (e: (typeof flattenedEmojiTable)[number]) =>
|
||||
flattenedEmojiTable.indexOf(e);
|
||||
export const EMOJI_ASSIST_ACCEPT = (["👍", "⛵", "🤝", "🎯"] as const).map(
|
||||
emojiId,
|
||||
);
|
||||
export const EMOJI_ASSIST_ACCEPT = (["👍", "🤝", "🎯"] as const).map(emojiId);
|
||||
export const EMOJI_ASSIST_RELATION_TOO_LOW = (["🥱", "🤦♂️"] as const).map(
|
||||
emojiId,
|
||||
);
|
||||
@@ -34,7 +32,7 @@ export const EMOJI_LOVE = (["❤️", "😊", "🥰"] as const).map(emojiId);
|
||||
export const EMOJI_CONFUSED = (["❓", "🤡"] as const).map(emojiId);
|
||||
export const EMOJI_BRAG = (["👑", "🥇", "💪"] as const).map(emojiId);
|
||||
export const EMOJI_CHARM_ALLIES = (["🤝", "😇", "💪"] as const).map(emojiId);
|
||||
export const EMOJI_CLOWN = (["🤡"] as const).map(emojiId);
|
||||
export const EMOJI_CLOWN = (["🤡", "🤦♂️"] as const).map(emojiId);
|
||||
export const EMOJI_RAT = (["🐀"] as const).map(emojiId);
|
||||
export const EMOJI_OVERWHELMED = (
|
||||
["💀", "🆘", "😱", "🥺", "😭", "😞", "🫡", "👋"] as const
|
||||
@@ -67,7 +65,7 @@ export class NationEmojiBehavior {
|
||||
}
|
||||
|
||||
private checkOverwhelmedByAttacks(): void {
|
||||
if (!this.random.chance(4)) return;
|
||||
if (!this.random.chance(16)) return;
|
||||
|
||||
const incomingAttacks = this.player.incomingAttacks();
|
||||
if (incomingAttacks.length === 0) return;
|
||||
@@ -85,7 +83,7 @@ export class NationEmojiBehavior {
|
||||
}
|
||||
|
||||
private checkVerySmallAttack(): void {
|
||||
if (!this.random.chance(4)) return;
|
||||
if (!this.random.chance(8)) return;
|
||||
|
||||
const incomingAttacks = this.player.incomingAttacks();
|
||||
if (incomingAttacks.length === 0) return;
|
||||
@@ -175,7 +173,7 @@ export class NationEmojiBehavior {
|
||||
|
||||
// Brag with our crown
|
||||
private brag(): void {
|
||||
if (!this.random.chance(100)) return;
|
||||
if (!this.random.chance(300)) return;
|
||||
|
||||
const sorted = this.game
|
||||
.players()
|
||||
@@ -218,6 +216,7 @@ export class NationEmojiBehavior {
|
||||
}
|
||||
|
||||
private findRat(): void {
|
||||
if (this.game.ticks() < 6000) return; // Ignore first 10 minutes (everybody is small in the early game)
|
||||
if (!this.random.chance(10000)) return;
|
||||
|
||||
const totalLand = this.game.numLandTiles();
|
||||
@@ -340,3 +339,19 @@ export function respondToEmoji(
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function respondToMIRV(
|
||||
game: Game,
|
||||
random: PseudoRandom,
|
||||
mirvTarget: Player,
|
||||
) {
|
||||
if (!random.chance(8)) return;
|
||||
|
||||
game.addExecution(
|
||||
new EmojiExecution(
|
||||
mirvTarget,
|
||||
AllPlayers,
|
||||
random.randElement(EMOJI_OVERWHELMED),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,11 @@ import { PseudoRandom } from "../../PseudoRandom";
|
||||
import { assertNever } from "../../Util";
|
||||
import { MirvExecution } from "../MIRVExecution";
|
||||
import { calculateTerritoryCenter } from "../Util";
|
||||
import { EMOJI_NUKE, NationEmojiBehavior } from "./NationEmojiBehavior";
|
||||
import {
|
||||
EMOJI_NUKE,
|
||||
NationEmojiBehavior,
|
||||
respondToMIRV,
|
||||
} from "./NationEmojiBehavior";
|
||||
|
||||
export class NationMIRVBehavior {
|
||||
constructor(
|
||||
@@ -258,6 +262,7 @@ export class NationMIRVBehavior {
|
||||
if (centerTile && this.player.canBuild(UnitType.MIRV, centerTile)) {
|
||||
this.game.addExecution(new MirvExecution(this.player, centerTile));
|
||||
this.emojiBehavior.sendEmoji(AllPlayers, EMOJI_NUKE);
|
||||
respondToMIRV(this.game, this.random, enemy);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user