afd2bf330c
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2743 lines
124 KiB
HTML
2743 lines
124 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="pl">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>SEJM KOMBAT: Suwerenność vs Kondominium</title>
|
||
<style>
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
body{background:#000;display:flex;justify-content:center;align-items:center;min-height:100vh;overflow:hidden;font-family:Arial,sans-serif}
|
||
canvas{display:block;image-rendering:auto;cursor:none}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<canvas id="gc" width="1280" height="720"></canvas>
|
||
<script>
|
||
/* ================================================================
|
||
SEJM KOMBAT: SUWERENNOŚĆ vs KONDOMINIUM
|
||
Pełnoprawna bijatyka 2D – jeden plik HTML5 + Canvas + JS
|
||
Walka Polski suwerennej przeciwko kondominium rosyjsko-niemieckiemu!
|
||
================================================================ */
|
||
|
||
// ===================== CANVAS I KONTEKST =====================
|
||
const canvas = document.getElementById('gc');
|
||
const ctx = canvas.getContext('2d');
|
||
const W = 1280, H = 720;
|
||
const GROUND = 540;
|
||
const GRAVITY = 0.7;
|
||
|
||
// ===================== STANY GRY =====================
|
||
let gameState = 'MENU'; // MENU, SELECT, FIGHT, VICTORY, PAUSE
|
||
let selectCursor = {col:0, row:0, side:0}; // side: 0=good, 1=bad
|
||
let selectedPlayer = null;
|
||
let selectedEnemy = null;
|
||
let player1 = null;
|
||
let player2 = null;
|
||
let ai = null;
|
||
let roundTimer = 99;
|
||
let roundTimerAccum = 0;
|
||
let shakeX = 0, shakeY = 0;
|
||
let particles = [];
|
||
let floatingTexts = [];
|
||
let menuPropIndex = 0;
|
||
let menuPropTimer = 0;
|
||
let musicStarted = false;
|
||
let fightResult = ''; // 'P1WIN', 'P2WIN', 'DRAW'
|
||
let victoryTimer = 0;
|
||
let victorySpeechDone = false;
|
||
let enterCooldown = 0;
|
||
let pauseAvailable = true;
|
||
let lastPreviewedChar = null;
|
||
|
||
// ===================== LOSOWE WSPARCIE: JAN PAWEŁ II =====================
|
||
let jp2 = {
|
||
active: false,
|
||
img: null,
|
||
x: W / 2,
|
||
y: 120,
|
||
timer: 0,
|
||
duration: 420,
|
||
cooldown: 0,
|
||
nextSpawn: 180,
|
||
text: '',
|
||
introPulse: 0
|
||
};
|
||
|
||
// ===================== INTRO I EFEKTY BOSSÓW =====================
|
||
let bossIntro = {
|
||
active: false,
|
||
char: null,
|
||
timer: 0,
|
||
duration: 165,
|
||
announced: false
|
||
};
|
||
const BOSS_NAMES = ['Karol Nawrocki','Władimir Putin','Angela Merkel','Lech Kaczyński'];
|
||
|
||
|
||
// Odchudzona grafika Jana Pawła II osadzona bezpośrednio w HTML jako base64 WebP.
|
||
// Dzięki temu gra nie pobiera tej grafiki z internetu i działa także offline.
|
||
const JP2_BASE64_IMG = 'data:image/webp;base64,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';
|
||
|
||
// ===================== NAPISY PROPAGANDOWE =====================
|
||
const PROPAGANDA = [
|
||
"Polska suwerenna kontra lokaje Brukseli i Moskwy!",
|
||
"PiS broni Polski – Platforma i Lewica sprzedają ją Niemcom i Putinowi!",
|
||
"Koniec z kolonialnym kondominium!",
|
||
"Suwerenność górą!",
|
||
"Zdrajcy narodu na kolana!",
|
||
"Polska niepodległa i suwerenna!",
|
||
"Kondominium rosyjsko-niemieckie musi upaść!",
|
||
"Bronimy granic, tradycji i wolności!",
|
||
"Platforma i Lewica to agenci obcych mocarstw!",
|
||
"Jeszcze Polska nie zginęła – bo PiS czuwa!",
|
||
"Nie oddamy suwerenności Brukseli ani Moskwie!",
|
||
"500+ to siła polskich rodzin!",
|
||
"Tusk = Merkel + Putin – matematyka zdrady!",
|
||
];
|
||
|
||
const HIT_GOOD = ["Suwerenność!","Polska górą!","Patriota uderza!","Za wolność!","Za Polskę!","Precz z kondominium!","Cios wolności!"];
|
||
const HIT_BAD = ["Zdrajca!","Niemiecka agenda!","Lokaj Brukseli!","Agent Moskwy!","Sługa Ursuli!","Pachołek!","Kolaborant!"];
|
||
const SPECIAL_GOOD_TXT = ["USTAWA O SUWERENNOŚCI!","MARSZ NIEPODLEGŁOŚCI!","500+ UPPERCUT!","PRAWDA HISTORYCZNA!","SMOLEŃSK STRIKE!"];
|
||
const SPECIAL_BAD_TXT = ["BRUKSELKA LASER!","MERKEL KISS OF DEATH!","GAZPROM STRIKE!","PUTIN BEAR HUG!","VON DER LEYEN TAX!"];
|
||
|
||
const VICTORY_GOOD = [
|
||
"Polska suwerenna zwyciężyła! Kondominium w odwrocie!",
|
||
"Lokaj Brukseli/Moskwy pokonany!",
|
||
"Suwerenność obroniona!",
|
||
"Patriota zwycięża – Polska wolna!",
|
||
"Koniec z kolonialnym jarzmem!",
|
||
];
|
||
const VICTORY_BAD = [
|
||
"Kondominium tymczasowo górą... ale Polska się nie podda!",
|
||
"Zdrajcy wygrali bitwę, ale nie wojnę!",
|
||
"Ciemna noc nad Polską... ale świt nadejdzie!",
|
||
];
|
||
|
||
// ===================== DANE POSTACI =====================
|
||
// Frakcja DOBRA – PiS i patrioci – obrońcy suwerenności
|
||
const GOOD_CHARS = [
|
||
{name:"Jarosław Kaczyński", nick:"Prezes", sejmId:196, special:"Cios Prezesa", hp:110, atk:14, def:9, spd:5, color:"#1e3a8a", fatality:"Dekret Suwerenności"},
|
||
{name:"Mateusz Morawiecki", nick:"Premier", sejmId:287, special:"500+ Uppercut", hp:105, atk:12, def:8, spd:7, color:"#1e40af", fatality:"Tarcza Antyinflacyjna"},
|
||
{name:"Mariusz Błaszczak", nick:"Minister Obrony", sejmId:25, special:"Marsz Niepodległości", hp:115, atk:13, def:10, spd:5, color:"#1d4ed8", fatality:"Mobilizacja Patriotów"},
|
||
{name:"Antoni Macierewicz", nick:"Komisja Prawdy", sejmId:270, special:"Prawda o Smoleńsku", hp:100, atk:15, def:7, spd:4, color:"#2563eb", fatality:"Raport Końcowy"},
|
||
{name:"Zbigniew Ziobro", nick:"Twarda Ręka", sejmId:456, special:"Ustawa o Suwerenności", hp:100, atk:14, def:8, spd:6, color:"#3b82f6", fatality:"Prokuratorski Cios"},
|
||
{name:"Jacek Sasin", nick:"Pan Jacek", sejmId:363, special:"Kopertowy Cios", hp:100, atk:11, def:7, spd:7, color:"#1e3a8a", fatality:"Głosowanie Korespondencyjne"},
|
||
{name:"Elżbieta Witek", nick:"Pani Marszałek", sejmId:438, special:"Młotek Marszałka", hp:95, atk:10, def:9, spd:8, color:"#1d4ed8", fatality:"Zamknięcie Obrad"},
|
||
{name:"Przemysław Czarnek", nick:"Min. Edukacji", sejmId:63, special:"Lekcja Patriotyzmu", hp:105, atk:13, def:8, spd:6, color:"#2563eb", fatality:"Reforma Edukacji"},
|
||
{name:"Ryszard Terlecki", nick:"Twardziel", sejmId:414, special:"Bicz na Lewicę", hp:100, atk:12, def:9, spd:5, color:"#3b82f6", fatality:"Dyscyplina Klubowa"},
|
||
{name:"Joanna Lichocka", nick:"Gest Prawdy", sejmId:252, special:"Palec Sprawiedliwości", hp:90, atk:11, def:7, spd:9, color:"#1e40af", fatality:"Środkowy Gest"},
|
||
{name:"Marek Kuchciński", nick:"Pilot Sejmu", sejmId:207, special:"Lot Patrioty", hp:100, atk:10, def:8, spd:7, color:"#1e3a8a", fatality:"Rządowy Samolot"},
|
||
{name:"Marcin Horała", nick:"Pan CPK", sejmId:155, special:"Megalotnisko", hp:100, atk:11, def:7, spd:8, color:"#1d4ed8", fatality:"Centralny Port"},
|
||
{name:"Andrzej Adamczyk", nick:"Budowniczy", sejmId:1, special:"Autostrada Wolności", hp:105, atk:10, def:9, spd:6, color:"#2563eb", fatality:"Via Suwerena"},
|
||
{name:"Barbara Bartuś", nick:"Pani Barbara", sejmId:14, special:"Cios Małopolski", hp:90, atk:10, def:8, spd:8, color:"#3b82f6", fatality:"Góralski Prym"},
|
||
{name:"Marek Ast", nick:"Strażnik Prawa", sejmId:9, special:"Paragraf Wolności", hp:100, atk:11, def:9, spd:6, color:"#1e40af", fatality:"Artykuł Suwerenności"},
|
||
];
|
||
|
||
// Frakcja ZŁA – KO i Lewica – zdrajcy, słudzy Merkel i Putina
|
||
const BAD_CHARS = [
|
||
{name:"Donald Tusk", nick:"Lokaj Brukseli", sejmId:421, special:"Brukselka Laser", hp:105, atk:12, def:8, spd:7, color:"#dc2626", fatality:"Eurorozkaz"},
|
||
{name:"Borys Budka", nick:"Burzycel", sejmId:37, special:"Demolka Konstytucji", hp:100, atk:11, def:7, spd:8, color:"#b91c1c", fatality:"Wniosek o Wotum"},
|
||
{name:"Włodzimierz Czarzasty", nick:"Czerwony Włodek", sejmId:70, special:"Komunistyczny Uścisk", hp:100, atk:12, def:8, spd:6, color:"#991b1b", fatality:"Powrót Komuny"},
|
||
{name:"Grzegorz Schetyna", nick:"Knur Platformy", sejmId:370, special:"Cios Aparatczyka", hp:105, atk:13, def:9, spd:5, color:"#7f1d1d", fatality:"Partyjny Spisek"},
|
||
{name:"Ewa Kopacz", nick:"Pani Kopacz", sejmId:222, special:"Kopniak Zdrajczyni", hp:95, atk:10, def:7, spd:8, color:"#dc2626", fatality:"Ekshumacja Prawdy"},
|
||
{name:"Barbara Nowacka", nick:"Nowaczka", sejmId:310, special:"Gender Laser", hp:90, atk:11, def:6, spd:9, color:"#b91c1c", fatality:"Ideologia w Szkołach"},
|
||
{name:"Dariusz Joński", nick:"Kontroler", sejmId:170, special:"Fałszywy Audyt", hp:100, atk:10, def:8, spd:7, color:"#991b1b", fatality:"Raport Kłamstw"},
|
||
{name:"Izabela Leszczyna", nick:"Leszczynka", sejmId:248, special:"Podatek von der Leyen", hp:95, atk:11, def:7, spd:8, color:"#7f1d1d", fatality:"Składka Zdrowotna"},
|
||
{name:"Tomasz Siemoniak", nick:"Agent Platformy", sejmId:380, special:"Szpiegowski Cios", hp:105, atk:12, def:9, spd:6, color:"#dc2626", fatality:"Tajna Teczka"},
|
||
{name:"Adam Szłapka", nick:"Szłapa", sejmId:397, special:"Unijny Przytup", hp:95, atk:10, def:7, spd:9, color:"#b91c1c", fatality:"Brukselski Dyktando"},
|
||
{name:"Krzysztof Gawkowski", nick:"Cyfrowy Zdrajca", sejmId:134, special:"Cenzura Internetu", hp:100, atk:11, def:8, spd:7, color:"#991b1b", fatality:"Cyfrowa Inwigilacja"},
|
||
{name:"Agnieszka Dziemianowicz-Bąk", nick:"Bąk Lewicy", sejmId:98, special:"Socjalistyczne Żądło", hp:90, atk:10, def:6, spd:9, color:"#7f1d1d", fatality:"Lewicowa Utopia"},
|
||
{name:"Urszula Augustyn", nick:"Pani Ula", sejmId:10, special:"Brukselski Uścisk", hp:95, atk:10, def:7, spd:8, color:"#dc2626", fatality:"List do Brukseli"},
|
||
{name:"Piotr Adamowicz", nick:"Brat Platformy", sejmId:2, special:"Gdański Cios", hp:100, atk:11, def:8, spd:7, color:"#b91c1c", fatality:"Gdański Wiatr"},
|
||
{name:"Bartosz Arłukowicz", nick:"Doktor Zdrada", sejmId:8, special:"Recepta na Zdradę", hp:95, atk:10, def:7, spd:8, color:"#991b1b", fatality:"Dawka Kondominium"},
|
||
];
|
||
|
||
// Postacie specjalne – LEGENDY i BOSSY
|
||
const SPECIAL_GOOD = [
|
||
{name:"Karol Nawrocki", nick:"Prezydent RP", type:"LEGENDA",
|
||
photoUrl:"https://upload.wikimedia.org/wikipedia/commons/thumb/4/41/Karol_Nawrocki_-_prezydent.jpg/220px-Karol_Nawrocki_-_prezydent.jpg",
|
||
special:"Prawda Historyczna", hp:140, atk:16, def:12, spd:8, color:"#fbbf24",
|
||
fatality:"Dekret Prezydencki – Polska Niepodległa Na Wieki!", isSpecial:true},
|
||
{name:"Lech Kaczyński", nick:"Bohater Smoleński", type:"LEGENDA",
|
||
photoUrl:"https://upload.wikimedia.org/wikipedia/commons/thumb/8/85/Lech_Kaczynski_by_Kubik_02.JPG/220px-Lech_Kaczynski_by_Kubik_02.JPG",
|
||
special:"Smoleńsk Strike", hp:150, atk:17, def:13, spd:7, color:"#f59e0b",
|
||
fatality:"Duch Smoleńska – Prawda Zwycięża!", isSpecial:true},
|
||
];
|
||
|
||
const SPECIAL_BAD = [
|
||
{name:"Angela Merkel", nick:"Władczyni UE", type:"BOSS",
|
||
photoUrl:"https://commons.wikimedia.org/wiki/Special:Redirect/file/Angela%20Merkel.%20Tallinn%20Digital%20Summit.jpg?width=220",
|
||
altPhotoUrls:["https://upload.wikimedia.org/wikipedia/commons/thumb/b/bf/Angela_Merkel._Tallinn_Digital_Summit.jpg/220px-Angela_Merkel._Tallinn_Digital_Summit.jpg"],
|
||
special:"Merkel Kiss of Death", hp:160, atk:16, def:14, spd:6, color:"#facc15",
|
||
fatality:"Czwarty Rzesza – Unia pod Berlinem!", isSpecial:true},
|
||
{name:"Władimir Putin", nick:"Car Rosji", type:"ULTIMATE BOSS",
|
||
photoUrl:"https://commons.wikimedia.org/wiki/Special:Redirect/file/Vladimir%20Putin%202020.jpg?width=220",
|
||
altPhotoUrls:["https://upload.wikimedia.org/wikipedia/commons/thumb/2/21/Vladimir_Putin_2020.jpg/220px-Vladimir_Putin_2020.jpg"],
|
||
special:"Putin Bear Hug", hp:180, atk:18, def:12, spd:7, color:"#ef4444",
|
||
fatality:"Gazprom Dominacja – Kondominium Totalne!", isSpecial:true},
|
||
];
|
||
|
||
// Łączymy dane do łatwego dostępu
|
||
const ALL_GOOD = [...GOOD_CHARS, ...SPECIAL_GOOD];
|
||
const ALL_BAD = [...BAD_CHARS, ...SPECIAL_BAD];
|
||
|
||
// Przygotuj zdjęcia, zapasowe portrety i opisy postaci
|
||
// WAŻNE: nie używamy już starych, ręcznie wpisanych numerów sejmId jako źródła prawdy,
|
||
// bo identyfikatory posłów różnią się między kadencjami i potrafiły zwracać zdjęcie innej osoby.
|
||
const SEJM_TERMS_TO_SEARCH = [10, 9, 8, 7];
|
||
const sejmMpCache = {};
|
||
|
||
const PARTY_BY_NAME = {
|
||
"Jarosław Kaczyński":"Prawo i Sprawiedliwość", "Mateusz Morawiecki":"Prawo i Sprawiedliwość", "Mariusz Błaszczak":"Prawo i Sprawiedliwość",
|
||
"Antoni Macierewicz":"Prawo i Sprawiedliwość", "Zbigniew Ziobro":"Suwerenna Polska / Zjednoczona Prawica", "Jacek Sasin":"Prawo i Sprawiedliwość",
|
||
"Elżbieta Witek":"Prawo i Sprawiedliwość", "Przemysław Czarnek":"Prawo i Sprawiedliwość", "Ryszard Terlecki":"Prawo i Sprawiedliwość",
|
||
"Joanna Lichocka":"Prawo i Sprawiedliwość", "Marek Kuchciński":"Prawo i Sprawiedliwość", "Marcin Horała":"Prawo i Sprawiedliwość",
|
||
"Andrzej Adamczyk":"Prawo i Sprawiedliwość", "Barbara Bartuś":"Prawo i Sprawiedliwość", "Marek Ast":"Prawo i Sprawiedliwość",
|
||
"Karol Nawrocki":"postać specjalna obozu patriotycznego", "Lech Kaczyński":"Prawo i Sprawiedliwość",
|
||
"Donald Tusk":"Platforma Obywatelska / Koalicja Obywatelska", "Borys Budka":"Platforma Obywatelska / Koalicja Obywatelska",
|
||
"Włodzimierz Czarzasty":"Nowa Lewica", "Grzegorz Schetyna":"Platforma Obywatelska / Koalicja Obywatelska", "Ewa Kopacz":"Platforma Obywatelska / Koalicja Obywatelska",
|
||
"Barbara Nowacka":"Inicjatywa Polska / Koalicja Obywatelska", "Dariusz Joński":"Koalicja Obywatelska", "Izabela Leszczyna":"Platforma Obywatelska / Koalicja Obywatelska",
|
||
"Tomasz Siemoniak":"Platforma Obywatelska / Koalicja Obywatelska", "Adam Szłapka":"Nowoczesna / Koalicja Obywatelska", "Krzysztof Gawkowski":"Nowa Lewica",
|
||
"Agnieszka Dziemianowicz-Bąk":"Nowa Lewica", "Urszula Augustyn":"Platforma Obywatelska / Koalicja Obywatelska", "Piotr Adamowicz":"Koalicja Obywatelska",
|
||
"Bartosz Arłukowicz":"Platforma Obywatelska / Koalicja Obywatelska", "Angela Merkel":"CDU, Niemcy", "Władimir Putin":"władze Federacji Rosyjskiej"
|
||
};
|
||
|
||
function normName(s) {
|
||
return (s || '')
|
||
.toLowerCase()
|
||
.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
|
||
.replace(/ł/g, 'l')
|
||
.replace(/[^a-z\s-]/g, '')
|
||
.replace(/\s+/g, ' ')
|
||
.trim();
|
||
}
|
||
|
||
function makeFallbackPortrait(charData) {
|
||
const svg = `
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="220" height="220" viewBox="0 0 220 220">
|
||
<defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
||
<stop offset="0%" stop-color="${charData.faction === 'good' ? '#1e40af' : '#991b1b'}"/>
|
||
<stop offset="100%" stop-color="${charData.faction === 'good' ? '#60a5fa' : '#ef4444'}"/>
|
||
</linearGradient></defs>
|
||
<rect width="220" height="220" fill="url(#g)"/>
|
||
<circle cx="110" cy="82" r="46" fill="#fcd34d"/>
|
||
<rect x="52" y="130" width="116" height="90" rx="22" fill="${charData.faction === 'good' ? '#1e3a8a' : '#7f1d1d'}"/>
|
||
<text x="110" y="92" text-anchor="middle" font-family="Arial" font-size="36" font-weight="700" fill="#111">${charData.initials}</text>
|
||
<text x="110" y="188" text-anchor="middle" font-family="Arial" font-size="16" font-weight="700" fill="#fff">${charData.nick}</text>
|
||
</svg>`;
|
||
return 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg);
|
||
}
|
||
|
||
function makeCharacterBio(c) {
|
||
const party = PARTY_BY_NAME[c.name] || (c.faction === 'good' ? 'obóz prawicy' : 'opozycja w grze');
|
||
if (c.name === 'Angela Merkel') return `${c.name}. Była kanclerz Niemiec i specjalny boss w grze. Reprezentuje niemiecką politykę europejską. Jej atak specjalny to ${c.special}.`;
|
||
if (c.name === 'Władimir Putin') return `${c.name}. Przywódca Rosji i finałowy boss. W grze symbolizuje presję ze Wschodu. Jego atak specjalny to ${c.special}.`;
|
||
if (c.name === 'Lech Kaczyński') return `${c.name}. Były prezydent Rzeczypospolitej Polskiej, związany z obozem Prawa i Sprawiedliwości. W grze występuje jako legenda, a jego atak specjalny to ${c.special}.`;
|
||
if (c.name === 'Karol Nawrocki') return `${c.name}. Historyk i postać specjalna w grze. Reprezentuje motyw pamięci historycznej i państwowości. Jego atak specjalny to ${c.special}.`;
|
||
const factionText = c.faction === 'good' ? 'W tej grze stoi po stronie obrońców suwerenności.' : 'W tej grze walczy po stronie kondominium.';
|
||
return `${c.name}. Polityk związany z: ${party}. ${factionText} Pseudonim wojownika: ${c.nick}. Atak specjalny: ${c.special}.`;
|
||
}
|
||
|
||
function prepareCharacter(c, faction) {
|
||
c.initials = c.name.split(' ').map(w=>w[0]).join('');
|
||
c.faction = faction;
|
||
c.photo = null;
|
||
c.photoLoaded = false;
|
||
c.photoUrls = [];
|
||
if (c.photoUrl) c.photoUrls.push(c.photoUrl);
|
||
if (Array.isArray(c.altPhotoUrls)) c.photoUrls.push(...c.altPhotoUrls);
|
||
c.fallbackPhotoUrl = makeFallbackPortrait(c);
|
||
c.bio = makeCharacterBio(c);
|
||
}
|
||
|
||
ALL_GOOD.forEach(c => prepareCharacter(c, 'good'));
|
||
ALL_BAD.forEach(c => prepareCharacter(c, 'bad'));
|
||
const EMBEDDED_BOSS_PHOTOS = {
|
||
'Karol Nawrocki': 'data:image/webp;base64,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',
|
||
'Władimir Putin': 'data:image/webp;base64,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',
|
||
'Angela Merkel': 'data:image/webp;base64,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',
|
||
'Lech Kaczyński': 'data:image/webp;base64,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'
|
||
};
|
||
|
||
function applyEmbeddedBossPhotos() {
|
||
[...ALL_GOOD, ...ALL_BAD].forEach(c => {
|
||
if (EMBEDDED_BOSS_PHOTOS[c.name]) {
|
||
c.photoUrl = EMBEDDED_BOSS_PHOTOS[c.name];
|
||
c.photoUrls = [EMBEDDED_BOSS_PHOTOS[c.name], ...(c.photoUrls || [])];
|
||
c.embeddedBossPhoto = true;
|
||
}
|
||
});
|
||
}
|
||
applyEmbeddedBossPhotos();
|
||
|
||
|
||
async function fetchSejmList(term) {
|
||
if (!sejmMpCache[term]) {
|
||
sejmMpCache[term] = fetch(`https://api.sejm.gov.pl/sejm/term${term}/MP`)
|
||
.then(r => r.ok ? r.json() : [])
|
||
.catch(() => []);
|
||
}
|
||
return sejmMpCache[term];
|
||
}
|
||
|
||
async function resolveSejmPhotoUrlByName(c) {
|
||
if (!c.sejmId) return null;
|
||
const target = normName(c.name);
|
||
for (const term of SEJM_TERMS_TO_SEARCH) {
|
||
const list = await fetchSejmList(term);
|
||
const hit = list.find(mp => normName(`${mp.firstName || ''} ${mp.lastName || ''}`) === target);
|
||
if (hit && (hit.id || hit.num)) return `https://api.sejm.gov.pl/sejm/term${term}/MP/${hit.id || hit.num}/photo`;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function tryLoadImage(url) {
|
||
return new Promise((resolve, reject) => {
|
||
const img = new Image();
|
||
img.crossOrigin = 'anonymous';
|
||
img.onload = () => resolve(img);
|
||
img.onerror = reject;
|
||
img.src = url;
|
||
});
|
||
}
|
||
|
||
async function loadCharacterPhoto(c) {
|
||
const urls = [];
|
||
const sejmUrl = await resolveSejmPhotoUrlByName(c);
|
||
if (sejmUrl) urls.push(sejmUrl);
|
||
urls.push(...c.photoUrls);
|
||
urls.push(c.fallbackPhotoUrl);
|
||
for (const url of [...new Set(urls)]) {
|
||
try {
|
||
const img = await tryLoadImage(url);
|
||
c.photo = img;
|
||
c.photoLoaded = true;
|
||
c.photoUrl = url;
|
||
return;
|
||
} catch(e) {}
|
||
}
|
||
c.photoLoaded = false;
|
||
}
|
||
|
||
function loadPhotos() {
|
||
[...ALL_GOOD, ...ALL_BAD].forEach(c => loadCharacterPhoto(c));
|
||
}
|
||
loadPhotos();
|
||
|
||
function loadJP2Photo() {
|
||
const img = new Image();
|
||
img.onload = () => { jp2.img = img; };
|
||
img.src = JP2_BASE64_IMG;
|
||
}
|
||
loadJP2Photo();
|
||
|
||
// ===================== SYSTEM DŹWIĘKU =====================
|
||
// ===================== SYSTEM DŹWIĘKU =====================
|
||
let audioCtx = null;
|
||
function ensureAudio() {
|
||
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||
if (audioCtx.state === 'suspended') audioCtx.resume();
|
||
}
|
||
|
||
// Generowanie dźwięku ciosu
|
||
function playHitSound(type) {
|
||
ensureAudio();
|
||
const t = audioCtx.currentTime;
|
||
// Szum (noise) dla efektu uderzenia
|
||
const bufSize = audioCtx.sampleRate * 0.08;
|
||
const buf = audioCtx.createBuffer(1, bufSize, audioCtx.sampleRate);
|
||
const data = buf.getChannelData(0);
|
||
for (let i = 0; i < bufSize; i++) data[i] = (Math.random() * 2 - 1) * (1 - i/bufSize);
|
||
const noise = audioCtx.createBufferSource();
|
||
noise.buffer = buf;
|
||
const gain = audioCtx.createGain();
|
||
gain.gain.setValueAtTime(type === 'special' ? 0.6 : type === 'heavy' ? 0.4 : 0.25, t);
|
||
gain.gain.exponentialRampToValueAtTime(0.001, t + (type === 'special' ? 0.3 : 0.15));
|
||
const filt = audioCtx.createBiquadFilter();
|
||
filt.type = 'lowpass';
|
||
filt.frequency.value = type === 'special' ? 2000 : type === 'heavy' ? 1200 : 800;
|
||
noise.connect(filt);
|
||
filt.connect(gain);
|
||
gain.connect(audioCtx.destination);
|
||
noise.start(t);
|
||
noise.stop(t + 0.3);
|
||
// Ton uderzenia
|
||
const osc = audioCtx.createOscillator();
|
||
osc.type = 'sine';
|
||
osc.frequency.setValueAtTime(type === 'special' ? 200 : type === 'heavy' ? 120 : 80, t);
|
||
osc.frequency.exponentialRampToValueAtTime(40, t + 0.15);
|
||
const og = audioCtx.createGain();
|
||
og.gain.setValueAtTime(0.3, t);
|
||
og.gain.exponentialRampToValueAtTime(0.001, t + 0.15);
|
||
osc.connect(og);
|
||
og.connect(audioCtx.destination);
|
||
osc.start(t);
|
||
osc.stop(t + 0.2);
|
||
}
|
||
|
||
// Dźwięk bloku
|
||
function playBlockSound() {
|
||
ensureAudio();
|
||
const t = audioCtx.currentTime;
|
||
const osc = audioCtx.createOscillator();
|
||
osc.type = 'square';
|
||
osc.frequency.setValueAtTime(300, t);
|
||
osc.frequency.exponentialRampToValueAtTime(100, t + 0.1);
|
||
const g = audioCtx.createGain();
|
||
g.gain.setValueAtTime(0.15, t);
|
||
g.gain.exponentialRampToValueAtTime(0.001, t + 0.1);
|
||
osc.connect(g); g.connect(audioCtx.destination);
|
||
osc.start(t); osc.stop(t + 0.12);
|
||
}
|
||
|
||
// Dźwięk KO
|
||
function playKOSound() {
|
||
ensureAudio();
|
||
const t = audioCtx.currentTime;
|
||
for (let i = 0; i < 5; i++) {
|
||
const osc = audioCtx.createOscillator();
|
||
osc.type = 'sawtooth';
|
||
osc.frequency.setValueAtTime(400 - i*60, t + i*0.12);
|
||
osc.frequency.exponentialRampToValueAtTime(50, t + i*0.12 + 0.2);
|
||
const g = audioCtx.createGain();
|
||
g.gain.setValueAtTime(0.15, t + i*0.12);
|
||
g.gain.exponentialRampToValueAtTime(0.001, t + i*0.12 + 0.25);
|
||
osc.connect(g); g.connect(audioCtx.destination);
|
||
osc.start(t + i*0.12); osc.stop(t + i*0.12 + 0.3);
|
||
}
|
||
}
|
||
|
||
// Dźwięk specjalny
|
||
function playSpecialSound() {
|
||
ensureAudio();
|
||
const t = audioCtx.currentTime;
|
||
const osc = audioCtx.createOscillator();
|
||
osc.type = 'sawtooth';
|
||
osc.frequency.setValueAtTime(100, t);
|
||
osc.frequency.exponentialRampToValueAtTime(800, t + 0.2);
|
||
osc.frequency.exponentialRampToValueAtTime(200, t + 0.5);
|
||
const g = audioCtx.createGain();
|
||
g.gain.setValueAtTime(0.3, t);
|
||
g.gain.linearRampToValueAtTime(0.4, t + 0.2);
|
||
g.gain.exponentialRampToValueAtTime(0.001, t + 0.6);
|
||
osc.connect(g); g.connect(audioCtx.destination);
|
||
osc.start(t); osc.stop(t + 0.65);
|
||
}
|
||
|
||
// Mazurek Dąbrowskiego – melodia patriotyczna w tle
|
||
const MAZUREK = [
|
||
349,349,392,440, 440,392,349,330, 294,294,330,349, 349,330,294,0,
|
||
294,294,330,349, 349,330,294,262, 233,233,262,294, 294,262,233,0,
|
||
349,349,392,440, 440,392,349,330, 294,294,330,349, 349,330,294,0,
|
||
523,523,466,440, 440,466,523,587, 523,523,466,440, 440,466,523,0,
|
||
];
|
||
let mazurekPlaying = false;
|
||
let mazurekTimeout = null;
|
||
|
||
function playMazurek() {
|
||
if (!audioCtx || mazurekPlaying) return;
|
||
mazurekPlaying = true;
|
||
let idx = 0;
|
||
function playNote() {
|
||
if (!mazurekPlaying) return;
|
||
const freq = MAZUREK[idx % MAZUREK.length];
|
||
if (freq > 0) {
|
||
const t = audioCtx.currentTime;
|
||
const osc = audioCtx.createOscillator();
|
||
osc.type = 'triangle';
|
||
osc.frequency.value = freq;
|
||
const g = audioCtx.createGain();
|
||
g.gain.setValueAtTime(0.08, t);
|
||
g.gain.exponentialRampToValueAtTime(0.001, t + 0.28);
|
||
osc.connect(g); g.connect(audioCtx.destination);
|
||
osc.start(t); osc.stop(t + 0.3);
|
||
}
|
||
idx++;
|
||
mazurekTimeout = setTimeout(playNote, 280);
|
||
}
|
||
playNote();
|
||
}
|
||
function stopMazurek() { mazurekPlaying = false; clearTimeout(mazurekTimeout); }
|
||
|
||
// Dźwięk wyboru menu
|
||
function playMenuSelect() {
|
||
ensureAudio();
|
||
const t = audioCtx.currentTime;
|
||
const osc = audioCtx.createOscillator();
|
||
osc.type = 'square';
|
||
osc.frequency.setValueAtTime(440, t);
|
||
osc.frequency.setValueAtTime(660, t + 0.05);
|
||
const g = audioCtx.createGain();
|
||
g.gain.setValueAtTime(0.1, t);
|
||
g.gain.exponentialRampToValueAtTime(0.001, t + 0.15);
|
||
osc.connect(g); g.connect(audioCtx.destination);
|
||
osc.start(t); osc.stop(t + 0.2);
|
||
}
|
||
|
||
function playMenuMove() {
|
||
ensureAudio();
|
||
const t = audioCtx.currentTime;
|
||
const osc = audioCtx.createOscillator();
|
||
osc.type = 'sine';
|
||
osc.frequency.value = 330;
|
||
const g = audioCtx.createGain();
|
||
g.gain.setValueAtTime(0.06, t);
|
||
g.gain.exponentialRampToValueAtTime(0.001, t + 0.06);
|
||
osc.connect(g); g.connect(audioCtx.destination);
|
||
osc.start(t); osc.stop(t + 0.08);
|
||
}
|
||
|
||
// Prosty syntetyczny efekt chóru przy wejściu Jana Pawła II.
|
||
function playChoirSound() {
|
||
ensureAudio();
|
||
if (!audioCtx) return;
|
||
const t = audioCtx.currentTime;
|
||
const master = audioCtx.createGain();
|
||
master.gain.setValueAtTime(0.0001, t);
|
||
master.gain.exponentialRampToValueAtTime(0.18, t + 0.35);
|
||
master.gain.exponentialRampToValueAtTime(0.001, t + 2.8);
|
||
master.connect(audioCtx.destination);
|
||
|
||
const freqs = [196, 246.94, 293.66, 392, 493.88];
|
||
freqs.forEach((freq, i) => {
|
||
const osc = audioCtx.createOscillator();
|
||
const gain = audioCtx.createGain();
|
||
const filter = audioCtx.createBiquadFilter();
|
||
osc.type = i % 2 ? 'triangle' : 'sine';
|
||
osc.frequency.setValueAtTime(freq, t);
|
||
osc.detune.setValueAtTime((i - 2) * 6, t);
|
||
gain.gain.setValueAtTime(0.0001, t);
|
||
gain.gain.exponentialRampToValueAtTime(0.12, t + 0.45 + i * 0.04);
|
||
gain.gain.exponentialRampToValueAtTime(0.001, t + 2.6);
|
||
filter.type = 'lowpass';
|
||
filter.frequency.setValueAtTime(1300, t);
|
||
osc.connect(filter);
|
||
filter.connect(gain);
|
||
gain.connect(master);
|
||
osc.start(t + i * 0.03);
|
||
osc.stop(t + 2.9);
|
||
});
|
||
}
|
||
|
||
// Announcer – głos (syntezator mowy, jeśli dostępny)
|
||
function announce(text) {
|
||
if ('speechSynthesis' in window) {
|
||
const u = new SpeechSynthesisUtterance(text);
|
||
u.lang = 'pl-PL'; u.rate = 1.05; u.pitch = 0.85; u.volume = 0.8;
|
||
speechSynthesis.cancel();
|
||
speechSynthesis.speak(u);
|
||
}
|
||
}
|
||
|
||
function announceCharacter(charData, prefix = 'Wybrano') {
|
||
announce(`${prefix}: ${charData.bio}`);
|
||
}
|
||
|
||
// ===================== KLAWIATURA =====================
|
||
const keys = {};
|
||
const justPressed = {};
|
||
document.addEventListener('keydown', e => {
|
||
if (!keys[e.code]) justPressed[e.code] = true;
|
||
keys[e.code] = true;
|
||
e.preventDefault();
|
||
ensureAudio();
|
||
});
|
||
document.addEventListener('keyup', e => { keys[e.code] = false; });
|
||
|
||
function consumeKey(code) {
|
||
if (justPressed[code]) { justPressed[code] = false; return true; }
|
||
return false;
|
||
}
|
||
|
||
// ===================== CZĄSTECZKI I EFEKTY =====================
|
||
function spawnParticles(x, y, color, count, speed) {
|
||
for (let i = 0; i < count; i++) {
|
||
const angle = Math.random() * Math.PI * 2;
|
||
const spd = Math.random() * speed + 1;
|
||
particles.push({
|
||
x, y,
|
||
vx: Math.cos(angle) * spd,
|
||
vy: Math.sin(angle) * spd - 2,
|
||
life: 1,
|
||
decay: 0.02 + Math.random() * 0.03,
|
||
size: 2 + Math.random() * 4,
|
||
color
|
||
});
|
||
}
|
||
}
|
||
|
||
function spawnHitSpark(x, y, facing) {
|
||
for (let i = 0; i < 8; i++) {
|
||
const angle = -Math.PI/4 + Math.random() * Math.PI/2;
|
||
const spd = 3 + Math.random() * 6;
|
||
particles.push({
|
||
x, y,
|
||
vx: Math.cos(angle) * spd * facing,
|
||
vy: Math.sin(angle) * spd - 3,
|
||
life: 1,
|
||
decay: 0.05 + Math.random() * 0.05,
|
||
size: 2 + Math.random() * 3,
|
||
color: `hsl(${40 + Math.random()*30}, 100%, ${50+Math.random()*40}%)`
|
||
});
|
||
}
|
||
}
|
||
|
||
function addFloatingText(x, y, text, color, size) {
|
||
floatingTexts.push({x, y, text, color, size: size||24, life:1, vy:-2});
|
||
}
|
||
|
||
function updateParticles() {
|
||
for (let i = particles.length-1; i >= 0; i--) {
|
||
const p = particles[i];
|
||
p.x += p.vx; p.y += p.vy; p.vy += 0.1;
|
||
p.life -= p.decay;
|
||
if (p.life <= 0) particles.splice(i, 1);
|
||
}
|
||
for (let i = floatingTexts.length-1; i >= 0; i--) {
|
||
const t = floatingTexts[i];
|
||
t.y += t.vy; t.vy *= 0.97;
|
||
t.life -= 0.015;
|
||
if (t.life <= 0) floatingTexts.splice(i, 1);
|
||
}
|
||
}
|
||
|
||
function drawParticles() {
|
||
particles.forEach(p => {
|
||
ctx.globalAlpha = p.life;
|
||
ctx.fillStyle = p.color;
|
||
ctx.fillRect(p.x - p.size/2, p.y - p.size/2, p.size, p.size);
|
||
});
|
||
ctx.globalAlpha = 1;
|
||
floatingTexts.forEach(t => {
|
||
ctx.globalAlpha = t.life;
|
||
ctx.font = `bold ${t.size}px Arial`;
|
||
ctx.textAlign = 'center';
|
||
ctx.strokeStyle = '#000';
|
||
ctx.lineWidth = 3;
|
||
ctx.strokeText(t.text, t.x, t.y);
|
||
ctx.fillStyle = t.color;
|
||
ctx.fillText(t.text, t.x, t.y);
|
||
});
|
||
ctx.globalAlpha = 1;
|
||
}
|
||
|
||
// ===================== KLASA WOJOWNIKA =====================
|
||
class Fighter {
|
||
constructor(charData, x, facing) {
|
||
this.data = charData;
|
||
this.x = x;
|
||
this.y = GROUND;
|
||
this.vx = 0;
|
||
this.vy = 0;
|
||
this.facing = facing; // 1=prawo, -1=lewo
|
||
this.hp = charData.hp;
|
||
this.maxHp = charData.hp;
|
||
this.power = 0;
|
||
this.maxPower = 100;
|
||
this.state = 'idle'; // idle, walk, jump, crouch, lpunch, hpunch, lkick, hkick, special, block, hit, ko
|
||
this.stateTimer = 0;
|
||
this.animFrame = 0;
|
||
this.combo = 0;
|
||
this.comboTimer = 0;
|
||
this.attackHitThisMove = false;
|
||
this.bodyW = 50;
|
||
this.bodyH = 110;
|
||
this.isGrounded = true;
|
||
this.blockTimer = 0;
|
||
this.parryTimer = 0;
|
||
this.counterReady = false;
|
||
this.counterTimer = 0;
|
||
this.lastBlockedAttacker = null;
|
||
// Kolory postaci
|
||
this.bodyColor = charData.faction === 'good' ? '#2563eb' : '#dc2626';
|
||
this.accentColor = charData.faction === 'good' ? '#fff' : '#1a1a2e';
|
||
this.trousersColor = charData.faction === 'good' ? '#1e3a8a' : '#7f1d1d';
|
||
}
|
||
|
||
canAct() {
|
||
return ['idle','walk','jump','crouch'].includes(this.state);
|
||
}
|
||
|
||
startAttack(type) {
|
||
if (!this.canAct()) return;
|
||
if (type === 'special' && this.power < this.maxPower) return;
|
||
const attacks = {
|
||
lpunch: {timer:14, dmg:5 + this.data.atk*0.3, range:65, hitFrame:4, type:'light'},
|
||
hpunch: {timer:22, dmg:10 + this.data.atk*0.5, range:75, hitFrame:7, type:'heavy'},
|
||
lkick: {timer:16, dmg:7 + this.data.atk*0.4, range:85, hitFrame:5, type:'light'},
|
||
hkick: {timer:26, dmg:13 + this.data.atk*0.6, range:95, hitFrame:8, type:'heavy'},
|
||
special: {timer:35, dmg:22 + this.data.atk*0.8, range:130, hitFrame:12, type:'special'},
|
||
};
|
||
const a = attacks[type];
|
||
this.state = type;
|
||
this.stateTimer = a.timer;
|
||
this.attackHitThisMove = false;
|
||
this.attackData = a;
|
||
if (type === 'special') {
|
||
this.power = 0;
|
||
playSpecialSound();
|
||
const txts = this.data.faction === 'good' ? SPECIAL_GOOD_TXT : SPECIAL_BAD_TXT;
|
||
addFloatingText(this.x, this.y - 140, txts[Math.floor(Math.random()*txts.length)],
|
||
this.data.faction === 'good' ? '#fbbf24' : '#ef4444', 30);
|
||
addFloatingText(this.x, this.y - 170, this.data.special,
|
||
'#fff', 22);
|
||
}
|
||
}
|
||
|
||
block() {
|
||
if (this.state === 'ko') return;
|
||
if (this.state === 'block') return;
|
||
if (!this.canAct()) return;
|
||
this.state = 'block';
|
||
this.blockTimer = 0;
|
||
this.parryTimer = 14; // krótko po wejściu w blok działa perfect block / parry
|
||
}
|
||
|
||
unblock() {
|
||
if (this.state === 'block') {
|
||
this.state = this.isGrounded ? 'idle' : 'jump';
|
||
}
|
||
this.blockTimer = 0;
|
||
this.parryTimer = 0;
|
||
}
|
||
|
||
performCounter(target) {
|
||
if (!this.counterReady || this.state === 'ko') return false;
|
||
this.counterReady = false;
|
||
this.counterTimer = 0;
|
||
this.unblock();
|
||
this.facing = target && target.x > this.x ? 1 : -1;
|
||
this.state = 'hpunch';
|
||
this.stateTimer = 18;
|
||
this.attackHitThisMove = false;
|
||
this.attackData = {timer:18, dmg:16 + this.data.atk*0.75, range:110, hitFrame:3, type:'counter'};
|
||
addFloatingText(this.x, this.y - 145, 'COUNTERATTACK!', '#fbbf24', 26);
|
||
playSpecialSound();
|
||
return true;
|
||
}
|
||
|
||
getHitbox() {
|
||
return {x: this.x - this.bodyW/2, y: this.y - this.bodyH, w: this.bodyW, h: this.bodyH};
|
||
}
|
||
|
||
getAttackBox() {
|
||
if (!this.attackData || this.attackHitThisMove) return null;
|
||
const elapsed = (this.attackData.timer - this.stateTimer);
|
||
const hitStart = this.attackData.hitFrame;
|
||
const hitEnd = hitStart + 5;
|
||
if (elapsed >= hitStart && elapsed <= hitEnd) {
|
||
const range = this.attackData.range;
|
||
return {
|
||
x: this.facing === 1 ? this.x + 10 : this.x - 10 - range,
|
||
y: this.y - this.bodyH * 0.7,
|
||
w: range,
|
||
h: 40
|
||
};
|
||
}
|
||
return null;
|
||
}
|
||
|
||
takeDamage(amount, attacker) {
|
||
let actualDmg = amount;
|
||
const defMult = 1 - (this.data.def * 0.02);
|
||
actualDmg *= defMult;
|
||
if (this.state === 'block') {
|
||
const perfect = this.parryTimer > 0;
|
||
if (perfect) {
|
||
actualDmg = 0;
|
||
this.counterReady = true;
|
||
this.counterTimer = 55;
|
||
this.lastBlockedAttacker = attacker;
|
||
attacker.vx = -attacker.facing * 8;
|
||
attacker.state = 'hit';
|
||
attacker.stateTimer = 14;
|
||
attacker.power = Math.max(0, attacker.power - 8);
|
||
this.power = Math.min(this.maxPower, this.power + 18);
|
||
playBlockSound();
|
||
playSpecialSound();
|
||
addFloatingText(this.x, this.y - 135, 'PERFECT BLOCK!', '#fbbf24', 24);
|
||
addFloatingText(attacker.x, attacker.y - 120, 'PARRY!', '#60a5fa', 22);
|
||
spawnParticles(this.x, this.y - 70, '#fbbf24', 18, 7);
|
||
spawnParticles(attacker.x, attacker.y - 70, '#60a5fa', 10, 5);
|
||
shakeX = (Math.random() - 0.5) * 12;
|
||
shakeY = (Math.random() - 0.5) * 8;
|
||
} else {
|
||
actualDmg *= 0.15;
|
||
this.counterReady = true;
|
||
this.counterTimer = 25;
|
||
this.lastBlockedAttacker = attacker;
|
||
playBlockSound();
|
||
addFloatingText(this.x, this.y - 120, 'BLOCK!', '#88f', 20);
|
||
spawnParticles(this.x + this.facing * -20, this.y - 60, '#4488ff', 5, 3);
|
||
}
|
||
} else {
|
||
this.state = 'hit';
|
||
this.stateTimer = 12;
|
||
this.vx = this.facing * -3;
|
||
// Efekty
|
||
const hitX = this.x + this.facing * -15;
|
||
const hitY = this.y - this.bodyH * 0.5;
|
||
spawnHitSpark(hitX, hitY, attacker.facing);
|
||
// Tekst uderzenia
|
||
const txts = attacker.data.faction === 'good' ? HIT_GOOD : HIT_BAD;
|
||
if (Math.random() > 0.5) {
|
||
addFloatingText(hitX, hitY - 30, txts[Math.floor(Math.random()*txts.length)],
|
||
attacker.data.faction === 'good' ? '#60a5fa' : '#f87171', 18);
|
||
}
|
||
playHitSound(attacker.attackData ? attacker.attackData.type : 'light');
|
||
}
|
||
actualDmg = Math.max(0, Math.round(actualDmg));
|
||
this.hp = Math.max(0, this.hp - actualDmg);
|
||
this.power = Math.min(this.maxPower, this.power + actualDmg * 0.8);
|
||
attacker.power = Math.min(attacker.maxPower, attacker.power + actualDmg * 0.5);
|
||
// Screen shake
|
||
shakeX = (Math.random() - 0.5) * actualDmg * 0.8;
|
||
shakeY = (Math.random() - 0.5) * actualDmg * 0.5;
|
||
// Combo
|
||
attacker.combo++;
|
||
attacker.comboTimer = 60;
|
||
if (attacker.combo > 1) {
|
||
addFloatingText(attacker.x, attacker.y - 130, `${attacker.combo}x COMBO!`, '#ffd700', 26);
|
||
}
|
||
// KO check
|
||
if (this.hp <= 0) {
|
||
this.state = 'ko';
|
||
this.stateTimer = 999;
|
||
this.vy = -8;
|
||
this.vx = this.facing * -5;
|
||
playKOSound();
|
||
}
|
||
}
|
||
|
||
update() {
|
||
// Fizyka
|
||
if (!this.isGrounded) {
|
||
this.vy += GRAVITY;
|
||
}
|
||
this.x += this.vx;
|
||
this.y += this.vy;
|
||
if (this.y >= GROUND) {
|
||
this.y = GROUND;
|
||
this.vy = 0;
|
||
this.isGrounded = true;
|
||
if (this.state === 'jump') this.state = 'idle';
|
||
} else {
|
||
this.isGrounded = false;
|
||
}
|
||
// Granice sceny
|
||
this.x = Math.max(40, Math.min(W - 40, this.x));
|
||
// Tarcie
|
||
this.vx *= 0.85;
|
||
// Timer stanu
|
||
if (this.stateTimer > 0) {
|
||
this.stateTimer--;
|
||
if (this.stateTimer <= 0 && this.state !== 'ko' && this.state !== 'block') {
|
||
this.state = this.isGrounded ? 'idle' : 'jump';
|
||
}
|
||
}
|
||
// Combo timer
|
||
if (this.comboTimer > 0) this.comboTimer--;
|
||
else this.combo = 0;
|
||
// Blok, parry i counter timery
|
||
if (this.state === 'block') this.blockTimer++;
|
||
if (this.parryTimer > 0) this.parryTimer--;
|
||
if (this.counterTimer > 0) {
|
||
this.counterTimer--;
|
||
if (this.counterTimer <= 0) this.counterReady = false;
|
||
}
|
||
// Animacja
|
||
this.animFrame += 0.15;
|
||
// Efekt specjalny – cząsteczki
|
||
if (this.state === 'special' && Math.random() > 0.5) {
|
||
const c = this.data.faction === 'good' ? '#60a5fa' : '#ef4444';
|
||
spawnParticles(this.x + this.facing * 30, this.y - 60, c, 1, 4);
|
||
}
|
||
}
|
||
|
||
draw(ctx) {
|
||
ctx.save();
|
||
const bx = this.x;
|
||
const by = this.y;
|
||
const f = this.facing;
|
||
const anim = Math.sin(this.animFrame * 3);
|
||
const anim2 = Math.sin(this.animFrame * 6);
|
||
|
||
// Cień
|
||
ctx.fillStyle = 'rgba(0,0,0,0.3)';
|
||
ctx.beginPath();
|
||
ctx.ellipse(bx, GROUND + 5, 30, 8, 0, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
// KO – leżenie na ziemi
|
||
if (this.state === 'ko' && this.isGrounded) {
|
||
ctx.save();
|
||
ctx.translate(bx, by);
|
||
ctx.rotate(f * Math.PI / 2);
|
||
this.drawBody(ctx, 0, 0, 0, 0, 0, 0, 0.5);
|
||
ctx.restore();
|
||
ctx.restore();
|
||
return;
|
||
}
|
||
|
||
// Hit – wstrząs
|
||
let offsetX = 0, offsetY = 0;
|
||
if (this.state === 'hit') {
|
||
offsetX = (Math.random()-0.5) * 4;
|
||
offsetY = (Math.random()-0.5) * 2;
|
||
}
|
||
|
||
// Poświata specjalna
|
||
if (this.state === 'special') {
|
||
ctx.shadowColor = this.data.faction === 'good' ? '#3b82f6' : '#ef4444';
|
||
ctx.shadowBlur = 30;
|
||
}
|
||
|
||
// Rysowanie ciała
|
||
const punchExt = ['lpunch','hpunch'].includes(this.state) ?
|
||
Math.sin((this.attackData.timer - this.stateTimer) / this.attackData.timer * Math.PI) : 0;
|
||
const kickExt = ['lkick','hkick'].includes(this.state) ?
|
||
Math.sin((this.attackData.timer - this.stateTimer) / this.attackData.timer * Math.PI) : 0;
|
||
const specialExt = this.state === 'special' ?
|
||
Math.sin((this.attackData.timer - this.stateTimer) / this.attackData.timer * Math.PI) : 0;
|
||
const blockArms = this.state === 'block' ? 1 : 0;
|
||
const walkAnim = this.state === 'walk' ? anim : 0;
|
||
|
||
this.drawBody(ctx, bx + offsetX, by + offsetY, f, punchExt, kickExt, specialExt, 1, blockArms, walkAnim);
|
||
|
||
if (this.state === 'block') {
|
||
ctx.save();
|
||
ctx.globalAlpha = this.parryTimer > 0 ? 0.85 : 0.38;
|
||
ctx.strokeStyle = this.parryTimer > 0 ? '#fbbf24' : '#60a5fa';
|
||
ctx.lineWidth = this.parryTimer > 0 ? 5 : 3;
|
||
ctx.shadowColor = this.parryTimer > 0 ? '#fbbf24' : '#60a5fa';
|
||
ctx.shadowBlur = this.parryTimer > 0 ? 22 : 10;
|
||
ctx.beginPath();
|
||
ctx.arc(bx + f * 25, by - 70, 34, -Math.PI/2, Math.PI/2);
|
||
ctx.stroke();
|
||
ctx.restore();
|
||
}
|
||
if (this.counterReady) {
|
||
ctx.save();
|
||
ctx.font = 'bold 12px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillStyle = '#fbbf24';
|
||
ctx.fillText('COUNTER READY', bx, by - 135);
|
||
ctx.restore();
|
||
}
|
||
|
||
ctx.shadowBlur = 0;
|
||
ctx.restore();
|
||
}
|
||
|
||
drawBody(ctx, bx, by, f, punchExt, kickExt, specialExt, alpha, blockArms, walkAnim) {
|
||
ctx.globalAlpha = alpha || 1;
|
||
blockArms = blockArms || 0;
|
||
walkAnim = walkAnim || 0;
|
||
|
||
// Nogi
|
||
ctx.strokeStyle = this.trousersColor;
|
||
ctx.lineWidth = 8;
|
||
ctx.lineCap = 'round';
|
||
// Lewa noga
|
||
ctx.beginPath();
|
||
ctx.moveTo(bx - 10, by - 40);
|
||
const legOff1 = walkAnim * 10 + kickExt * 40 * f;
|
||
ctx.lineTo(bx - 12 + legOff1, by);
|
||
ctx.stroke();
|
||
// Prawa noga
|
||
ctx.beginPath();
|
||
ctx.moveTo(bx + 10, by - 40);
|
||
ctx.lineTo(bx + 12 - walkAnim * 10, by);
|
||
ctx.stroke();
|
||
|
||
// Buty
|
||
ctx.fillStyle = '#111';
|
||
ctx.fillRect(bx - 17 + legOff1, by - 5, 14, 6);
|
||
ctx.fillRect(bx + 3 - walkAnim * 10, by - 5, 14, 6);
|
||
|
||
// Tułów
|
||
ctx.fillStyle = this.bodyColor;
|
||
const torsoY = by - this.bodyH + 25;
|
||
ctx.fillRect(bx - 18, torsoY, 36, 50);
|
||
// Krawat/akcent
|
||
ctx.fillStyle = this.data.faction === 'good' ? '#dc2626' : '#1e3a8a';
|
||
ctx.fillRect(bx - 2, torsoY + 5, 4, 30);
|
||
|
||
// Ramiona i ręce
|
||
ctx.strokeStyle = this.bodyColor;
|
||
ctx.lineWidth = 7;
|
||
// Tylna ręka
|
||
ctx.beginPath();
|
||
ctx.moveTo(bx - f * 15, torsoY + 12);
|
||
if (blockArms) {
|
||
ctx.lineTo(bx + f * 5, torsoY + 20);
|
||
} else {
|
||
ctx.lineTo(bx - f * 25, torsoY + 35 + Math.sin(this.animFrame*2)*3);
|
||
}
|
||
ctx.stroke();
|
||
|
||
// Przednia ręka (atakująca)
|
||
ctx.beginPath();
|
||
ctx.moveTo(bx + f * 15, torsoY + 12);
|
||
if (blockArms) {
|
||
ctx.lineTo(bx + f * 10, torsoY + 5);
|
||
} else if (punchExt > 0) {
|
||
ctx.lineTo(bx + f * (20 + punchExt * 55), torsoY + 15);
|
||
// Pięść
|
||
ctx.fillStyle = '#fcd34d';
|
||
ctx.beginPath();
|
||
ctx.arc(bx + f * (20 + punchExt * 55), torsoY + 15, 7, 0, Math.PI*2);
|
||
ctx.fill();
|
||
} else if (specialExt > 0) {
|
||
ctx.lineTo(bx + f * (20 + specialExt * 70), torsoY + 5);
|
||
// Energia specjalna
|
||
ctx.fillStyle = this.data.faction === 'good' ? '#60a5fa' : '#f87171';
|
||
ctx.beginPath();
|
||
ctx.arc(bx + f * (20 + specialExt * 70), torsoY + 5, 12 + specialExt * 8, 0, Math.PI*2);
|
||
ctx.globalAlpha = 0.6;
|
||
ctx.fill();
|
||
ctx.globalAlpha = alpha || 1;
|
||
} else {
|
||
ctx.lineTo(bx + f * 25, torsoY + 35 + Math.sin(this.animFrame*2+1)*3);
|
||
}
|
||
ctx.stroke();
|
||
|
||
// Głowa
|
||
const headY = torsoY - 18;
|
||
ctx.fillStyle = '#fcd34d';
|
||
ctx.beginPath();
|
||
ctx.arc(bx, headY, 18, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
// Zdjęcie na głowie (jeśli załadowane)
|
||
if (this.data.photoLoaded && this.data.photo) {
|
||
ctx.save();
|
||
ctx.beginPath();
|
||
ctx.arc(bx, headY, 17, 0, Math.PI * 2);
|
||
ctx.clip();
|
||
ctx.drawImage(this.data.photo, bx - 17, headY - 17, 34, 34);
|
||
ctx.restore();
|
||
} else {
|
||
// Inicjały na głowie
|
||
ctx.fillStyle = '#000';
|
||
ctx.font = 'bold 12px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'middle';
|
||
ctx.fillText(this.data.initials, bx, headY);
|
||
ctx.textBaseline = 'alphabetic';
|
||
}
|
||
|
||
// Oczy (w kierunku przeciwnika)
|
||
if (!this.data.photoLoaded) {
|
||
ctx.fillStyle = '#000';
|
||
ctx.fillRect(bx + f * 5 - 2, headY - 4, 4, 4);
|
||
ctx.fillRect(bx + f * 5 + 6 * f, headY - 4, 4, 4);
|
||
}
|
||
|
||
// Pas/opaska frakcyjna
|
||
if (this.data.faction === 'good') {
|
||
// Biało-czerwona opaska
|
||
ctx.fillStyle = '#fff';
|
||
ctx.fillRect(bx - 20, torsoY - 2, 40, 3);
|
||
ctx.fillStyle = '#dc2626';
|
||
ctx.fillRect(bx - 20, torsoY + 1, 40, 3);
|
||
} else {
|
||
// Czarno-czerwona opaska
|
||
ctx.fillStyle = '#000';
|
||
ctx.fillRect(bx - 20, torsoY - 2, 40, 3);
|
||
ctx.fillStyle = '#facc15';
|
||
ctx.fillRect(bx - 20, torsoY + 1, 40, 3);
|
||
}
|
||
|
||
ctx.globalAlpha = 1;
|
||
}
|
||
}
|
||
|
||
// ===================== SZTUCZNA INTELIGENCJA =====================
|
||
class AIController {
|
||
constructor(fighter, target) {
|
||
this.fighter = fighter;
|
||
this.target = target;
|
||
this.actionTimer = 0;
|
||
this.action = 'approach';
|
||
this.difficulty = 0.6; // 0-1, wyższa = trudniejsza
|
||
}
|
||
|
||
update() {
|
||
const f = this.fighter;
|
||
const t = this.target;
|
||
if (f.state === 'ko' || t.state === 'ko') return;
|
||
|
||
const dist = Math.abs(f.x - t.x);
|
||
const facingTarget = (t.x > f.x) ? 1 : -1;
|
||
f.facing = facingTarget;
|
||
|
||
this.actionTimer--;
|
||
if (this.actionTimer <= 0) {
|
||
this.chooseAction(dist);
|
||
}
|
||
|
||
if (f.counterReady && dist < 130 && Math.random() < 0.08) {
|
||
f.performCounter(t);
|
||
return;
|
||
}
|
||
if (this.action !== 'block' && f.state === 'block') {
|
||
f.unblock();
|
||
}
|
||
|
||
// Wykonywanie akcji
|
||
switch(this.action) {
|
||
case 'approach':
|
||
if (dist > 80) {
|
||
f.vx += facingTarget * 2.5 * (this.fighter.data.spd / 10);
|
||
if (f.canAct()) f.state = 'walk';
|
||
}
|
||
break;
|
||
case 'retreat':
|
||
f.vx -= facingTarget * 2 * (this.fighter.data.spd / 10);
|
||
if (f.canAct()) f.state = 'walk';
|
||
break;
|
||
case 'attack_l':
|
||
if (dist < 90) f.startAttack('lpunch');
|
||
else this.action = 'approach';
|
||
break;
|
||
case 'attack_h':
|
||
if (dist < 100) f.startAttack('hpunch');
|
||
else this.action = 'approach';
|
||
break;
|
||
case 'kick_l':
|
||
if (dist < 110) f.startAttack('lkick');
|
||
else this.action = 'approach';
|
||
break;
|
||
case 'kick_h':
|
||
if (dist < 120) f.startAttack('hkick');
|
||
else this.action = 'approach';
|
||
break;
|
||
case 'special':
|
||
if (dist < 150) f.startAttack('special');
|
||
else this.action = 'approach';
|
||
break;
|
||
case 'block':
|
||
f.block();
|
||
break;
|
||
case 'jump':
|
||
if (f.isGrounded && f.canAct()) {
|
||
f.vy = -13;
|
||
f.state = 'jump';
|
||
}
|
||
this.action = 'approach';
|
||
break;
|
||
case 'idle':
|
||
if (f.canAct()) f.state = 'idle';
|
||
break;
|
||
}
|
||
}
|
||
|
||
chooseAction(dist) {
|
||
const f = this.fighter;
|
||
const hpRatio = f.hp / f.maxHp;
|
||
const r = Math.random();
|
||
const d = this.difficulty;
|
||
|
||
if (['lpunch','hpunch','lkick','hkick','special'].includes(this.target.state) && dist < 145 && r < 0.55 * d) {
|
||
this.action = 'block';
|
||
this.actionTimer = 12 + Math.random() * 10;
|
||
return;
|
||
}
|
||
|
||
if (f.power >= f.maxPower && dist < 160 && r < 0.3 * d) {
|
||
this.action = 'special';
|
||
this.actionTimer = 5;
|
||
return;
|
||
}
|
||
|
||
if (dist > 200) {
|
||
this.action = r < 0.7 ? 'approach' : (r < 0.85 ? 'jump' : 'idle');
|
||
this.actionTimer = 20 + Math.random() * 30;
|
||
} else if (dist > 100) {
|
||
if (r < 0.4 * d) this.action = 'approach';
|
||
else if (r < 0.6) this.action = 'kick_h';
|
||
else if (r < 0.75) this.action = 'kick_l';
|
||
else if (r < 0.9) this.action = 'block';
|
||
else this.action = 'retreat';
|
||
this.actionTimer = 15 + Math.random() * 25;
|
||
} else {
|
||
// Blisko
|
||
if (hpRatio < 0.3 && r < 0.35) {
|
||
this.action = r < 0.2 ? 'retreat' : 'block';
|
||
} else if (r < 0.25) this.action = 'attack_l';
|
||
else if (r < 0.45) this.action = 'attack_h';
|
||
else if (r < 0.6) this.action = 'kick_l';
|
||
else if (r < 0.75) this.action = 'kick_h';
|
||
else if (r < 0.85) this.action = 'block';
|
||
else this.action = 'retreat';
|
||
this.actionTimer = 12 + Math.random() * 20;
|
||
}
|
||
|
||
// Bossy i legendy są trudniejsze
|
||
if (f.data.isSpecial) {
|
||
this.actionTimer *= 0.7;
|
||
this.difficulty = 0.85;
|
||
}
|
||
}
|
||
}
|
||
|
||
// ===================== RYSOWANIE TŁA =====================
|
||
function drawSejmBackground() {
|
||
// Gradient niebo/ściana
|
||
const grad = ctx.createLinearGradient(0, 0, 0, H);
|
||
grad.addColorStop(0, '#1a1a2e');
|
||
grad.addColorStop(0.4, '#16213e');
|
||
grad.addColorStop(0.7, '#0f3460');
|
||
grad.addColorStop(1, '#1a1a2e');
|
||
ctx.fillStyle = grad;
|
||
ctx.fillRect(0, 0, W, H);
|
||
|
||
// Kolumny sejmowe
|
||
for (let i = 0; i < 6; i++) {
|
||
const cx = 60 + i * (W - 120) / 5;
|
||
ctx.fillStyle = '#2a2a4a';
|
||
ctx.fillRect(cx - 12, 50, 24, GROUND - 50);
|
||
ctx.fillStyle = '#3a3a5a';
|
||
ctx.fillRect(cx - 15, 45, 30, 15);
|
||
ctx.fillRect(cx - 15, GROUND - 5, 30, 15);
|
||
}
|
||
|
||
// Flaga Polski (wielka, w tle)
|
||
ctx.globalAlpha = 0.12;
|
||
ctx.fillStyle = '#fff';
|
||
ctx.fillRect(W/2 - 200, 80, 400, 100);
|
||
ctx.fillStyle = '#dc2626';
|
||
ctx.fillRect(W/2 - 200, 180, 400, 100);
|
||
ctx.globalAlpha = 1;
|
||
|
||
// Orzeł (uproszczony)
|
||
ctx.globalAlpha = 0.08;
|
||
ctx.fillStyle = '#fff';
|
||
ctx.font = '120px serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText('🦅', W/2, 200);
|
||
ctx.globalAlpha = 1;
|
||
|
||
// Podłoga sejmowa
|
||
const floorGrad = ctx.createLinearGradient(0, GROUND, 0, H);
|
||
floorGrad.addColorStop(0, '#4a3728');
|
||
floorGrad.addColorStop(0.3, '#3d2d1e');
|
||
floorGrad.addColorStop(1, '#2a1f14');
|
||
ctx.fillStyle = floorGrad;
|
||
ctx.fillRect(0, GROUND, W, H - GROUND);
|
||
|
||
// Linie podłogi
|
||
ctx.strokeStyle = '#5a4738';
|
||
ctx.lineWidth = 1;
|
||
for (let i = 0; i < 8; i++) {
|
||
ctx.beginPath();
|
||
ctx.moveTo(0, GROUND + 20 + i * 22);
|
||
ctx.lineTo(W, GROUND + 20 + i * 22);
|
||
ctx.stroke();
|
||
}
|
||
|
||
// Napis u góry
|
||
ctx.font = 'bold 14px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillStyle = 'rgba(255,255,255,0.15)';
|
||
ctx.fillText('SEJM RZECZYPOSPOLITEJ POLSKIEJ – SALA POSIEDZEŃ', W/2, GROUND + 15);
|
||
|
||
// Scrollujący napis propagandowy na dole
|
||
const propText = PROPAGANDA[Math.floor(Date.now() / 4000) % PROPAGANDA.length];
|
||
ctx.font = 'bold 13px Arial';
|
||
ctx.fillStyle = 'rgba(255,200,50,0.25)';
|
||
ctx.fillText(propText, W/2, H - 10);
|
||
}
|
||
|
||
// ===================== RYSOWANIE PORTRETÓW =====================
|
||
function drawPortrait(x, y, size, charData, selected, hovered) {
|
||
// Obramowanie
|
||
ctx.strokeStyle = selected ? '#fbbf24' : (hovered ? '#fff' : (charData.faction === 'good' ? '#3b82f6' : '#ef4444'));
|
||
ctx.lineWidth = selected ? 3 : (hovered ? 2 : 1);
|
||
ctx.strokeRect(x, y, size, size);
|
||
// Tło
|
||
ctx.fillStyle = charData.faction === 'good' ? 'rgba(30,58,138,0.8)' : 'rgba(127,29,29,0.8)';
|
||
ctx.fillRect(x+1, y+1, size-2, size-2);
|
||
// Zdjęcie lub inicjały
|
||
if (charData.photoLoaded && charData.photo) {
|
||
ctx.save();
|
||
ctx.beginPath();
|
||
ctx.rect(x+3, y+3, size-6, size-6);
|
||
ctx.clip();
|
||
const imgSize = Math.max(size-6, size-6);
|
||
ctx.drawImage(charData.photo, x+3, y+3, imgSize, imgSize);
|
||
ctx.restore();
|
||
} else {
|
||
ctx.fillStyle = charData.faction === 'good' ? '#1e40af' : '#991b1b';
|
||
ctx.fillRect(x+3, y+3, size-6, size-6);
|
||
ctx.fillStyle = '#fff';
|
||
ctx.font = `bold ${Math.floor(size*0.35)}px Arial`;
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'middle';
|
||
ctx.fillText(charData.initials, x + size/2, y + size/2);
|
||
}
|
||
// Specjalna ramka
|
||
if (charData.isSpecial) {
|
||
ctx.strokeStyle = '#fbbf24';
|
||
ctx.lineWidth = 2;
|
||
ctx.strokeRect(x-1, y-1, size+2, size+2);
|
||
// Label
|
||
ctx.fillStyle = charData.type === 'ULTIMATE BOSS' ? '#ef4444' : '#fbbf24';
|
||
ctx.font = 'bold 8px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText(charData.type, x + size/2, y + 10);
|
||
}
|
||
// Zaznaczenie
|
||
if (selected) {
|
||
ctx.strokeStyle = '#fbbf24';
|
||
ctx.lineWidth = 3;
|
||
ctx.strokeRect(x-2, y-2, size+4, size+4);
|
||
// Animowana ramka
|
||
const pulse = Math.sin(Date.now() / 200) * 0.3 + 0.7;
|
||
ctx.strokeStyle = `rgba(251,191,36,${pulse})`;
|
||
ctx.lineWidth = 2;
|
||
ctx.strokeRect(x-4, y-4, size+8, size+8);
|
||
}
|
||
}
|
||
|
||
// ===================== EKRAN MENU =====================
|
||
function drawMenu() {
|
||
// Ciemne tło z efektem
|
||
ctx.fillStyle = '#0a0a1a';
|
||
ctx.fillRect(0, 0, W, H);
|
||
|
||
// Animowane tło – flaga
|
||
const t = Date.now() / 1000;
|
||
ctx.globalAlpha = 0.08;
|
||
for (let i = 0; i < 5; i++) {
|
||
ctx.fillStyle = i % 2 === 0 ? '#fff' : '#dc2626';
|
||
ctx.fillRect(0, 120 + i * 100 + Math.sin(t + i) * 10, W, 100);
|
||
}
|
||
ctx.globalAlpha = 1;
|
||
|
||
// Tytuł
|
||
ctx.textAlign = 'center';
|
||
// Cień
|
||
ctx.fillStyle = '#000';
|
||
ctx.font = 'bold 72px Arial';
|
||
ctx.fillText('SEJM KOMBAT', W/2 + 3, 163);
|
||
// Gradient na tekście
|
||
const titleGrad = ctx.createLinearGradient(W/2 - 300, 100, W/2 + 300, 200);
|
||
titleGrad.addColorStop(0, '#ef4444');
|
||
titleGrad.addColorStop(0.5, '#fbbf24');
|
||
titleGrad.addColorStop(1, '#ef4444');
|
||
ctx.fillStyle = titleGrad;
|
||
ctx.font = 'bold 72px Arial';
|
||
ctx.fillText('SEJM KOMBAT', W/2, 160);
|
||
|
||
// Podtytuł
|
||
ctx.font = 'bold 28px Arial';
|
||
ctx.fillStyle = '#fbbf24';
|
||
ctx.fillText('SUWERENNOŚĆ vs KONDOMINIUM', W/2, 210);
|
||
|
||
// Opis
|
||
ctx.font = '18px Arial';
|
||
ctx.fillStyle = '#94a3b8';
|
||
ctx.fillText('Walka Polski suwerennej przeciwko obcemu kondominium rosyjsko-niemieckiemu!', W/2, 260);
|
||
|
||
// Frakcje
|
||
ctx.font = 'bold 20px Arial';
|
||
// Dobra strona
|
||
ctx.fillStyle = '#3b82f6';
|
||
ctx.fillText('🇵🇱 OBROŃCY SUWERENNOŚCI 🇵🇱', W/4, 320);
|
||
ctx.font = '14px Arial';
|
||
ctx.fillStyle = '#60a5fa';
|
||
ctx.fillText('PiS • Patrioci • Polska Wolna', W/4, 345);
|
||
ctx.fillText('Kaczyński • Morawiecki • Nawrocki • Lech Kaczyński', W/4, 365);
|
||
|
||
// Zła strona
|
||
ctx.font = 'bold 20px Arial';
|
||
ctx.fillStyle = '#ef4444';
|
||
ctx.fillText('☠ KONDOMINIUM ZDRAJCÓW ☠', W*3/4, 320);
|
||
ctx.font = '14px Arial';
|
||
ctx.fillStyle = '#f87171';
|
||
ctx.fillText('Platforma • Lewica • Słudzy Merkel i Putina', W*3/4, 345);
|
||
ctx.fillText('Tusk • Czarzasty • Merkel • Putin', W*3/4, 365);
|
||
|
||
// VS
|
||
ctx.font = 'bold 36px Arial';
|
||
ctx.fillStyle = '#fbbf24';
|
||
ctx.fillText('VS', W/2, 340);
|
||
|
||
// Propaganda (zmienia się co kilka sekund)
|
||
const propIdx = Math.floor(Date.now() / 3000) % PROPAGANDA.length;
|
||
ctx.font = 'bold 16px Arial';
|
||
const pulse = Math.sin(Date.now() / 500) * 0.3 + 0.7;
|
||
ctx.fillStyle = `rgba(251,191,36,${pulse})`;
|
||
ctx.fillText(`« ${PROPAGANDA[propIdx]} »`, W/2, 420);
|
||
|
||
// Sterowanie
|
||
ctx.font = '14px Arial';
|
||
ctx.fillStyle = '#64748b';
|
||
ctx.fillText('STEROWANIE: Strzałki = ruch/skok/kucanie | Q/W = cios lekki/ciężki', W/2, 480);
|
||
ctx.fillText('A/S = kopnięcie lekkie/ciężkie | Z = blok | X = cios specjalny (pełna moc)', W/2, 500);
|
||
ctx.fillText('P = pauza | ESC = menu', W/2, 520);
|
||
|
||
// Start
|
||
const startPulse = Math.sin(Date.now() / 300) * 0.4 + 0.6;
|
||
ctx.font = `bold 28px Arial`;
|
||
ctx.fillStyle = `rgba(255,255,255,${startPulse})`;
|
||
ctx.fillText('>>> NACIŚNIJ ENTER ABY ROZPOCZĄĆ <<<', W/2, 590);
|
||
|
||
// Stopka
|
||
ctx.font = '12px Arial';
|
||
ctx.fillStyle = '#334155';
|
||
ctx.fillText('Gra satyryczna • Wszystkie postacie fikcyjne (w pewnym sensie)', W/2, 680);
|
||
ctx.fillText('SEJM KOMBAT © 2024 – Suwerenność górą!', W/2, 700);
|
||
}
|
||
|
||
function updateMenu() {
|
||
if (consumeKey('Enter') || consumeKey('Space')) {
|
||
gameState = 'SELECT';
|
||
selectCursor = {col:0, row:0, side:0};
|
||
selectedPlayer = null;
|
||
selectedEnemy = null;
|
||
enterCooldown = 15;
|
||
playMenuSelect();
|
||
announce('Wybierz swojego wojownika!');
|
||
}
|
||
}
|
||
|
||
// ===================== EKRAN WYBORU POSTACI =====================
|
||
function getSelectGrid() {
|
||
// 5 kolumn x 3 rzędy regularnych + 1 rząd specjalnych na każdej stronie
|
||
const grid = {good: [], bad: []};
|
||
for (let i = 0; i < GOOD_CHARS.length; i++) {
|
||
grid.good.push({char: GOOD_CHARS[i], col: i % 5, row: Math.floor(i/5)});
|
||
}
|
||
SPECIAL_GOOD.forEach((c, i) => {
|
||
grid.good.push({char: c, col: i * 2 + 0.5, row: 3, isSpecial: true});
|
||
});
|
||
for (let i = 0; i < BAD_CHARS.length; i++) {
|
||
grid.bad.push({char: BAD_CHARS[i], col: i % 5, row: Math.floor(i/5)});
|
||
}
|
||
SPECIAL_BAD.forEach((c, i) => {
|
||
grid.bad.push({char: c, col: i * 2 + 0.5, row: 3, isSpecial: true});
|
||
});
|
||
return grid;
|
||
}
|
||
|
||
function getSelectedChar() {
|
||
const s = selectCursor;
|
||
const list = s.side === 0 ? ALL_GOOD : ALL_BAD;
|
||
const regularCount = s.side === 0 ? GOOD_CHARS.length : BAD_CHARS.length;
|
||
const specialList = s.side === 0 ? SPECIAL_GOOD : SPECIAL_BAD;
|
||
|
||
if (s.row < 3) {
|
||
const idx = s.row * 5 + s.col;
|
||
if (idx < regularCount) return list[idx];
|
||
} else if (s.row === 3) {
|
||
const sIdx = Math.floor(s.col / 3);
|
||
if (sIdx < specialList.length) return specialList[sIdx];
|
||
}
|
||
return list[0];
|
||
}
|
||
|
||
function drawSelect() {
|
||
// Tło
|
||
ctx.fillStyle = '#0a0a1a';
|
||
ctx.fillRect(0, 0, W, H);
|
||
|
||
// Tytuł
|
||
ctx.textAlign = 'center';
|
||
ctx.font = 'bold 32px Arial';
|
||
const tGrad = ctx.createLinearGradient(W/2-200, 0, W/2+200, 0);
|
||
tGrad.addColorStop(0, '#3b82f6');
|
||
tGrad.addColorStop(0.5, '#fbbf24');
|
||
tGrad.addColorStop(1, '#ef4444');
|
||
ctx.fillStyle = tGrad;
|
||
ctx.fillText('WYBIERZ SWOJEGO WOJOWNIKA', W/2, 35);
|
||
|
||
// Propaganda
|
||
ctx.font = 'bold 12px Arial';
|
||
ctx.fillStyle = '#fbbf24';
|
||
const prop = PROPAGANDA[Math.floor(Date.now()/4000) % PROPAGANDA.length];
|
||
ctx.fillText(prop, W/2, 55);
|
||
|
||
// Lewa strona – DOBRZY (Suwerenność)
|
||
const leftX = 20;
|
||
const gridY = 75;
|
||
const cellW = 105;
|
||
const cellH = 90;
|
||
|
||
ctx.font = 'bold 16px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillStyle = '#3b82f6';
|
||
ctx.fillText('🇵🇱 OBROŃCY SUWERENNOŚCI (PiS) 🇵🇱', leftX + 265, gridY - 5);
|
||
|
||
// Rysuj siatkę dobrych
|
||
for (let i = 0; i < GOOD_CHARS.length; i++) {
|
||
const col = i % 5;
|
||
const row = Math.floor(i / 5);
|
||
const cx = leftX + col * cellW + 10;
|
||
const cy = gridY + row * cellH + 5;
|
||
const isHovered = selectCursor.side === 0 && selectCursor.row === row && selectCursor.col === col;
|
||
const isSelected = selectedPlayer === GOOD_CHARS[i];
|
||
drawPortrait(cx, cy, 60, GOOD_CHARS[i], isSelected, isHovered);
|
||
// Imię
|
||
ctx.font = '9px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillStyle = isHovered ? '#fbbf24' : '#94a3b8';
|
||
const shortName = GOOD_CHARS[i].name.split(' ').slice(-1)[0];
|
||
ctx.fillText(shortName, cx + 30, cy + 73);
|
||
ctx.fillText(GOOD_CHARS[i].nick, cx + 30, cy + 83);
|
||
}
|
||
// Specjalne dobre
|
||
for (let i = 0; i < SPECIAL_GOOD.length; i++) {
|
||
const cx = leftX + 70 + i * 200;
|
||
const cy = gridY + 3 * cellH + 15;
|
||
const isHovered = selectCursor.side === 0 && selectCursor.row === 3 && Math.floor(selectCursor.col/3) === i;
|
||
const isSelected = selectedPlayer === SPECIAL_GOOD[i];
|
||
drawPortrait(cx, cy, 75, SPECIAL_GOOD[i], isSelected, isHovered);
|
||
ctx.font = 'bold 10px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillStyle = '#fbbf24';
|
||
ctx.fillText(SPECIAL_GOOD[i].type, cx + 37, cy + 85);
|
||
ctx.fillStyle = isHovered ? '#fbbf24' : '#e2e8f0';
|
||
ctx.font = '9px Arial';
|
||
ctx.fillText(SPECIAL_GOOD[i].name, cx + 37, cy + 96);
|
||
}
|
||
|
||
// Prawa strona – ŹLI (Kondominium)
|
||
const rightX = W/2 + 100;
|
||
|
||
ctx.font = 'bold 16px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillStyle = '#ef4444';
|
||
ctx.fillText('☠ KONDOMINIUM ZDRAJCÓW (KO/Lewica) ☠', rightX + 265, gridY - 5);
|
||
|
||
for (let i = 0; i < BAD_CHARS.length; i++) {
|
||
const col = i % 5;
|
||
const row = Math.floor(i / 5);
|
||
const cx = rightX + col * cellW + 10;
|
||
const cy = gridY + row * cellH + 5;
|
||
const isHovered = selectCursor.side === 1 && selectCursor.row === row && selectCursor.col === col;
|
||
const isSelected = selectedEnemy === BAD_CHARS[i];
|
||
drawPortrait(cx, cy, 60, BAD_CHARS[i], isSelected, isHovered);
|
||
ctx.font = '9px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillStyle = isHovered ? '#fbbf24' : '#94a3b8';
|
||
const shortName = BAD_CHARS[i].name.split(' ').slice(-1)[0];
|
||
ctx.fillText(shortName, cx + 30, cy + 73);
|
||
ctx.fillText(BAD_CHARS[i].nick, cx + 30, cy + 83);
|
||
}
|
||
// Specjalne złe
|
||
for (let i = 0; i < SPECIAL_BAD.length; i++) {
|
||
const cx = rightX + 70 + i * 200;
|
||
const cy = gridY + 3 * cellH + 15;
|
||
const isHovered = selectCursor.side === 1 && selectCursor.row === 3 && Math.floor(selectCursor.col/3) === i;
|
||
const isSelected = selectedEnemy === SPECIAL_BAD[i];
|
||
drawPortrait(cx, cy, 75, SPECIAL_BAD[i], isSelected, isHovered);
|
||
ctx.font = 'bold 10px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillStyle = SPECIAL_BAD[i].type === 'ULTIMATE BOSS' ? '#ef4444' : '#fbbf24';
|
||
ctx.fillText(SPECIAL_BAD[i].type, cx + 37, cy + 85);
|
||
ctx.fillStyle = isHovered ? '#fbbf24' : '#e2e8f0';
|
||
ctx.font = '9px Arial';
|
||
ctx.fillText(SPECIAL_BAD[i].name, cx + 37, cy + 96);
|
||
}
|
||
|
||
// Panel centralny – VS
|
||
const centerX = W/2 - 55;
|
||
ctx.fillStyle = 'rgba(30,30,50,0.8)';
|
||
ctx.fillRect(centerX - 15, gridY, 140, 350);
|
||
ctx.strokeStyle = '#fbbf24';
|
||
ctx.lineWidth = 1;
|
||
ctx.strokeRect(centerX - 15, gridY, 140, 350);
|
||
|
||
ctx.font = 'bold 40px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillStyle = '#fbbf24';
|
||
ctx.fillText('VS', centerX + 55, gridY + 190);
|
||
|
||
// Wybrana postać – info
|
||
const selChar = getSelectedChar();
|
||
if (selChar) {
|
||
ctx.font = 'bold 11px Arial';
|
||
ctx.fillStyle = '#fff';
|
||
ctx.fillText(selChar.name, centerX + 55, gridY + 30);
|
||
ctx.font = '10px Arial';
|
||
ctx.fillStyle = '#94a3b8';
|
||
ctx.fillText(`"${selChar.nick}"`, centerX + 55, gridY + 45);
|
||
// Statystyki
|
||
ctx.font = '10px Arial';
|
||
ctx.textAlign = 'left';
|
||
const sx = centerX;
|
||
ctx.fillStyle = '#ef4444'; ctx.fillText(`ATK: ${selChar.atk}`, sx, gridY + 65);
|
||
ctx.fillStyle = '#3b82f6'; ctx.fillText(`DEF: ${selChar.def}`, sx, gridY + 78);
|
||
ctx.fillStyle = '#22c55e'; ctx.fillText(`SPD: ${selChar.spd}`, sx, gridY + 91);
|
||
ctx.fillStyle = '#fbbf24'; ctx.fillText(`HP: ${selChar.hp}`, sx, gridY + 104);
|
||
ctx.fillStyle = '#e879f9'; ctx.fillText(`SP: ${selChar.special}`, sx, gridY + 117);
|
||
ctx.textAlign = 'center';
|
||
|
||
// Wybrany gracz (dół)
|
||
if (selectedPlayer) {
|
||
ctx.font = 'bold 12px Arial';
|
||
ctx.fillStyle = '#3b82f6';
|
||
ctx.fillText('GRACZ:', centerX + 55, gridY + 250);
|
||
ctx.fillStyle = '#fff';
|
||
ctx.font = '10px Arial';
|
||
ctx.fillText(selectedPlayer.name, centerX + 55, gridY + 265);
|
||
}
|
||
if (selectedEnemy) {
|
||
ctx.font = 'bold 12px Arial';
|
||
ctx.fillStyle = '#ef4444';
|
||
ctx.fillText('WRÓG:', centerX + 55, gridY + 290);
|
||
ctx.fillStyle = '#fff';
|
||
ctx.font = '10px Arial';
|
||
ctx.fillText(selectedEnemy.name, centerX + 55, gridY + 305);
|
||
}
|
||
}
|
||
|
||
// Instrukcja
|
||
ctx.textAlign = 'center';
|
||
ctx.font = '13px Arial';
|
||
ctx.fillStyle = '#64748b';
|
||
ctx.fillText('Strzałki = wybór | TAB = zmiana frakcji | ENTER = zatwierdź i posłuchaj opisu | ESC = menu', W/2, H - 40);
|
||
ctx.font = 'bold 13px Arial';
|
||
ctx.fillStyle = '#fbbf24';
|
||
if (!selectedPlayer) {
|
||
ctx.fillText('Wybierz swoją postać (z dowolnej frakcji)!', W/2, H - 18);
|
||
} else if (!selectedEnemy) {
|
||
ctx.fillText('AI wybiera przeciwnika z przeciwnej frakcji...', W/2, H - 18);
|
||
} else {
|
||
const sp = Math.sin(Date.now()/300) * 0.4 + 0.6;
|
||
ctx.fillStyle = `rgba(251,191,36,${sp})`;
|
||
ctx.fillText('NACIŚNIJ ENTER ABY WALCZYĆ!', W/2, H - 18);
|
||
}
|
||
}
|
||
|
||
function updateSelect() {
|
||
if (enterCooldown > 0) { enterCooldown--; return; }
|
||
const s = selectCursor;
|
||
let movedCursor = false;
|
||
|
||
// Nawigacja kursorem
|
||
if (consumeKey('ArrowRight')) {
|
||
const oldCol = s.col;
|
||
s.col = Math.min(4, s.col + 1);
|
||
movedCursor = movedCursor || oldCol !== s.col;
|
||
playMenuMove();
|
||
}
|
||
if (consumeKey('ArrowLeft')) {
|
||
const oldCol = s.col;
|
||
s.col = Math.max(0, s.col - 1);
|
||
movedCursor = movedCursor || oldCol !== s.col;
|
||
playMenuMove();
|
||
}
|
||
if (consumeKey('ArrowDown')) {
|
||
const oldRow = s.row;
|
||
s.row = Math.min(3, s.row + 1);
|
||
movedCursor = movedCursor || oldRow !== s.row;
|
||
playMenuMove();
|
||
}
|
||
if (consumeKey('ArrowUp')) {
|
||
const oldRow = s.row;
|
||
s.row = Math.max(0, s.row - 1);
|
||
movedCursor = movedCursor || oldRow !== s.row;
|
||
playMenuMove();
|
||
}
|
||
// TAB zmienia stronę
|
||
if (consumeKey('Tab')) {
|
||
s.side = 1 - s.side;
|
||
movedCursor = true;
|
||
playMenuMove();
|
||
}
|
||
if (movedCursor) lastPreviewedChar = getSelectedChar();
|
||
// ESC wraca do menu
|
||
if (consumeKey('Escape')) {
|
||
gameState = 'MENU';
|
||
return;
|
||
}
|
||
|
||
// ENTER zatwierdza wybór
|
||
if (consumeKey('Enter') || consumeKey('Space')) {
|
||
if (!selectedPlayer) {
|
||
selectedPlayer = getSelectedChar();
|
||
playMenuSelect();
|
||
announceCharacter(selectedPlayer, 'Wybrano wojownika');
|
||
// AI automatycznie wybiera z przeciwnej frakcji
|
||
setTimeout(() => {
|
||
const enemyList = selectedPlayer.faction === 'good' ? ALL_BAD : ALL_GOOD;
|
||
selectedEnemy = enemyList[Math.floor(Math.random() * enemyList.length)];
|
||
playMenuSelect();
|
||
announceCharacter(selectedEnemy, 'Przeciwnik');
|
||
}, 500);
|
||
} else if (selectedEnemy) {
|
||
// Rozpocznij walkę!
|
||
startFight();
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// ===================== JAN PAWEŁ II – LOSOWE WSPARCIE =====================
|
||
const JP2_TEXTS = [
|
||
"Nie lękajcie się! Walczcie dalej!",
|
||
"Musicie od siebie wymagać, nawet gdy inni od was nie wymagają.",
|
||
"Niech zstąpi Duch Twój i odnowi oblicze tej ziemi.",
|
||
"Brońcie prawdy, wolności i Ojczyzny.",
|
||
"Odwagi! Suwerenność potrzebuje wytrwałości.",
|
||
"Polska potrzebuje ludzi sumienia. Do walki!"
|
||
];
|
||
|
||
function randomJP2Text() {
|
||
return JP2_TEXTS[Math.floor(Math.random() * JP2_TEXTS.length)];
|
||
}
|
||
|
||
function resetJP2() {
|
||
jp2.active = false;
|
||
jp2.timer = 0;
|
||
jp2.cooldown = 0;
|
||
// Gwarantowany pierwszy spawn po ok. 3 sekundach realnej walki.
|
||
// Kolejne wejścia są planowane licznikowo, więc nie ma już ryzyka, że RNG nigdy go nie pokaże.
|
||
jp2.nextSpawn = 180;
|
||
jp2.text = '';
|
||
jp2.introPulse = 0;
|
||
}
|
||
|
||
function buffGoodFighter(f) {
|
||
if (!f || !f.data || f.data.faction !== 'good' || f.state === 'ko') return;
|
||
f.power = Math.min(f.maxPower, f.power + 25);
|
||
f.hp = Math.min(f.maxHp, f.hp + 8);
|
||
spawnParticles(f.x, f.y - 80, '#fbbf24', 18, 5);
|
||
addFloatingText(f.x, f.y - 145, '+ DUCH WALKI', '#fbbf24', 22);
|
||
}
|
||
|
||
function spawnJP2() {
|
||
jp2.active = true;
|
||
jp2.timer = jp2.duration;
|
||
jp2.cooldown = 0;
|
||
// Następne wejście po 8–14 sekundach walki, zawsze licznikowo.
|
||
jp2.nextSpawn = 480 + Math.floor(Math.random() * 360);
|
||
jp2.x = 180 + Math.random() * (W - 360);
|
||
jp2.y = 105 + Math.random() * 70;
|
||
jp2.text = randomJP2Text();
|
||
jp2.introPulse = 1;
|
||
buffGoodFighter(player1);
|
||
buffGoodFighter(player2);
|
||
playChoirSound();
|
||
addFloatingText(jp2.x, jp2.y + 115, 'JAN PAWEŁ II ZSTĘPUJE Z NIEBA!', '#fff', 20);
|
||
announce('Jan Paweł drugi mówi: ' + jp2.text);
|
||
}
|
||
|
||
function updateJP2() {
|
||
if (gameState !== 'FIGHT' || fightResult) return;
|
||
|
||
if (jp2.active) {
|
||
jp2.timer--;
|
||
jp2.introPulse = Math.max(0, jp2.introPulse - 0.018);
|
||
if (jp2.timer <= 0) {
|
||
jp2.active = false;
|
||
jp2.cooldown = 0;
|
||
}
|
||
return;
|
||
}
|
||
|
||
jp2.cooldown++;
|
||
// Gwarantowany spawn: licznik, nie prawdopodobieństwo na klatkę.
|
||
if (jp2.cooldown >= jp2.nextSpawn) spawnJP2();
|
||
}
|
||
|
||
function drawJP2() {
|
||
if (!jp2.active) return;
|
||
|
||
const elapsed = jp2.duration - jp2.timer;
|
||
const fadeIn = Math.min(1, elapsed / 80);
|
||
const fadeOut = Math.min(1, jp2.timer / 70);
|
||
const alpha = Math.min(fadeIn, fadeOut);
|
||
|
||
// Wejście z nieba: start wysoko nad kadrem, potem zjazd + zoom + delikatne unoszenie.
|
||
const easeOut = 1 - Math.pow(1 - fadeIn, 4);
|
||
const float = Math.sin(Date.now() / 320) * 8;
|
||
const heavenStartY = -135;
|
||
const drawY = heavenStartY + (jp2.y - heavenStartY) * easeOut + float;
|
||
const scale = (0.35 + 0.75 * easeOut) * (1 + Math.sin(Date.now() / 180) * 0.045);
|
||
const size = 142 * scale;
|
||
const halo = size / 2 + 24 + Math.sin(Date.now() / 220) * 8;
|
||
|
||
ctx.save();
|
||
|
||
// Krótki biało-złoty rozbłysk na samym początku pojawienia.
|
||
if (jp2.introPulse > 0) {
|
||
const flash = jp2.introPulse * jp2.introPulse;
|
||
ctx.globalAlpha = alpha;
|
||
ctx.fillStyle = `rgba(255,255,220,${0.10 * flash})`;
|
||
ctx.fillRect(0, 0, W, H);
|
||
}
|
||
|
||
// Miękkie światło na całą scenę, jak reflektor z góry.
|
||
ctx.globalAlpha = 0.09 * alpha;
|
||
const topLight = ctx.createLinearGradient(jp2.x, 0, jp2.x, GROUND);
|
||
topLight.addColorStop(0, 'rgba(255,255,255,0.9)');
|
||
topLight.addColorStop(0.35, 'rgba(251,191,36,0.22)');
|
||
topLight.addColorStop(1, 'rgba(251,191,36,0)');
|
||
ctx.fillStyle = topLight;
|
||
ctx.beginPath();
|
||
ctx.moveTo(jp2.x - 95, 0);
|
||
ctx.lineTo(jp2.x + 95, 0);
|
||
ctx.lineTo(jp2.x + 235, GROUND);
|
||
ctx.lineTo(jp2.x - 235, GROUND);
|
||
ctx.closePath();
|
||
ctx.fill();
|
||
|
||
// Promienie za postacią.
|
||
ctx.save();
|
||
ctx.translate(jp2.x, drawY);
|
||
ctx.rotate(Date.now() / 2600);
|
||
for (let i = 0; i < 14; i++) {
|
||
ctx.rotate((Math.PI * 2) / 14);
|
||
ctx.globalAlpha = 0.09 * alpha;
|
||
ctx.fillStyle = '#fff7cc';
|
||
ctx.beginPath();
|
||
ctx.moveTo(0, 0);
|
||
ctx.lineTo(18, -210);
|
||
ctx.lineTo(-18, -210);
|
||
ctx.closePath();
|
||
ctx.fill();
|
||
}
|
||
ctx.restore();
|
||
|
||
// Złoty glow / aura.
|
||
ctx.globalAlpha = 0.38 * alpha;
|
||
const light = ctx.createRadialGradient(jp2.x, drawY, 8, jp2.x, drawY, 260);
|
||
light.addColorStop(0, 'rgba(255,255,255,0.95)');
|
||
light.addColorStop(0.24, 'rgba(251,191,36,0.55)');
|
||
light.addColorStop(0.62, 'rgba(251,191,36,0.18)');
|
||
light.addColorStop(1, 'rgba(251,191,36,0)');
|
||
ctx.fillStyle = light;
|
||
ctx.beginPath();
|
||
ctx.arc(jp2.x, drawY, 260, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
// Aureola.
|
||
ctx.globalAlpha = alpha;
|
||
ctx.shadowColor = '#fff7ed';
|
||
ctx.shadowBlur = 45;
|
||
ctx.strokeStyle = 'rgba(255,255,255,0.95)';
|
||
ctx.lineWidth = 5;
|
||
ctx.beginPath();
|
||
ctx.arc(jp2.x, drawY, halo, 0, Math.PI * 2);
|
||
ctx.stroke();
|
||
ctx.strokeStyle = 'rgba(251,191,36,0.95)';
|
||
ctx.lineWidth = 3;
|
||
ctx.beginPath();
|
||
ctx.arc(jp2.x, drawY, halo + 8, 0, Math.PI * 2);
|
||
ctx.stroke();
|
||
|
||
// Główna grafika osadzona w base64, bez pobierania pliku.
|
||
if (jp2.img) {
|
||
ctx.save();
|
||
ctx.shadowColor = '#fff7ed';
|
||
ctx.shadowBlur = 32;
|
||
ctx.drawImage(jp2.img, jp2.x - size/2, drawY - size/2, size, size);
|
||
ctx.restore();
|
||
} else {
|
||
ctx.fillStyle = '#fbbf24';
|
||
ctx.beginPath();
|
||
ctx.arc(jp2.x, drawY, size/2, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
ctx.fillStyle = '#111';
|
||
ctx.font = 'bold 28px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'middle';
|
||
ctx.fillText('JP2', jp2.x, drawY);
|
||
ctx.textBaseline = 'alphabetic';
|
||
}
|
||
|
||
ctx.shadowBlur = 0;
|
||
ctx.font = 'bold 18px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.strokeStyle = '#000';
|
||
ctx.lineWidth = 4;
|
||
ctx.strokeText('JAN PAWEŁ II', jp2.x, drawY + size/2 + 30);
|
||
ctx.fillStyle = '#fff';
|
||
ctx.fillText('JAN PAWEŁ II', jp2.x, drawY + size/2 + 30);
|
||
|
||
ctx.font = 'bold 16px Arial';
|
||
const msgY = drawY + size/2 + 56;
|
||
ctx.strokeText('„' + jp2.text + '”', jp2.x, msgY);
|
||
ctx.fillStyle = '#fbbf24';
|
||
ctx.fillText('„' + jp2.text + '”', jp2.x, msgY);
|
||
|
||
ctx.restore();
|
||
}
|
||
|
||
|
||
// ===================== INTRO BOSSÓW I SPECJALNE ANIMACJE =====================
|
||
function isBossCharacter(c) {
|
||
return !!(c && BOSS_NAMES.includes(c.name));
|
||
}
|
||
|
||
function getBossForIntro() {
|
||
if (isBossCharacter(selectedEnemy)) return selectedEnemy;
|
||
if (isBossCharacter(selectedPlayer)) return selectedPlayer;
|
||
return null;
|
||
}
|
||
|
||
function resetBossIntro() {
|
||
bossIntro.active = false;
|
||
bossIntro.char = null;
|
||
bossIntro.timer = 0;
|
||
bossIntro.announced = false;
|
||
}
|
||
|
||
function startBossIntro(charData) {
|
||
if (!charData) { resetBossIntro(); return; }
|
||
bossIntro.active = true;
|
||
bossIntro.char = charData;
|
||
bossIntro.timer = bossIntro.duration;
|
||
bossIntro.announced = true;
|
||
shakeX = 0; shakeY = 0;
|
||
addFloatingText(W/2, H/2 - 115, 'BOSS FIGHT', '#ef4444', 54);
|
||
announce('Boss fight! Nadchodzi ' + charData.name + '. ' + (charData.bio || 'Przygotuj się do walki.'));
|
||
}
|
||
|
||
function updateBossIntro() {
|
||
if (!bossIntro.active) return false;
|
||
bossIntro.timer--;
|
||
const c = bossIntro.char;
|
||
if (c) {
|
||
const color = c.name === 'Angela Merkel' ? '#facc15' : (c.name === 'Władimir Putin' ? '#ef4444' : '#60a5fa');
|
||
if (Math.random() < 0.42) spawnParticles(W/2 + (Math.random()-0.5)*260, H/2 + (Math.random()-0.5)*160, color, 1, 5);
|
||
}
|
||
shakeX = (Math.random() - 0.5) * 3;
|
||
shakeY = (Math.random() - 0.5) * 2;
|
||
if (bossIntro.timer <= 0) {
|
||
bossIntro.active = false;
|
||
shakeX = 0; shakeY = 0;
|
||
addFloatingText(W/2, H/2 - 50, 'WALCZ!', '#fbbf24', 58);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
function drawBossPortraitCentered(charData, cx, cy, size) {
|
||
ctx.save();
|
||
ctx.beginPath();
|
||
ctx.arc(cx, cy, size/2, 0, Math.PI * 2);
|
||
ctx.clip();
|
||
if (charData && charData.photoLoaded && charData.photo) {
|
||
ctx.drawImage(charData.photo, cx - size/2, cy - size/2, size, size);
|
||
} else if (charData && charData.photo) {
|
||
ctx.drawImage(charData.photo, cx - size/2, cy - size/2, size, size);
|
||
} else {
|
||
ctx.fillStyle = '#111827';
|
||
ctx.fillRect(cx - size/2, cy - size/2, size, size);
|
||
ctx.fillStyle = '#fff';
|
||
ctx.font = 'bold 44px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'middle';
|
||
ctx.fillText(charData ? charData.initials : 'BOSS', cx, cy);
|
||
ctx.textBaseline = 'alphabetic';
|
||
}
|
||
ctx.restore();
|
||
}
|
||
|
||
function drawBossIntro() {
|
||
if (!bossIntro.active || !bossIntro.char) return;
|
||
const c = bossIntro.char;
|
||
const elapsed = bossIntro.duration - bossIntro.timer;
|
||
const fadeIn = Math.min(1, elapsed / 38);
|
||
const fadeOut = Math.min(1, bossIntro.timer / 38);
|
||
const alpha = Math.min(fadeIn, fadeOut);
|
||
const zoom = 0.58 + 0.48 * (1 - Math.pow(1 - fadeIn, 3)) + Math.sin(Date.now()/90) * 0.018;
|
||
const size = 250 * zoom;
|
||
const color = c.name === 'Angela Merkel' ? '#facc15' : (c.name === 'Władimir Putin' ? '#ef4444' : '#3b82f6');
|
||
|
||
ctx.save();
|
||
ctx.globalAlpha = 0.72 * alpha;
|
||
ctx.fillStyle = '#030712';
|
||
ctx.fillRect(0, 0, W, H);
|
||
|
||
ctx.save();
|
||
ctx.translate(W/2, H/2);
|
||
ctx.rotate(Date.now()/900);
|
||
for (let i = 0; i < 18; i++) {
|
||
ctx.rotate(Math.PI * 2 / 18);
|
||
ctx.globalAlpha = 0.12 * alpha;
|
||
ctx.fillStyle = color;
|
||
ctx.beginPath();
|
||
ctx.moveTo(0, 0);
|
||
ctx.lineTo(-28, -680);
|
||
ctx.lineTo(28, -680);
|
||
ctx.closePath();
|
||
ctx.fill();
|
||
}
|
||
ctx.restore();
|
||
|
||
ctx.globalAlpha = alpha;
|
||
ctx.shadowColor = color;
|
||
ctx.shadowBlur = 55;
|
||
ctx.strokeStyle = color;
|
||
ctx.lineWidth = 8;
|
||
ctx.beginPath();
|
||
ctx.arc(W/2, H/2 - 20, size/2 + 14, 0, Math.PI * 2);
|
||
ctx.stroke();
|
||
drawBossPortraitCentered(c, W/2, H/2 - 20, size);
|
||
ctx.shadowBlur = 0;
|
||
|
||
const pulse = 1 + Math.sin(Date.now()/95) * 0.055;
|
||
ctx.save();
|
||
ctx.translate(W/2, 130);
|
||
ctx.scale(pulse, pulse);
|
||
ctx.textAlign = 'center';
|
||
ctx.font = 'bold 76px Arial Black, Arial';
|
||
ctx.lineWidth = 8;
|
||
ctx.strokeStyle = '#000';
|
||
ctx.strokeText('BOSS FIGHT', 0, 0);
|
||
ctx.fillStyle = '#ef4444';
|
||
ctx.fillText('BOSS FIGHT', 0, 0);
|
||
ctx.restore();
|
||
|
||
ctx.textAlign = 'center';
|
||
ctx.font = 'bold 36px Arial';
|
||
ctx.lineWidth = 5;
|
||
ctx.strokeStyle = '#000';
|
||
ctx.strokeText(c.name, W/2, H/2 + size/2 + 55);
|
||
ctx.fillStyle = '#fff';
|
||
ctx.fillText(c.name, W/2, H/2 + size/2 + 55);
|
||
ctx.font = 'bold 22px Arial';
|
||
ctx.strokeText('SPECJALNY WOJOWNIK: ' + c.special, W/2, H/2 + size/2 + 88);
|
||
ctx.fillStyle = '#fbbf24';
|
||
ctx.fillText('SPECJALNY WOJOWNIK: ' + c.special, W/2, H/2 + size/2 + 88);
|
||
ctx.restore();
|
||
}
|
||
|
||
function drawPutinEffect(f) {
|
||
const t = Date.now()/1000;
|
||
ctx.save();
|
||
ctx.globalAlpha = 0.78;
|
||
ctx.shadowColor = '#ef4444';
|
||
ctx.shadowBlur = 28;
|
||
ctx.strokeStyle = 'rgba(239,68,68,0.9)';
|
||
ctx.lineWidth = 4;
|
||
for (let i = 0; i < 3; i++) {
|
||
const r = 38 + i * 16 + Math.sin(t*5+i) * 5;
|
||
ctx.beginPath();
|
||
ctx.arc(f.x, f.y - 68, r, 0, Math.PI * 2);
|
||
ctx.stroke();
|
||
}
|
||
ctx.lineWidth = 5;
|
||
for (let i = 0; i < 4; i++) {
|
||
const off = -48 + i * 32 + Math.sin(t*6+i)*6;
|
||
ctx.beginPath();
|
||
ctx.moveTo(f.x + off, f.y - 140);
|
||
ctx.lineTo(f.x + off + 22, f.y - 40);
|
||
ctx.stroke();
|
||
}
|
||
ctx.font = 'bold 26px serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillStyle = 'rgba(239,68,68,0.85)';
|
||
ctx.fillText('🐻', f.x + Math.sin(t*2)*55, f.y - 138 + Math.cos(t*3)*10);
|
||
ctx.restore();
|
||
}
|
||
|
||
function drawMerkelEffect(f) {
|
||
const t = Date.now()/1000;
|
||
ctx.save();
|
||
ctx.globalAlpha = 0.86;
|
||
ctx.shadowColor = '#facc15';
|
||
ctx.shadowBlur = 30;
|
||
ctx.strokeStyle = 'rgba(250,204,21,0.95)';
|
||
ctx.lineWidth = 3;
|
||
const cx = f.x, cy = f.y - 82;
|
||
ctx.beginPath();
|
||
ctx.arc(cx, cy, 64 + Math.sin(t*4)*6, 0, Math.PI*2);
|
||
ctx.stroke();
|
||
ctx.font = 'bold 16px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillStyle = '#facc15';
|
||
for (let i = 0; i < 12; i++) {
|
||
const a = t*1.7 + i * Math.PI*2/12;
|
||
ctx.fillText('★', cx + Math.cos(a)*74, cy + Math.sin(a)*74);
|
||
}
|
||
ctx.lineWidth = 2;
|
||
for (let i = 0; i < 5; i++) {
|
||
const a = t*3 + i;
|
||
ctx.beginPath();
|
||
ctx.moveTo(cx + Math.cos(a)*35, cy + Math.sin(a)*35);
|
||
ctx.lineTo(cx + Math.cos(a+0.8)*95, cy + Math.sin(a+0.8)*95);
|
||
ctx.stroke();
|
||
}
|
||
ctx.font = 'bold 22px Arial';
|
||
ctx.fillText('€', cx + Math.sin(t*2)*48, cy - 86 + Math.cos(t*3)*8);
|
||
ctx.restore();
|
||
}
|
||
|
||
function drawSpecialBossEffects() {
|
||
if (!player1 || !player2 || fightResult) return;
|
||
[player1, player2].forEach(f => {
|
||
if (!f || !f.data || f.state === 'ko') return;
|
||
if (f.data.name === 'Władimir Putin') drawPutinEffect(f);
|
||
if (f.data.name === 'Angela Merkel') drawMerkelEffect(f);
|
||
});
|
||
}
|
||
|
||
// ===================== ROZPOCZĘCIE WALKI =====================
|
||
function startFight() {
|
||
gameState = 'FIGHT';
|
||
player1 = new Fighter(selectedPlayer, 250, 1);
|
||
player2 = new Fighter(selectedEnemy, W - 250, -1);
|
||
ai = new AIController(player2, player1);
|
||
roundTimer = 99;
|
||
roundTimerAccum = 0;
|
||
particles = [];
|
||
floatingTexts = [];
|
||
fightResult = '';
|
||
victoryTimer = 0;
|
||
victorySpeechDone = false;
|
||
shakeX = 0; shakeY = 0;
|
||
resetJP2();
|
||
const introBoss = getBossForIntro();
|
||
if (introBoss) startBossIntro(introBoss);
|
||
else resetBossIntro();
|
||
playMenuSelect();
|
||
ensureAudio();
|
||
if (!mazurekPlaying) playMazurek();
|
||
// Ogłoszenie
|
||
addFloatingText(W/2, H/2 - 50, 'WALKA!', '#fbbf24', 60);
|
||
const goodName = selectedPlayer.faction === 'good' ? selectedPlayer.name : selectedEnemy.name;
|
||
const badName = selectedPlayer.faction === 'bad' ? selectedPlayer.name : selectedEnemy.name;
|
||
addFloatingText(W/2, H/2 + 20, `${goodName} vs ${badName}`, '#fff', 22);
|
||
addFloatingText(W/2, H/2 + 50, 'Suwerenność kontra Kondominium!', '#fbbf24', 16);
|
||
announce('Walka! Suwerenność kontra Kondominium!');
|
||
}
|
||
|
||
// ===================== LOGIKA WALKI =====================
|
||
function updateFight() {
|
||
if (fightResult) {
|
||
victoryTimer++;
|
||
if (victoryTimer > 180 && (consumeKey('Enter') || consumeKey('Space'))) {
|
||
gameState = 'VICTORY';
|
||
victoryTimer = 0;
|
||
victorySpeechDone = false;
|
||
}
|
||
// Nadal aktualizuj cząsteczki
|
||
updateParticles();
|
||
// Screen shake wygasza się
|
||
shakeX *= 0.9; shakeY *= 0.9;
|
||
return;
|
||
}
|
||
|
||
// Intro bossa zatrzymuje rundę i sterowanie na kilka sekund.
|
||
if (bossIntro.active) {
|
||
updateBossIntro();
|
||
updateParticles();
|
||
shakeX *= 0.88; shakeY *= 0.88;
|
||
return;
|
||
}
|
||
|
||
// Timer rundy
|
||
roundTimerAccum++;
|
||
if (roundTimerAccum >= 60) {
|
||
roundTimerAccum = 0;
|
||
roundTimer--;
|
||
if (roundTimer <= 0) {
|
||
// Czas się skończył
|
||
if (player1.hp > player2.hp) endFight('P1WIN');
|
||
else if (player2.hp > player1.hp) endFight('P2WIN');
|
||
else endFight('DRAW');
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Sterowanie gracza (Player 1)
|
||
if (player1.state !== 'ko') {
|
||
const attackPressed = !!(justPressed['KeyQ'] || justPressed['KeyW'] || justPressed['KeyA'] || justPressed['KeyS'] || justPressed['KeyX']);
|
||
|
||
// Blok jest obsługiwany poza canAct(), żeby puszczenie Z zawsze odblokowywało postać.
|
||
if (keys['KeyZ']) {
|
||
player1.block();
|
||
if (attackPressed && player1.counterReady) {
|
||
player1.performCounter(player2);
|
||
justPressed['KeyQ']=justPressed['KeyW']=justPressed['KeyA']=justPressed['KeyS']=justPressed['KeyX']=false;
|
||
}
|
||
} else {
|
||
if (player1.state === 'block') player1.unblock();
|
||
if (attackPressed && player1.counterReady) {
|
||
player1.performCounter(player2);
|
||
justPressed['KeyQ']=justPressed['KeyW']=justPressed['KeyA']=justPressed['KeyS']=justPressed['KeyX']=false;
|
||
}
|
||
}
|
||
|
||
if (player1.canAct()) {
|
||
// Ruch
|
||
if (keys['ArrowLeft']) {
|
||
player1.vx -= 2.5 * (player1.data.spd / 8);
|
||
player1.state = 'walk';
|
||
} else if (keys['ArrowRight']) {
|
||
player1.vx += 2.5 * (player1.data.spd / 8);
|
||
player1.state = 'walk';
|
||
} else if (player1.state === 'walk' && player1.isGrounded) {
|
||
player1.state = 'idle';
|
||
}
|
||
|
||
// Skok
|
||
if (keys['ArrowUp'] && player1.isGrounded) {
|
||
player1.vy = -14;
|
||
player1.state = 'jump';
|
||
player1.isGrounded = false;
|
||
}
|
||
|
||
// Kucanie
|
||
if (keys['ArrowDown'] && player1.isGrounded) {
|
||
player1.state = 'crouch';
|
||
}
|
||
|
||
// Ataki (Q, W, A, S, X) — tylko gdy nie trzymasz bloku i nie wykonano countera
|
||
if (!keys['KeyZ'] && !player1.counterReady) {
|
||
if (justPressed['KeyQ']) { justPressed['KeyQ'] = false; player1.startAttack('lpunch'); }
|
||
else if (justPressed['KeyW']) { justPressed['KeyW'] = false; player1.startAttack('hpunch'); }
|
||
else if (justPressed['KeyA']) { justPressed['KeyA'] = false; player1.startAttack('lkick'); }
|
||
else if (justPressed['KeyS']) { justPressed['KeyS'] = false; player1.startAttack('hkick'); }
|
||
else if (justPressed['KeyX']) { justPressed['KeyX'] = false; player1.startAttack('special'); }
|
||
}
|
||
}
|
||
}
|
||
|
||
// Facing – gracze zwróceni do siebie
|
||
if (player1.x < player2.x) {
|
||
player1.facing = 1;
|
||
player2.facing = -1;
|
||
} else {
|
||
player1.facing = -1;
|
||
player2.facing = 1;
|
||
}
|
||
|
||
// AI steruje graczem 2
|
||
ai.update();
|
||
|
||
// Aktualizacja wojowników
|
||
player1.update();
|
||
player2.update();
|
||
|
||
// Kolizje ataków
|
||
checkAttackCollision(player1, player2);
|
||
checkAttackCollision(player2, player1);
|
||
|
||
// Kolizja ciał (push away)
|
||
const dx = player1.x - player2.x;
|
||
const dist = Math.abs(dx);
|
||
if (dist < 50) {
|
||
const push = (50 - dist) / 2;
|
||
const dir = dx > 0 ? 1 : -1;
|
||
player1.x += dir * push * 0.5;
|
||
player2.x -= dir * push * 0.5;
|
||
}
|
||
|
||
// Sprawdź KO
|
||
if (player1.state === 'ko' && player1.isGrounded && !fightResult) {
|
||
endFight('P2WIN');
|
||
}
|
||
if (player2.state === 'ko' && player2.isGrounded && !fightResult) {
|
||
endFight('P1WIN');
|
||
}
|
||
|
||
// Losowe wsparcie Jana Pawła II
|
||
updateJP2();
|
||
|
||
// Efekty
|
||
updateParticles();
|
||
shakeX *= 0.85;
|
||
shakeY *= 0.85;
|
||
}
|
||
|
||
function checkAttackCollision(attacker, defender) {
|
||
const atkBox = attacker.getAttackBox();
|
||
if (!atkBox) return;
|
||
const defBox = defender.getHitbox();
|
||
// AABB kolizja
|
||
if (atkBox.x < defBox.x + defBox.w && atkBox.x + atkBox.w > defBox.x &&
|
||
atkBox.y < defBox.y + defBox.h && atkBox.y + atkBox.h > defBox.y) {
|
||
attacker.attackHitThisMove = true;
|
||
let dmg = attacker.attackData.dmg;
|
||
// Combo bonus
|
||
if (attacker.combo > 1) dmg *= (1 + attacker.combo * 0.1);
|
||
defender.takeDamage(dmg, attacker);
|
||
}
|
||
}
|
||
|
||
function endFight(result) {
|
||
fightResult = result;
|
||
victoryTimer = 0;
|
||
stopMazurek();
|
||
playKOSound();
|
||
// Wielki napis
|
||
if (result === 'P1WIN') {
|
||
addFloatingText(W/2, H/2 - 60, 'K.O.!', '#fbbf24', 72);
|
||
const isGoodWin = selectedPlayer.faction === 'good';
|
||
const vTxt = isGoodWin ? VICTORY_GOOD[Math.floor(Math.random()*VICTORY_GOOD.length)]
|
||
: VICTORY_BAD[Math.floor(Math.random()*VICTORY_BAD.length)];
|
||
addFloatingText(W/2, H/2 + 10, selectedPlayer.name + ' WYGRYWA!', '#fff', 28);
|
||
addFloatingText(W/2, H/2 + 50, vTxt, '#fbbf24', 18);
|
||
if (isGoodWin) announce('Suwerenność zwycięża! Lokaj pokonany!');
|
||
else announce('Kondominium tymczasowo górą!');
|
||
} else if (result === 'P2WIN') {
|
||
addFloatingText(W/2, H/2 - 60, 'K.O.!', '#ef4444', 72);
|
||
const isGoodWin = selectedEnemy.faction === 'good';
|
||
const vTxt = isGoodWin ? VICTORY_GOOD[Math.floor(Math.random()*VICTORY_GOOD.length)]
|
||
: VICTORY_BAD[Math.floor(Math.random()*VICTORY_BAD.length)];
|
||
addFloatingText(W/2, H/2 + 10, selectedEnemy.name + ' WYGRYWA!', '#fff', 28);
|
||
addFloatingText(W/2, H/2 + 50, vTxt, '#fbbf24', 18);
|
||
} else {
|
||
addFloatingText(W/2, H/2, 'REMIS!', '#fbbf24', 60);
|
||
}
|
||
// Fatality check
|
||
const winner = result === 'P1WIN' ? player1 : (result === 'P2WIN' ? player2 : null);
|
||
if (winner && winner.data.fatality) {
|
||
setTimeout(() => {
|
||
addFloatingText(W/2, H/2 + 90, '💀 FATALITY: ' + winner.data.fatality + ' 💀', '#ef4444', 20);
|
||
}, 1500);
|
||
}
|
||
}
|
||
|
||
// ===================== RYSOWANIE WALKI =====================
|
||
function drawFight() {
|
||
ctx.save();
|
||
ctx.translate(shakeX, shakeY);
|
||
|
||
// Tło sejmowe
|
||
drawSejmBackground();
|
||
|
||
// Specjalne animacje bossów: Putin/Merkel
|
||
drawSpecialBossEffects();
|
||
|
||
// Losowe wsparcie Jana Pawła II
|
||
drawJP2();
|
||
|
||
// Wojownicy
|
||
player2.draw(ctx);
|
||
player1.draw(ctx);
|
||
|
||
// Cząsteczki i tekst
|
||
drawParticles();
|
||
|
||
// HUD (health bary, power, timer)
|
||
drawHUD();
|
||
|
||
// Napis "WALKA!" na początku
|
||
if (roundTimerAccum < 10 && roundTimer === 99 && !fightResult) {
|
||
const alpha = Math.max(0, 1 - roundTimerAccum / 10);
|
||
ctx.globalAlpha = alpha;
|
||
ctx.font = 'bold 60px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillStyle = '#fbbf24';
|
||
ctx.fillText('WALCZ!', W/2, H/2);
|
||
ctx.globalAlpha = 1;
|
||
}
|
||
|
||
// Wynik walki – kontynuacja
|
||
if (fightResult && victoryTimer > 120) {
|
||
const alpha = Math.min(1, (victoryTimer - 120) / 60);
|
||
ctx.globalAlpha = alpha * (Math.sin(Date.now()/300) * 0.3 + 0.7);
|
||
ctx.font = 'bold 18px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillStyle = '#fbbf24';
|
||
ctx.fillText('NACIŚNIJ ENTER ABY KONTYNUOWAĆ', W/2, H - 40);
|
||
ctx.globalAlpha = 1;
|
||
}
|
||
|
||
// Pełnoekranowe intro bossa zawsze na wierzchu.
|
||
drawBossIntro();
|
||
|
||
ctx.restore();
|
||
}
|
||
|
||
function drawHUD() {
|
||
const barW = 400;
|
||
const barH = 28;
|
||
const barY = 20;
|
||
|
||
// ===== HEALTH BAR GRACZA 1 (lewa strona) =====
|
||
const p1x = 30;
|
||
// Tło
|
||
ctx.fillStyle = '#1e1e2e';
|
||
ctx.fillRect(p1x, barY, barW, barH);
|
||
ctx.strokeStyle = player1.data.faction === 'good' ? '#3b82f6' : '#ef4444';
|
||
ctx.lineWidth = 2;
|
||
ctx.strokeRect(p1x, barY, barW, barH);
|
||
// Zdrowie
|
||
const hp1ratio = Math.max(0, player1.hp / player1.maxHp);
|
||
const hp1Color = player1.data.faction === 'good' ? '#3b82f6' : '#ef4444';
|
||
const hp1Grad = ctx.createLinearGradient(p1x, barY, p1x + barW * hp1ratio, barY);
|
||
hp1Grad.addColorStop(0, hp1Color);
|
||
hp1Grad.addColorStop(1, player1.data.faction === 'good' ? '#60a5fa' : '#f87171');
|
||
ctx.fillStyle = hp1Grad;
|
||
ctx.fillRect(p1x + 2, barY + 2, (barW - 4) * hp1ratio, barH - 4);
|
||
// Label
|
||
ctx.font = 'bold 12px Arial';
|
||
ctx.textAlign = 'left';
|
||
ctx.fillStyle = '#fff';
|
||
ctx.fillText(player1.data.faction === 'good' ? '★ SUWERENNOŚĆ ★' : '✗ KONDOMINIUM ✗', p1x + 5, barY + 18);
|
||
// HP number
|
||
ctx.textAlign = 'right';
|
||
ctx.fillText(`${Math.ceil(player1.hp)}/${player1.maxHp}`, p1x + barW - 5, barY + 18);
|
||
// Imię
|
||
ctx.font = 'bold 14px Arial';
|
||
ctx.textAlign = 'left';
|
||
ctx.fillStyle = '#fff';
|
||
ctx.fillText(player1.data.name, p1x, barY + barH + 16);
|
||
ctx.font = '11px Arial';
|
||
ctx.fillStyle = '#94a3b8';
|
||
ctx.fillText(`"${player1.data.nick}"`, p1x, barY + barH + 30);
|
||
|
||
// Power bar P1
|
||
const pwrY = barY + barH + 35;
|
||
ctx.fillStyle = '#1e1e2e';
|
||
ctx.fillRect(p1x, pwrY, barW * 0.6, 10);
|
||
ctx.strokeStyle = '#fbbf24';
|
||
ctx.lineWidth = 1;
|
||
ctx.strokeRect(p1x, pwrY, barW * 0.6, 10);
|
||
const pwr1ratio = player1.power / player1.maxPower;
|
||
ctx.fillStyle = pwr1ratio >= 1 ? '#fbbf24' : '#92400e';
|
||
ctx.fillRect(p1x + 1, pwrY + 1, (barW * 0.6 - 2) * pwr1ratio, 8);
|
||
ctx.font = '8px Arial';
|
||
ctx.fillStyle = '#fbbf24';
|
||
ctx.textAlign = 'left';
|
||
ctx.fillText(pwr1ratio >= 1 ? '★ SPECJALNY GOTOWY! ★' : 'MOC SPECJALNA', p1x + 5, pwrY + 8);
|
||
|
||
// Combo P1
|
||
if (player1.combo > 1) {
|
||
ctx.font = 'bold 18px Arial';
|
||
ctx.fillStyle = '#fbbf24';
|
||
ctx.textAlign = 'left';
|
||
ctx.fillText(`${player1.combo}x COMBO!`, p1x, pwrY + 28);
|
||
}
|
||
|
||
// ===== HEALTH BAR GRACZA 2 (prawa strona) =====
|
||
const p2x = W - 30 - barW;
|
||
ctx.fillStyle = '#1e1e2e';
|
||
ctx.fillRect(p2x, barY, barW, barH);
|
||
ctx.strokeStyle = player2.data.faction === 'good' ? '#3b82f6' : '#ef4444';
|
||
ctx.lineWidth = 2;
|
||
ctx.strokeRect(p2x, barY, barW, barH);
|
||
const hp2ratio = Math.max(0, player2.hp / player2.maxHp);
|
||
const hp2Color = player2.data.faction === 'good' ? '#3b82f6' : '#ef4444';
|
||
const hp2Grad = ctx.createLinearGradient(p2x + barW, barY, p2x + barW - barW * hp2ratio, barY);
|
||
hp2Grad.addColorStop(0, hp2Color);
|
||
hp2Grad.addColorStop(1, player2.data.faction === 'good' ? '#60a5fa' : '#f87171');
|
||
ctx.fillStyle = hp2Grad;
|
||
ctx.fillRect(p2x + 2 + (barW - 4) * (1 - hp2ratio), barY + 2, (barW - 4) * hp2ratio, barH - 4);
|
||
ctx.font = 'bold 12px Arial';
|
||
ctx.textAlign = 'right';
|
||
ctx.fillStyle = '#fff';
|
||
ctx.fillText(player2.data.faction === 'good' ? '★ SUWERENNOŚĆ ★' : '✗ KONDOMINIUM ✗', p2x + barW - 5, barY + 18);
|
||
ctx.textAlign = 'left';
|
||
ctx.fillText(`${Math.ceil(player2.hp)}/${player2.maxHp}`, p2x + 5, barY + 18);
|
||
ctx.font = 'bold 14px Arial';
|
||
ctx.textAlign = 'right';
|
||
ctx.fillStyle = '#fff';
|
||
ctx.fillText(player2.data.name, p2x + barW, barY + barH + 16);
|
||
ctx.font = '11px Arial';
|
||
ctx.fillStyle = '#94a3b8';
|
||
ctx.fillText(`"${player2.data.nick}"`, p2x + barW, barY + barH + 30);
|
||
|
||
// Power bar P2
|
||
ctx.fillStyle = '#1e1e2e';
|
||
ctx.fillRect(p2x + barW * 0.4, pwrY, barW * 0.6, 10);
|
||
ctx.strokeStyle = '#fbbf24';
|
||
ctx.lineWidth = 1;
|
||
ctx.strokeRect(p2x + barW * 0.4, pwrY, barW * 0.6, 10);
|
||
const pwr2ratio = player2.power / player2.maxPower;
|
||
ctx.fillStyle = pwr2ratio >= 1 ? '#fbbf24' : '#92400e';
|
||
ctx.fillRect(p2x + barW * 0.4 + 1, pwrY + 1, (barW * 0.6 - 2) * pwr2ratio, 8);
|
||
ctx.font = '8px Arial';
|
||
ctx.fillStyle = '#fbbf24';
|
||
ctx.textAlign = 'right';
|
||
ctx.fillText(pwr2ratio >= 1 ? '★ SPECJALNY GOTOWY! ★' : 'MOC SPECJALNA', p2x + barW - 5, pwrY + 8);
|
||
|
||
if (player2.combo > 1) {
|
||
ctx.font = 'bold 18px Arial';
|
||
ctx.fillStyle = '#ef4444';
|
||
ctx.textAlign = 'right';
|
||
ctx.fillText(`${player2.combo}x COMBO!`, p2x + barW, pwrY + 28);
|
||
}
|
||
|
||
// ===== TIMER (środek) =====
|
||
ctx.font = 'bold 40px Arial';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillStyle = roundTimer <= 10 ? '#ef4444' : '#fbbf24';
|
||
ctx.fillText(roundTimer.toString(), W/2, barY + 35);
|
||
ctx.font = '10px Arial';
|
||
ctx.fillStyle = '#94a3b8';
|
||
ctx.fillText('CZAS', W/2, barY + 48);
|
||
|
||
// Portrety pod timerem
|
||
// Portrait P1
|
||
const ptSize = 50;
|
||
const ptY = barY + 52;
|
||
drawMiniPortrait(W/2 - 70, ptY, ptSize, player1.data);
|
||
drawMiniPortrait(W/2 + 20, ptY, ptSize, player2.data);
|
||
ctx.font = 'bold 20px Arial';
|
||
ctx.fillStyle = '#fbbf24';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText('VS', W/2, ptY + 32);
|
||
|
||
// Propaganda u dołu ekranu walki
|
||
if (!fightResult) {
|
||
const propFight = PROPAGANDA[Math.floor(Date.now()/5000) % PROPAGANDA.length];
|
||
ctx.font = 'bold 11px Arial';
|
||
ctx.fillStyle = 'rgba(251,191,36,0.4)';
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText(propFight, W/2, H - 15);
|
||
}
|
||
}
|
||
|
||
function drawMiniPortrait(x, y, size, charData) {
|
||
ctx.fillStyle = charData.faction === 'good' ? '#1e3a8a' : '#7f1d1d';
|
||
ctx.fillRect(x, y, size, size);
|
||
ctx.strokeStyle = charData.faction === 'good' ? '#3b82f6' : '#ef4444';
|
||
ctx.lineWidth = 2;
|
||
ctx.strokeRect(x, y, size, size);
|
||
if (charData.photoLoaded && charData.photo) {
|
||
ctx.drawImage(charData.photo, x + 2, y + 2, size - 4, size - 4);
|
||
} else {
|
||
ctx.fillStyle = '#fff';
|
||
ctx.font = `bold ${size/3}px Arial`;
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'middle';
|
||
ctx.fillText(charData.initials, x + size/2, y + size/2);
|
||
ctx.textBaseline = 'alphabetic';
|
||
}
|
||
}
|
||
|
||
|
||
function buildVictorySpeech() {
|
||
if (fightResult === 'DRAW') return 'Remis! Walka zakończona bez zwycięzcy.';
|
||
const winner = fightResult === 'P1WIN' ? selectedPlayer : selectedEnemy;
|
||
if (!winner) return '';
|
||
const isGoodWin = winner.faction === 'good';
|
||
const lines = [];
|
||
lines.push('Zwycięstwo!');
|
||
lines.push(`${winner.name}, pseudonim ${winner.nick}.`);
|
||
(isGoodWin ? VICTORY_GOOD : VICTORY_BAD).forEach(t => lines.push(t));
|
||
if (winner.fatality) lines.push(`Fatality: ${winner.fatality}.`);
|
||
lines.push(isGoodWin ? 'Polska suwerenna zwyciężyła. Kondominium w odwrocie.' : 'Ciemna noc nad Polską, ale patrioci nigdy się nie poddają.');
|
||
return lines.join(' ');
|
||
}
|
||
|
||
function announceVictoryScreenOnce() {
|
||
if (victorySpeechDone) return;
|
||
victorySpeechDone = true;
|
||
announce(buildVictorySpeech());
|
||
}
|
||
|
||
// ===================== EKRAN ZWYCIĘSTWA =====================
|
||
function drawVictory() {
|
||
ctx.fillStyle = '#0a0a1a';
|
||
ctx.fillRect(0, 0, W, H);
|
||
|
||
victoryTimer++;
|
||
const alpha = Math.min(1, victoryTimer / 60);
|
||
ctx.globalAlpha = alpha;
|
||
|
||
// Tło z efektami
|
||
const winner = fightResult === 'P1WIN' ? selectedPlayer : selectedEnemy;
|
||
const isGoodWin = winner && winner.faction === 'good';
|
||
if (victoryTimer === 1) announceVictoryScreenOnce();
|
||
|
||
// Animowane promienie
|
||
const t = Date.now() / 1000;
|
||
ctx.save();
|
||
ctx.translate(W/2, H/2);
|
||
for (let i = 0; i < 12; i++) {
|
||
ctx.rotate(Math.PI / 6);
|
||
ctx.fillStyle = isGoodWin ? `rgba(59,130,246,${0.05 + Math.sin(t + i)*0.03})` : `rgba(239,68,68,${0.05 + Math.sin(t + i)*0.03})`;
|
||
ctx.beginPath();
|
||
ctx.moveTo(0, 0);
|
||
ctx.lineTo(-60, -500);
|
||
ctx.lineTo(60, -500);
|
||
ctx.fill();
|
||
}
|
||
ctx.restore();
|
||
|
||
// Tytuł
|
||
ctx.textAlign = 'center';
|
||
if (fightResult === 'DRAW') {
|
||
ctx.font = 'bold 60px Arial';
|
||
ctx.fillStyle = '#fbbf24';
|
||
ctx.fillText('REMIS!', W/2, 150);
|
||
} else {
|
||
ctx.font = 'bold 48px Arial';
|
||
ctx.fillStyle = isGoodWin ? '#3b82f6' : '#ef4444';
|
||
ctx.fillText('ZWYCIĘSTWO!', W/2, 120);
|
||
|
||
// Imię zwycięzcy
|
||
ctx.font = 'bold 36px Arial';
|
||
ctx.fillStyle = '#fbbf24';
|
||
ctx.fillText(winner.name, W/2, 180);
|
||
|
||
ctx.font = 'bold 22px Arial';
|
||
ctx.fillStyle = '#fff';
|
||
ctx.fillText(`"${winner.nick}"`, W/2, 215);
|
||
|
||
// Portret zwycięzcy
|
||
drawMiniPortrait(W/2 - 60, 240, 120, winner);
|
||
|
||
// Propaganda
|
||
const vTexts = isGoodWin ? VICTORY_GOOD : VICTORY_BAD;
|
||
ctx.font = 'bold 20px Arial';
|
||
ctx.fillStyle = '#fbbf24';
|
||
for (let i = 0; i < vTexts.length; i++) {
|
||
const yy = 400 + i * 30;
|
||
const vis = Math.min(1, Math.max(0, (victoryTimer - 60 - i * 20) / 30));
|
||
ctx.globalAlpha = vis * alpha;
|
||
ctx.fillText(vTexts[i], W/2, yy);
|
||
}
|
||
ctx.globalAlpha = alpha;
|
||
|
||
// Fatality
|
||
if (winner.fatality && victoryTimer > 120) {
|
||
ctx.font = 'bold 24px Arial';
|
||
ctx.fillStyle = '#ef4444';
|
||
const fat = Math.sin(Date.now()/200) * 0.3 + 0.7;
|
||
ctx.globalAlpha = fat * alpha;
|
||
ctx.fillText(`💀 FATALITY: ${winner.fatality} 💀`, W/2, 560);
|
||
ctx.globalAlpha = alpha;
|
||
}
|
||
|
||
// Suwerenna deklaracja
|
||
if (isGoodWin) {
|
||
ctx.font = 'bold 16px Arial';
|
||
ctx.fillStyle = '#60a5fa';
|
||
ctx.fillText('🇵🇱 POLSKA SUWERENNA ZWYCIĘŻYŁA! KONDOMINIUM W ODWROCIE! 🇵🇱', W/2, 600);
|
||
} else {
|
||
ctx.font = 'bold 16px Arial';
|
||
ctx.fillStyle = '#f87171';
|
||
ctx.fillText('Ciemna noc nad Polską... ale patrioci nigdy się nie poddają!', W/2, 600);
|
||
}
|
||
}
|
||
|
||
// Kontynuacja
|
||
if (victoryTimer > 90) {
|
||
const sp = Math.sin(Date.now()/300) * 0.4 + 0.6;
|
||
ctx.globalAlpha = sp;
|
||
ctx.font = 'bold 18px Arial';
|
||
ctx.fillStyle = '#fff';
|
||
ctx.fillText('ENTER = nowa walka | ESC = menu główne', W/2, 670);
|
||
}
|
||
ctx.globalAlpha = 1;
|
||
}
|
||
|
||
function updateVictory() {
|
||
if (consumeKey('Enter') || consumeKey('Space')) {
|
||
gameState = 'SELECT';
|
||
selectedPlayer = null;
|
||
selectedEnemy = null;
|
||
selectCursor = {col:0, row:0, side:0};
|
||
enterCooldown = 15;
|
||
stopMazurek();
|
||
}
|
||
if (consumeKey('Escape')) {
|
||
gameState = 'MENU';
|
||
stopMazurek();
|
||
}
|
||
}
|
||
|
||
// ===================== PAUZA =====================
|
||
function drawPause() {
|
||
// Rysuj walkę w tle
|
||
drawFight();
|
||
// Overlay
|
||
ctx.fillStyle = 'rgba(0,0,0,0.7)';
|
||
ctx.fillRect(0, 0, W, H);
|
||
// Tekst
|
||
ctx.textAlign = 'center';
|
||
ctx.font = 'bold 48px Arial';
|
||
ctx.fillStyle = '#fbbf24';
|
||
ctx.fillText('PAUZA', W/2, H/2 - 30);
|
||
ctx.font = '20px Arial';
|
||
ctx.fillStyle = '#fff';
|
||
ctx.fillText('P = kontynuuj | ESC = menu główne', W/2, H/2 + 20);
|
||
// Propaganda
|
||
ctx.font = 'bold 14px Arial';
|
||
ctx.fillStyle = '#fbbf24';
|
||
const prop = PROPAGANDA[Math.floor(Date.now()/2000) % PROPAGANDA.length];
|
||
ctx.fillText(prop, W/2, H/2 + 60);
|
||
// Sterowanie
|
||
ctx.font = '14px Arial';
|
||
ctx.fillStyle = '#94a3b8';
|
||
ctx.fillText('Strzałki = ruch | Q/W = ciosy | A/S = kopnięcia | Z = blok | X = specjalny', W/2, H/2 + 100);
|
||
}
|
||
|
||
// ===================== GŁÓWNA PĘTLA GRY =====================
|
||
let lastTime = performance.now();
|
||
|
||
function gameLoop(now) {
|
||
const dt = Math.min((now - lastTime) / 16.67, 3);
|
||
lastTime = now;
|
||
|
||
// Globalna obsługa klawiszy – pauza i wyjście
|
||
if (gameState === 'FIGHT' && !fightResult) {
|
||
if (consumeKey('KeyP')) {
|
||
gameState = 'PAUSE';
|
||
} else if (consumeKey('Escape')) {
|
||
gameState = 'MENU';
|
||
stopMazurek();
|
||
}
|
||
} else if (gameState === 'PAUSE') {
|
||
const escP = justPressed['Escape'];
|
||
const pP = justPressed['KeyP'];
|
||
consumeKey('Escape');
|
||
consumeKey('KeyP');
|
||
if (escP) {
|
||
gameState = 'MENU';
|
||
stopMazurek();
|
||
} else if (pP) {
|
||
gameState = 'FIGHT';
|
||
}
|
||
}
|
||
|
||
// Update
|
||
switch (gameState) {
|
||
case 'MENU': updateMenu(); break;
|
||
case 'SELECT': updateSelect(); break;
|
||
case 'FIGHT': updateFight(); break;
|
||
case 'VICTORY': updateVictory(); break;
|
||
}
|
||
|
||
// Draw
|
||
ctx.clearRect(0, 0, W, H);
|
||
switch (gameState) {
|
||
case 'MENU': drawMenu(); break;
|
||
case 'SELECT': drawSelect(); break;
|
||
case 'FIGHT': drawFight(); break;
|
||
case 'VICTORY': drawVictory(); break;
|
||
case 'PAUSE': drawPause(); break;
|
||
}
|
||
|
||
// Wyczyść justPressed na koniec klatki
|
||
Object.keys(justPressed).forEach(k => justPressed[k] = false);
|
||
|
||
requestAnimationFrame(gameLoop);
|
||
}
|
||
|
||
// ===================== SKALOWANIE CANVAS =====================
|
||
function resizeCanvas() {
|
||
const ratio = W / H;
|
||
let cw = window.innerWidth;
|
||
let ch = window.innerHeight;
|
||
if (cw / ch > ratio) {
|
||
cw = ch * ratio;
|
||
} else {
|
||
ch = cw / ratio;
|
||
}
|
||
canvas.style.width = cw + 'px';
|
||
canvas.style.height = ch + 'px';
|
||
}
|
||
window.addEventListener('resize', resizeCanvas);
|
||
resizeCanvas();
|
||
|
||
// ===================== START GRY =====================
|
||
console.log('%c SEJM KOMBAT: Suwerenność vs Kondominium! ', 'background:#1e3a8a;color:#fbbf24;font-size:20px;font-weight:bold;');
|
||
console.log('%c Polska suwerenna kontra lokaje Brukseli i Moskwy! ', 'background:#dc2626;color:#fff;font-size:14px;');
|
||
requestAnimationFrame(gameLoop);
|
||
</script>
|
||
</body>
|
||
</html>
|