Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Mass Battle Macro #147

Open
brunocalado opened this issue Apr 23, 2024 · 2 comments
Open

[FEATURE] Mass Battle Macro #147

brunocalado opened this issue Apr 23, 2024 · 2 comments
Labels
enhancement New feature or request

Comments

@brunocalado
Copy link

Can you add the mass battle macro?

const version = 'v2.0';
const chatimage = "icons/environment/people/charge.webp";
let coreRules = false;
if (game.modules.get("swade-core-rules")?.active) { coreRules = true; }

/* Mass Battle p133 SWADE

source: https://raw.githubusercontent.com/brunocalado/mestre-digital/master/Foundry%20VTT/Macros/Savage%20Worlds/MassBattle.js
icon: icons/environment/people/charge.webp
*/

main();

function main() {  
  let template = `  
    <style type="text/css">
      div.purpleHorizon {
        border: 4px solid #ff0000;
        background-color: #000000;
        width: 100%;
        text-align: center;
        border-collapse: collapse;
      }
      .divTable.purpleHorizon .divTableCell, .divTable.purpleHorizon .divTableHead {
        border: 0px solid #550000;
        padding: 5px 2px;
      }
      .divTable.purpleHorizon .divTableBody .divTableCell {
        font-size: 13px;
        font-weight: bold;
        color: #FFFFFF;
      }
      
      .divTable{ display: table; }
      .divTableRow { display: table-row; }
      .divTableHeading { display: table-header-group;}
      .divTableCell, .divTableHead { display: table-cell;}
      .divTableHeading { display: table-header-group;}
      .divTableFoot { display: table-footer-group;}
      .divTableBody { display: table-row-group;}

      /* HIDE RADIO */
      [type=radio] { 
      position: absolute;
      opacity: 0;
      width: 0;
      height: 0;
      }

      /* IMAGE STYLES */
      [type=radio] + img {
      cursor: pointer;
      }

      /* CHECKED STYLES */
      [type=radio]:checked + img {
      outline: 4px solid #f00;
      }
      
      .container {
        position: relative;
        text-align: center;
        color: white;
      }
      /* Centered text */
      .centered {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        font-size: 18px;
      }    

      #kultcss .window-content {    
        background: #000000;
      }     
      #kultcss .dialog-button {
        height: 40px;
        background: #000000;
        color: #ffffff;
        justify-content: space-evenly;
        align-items: center;
        cursor: pointer;
        border: none;    
      }  
      #kultcss header {
        background: #000000;
        border-radius: 0;    
        border: none;    
        margin-bottom: 2px;
        font-size: .75rem;
      }
    </style>    
    
    <h1 style="color:white">Force 1</h1>
    <div class="divTable purpleHorizon">
    <div class="divTableBody">
    
    <div class="divTableRow">
    <div class="divTableCell">
        <p>Name</p>
        <input id="name1" type="text" max="20" style="width: 100px; box-sizing: border-box;border: none;background-color: #ff0000;color: white; text-align: center;" value="Force1">    
    </div>
    <div class="divTableCell">      
      <label>        
        <div class="container">
          <p>Force</p>
        <input id="force1" type="number" min="10" max="10" style="width: 60px; box-sizing: border-box;border: none;background-color: #ff0000;color: white; text-align: center;" value=10>
        </div>
      </label>      
    </div>    
    <div class="divTableCell">
        <p>Die</p>
        <select id="commanderdie1" type="text" style="width: 60px; box-sizing: border-box;border: none;background-color: #ff0000;color: white; text-align: center;">
        <option value="1d4x">1d4x</option>
        <option value="1d6x">1d6x</option>
        <option value="1d8x" selected="selected">1d8x</option>
        <option value="1d10x">1d10x</option>
        <option value="1d12x">1d12x</option>
        </select>
    </div>
    <div class="divTableCell">
        <p>Bonus</p>
        <input id="commanderbonus1" type="text" style="width: 60px; box-sizing: border-box;border: none;background-color: #ff0000;color: white; text-align: center;" value="3">       
    </div>       
    </div>

    <div class="divTableRow">
    <div class="divTableCell">
        <p>Advantage</p>
        <input id="tacticaladvantage1" type="number" min="0" max="4" style="width: 60px; box-sizing: border-box;border: none;background-color: #ff0000;color: white; text-align: center;" value=0>       
    </div>
    <div class="divTableCell">      
      <p>Plan</p>
      <input id="battleplan1" type="number" min="0" max="4" style="width: 60px; box-sizing: border-box;border: none;background-color: #ff0000;color: white; text-align: center;" value=0>       
    </div>    
    <div class="divTableCell">
        <p>Players Bonus</p>
        <input id="playersbonus1" type="number" min="-20" max="20" style="width: 60px; box-sizing: border-box;border: none;background-color: #ff0000;color: white; text-align: center;" value=0>       
    </div>
    <div class="divTableCell">
        <p>Is Wild?</p>
        <input id="wildcard1" type="checkbox" style="width: 60px; box-sizing: border-box;border: none;background-color: #ff0000;color: white; text-align: center;" checked>       
    </div>    
    </div>
    
    </div>
    </div>
    

    <h1 style="color:white">Force 2</h1>
    <div class="divTable purpleHorizon">
    <div class="divTableBody">
    
    <div class="divTableRow">
    <div class="divTableCell">
        <p>Name</p>
        <input id="name2" type="text" max="20" style="width: 100px; box-sizing: border-box;border: none;background-color: #ff0000;color: white; text-align: center;" value="Force2">    
    </div>
    <div class="divTableCell">      
      <label>        
        <div class="container">
          <p>Force</p>
        <input id="force2" type="number" min="10" max="10" style="width: 60px; box-sizing: border-box;border: none;background-color: #ff0000;color: white; text-align: center;" value=7>
        </div>
      </label>      
    </div>   
    <div class="divTableCell">
        <p>Die</p>
        <select id="commanderdie2" type="text" style="width: 60px; box-sizing: border-box;border: none;background-color: #ff0000;color: white; text-align: center;">
        <option value="1d4x">1d4x</option>
        <option value="1d6x">1d6x</option>
        <option value="1d8x" selected="selected">1d8x</option>
        <option value="1d10x">1d10x</option>
        <option value="1d12x">1d12x</option>
        </select>
    </div>
    <div class="divTableCell">
        <p>Bonus</p>
        <input id="commanderbonus2" type="text" style="width: 60px; box-sizing: border-box;border: none;background-color: #ff0000;color: white; text-align: center;" value="3">       
    </div>    
    </div>

    <div class="divTableRow">
    <div class="divTableCell">
        <p>Advantage</p>
        <input id="tacticaladvantage2" type="number" min="0" max="4" style="width: 60px; box-sizing: border-box;border: none;background-color: #ff0000;color: white; text-align: center;" value=0>       
    </div>
    <div class="divTableCell">      
      <label>        
        <div class="container">
          <p>Plan</p>
        <input id="battleplan2" type="number" min="0" max="4" style="width: 60px; box-sizing: border-box;border: none;background-color: #ff0000;color: white; text-align: center;" value=0>       
        </div>
      </label>      
    </div>    
    <div class="divTableCell">
        <p>Players Bonus</p>
        <input id="playersbonus2" type="number" min="-20" max="20" style="width: 60px; box-sizing: border-box;border: none;background-color: #ff0000;color: white; text-align: center;" value=0>       
    </div>
    <div class="divTableCell">
        <p>Is Wild?</p>
        <input id="wildcard2" type="checkbox" style="width: 60px; box-sizing: border-box;border: none;background-color: #ff0000;color: white; text-align: center;" checked>       
    </div>      
    </div>
    
    </div>
    </div>
    
  `;
  
  new Dialog({
    title: `Mass Battle - ${version}`,
    content: template,
    buttons: {
      ok: {
        label: "Battle!",
        callback: async (html) => {
          massbattle(html);
        },
      },
      cancel: {
        label: "Cancel",
      }
    },
    default: "ok"
  }, { id: 'kultcss'}).render(true);
}

async function massbattle(html){
  // FORCE 1
  const name1 =  html.find("#name1")[0].value;    
  const force1 = parseInt( html.find("#force1")[0].value );    
  const commanderdie1 = html.find("#commanderdie1")[0].value;  
  const commanderbonus1 = html.find("#commanderbonus1")[0].value;    
  const tacticaladvantage1 =  parseInt( html.find("#tacticaladvantage1")[0].value );    
  const battleplan1 = parseInt( html.find("#battleplan1")[0].value );    
  const playersbonus1 = parseInt( html.find("#playersbonus1")[0].value );  
  const wildcard1 = html.find("#wildcard1")[0].checked;
  //const stackupcards = html.find("#stackupcards")[0].checked;

  // FORCE 2
  const name2 =  html.find("#name2")[0].value;    
  const force2 = parseInt( html.find("#force2")[0].value );    
  const commanderdie2 = html.find("#commanderdie2")[0].value;  
  const commanderbonus2 = html.find("#commanderbonus2")[0].value;    
  const tacticaladvantage2 =  parseInt( html.find("#tacticaladvantage2")[0].value );    
  const battleplan2 = parseInt( html.find("#battleplan2")[0].value );    
  const playersbonus2 = parseInt( html.find("#playersbonus2")[0].value ); 
  const wildcard2 = html.find("#wildcard2")[0].checked;

  // 
  let commander1DiceExpression;
  if (wildcard1) {
    commander1DiceExpression = '{' + commanderdie1 + '+' + commanderbonus1 + ', 1d6x+' + commanderbonus1 + '}';
  } else {
    commander1DiceExpression = commanderdie1 + '+' + commanderbonus1;
  }  
  if (wildcard2) {
    commander2DiceExpression = '{' + commanderdie2 + '+' + commanderbonus2 + ', 1d6x+' + commanderbonus2 + '}';
  } else {
    commander2DiceExpression = commanderdie2 + '+' + commanderbonus2;
  }  
  
  let commander1Dice = await new Roll(commander1DiceExpression).roll({ async : false });  
  let commanderRolled1;
  if (wildcard1) {
    commanderRolled1 = Math.max( commander1Dice.terms[0].rolls[0].total,  commander1Dice.terms[0].rolls[1].total );
  } else {
    commanderRolled1 = commander1Dice.total;
  }
  
  let commander2Dice = new Roll(commander2DiceExpression).roll({ async : false });  
  let commanderRolled2;
  if (wildcard2) {
    commanderRolled2 = Math.max( commander2Dice.terms[0].rolls[0].total,  commander2Dice.terms[0].rolls[1].total );
  } else {
    commanderRolled2 = commander2Dice.total;
  }
  let result1=0;
  let result2=0;
  
  let message;
  if (coreRules) {
    message = `<div class="swade-core"><h2><img style="vertical-align:middle" src=${chatimage} width="28" height="28"> @Compendium[swade-core-rules.swade-rules.IJkWxV4kddDaU0Gl]{ Mass Battles}</h2></div>`;
  } else {
    message = `<div><h2><img style="vertical-align:middle" src=${chatimage} width="28" height="28"> Mass Battle</h2><div>`;
  }
  
  let forceMessage1;
  let forceMessage2;
  if (force1>force2) {
    result1 += forceBonus(force1, force2);
    forceMessage1 = `<li><b>Force Size:</b> ${force1} <b style="color:red">(+${forceBonus(force1, force2)})</b></li>`;
    forceMessage2 = `<li><b>Force Size:</b> ${force2} <b style="color:red">(+0)</b></li>`;
  } else {
    result2 += forceBonus(force1, force2);
    forceMessage2 = `<li><b>Force Size:</b> ${force2} <b style="color:red">(+${forceBonus(force1, force2)})</b></li>`;
    forceMessage1 = `<li><b>Force Size:</b> ${force1} <b style="color:red">(+0)</b></li>`;    
  }
  result1 += tacticaladvantage1;
  result2 += tacticaladvantage2;
  result1 += battleplan1;
  result2 += battleplan2;
  result1 += playersbonus1;
  result2 += playersbonus2;
  result1 += commanderRolled1;
  result2 += commanderRolled2;
  
  if (wildcard1) {
    message += `<h2>(Wild Card) ${name1}</h2>`;
  } else {
    message += `<h2>${name1}</h2>`;
  }
   
  message += `<ul>
  <li><b style="color:red">Total:</b> ${result1}</li>
  <li><b>Commander Roll:</b> ${commanderRolled1}</li>
  ${forceMessage1}
  <li><b>Tactical Advantage:</b> ${tacticaladvantage1}</li>
  <li><b>Battle Plan:</b> ${battleplan1}</li>
  <li><b>Players Bonus:</b> ${playersbonus1}</li>
  </ul>`;
  
  if (wildcard2) {
    message += `<h2>(Wild Card) ${name2}</h2>`;
  } else {
    message += `<h2>${name2}</h2>`;
  }
  message += `<ul>
  <li><b style="color:red">Total:</b> ${result2}</li>
  <li><b>Commander Roll:</b> ${commanderRolled2}</li>
  ${forceMessage2}
  <li><b>Tactical Advantage:</b> ${tacticaladvantage2}</li>
  <li><b>Battle Plan:</b> ${battleplan2}</li>
  <li><b>Players Bonus:</b> ${playersbonus2}</li>
  </ul>`;
  
  message += winnerCheck(result1, result2, name1, name2, force1, force2);
  let cleanMessage = message;
  
  // 3D Dice
  commander1Dice.toMessage({flavor: `${name1}`, whisper : ChatMessage.getWhisperRecipients("GM") });
  commander2Dice.toMessage({flavor: `${name2}`, whisper : ChatMessage.getWhisperRecipients("GM") });
  
  // Create Journal on Click
  const buttonID = Math.floor(Math.random(0.1)*1000000000);

  let data = {
    name: 'Mass Battle',
    content: cleanMessage
  };    
  createJournalEntry(data);

  let chatData = {
    content: message,
    whisper : ChatMessage.getWhisperRecipients("GM")
  };  
  ChatMessage.create(chatData, {});  
}

function forceBonus(force1, force2) {
  if (force1>force2) {
    return (force1-force2)
  } else if (force2>force1) {
    return (force2-force1)
  } else {
    return 0;
  }
}

function winnerCheck(result1, result2, name1, name2, force1, force2) {
  let message = `<h2 style="color:red">Result</h2>`;
  let force1after = force1;
  let force2after = force2;
  if (result1>result2) {
    if ( (result1+4)>=result2 ) { // raise
      message += `<p><b>Victory:</b> The defeated army loses two Force Tokens.</p>`;        
      force2after -= 2; 
    } else {
      message += `<p><b>Marginal Victory:</b> The victor loses one Force Token, the defeated loses two.</p>`;        
      force2after -= 2; 
      force1after -= 1;          
    }    
    message += `<ul><li>Winner: <b>${name1}</b></li>`;
    message += `<li>Loser: <b>${name2}</b></li>`;         
  } else if (result2>result1) {
    if ( (result2+4)>=result1 ) { // raise
      message += `<p><b>Victory:</b> The defeated army loses two Force Tokens.</p>`;        
      force1after -= 2; 
    } else {
      message += `<p><b>Marginal Victory:</b> The victor loses one Force Token, the defeated loses two.</p>`;        
      force1after -= 2; 
      force2after -= 1;          
    }
    message += `<ul><li>Winner: <b>${name2}</b></li>`;
    message += `<li>Loser: <b>${name1}</b></li>`;    
  } else {
    message += `<p><b>Draw:</b> Both sides lose one Force Token.</p>`;    
    force1after -= 1; 
    force2after -= 1;
    message += `<ul><li>The <b>${name1}</b> and <b>${name2}</b> are tied.</li>`;    
  }

  message += `<li><b>${name1}:</b> has ${force1after} force tokens</li>`;
  message += `<li><b>${name2}:</b> has ${force2after} force tokens</li></ul>`;
  
  message += `<h2 style="color:darkblue">Morale</h2><ul>`;
  if ( (force1-force1after)>0 ) {
    message += `<li>-${force1-force1after}: for <b>${name1}</b> spirit roll. It's -1 for each token lost.</li>`;
  }
  if ( (force2-force2after)>0 ) {
    message += `<li>-${force2-force2after}: for <b>${name2}</b> spirit roll. It's -1 for each token lost.</li>`;
  }
  message += `<li>+2: The army is made up mostly of undead or other fearless troops.</li>`;
  message += `<li>+2: The army is within fortifications or prepared positions.</li>`;
  message += `<li>+2: The army cannot retreat or will be killed if it does.</li></ul>`;

  return message;
}

async function createJournalEntry(data) {  
  const instantAdventure = await JournalEntry.create(data);
  await instantAdventure.sheet.render(true);    
}
@brunocalado brunocalado added the enhancement New feature or request label Apr 23, 2024
@brunocalado
Copy link
Author

image

@SalieriC
Copy link
Owner

Thank you for your suggestion. I do remember this from your module. But there are some things that concern me, those will have to be addressed.

  • The CSS isn't going well with the rest of foundry imo. Colours and background need to be adjusted so that the dialogue is looking like any other in foundry for consistency reasons.
  • There is no way to reroll a result by spending a Benny. That makes it quicker but is also taking away one of the core elements of the game.
  • It floods the world with journal entries which is a problem because you usually do multiple rounds of mass battle. I think it would be better to use a single journal (create if not present or new page if it is) and also store the results in its flags. This would allow to reuse the previous results for the new round (or use default values if not found). I think this would greatly increase its usefulness.
  • Maybe also use selected/targeted tokens would be good to access their data for Bennies, trait die, wild card status and Edges. This would also allow for some decluttering of the dialogue and making the process more streamlined.

Feel free to give it a touch yourself and make a PR for it. (If you do please don't push any compendiums, that could cause problems.) I don't think I'd have time to work on it in a timely manner.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants