Asynchroniczność, problem z odpowiednim sprawdzeniem flagi

0

Mam pewniej problem z listenerem i funkcjami zawartymi w niej.
Spróbuje wyjaśnić problem jak najlepiej potrafię.
Przechwytywanie.PNG
Mamy tutaj normal attack i strong attack, Bohater jest generowany dynamicznie po wybraniu jednego z nich więc cały html dotyczący postaci nie jest dodawany po załadowaniu strony.

if (selectedHero) {
    const playerHtml = `
      <div class="player-block hero-img" data-hero-id="${selectedHero.id}" style="background-image: url('${selectedHero.img}')">
        <p>${selectedHero.name}</p>
        <div>
          <p class="hp-amount">HP: ${selectedHero.hp}</p>
          <p class="mp-amount">MP: ${selectedHero.mp}</p>
        </div>
        <div class="ability-buttons">
          <div class="ability-section">
            <div class="focus-hover-placement focus-placement">
             ${focusHTML}
             <p class="hidden-info">Focus your mind to regain mana MP + 25</p>
            </div>
          </div>
          <div class="ability-section skills-ability-section">
            <button class="all-buttons js-skills-button">Skills</button>
            <div class="new-skills-button hidden js-new-skills-button">${skillsHTML}</div>
          </div>
          <div class="ability-section magic-ability-section">
            <button class="all-buttons js-magic-button">Magic</button>
            <div class="new-magic-button hidden js-new-magic-button">${magicHTML}</div>
          </div>
        </div>
      </div>
    `;
    playerSection.insertAdjacentHTML("beforeend", playerHtml);
    if (currentPlayerIndex < Object.keys(playerNumber).length) {
      playerNumber[`player${currentPlayerIndex + 1}`] = selectedHero;
      currentPlayerIndex++;
    } else {
      console.log("Wszyscy gracze są już zajęci!");
    }

    const skillsButton = playerSection.querySelector(
      `.player-block[data-hero-id="${selectedHero.id}"] .skills-ability-section`
    );
    const addSkillsList = playerSection.querySelector(
      `.player-block[data-hero-id="${selectedHero.id}"] .js-new-skills-button`
    );
    const magicButton = playerSection.querySelector(
      `.player-block[data-hero-id="${selectedHero.id}"] .magic-ability-section`
    );
    const addMagicList = playerSection.querySelector(
      `.player-block[data-hero-id="${selectedHero.id}"] .js-new-magic-button`
    );
    const addfocusButton = playerSection.querySelector(
      `.player-block[data-hero-id="${selectedHero.id}"] .focus-placement`
    );

    skillsButton.addEventListener("click", () => {
      addSkillsList.classList.toggle("hidden");
      if (!addMagicList.classList.contains("hidden")) {
        addMagicList.classList.add("hidden");
      }
    });

    magicButton.addEventListener("click", () => {
      addMagicList.classList.toggle("hidden");
      if (!addSkillsList.classList.contains("hidden")) {
        addSkillsList.classList.add("hidden");
      }
    });

kod jest oczywiście częściowy
Teraz tak, klikając którykolwiek z umiejętności pojawia się wybór celu, czyli funkcja zadająca obrażenia czeka na wybranie celu.
Problem jest taki że gdy wybiorę normal attack, nie wybiorę celu, kliknę strong attack to po wybraniu celu wykonuje się normal attack. Nie dawno miałem problem z tym że listenery się nakładały więc kliknięcie normal attack, po czym strong attack skutkowało nałożeniem się efektów co sprawiło że zadane obrażenia sumowały się z obu ataków. Postawiłem flage sprawdzającą, teraz efekty się nie nakładają za to nie wykonuję ostatnio wybranego skilla.
podam funkcje zajmujące się tym wszystkim, aktualnie pracuję tylko nad normal i strong attack więc reszty nie bierzcie pod uwagę.
To jest event listener:

addSkillsList.addEventListener("click", (event) => {
      console.log("reakcja");
      if (event.target.classList.contains("warrior-normal-attack")) {
        if (currentAttack !== "warrior-normal") {
          currentAttack = "warrior-normal";
          checkAttack = "warrior-normal";
        }
        // checkAttack = "warrior-normal";
        // currentAttack = "warrior-normal";
        handleAbilityUse(event, "warrior-normal-attack");
      } else if (event.target.classList.contains("warrior-strong-attack")) {
        if (currentAttack !== "warrior-strong") {
          currentAttack = "warrior-strong";
          checkAttack = "warrior-strong";
        }
        // checkAttack = "warrior-strong";
        // currentAttack = "warrior-strong";
        handleAbilityUse(event, "warrior-strong-attack");
      } else if (event.target.classList.contains("spear-stab")) {
        checkAttack = "spear-stab";
        currentAttack = "spear-stab";
        if (currentAttack === "spear-stab") {
          handleAbilityUse(event, "spear-stab");
        }
      } else if (event.target.classList.contains("enrage")) {
        checkAttack = "enrage";
        currentAttack = "enrage";
        if (currentAttack === "enrage") {
          handleAbilityUse(event, "enrage");
        }
      } else if (event.target.classList.contains("leap")) {
        checkAttack = "leap";
        currentAttack = "leap";
        if (currentAttack === "leap") {
          handleAbilityUse(event, "leap");
        }
      } else if (event.target.classList.contains("brutal-takedown")) {
        checkAttack = "brutal-takedown";
        currentAttack = "brutal-takedown";
        if (currentAttack === "brutal-takedown") {
          handleAbilityUse(event, "brutal-takedown");
        }
      }
    });

Teraz funkcja handleAbility, zawiera tylko to co posiada Warrior więc jest ucięta

async function handleAbilityUse(event, abilityName) {
      switch (abilityName) {
        //warrior
        case "warrior-normal-attack":
          createAbilityManaPlus(
            event,
            "warrior-normal-attack",
            15,
            20,
            "normal attack",
            1.7,
            null,
            10
          );
          break;
        case "warrior-strong-attack":
          createAbilityManaPlus(
            event,
            "warrior-strong-attack",
            20,
            30,
            "strong attack",
            1.7,
            null,
            5
          );
          break;
        case "spear-stab":
          createAbilitySkill(
            event,
            "spear-stab",
            30,
            45,
            "spear stab",
            8,
            1.7,
            null
          );
          break;
        case "enrage":
          createAbilitySkill(event, "enrage", 50, 70, "enrage", 15, 1.7, null);
          break;
        case "leap":
          createAbilitySkill(event, "leap", 70, 100, "leap", 22, 1.7, null);
          break;
        case "brutal-takedown":
          createAbilitySkill(
            event,
            "brutal-takedown",
            150,
            300,
            "brutal takedown",
            40,
            1.7,
            null
          );

Tutaj funkcja zajmująca się atakiem:

async function createAbilityManaPlus(
      event,
      selector,
      minDamage,
      maxDamage,
      infoDisplay,
      buffRatio,
      buffEnhance,
      mp
    ) {
      if (event.target.classList.contains(selector)) {
        await selectTarget();
        console.log(checkAttack, "checkAttack w createA przed sprawdzeniem");
        console.log(
          currentAttack,
          "currentAttack w createA przed sprawdzeniem"
        );
        if (checkAttack !== currentAttack) {
          console.log(
            checkAttack,
            "check attack w createA po sprawdzeniu, ",
            currentAttack,
            "currentAttack w createA po sprawdzeniu, ",
            "atak sie zmienił, anulowanie w createAbility, "
          );
          return;
        }
        //
        if (!selectedTarget) {
          return;
        }

        let targetElement = selectMonster(selectedTarget);
        let playerDmg = calculateDmg(minDamage, maxDamage);
        if (buffRatio === null) {
        } else if (buffStats === true) {
          playerDmg = Math.round((playerDmg *= buffRatio));
          buffStats = false;
        }
        if (buffEnhance === null) {
        } else if (buffEnhance === true) {
          playerDmg = Math.round((playerDmg *= enhanceBuff));
          buffEnhance = false;
        }

        reduceHp(selectedTarget, targetElement, playerDmg);
        const playerBlock = event.target.closest(".player-block");
        const heroId = playerBlock.getAttribute("data-hero-id");
        const heroMPElement = playerBlock.querySelector(".mp-amount");
        const hero = heroes.find((hero) => hero.id === heroId);
        gainMana(mp, hero, heroMPElement);
        showInfo(playerDmg, hero, infoDisplay);

        if (checkAttack === currentAttack) {
          checkAttack = null;
        }
        hideSelectTarget();
      }
    }

Tutaj funkcja oczekująca wybranie celu:

async function selectTarget() {
  return new Promise((resolve) => {
    const targetButtons = document.querySelectorAll(".target");
    console.log(checkAttack, "checkAttack przed sprawdzeniem w selectTarget, ");
    console.log(
      currentAttack,
      "currentAttak przed sprawdzeniem w selectTarget, "
    );
    targetButtons.forEach((button) => {
      showSelectTarget();
      button.addEventListener("click", (event) => {
        if (checkAttack !== currentAttack) {
          console.log(
            checkAttack,
            "check attack w sprawdzeniu, ",
            currentAttack,
            "current attack w sprawdzeniu, ",
            "atak się zmienił, anulowanie ataku w selectedTarget, "
          );
          resolve(null);
          return;
        }
        selectedTarget = event.target.getAttribute("data-target");
        resolve(selectedTarget);
      });
    });
  });
}

Wyjaśnienie flagi: checkAttack i currentAttack to flagi sprawdzające żeby nie można było nałożyć efektów klikając tego samego skilla kilkukrotnie i by nie nakładać efektów klikając różne skille typu klik 3x normal attack skutkujące wykonanie listenera 3 razy bądz kliknięcie wszystkich umiejętności i wykonanie się wszystkich na raz po wybraniu celu.

Sprawdzając console.log checkAttack (hA) i currentAttack (cA) w róznych miejscach zauważyłem że np. po kliknięciu normal attack, hA i cA są prawidłowe, po wybraniu celu i wykonaniu całej logiki hA i cA są prawidłowe. Teraz po kliknięciu normal attack hA i cA są prawidłowe (warrior-normal), po wybraniu strong attack hA i cA jest prawidłowe (warrior-strong), po wybraniu celu checkAttack jest null a currentAttack jest prawidłowy (warrior-strong). Skutkuje to wykonanie normal attack a to jego powinno anulować a strong attack jest blokowany.
w skrócie po wybraniu celu czyli wykonaniu funkcji selectTarget i createAbility wartość checkAttack jest null na skillu który aktualnie się wybrało.
console logi:
Przechwytywanie2.PNG

0

Facet zamęczysz się, daj jakigoś diva z z-Index wysokim ponad wszystkim i tam sobie wyświetlaj.

1

addEventListener ma opcję once, której nie używasz https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

Problem jest taki że gdy wybiorę normal attack, nie wybiorę celu, kliknę strong attack to po wybraniu celu wykonuje się normal attack.

Nie analizowałem dogłębnie, ale być może dlatego, że handler odpalony podczas kliknięcia normal attacku wciąż czeka na await selectTarget();.

Być może potrzebujesz metody, żeby jakoś anulować. Jednak ty już sprawdzasz if (checkAttack !== currentAttack) { więc to niby nie powinno dalej przechodzić.

No i użyłeś w selectTarget ciekawego patternu z zamianą addEventListener na await - sam go używam i dziwię się, że tak mało ludzi go używa, bo tak można uprościć zaawansowane interakcje. Problem w tym, że nie jest out of the box i w zasadzie do tego warto by napisać bibliotekę odpowiednią (tj. wystarczyłoby kilka funkcji, które ułatwiają zarządzanie tym - ja coś tam pisałem i może ogarnę to, żeby z mojego kodu mógłbym korzystać nie tylko ja). Bo wtedy po prostu byś awaity robił odpowiednie.

0
LukeJL napisał(a):

addEventListener ma opcję once, której nie używasz https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

Problem jest taki że gdy wybiorę normal attack, nie wybiorę celu, kliknę strong attack to po wybraniu celu wykonuje się normal attack.

Nie analizowałem dogłębnie, ale być może dlatego, że handler odpalony podczas kliknięcia normal attacku wciąż czeka na await selectTarget();.

Być może potrzebujesz metody, żeby jakoś anulować. Jednak ty już sprawdzasz if (checkAttack !== currentAttack) { więc to niby nie powinno dalej przechodzić.

No i użyłeś w selectTarget ciekawego patternu z zamianą addEventListener na await - sam go używam i dziwię się, że tak mało ludzi go używa, bo tak można uprościć zaawansowane interakcje. Problem w tym, że nie jest out of the box i w zasadzie do tego warto by napisać bibliotekę odpowiednią (tj. wystarczyłoby kilka funkcji, które ułatwiają zarządzanie tym - ja coś tam pisałem i może ogarnę to, żeby z mojego kodu mógłbym korzystać nie tylko ja). Bo wtedy po prostu byś awaity robił odpowiednie.

Użyłem once tak jak napisałeś i skutkowało tym że po użyciu np normal attack, wybraniu celu i ponownym kliknięciu normal attack nie pojawiał się wybór celu.
Tak mam sprawdzenie i sam się dziwie czemu to reaguje na odwrót, dlatego proszę tu o pomoc. Nie rozumiem o co Ci chodzi z "out of the box"
Właśnie tak mi się wydaje że cały czas czeka na selectTarget już po wybraniu innej umiejętności ale po wybraniu np strong attack to obie zmienne sprawdzające są ustawiane pod strong attack.
wychodziło by na to że po kliknięciu normal attack, po czym kliknięciu strong attack, oczekiwanie selectTarget dalej działa na normal attack i ma priorytet więc zmienne zajmujące się sprawdzaniem tego (które w tym momencie są warrior-strong), są zmieniane z powrotem na normal-warrior co skutkuje tym problemem. Mogło by tak być?

1
targetButtons.forEach((button) => {
      showSelectTarget();
      button.addEventListener("click", (event) => {

zauważ, że przypisujesz do każdego buttona handler i nawet jak dasz once, to po kliknięciu przycisku tylko jeden handler się skasuje, a reszta handlerów będzie czekać. Tutaj lepiej byłoby użyć delegacji i podczepić się raz pod element wyżej (pod parenta tych przycisków), a nie pod każdy przycisk osobno.

Nie rozumiem o co Ci chodzi z "out of the box"

Chodzi mi o to, że teraz robisz wszystko ręcznie, podpinasz addEventListenery doraźnie na potrzeby jednej interakcji, przez co potem masz bałagan. Gołe addEventListenery są spoko, jeśli człowiek je ustawi raz i już. Ale jeśli to ma być dynamicznie, tutaj dokładamy addEventListener na potrzeby jednej interakcji itp. to potem ciężko nad tym panować, bo możesz kilka razy ustawić itp.

Można tego uniknąć i zrobić bardziej centralny system zarządzania eventami i zamienić je na promisy (podobnie do tego, co zrobiłeś w selectTarget) i potem robić wszędzie await i np. coś takiego robić (w dużym uproszczeniu)

async function interaction() {
  while (true) {
    const attackKind = await selectAttackKind(); // user kliknął w przycisk
    const target = (await once('click', '.target')) // i w kolejny
        .target.getAttribute("data-target");
    // możemy atakować
  }
}

przy czym to nie zadziała out of the box, tylko trzeba by samemu albo napisać doraźnie funkcje pomocnicze (takie jak funkcja once w kodzie powyżej) albo skorzystać z biblioteki (nie wiem, czy jest jakaś poza tym, co ja sam kombinuję. Tam musiałem np. myśleć nad sposobem anulowania takich promisów).

0
LukeJL napisał(a):
targetButtons.forEach((button) => {
      showSelectTarget();
      button.addEventListener("click", (event) => {

zauważ, że przypisujesz do każdego buttona handler i nawet jak dasz once, to po kliknięciu przycisku tylko jeden handler się skasuje, a reszta handlerów będzie czekać. Tutaj lepiej byłoby użyć delegacji i podczepić się raz pod element wyżej (pod parenta tych przycisków), a nie pod każdy przycisk osobno.

Nie rozumiem o co Ci chodzi z "out of the box"

Chodzi mi o to, że teraz robisz wszystko ręcznie, podpinasz addEventListenery doraźnie na potrzeby jednej interakcji, przez co potem masz bałagan. Gołe addEventListenery są spoko, jeśli człowiek je ustawi raz i już. Ale jeśli to ma być dynamicznie, tutaj dokładamy addEventListener na potrzeby jednej interakcji itp. to potem ciężko nad tym panować, bo możesz kilka razy ustawić itp.

Można tego uniknąć i zrobić bardziej centralny system zarządzania eventami i zamienić je na promisy (podobnie do tego, co zrobiłeś w selectTarget) i potem robić wszędzie await i np. coś takiego robić (w dużym uproszczeniu)

async function interaction() {
  while (true) {
    const attackKind = await selectAttackKind(); // user kliknął w przycisk
    const target = await once('click', '.target') // i w kolejny
        .target.getAttribute("data-target");
    // możemy atakować
  }
}

przy czym to nie zadziała out of the box, tylko trzeba by samemu albo napisać doraźnie funkcje pomocnicze (takie jak funkcja once w kodzie powyżej) albo skorzystać z biblioteki (nie wiem, czy jest jakaś poza tym, co ja sam kombinuję. Tam musiałem np. myśleć nad sposobem anulowania takich promisów).

Próbuję coś wymyśleć z tego przykładu co podałeś ale to raczej za trudne dla mnie, nie ogarniam kompletnie i nie wiem jak to złożyć. Dla mnie za wysokie progi najwidoczniej,Chyba będę musiał porzucić tworzenie tej gry bo brakuje mi wiedzy. No po prostu nie jestem w stanie nad tym zapanować i tworze jakieś dziwactwa które sprawia że działa jeszcze gorzej 😀 ,
coś takiego wytworzyłem teraz ale i tak nie działa: 😅

async function selectTarget() {
  return new Promise(async (resolve) => {
    const targetButtons = document.querySelectorAll(".target");
    console.log(checkAttack, "checkAttack przed sprawdzeniem w selectTarget");
    console.log(currentAttack, "currentAttack przed sprawdzeniem w selectTarget");
    showSelectTarget();
    
    const targetEvent = await once('click');  
    const selectedTarget = targetEvent.target.getAttribute("data-target");

    console.log(checkAttack, 'check po wyborze celu');
    console.log(currentAttack, 'current po wyborze celu');

    resolve(selectedTarget);
  });
}

function once(eventName) {
    return new Promise((resolve) => {
      const element = document.querySelector('.target');
      const listener = (event) => {
        element.removeEventListener(eventName, listener);
        resolve(event);  
      };
      element.addEventListener(eventName, listener);
    });
  }
  async function interaction() {
    while (true) {
      const attackKind = await selectAttackKind();  
      console.log("Wybrano atak:", attackKind);
   
      const target = await selectTarget();
      console.log("Wybrano cel:", target);
      
      await executeAttack(attackKind, target);
    }
  }
  
  async function selectAttackKind() {
    return new Promise((resolve) => {
      const attackButtons = document.querySelectorAll(".js-skills");  
      attackButtons.forEach((button) => {
        button.addEventListener("click", async (event) => {
          const abilityName = event.target.getAttribute("data-skill");
          resolve(abilityName);  
        });
      });
    });
  }
  
  async function executeAttack(attackName) {
    switch (attackName) {
      case "warrior-normal-attack":
        
        await createAbilityManaPlus(
          null,  
          "warrior-normal-attack",
          15,
          20,
          "normal attack",
          1.7,
          null,
          10
        );
        break;
      case "warrior-strong-attack":
        
        await createAbilityManaPlus(
          null,  
          "warrior-strong-attack",
          20,
          30,
          "strong attack",
          1.7,
          null,
          5
        );
        break;
      
      default:
        console.log("Nieobsługiwany atak:", attackName);
    }
  }
0
async function selectTarget() {
  return new Promise(async (resolve) => {
    const targetButtons = document.querySelectorAll(".target");
    console.log(checkAttack, "checkAttack przed sprawdzeniem w selectTarget");
    console.log(currentAttack, "currentAttack przed sprawdzeniem w selectTarget");
    showSelectTarget();
    
    const targetEvent = await once('click');  
    const selectedTarget = targetEvent.target.getAttribute("data-target");

    console.log(checkAttack, 'check po wyborze celu');
    console.log(currentAttack, 'current po wyborze celu');

    resolve(selectedTarget);
  });
}

Tu taka uwaga. Funkcje asynchroniczne zwracają promis (cokolwiek zwrócisz, będzie opakowane w promis). Więc tu wystarczyłoby:

async function selectTarget() {
    const targetButtons = document.querySelectorAll(".target");
    console.log(checkAttack, "checkAttack przed sprawdzeniem w selectTarget");
    console.log(currentAttack, "currentAttack przed sprawdzeniem w selectTarget");
    showSelectTarget();
     
    const targetEvent = await once('click');  
    const selectedTarget = targetEvent.target.getAttribute("data-target");
 
    return selectedTarget;
}

no ale myślę, że jeśli coś nie działa, to z innego powodu. No i new Promise dalej się przydaje tam, gdzie zamieniasz callbacki na promise.
co dokładnie robi funkcja showSelectTarget?

i tak nie działa: 😅

Co dokładnie nie dziala?

0

showSelectTarget jedynie pokazuje przyciski do wybrania celu, mam też funkcje która chowa przyciski

dopiero teraz miałem czas usiąść, nie działa dlatego że funkcja która służy do obliczania obrażeń wychodzi undefined, nie miałem też czasu do tego usiąść i sprawdzać dlaczego. To i tak mnie dziwi dlaczego wyszło undefined, jestem w momencie gdzie nie bardzo rozumiem co się już dzieje 😅
funkcja ta która wychodzi undefined to jest createAbilityManaPlus z pierwszego posta tego tematu.

może mi się uda dziś coś z tym zrobić to edytuje tego posta

Teraz wygląda to tak:

async function createAbilityManaPlus(
      event,
      selector,
      minDamage,
      maxDamage,
      infoDisplay,
      buffRatio,
      buffEnhance,
      mp,
      abilityName,
    ) {
      if (event.target.classList.contains(selector)) {
        await selectTarget();
        console.log(checkAttack, "checkAttack w createA przed sprawdzeniem");
        console.log(
          currentAttack,
          "currentAttack w createA przed sprawdzeniem"
        );
        if (ifAttackChanged()) {
          console.log(
            checkAttack,
            "check attack w createA po sprawdzeniu, ",
            currentAttack,
            "currentAttack w createA po sprawdzeniu, ",
            "atak sie zmienił, anulowanie w createAbility, "
          );
          return;
        }
        //
        if (!selectedTarget) {
          return;
        }

        let targetElement = selectMonster(selectedTarget);
        let playerDmg = calculateDmg(minDamage, maxDamage);
        if (buffRatio === null) {
        } else if (buffStats === true) {
          playerDmg = Math.round((playerDmg *= buffRatio));
          buffStats = false;
        }
        if (buffEnhance === null) {
        } else if (buffEnhance === true) {
          playerDmg = Math.round((playerDmg *= enhanceBuff));
          buffEnhance = false;
        }

        reduceHp(selectedTarget, targetElement, playerDmg);
        const playerBlock = event.target.closest(".player-block");
        const heroId = playerBlock.getAttribute("data-hero-id");
        const heroMPElement = playerBlock.querySelector(".mp-amount");
        const hero = heroes.find((hero) => hero.id === heroId);
        gainMana(mp, hero, heroMPElement);
        showInfo(playerDmg, hero, infoDisplay);
        checkAttack = null;
        console.log(checkAttack, "checkAttack po skillu");
        hideSelectTarget();
      }
    }

 async function interaction() {
    while (true) {
      const attackKind = await selectAttackKind();  
      console.log("Wybrano atak:", attackKind);
   
      const target = await selectTarget();
      console.log("Wybrano cel:", target);
      
      await executeAttack(attackKind, target);
    }
  }
  
  async function selectAttackKind() {
    return new Promise((resolve) => {
      const attackButtons = document.querySelectorAll(".js-skills");  
      attackButtons.forEach((button) => {
        button.addEventListener("click", async (event) => {
          const abilityName = event.target.getAttribute("data-skill");
          resolve(abilityName);  
        });
      });
    });
  }
  
  async function executeAttack(attackName) {
    switch (attackName) {
      case "warrior-normal-attack":
        
        await createAbilityManaPlus(
          null,  
          "warrior-normal-attack",
          15,
          20,
          "normal attack",
          1.7,
          null,
          10,
          abilityName
        );
        break;
      case "warrior-strong-attack":
        
        await createAbilityManaPlus(
          null,  
          "warrior-strong-attack",
          20,
          30,
          "strong attack",
          1.7,
          null,
          5,
          abilityName
        );
        break;
      
      default:
        console.log("Nieobsługiwany atak:", attackName);
    }
  }
}

async function handleAbilityUse(event, abilityName) {
      switch (abilityName) {
        //warrior
        case "warrior-normal-attack":
          currentAttack = "warrior-normal";
          checkAttack = "warrior-normal";
          await interaction()
          break;
        case "warrior-strong-attack":
          currentAttack = "warrior-strong";
          checkAttack = "warrior-strong";
          await interaction()
          break;

const heroContainer = document.getElementById("hero-container");
    heroContainer.addEventListener(
      "click",
      async (event) => {
        if (event.target && event.target.matches(".js-skills")) {
          const abilityName = event.target.getAttribute("data-skill");
          if (checkAttack !== currentAttack) {
            checkAttack = currentAttack;
          }
          await handleAbilityUse(event, abilityName);
        }
      }

Może któraś funkcja jest tam gdzie być nie powinna albo nie działa z innego powodu?
Aktualnie jak klikne np. normal attack to nie pojawia się wybór celu, po ponownym kliknięciu pojawia się ale po wybraniu celu createAbility jest undefined

0

Zwróć uwagę, jak używasz zmiennych i jak je deklarujesz, jak je przekazujesz.

Np. zmienna checkAttack nie widzę, żeby była gdziekolwiek deklarowana. Jeśli to zmienna globalna, to powinna być zadeklarowana np. let checkAttack na zewnętrznej części plików (tj. poza funkcjami).

No i jak teraz jest zaimplementowana funkcja selectTarget?

Bo z tego kodu:

        await selectTarget();
        // ...
        if (!selectedTarget) {
          return;
        }

to wygląda jakby funkcja selectTarget ustawiała jakąś zmienną globalną selectedTarget. Czy na pewno tak jest?

No i o ile ze zmiennymi globalnymi też zadziała, to jakoś lepiej byłoby to zrobić tak, żeby funkcja selectTarget zwracała ten wybrany cel. Wtedy można było uniknąć niepotrzebnej zmiennej globalnej. Ale wtedy musiałbyś tak zrobić:

const selectedTarget = await selectTarget();

zresztą w ostatnim poście masz też kod:

 const target = await selectTarget();

Jak dokładnie wygląda ta funkcja selectTarget() w środku? Ustawiasz zmienną globalną czy zwracasz selectedTarget? Czy może jedno i drugie?

1

Może rozważ użycie <input type="radio"> w taki sposób zamiast <button> wtedy wartości są natywnie przez przeglądarkę sprowadzone do jednego wyboru z każdej kategorii, sczytujesz tylko value z oznaczonych <input type="radio"> (patrz kod poniżej sekcja js).

[ kod przykładu on-line ]

<div class="player-block" data-hero-id="">
  <div class="description-area">
    <p class="hero-name" data-hero-name=""></p>
    <p class="hp-amount" data-hero-hp="">HP: <span></span></p>
    <p class="mp-amount" data-hero-mp="">MP: <span></span></p>
  </div>

  <div class="ability-area">
    <ul id="focus" class="show">
      <li>
        <input type="radio" name="focus" id="focus-1" value="focus 1">
        <label for="focus-1">focus 1</label>
      </li>
      <li>
        <input type="radio" name="focus" id="focus-2" value="focus 2">
        <label for="focus-2">focus 2</label>
      </li>      
    </ul>
    <ul id="skills">
      <li>
        <input type="radio" name="skills" id="skills-1" value="normal attack">
        <label for="skills-1">normal attack</label>
      </li>
      <li>
        <input type="radio" name="skills" id="skills-2" value="strong attack">
        <label for="skills-2">strong attack</label>
      </li>
      <li>
        <input type="radio" name="skills" id="skills-3" value="spear stab">
        <label for="skills-3">spear stab</label>
      </li>
      <li>
        <input type="radio" name="skills" id="skills-4" value="enrage">
        <label for="skills-4">enrage</label>
      </li>
      <li>
        <input type="radio" name="skills" id="skills-5" value="leap">
        <label for="skills-5">leap</label>
      </li>      
    </ul>
    <ul id="magic">
      <li>
        <input type="radio" name="magic" id="magic-1" value="magic 1">
        <label for="magic-1">magic 1</label>
      </li>
      <li>
        <input type="radio" name="magic" id="magic-2" value="magic 2">
        <label for="magic-2">magic 2</label>
      </li>
      <li>
        <input type="radio" name="magic" id="magic-3" value="magic 3">
        <label for="magic-3">magic 3</label>
      </li>
    </ul>
  </div>

  <div class="actions-area">
    <button data-action="focus">focus</button>
    <button data-action="skills">skills</button>
    <button data-action="magic">magic</button>
  </div>
</div>

<!-- dla testów -->
<pre id="console"></pre>

<style>
  .player-block {
    display: grid;  
    grid-template-columns: 1fr 1fr; 
    grid-template-rows: 1fr 50px; 
    gap: 0px 0px; 
    grid-template-areas: 
      "description-area ability-area"
      "actions-area actions-area";

    width: 60dvw;
    height: 70dvh;
    margin: 1rem auto;
    padding: 1rem;
    font: 300 .9rem/1.05 system-ui, monospace, sans-serif;
    background-repeat: no-repeat;
    background-size: cover;
    background-position: left top;
    border-radius: .25rem;
    user-select: none;
  }
  .description-area { grid-area: description-area; }
  .ability-area { grid-area: ability-area; }
  .actions-area { grid-area: actions-area; }

  .player-block p.hero-name {
    text-transform: uppercase;
    font-size: 180%;
    font-weight: bold;
    text-shadow: 4px 4px 8px black;
  }
  .player-block p.hero-name::first-letter {
    font-size: 135%;
  }  
  .player-block p.hp-amount, p.mp-amount {
    font-weight: bold;
    text-shadow: 4px 4px 8px black;
  }
  .player-block p.hp-amount span, p.mp-amount span  {
    display: inline-block;
    font-weight: normal;
    padding-left: .15rem;
  }
  .player-block p {
    margin: .5rem;
    color: goldenrod;
  }
  .player-block ul {
    display: none;
    list-style: none;
  }
  .player-block ul li + li {
    margin-top: .35rem;
  }
  .player-block ul input[type="radio"] {
    display: none;
  }
  .player-block ul input[type="radio"]:checked ~ label {
    border-color: darkgoldenrod;
    background-color: rgba(218, 165, 32, .9); /* goldenrod */
    color: white;
  }
  .player-block ul label {
    display: block;
    font: 300 .7rem/1.5 system-ui, monospace, sans-serif;
    width: 20ch;
    height: 4ch;
    border-radius: .5rem;
    background-color: rgba(255, 255, 255, .7);
    text-transform: uppercase;
    text-align: center;
    cursor: pointer;
    border: 2px solid black;
    transition: all 150ms;
  }
  .player-block ul label::first-letter {
    font-size: 135%;
  }
  .player-block ul label:hover {
    background-color: rgba(255, 255, 255, .9);
  }
  .player-block .actions-area {
    display: flex;
    justify-content: center;
  }
  .player-block .actions-area button {
    width: 12ch;
    height: 6ch;
    border-radius: .5rem;
    background-color: rgba(255,255,255, .7);
    text-transform: uppercase;
    cursor: pointer;
    transition: background-color 150ms;
  }
  .player-block .actions-area button::first-letter {
    font-size: 140%; 
  }
  .player-block .actions-area button + button {
    margin-left: 1rem;
  }
  .player-block .actions-area button:hover {
    background-color: rgba(255,255,255, .9);
  }

  .show {
    display: block !important;
  }


  #console {
    display: block;
    position: absolute;
    bottom: 0;
    left: 50%;
    transform: translatex(-50%);
    width: 65dvw;
    height: 15ch;
    font: 300 .9rem/1 system-ui, monospace, sans-serif;
    background-color: black;
    color: limegreen;
    padding: .5rem;
    overflow-y: auto;
    z-index: 9999;
  }
  #consola::-webkit-scrollbar {
    width: 10px;
  }
  #consola::-webkit-scrollbar-track {
    background: black;
  }
  #consola::-webkit-scrollbar-thumb {
    background: limegreen;
    border-radius: 2px;
  }
</style>

<script>
  const selectedHero = [
    { 
      id:1, 
      name:'Bolok',
      img:'https://pics.craiyon.com/2023-11-03/ce03f7fd90714bc295dbdb688e3ec324.webp',
      hp: 20,
      mp: 45
    }
  ];

  const player_block = document.querySelector('.player-block');
  player_block.addEventListener('click', playerAction);
  player_block.addEventListener('change', handleCheckSelected);

  function playerAction(e) {
    if (e.target.matches('.actions-area button') && e.target.dataset.action) {
      [...player_block
       .querySelectorAll('.ability-area ul')]
        .map(ul => ul.classList.remove('show'));
      player_block
        .querySelector(`.ability-area ul#${e.target.dataset.action}`)
        .classList.add('show');      
    }
  }

  function handleCheckSelected(e) {
    if (e.target.matches('.ability-area input[type="radio"]')) {
      const selected = player_block.querySelectorAll('.ability-area input[type="radio"]:checked');
      if (selected.length < 3) {
        consoleLog(`Wybrano ${selected.length} / 3 : ostatnio wybrany: ${e.target.value}`);
        return;
      }

      let log = `Wybrano 3 / 3 - Start tryb ataku!\nWybrane pozycje:\n`;
      for (const select of selected)
        log += ` + ${select.value}` + '\n';
      consoleLog(log);      
    }   
  }

  function consoleLog(log) {
    const console_ = document.querySelector('#console);
    console_.textContent += log + '\n';
    console_.scrollTop = console_.scrollHeight;    
  }


  player_block.dataset.heroId = selectedHero[0].id;
  player_block.style.backgroundImage = `url(${selectedHero[0].img})`;  
  player_block.querySelector('[data-hero-name]').textContent = selectedHero[0].name;
  player_block.querySelector('[data-hero-hp] span').textContent = selectedHero[0].hp;
  player_block.querySelector('[data-hero-mp] span').textContent = selectedHero[0].mp;
</script>

image

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.