mirror of
				https://github.com/bra1n/townsquare.git
				synced 2025-10-21 16:55:12 +00:00 
			
		
		
		
	* Update CHANGELOG.md * Update CHANGELOG.md * Adding all new role icons (1/2) Impossible to update all files at once, these are the 100 first. * Adding all new role icons (2/2) All missing roles icons. NB: The Summoner was already added with the new-style icon. So it don't need to be updated. * Adding a default icon for custom Travellers * Delete the default icon for Fabled * Updating code for new default icons --------- Co-authored-by: Pingumask <68610022+Pingumask@users.noreply.github.com>
		
			
				
	
	
		
			289 lines
		
	
	
	
		
			8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			289 lines
		
	
	
	
		
			8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
import Vue from "vue";
 | 
						|
import Vuex from "vuex";
 | 
						|
import persistence from "./persistence";
 | 
						|
import socket from "./socket";
 | 
						|
import players from "./modules/players";
 | 
						|
import session from "./modules/session";
 | 
						|
import editionJSON from "../editions.json";
 | 
						|
 | 
						|
import { locale, rolesJSON, jinxesJSON, fabledJSON } from "./modules/locale";
 | 
						|
 | 
						|
Vue.use(Vuex);
 | 
						|
 | 
						|
// helper functions
 | 
						|
const getRolesByEdition = (edition = editionJSON.official[0]) => {
 | 
						|
  return new Map(
 | 
						|
    rolesJSON
 | 
						|
      .filter((r) => r.edition === edition.id || edition.roles.includes(r.id))
 | 
						|
      .sort((a, b) => b.team.localeCompare(a.team))
 | 
						|
      .map((role) => [role.id, role]),
 | 
						|
  );
 | 
						|
};
 | 
						|
 | 
						|
const getTravelersNotInEdition = (edition = editionJSON.official[0]) => {
 | 
						|
  return new Map(
 | 
						|
    rolesJSON
 | 
						|
      .filter(
 | 
						|
        (r) =>
 | 
						|
          r.team === "traveler" &&
 | 
						|
          r.edition !== edition.id &&
 | 
						|
          !edition.roles.includes(r.id),
 | 
						|
      )
 | 
						|
      .map((role) => [role.id, role]),
 | 
						|
  );
 | 
						|
};
 | 
						|
 | 
						|
const set =
 | 
						|
  (key) =>
 | 
						|
  ({ grimoire }, val) => {
 | 
						|
    grimoire[key] = val;
 | 
						|
  };
 | 
						|
 | 
						|
const toggle =
 | 
						|
  (key) =>
 | 
						|
  ({ grimoire }, val) => {
 | 
						|
    if (val === true || val === false) {
 | 
						|
      grimoire[key] = val;
 | 
						|
    } else {
 | 
						|
      grimoire[key] = !grimoire[key];
 | 
						|
    }
 | 
						|
  };
 | 
						|
 | 
						|
const clean = (id) => id.toLocaleLowerCase().replace(/[^a-z0-9]/g, "");
 | 
						|
 | 
						|
// global data maps
 | 
						|
const editionJSONbyId = new Map(
 | 
						|
  editionJSON.official.map((edition) => [edition.id, edition]),
 | 
						|
);
 | 
						|
const rolesJSONbyId = new Map(rolesJSON.map((role) => [role.id, role]));
 | 
						|
const fabled = new Map(fabledJSON.map((role) => [role.id, role]));
 | 
						|
 | 
						|
// jinxes
 | 
						|
let jinxes = {};
 | 
						|
try {
 | 
						|
  // Note: can't fetch live list due to lack of CORS headers
 | 
						|
  // fetch("https://bloodontheclocktower.com/script/data/hatred.json")
 | 
						|
  //   .then(res => res.json())
 | 
						|
  //   .then(jinxesJSON => {
 | 
						|
  jinxes = new Map(
 | 
						|
    jinxesJSON.map(({ id, hatred }) => [
 | 
						|
      clean(id),
 | 
						|
      new Map(hatred.map(({ id, reason }) => [clean(id), reason])),
 | 
						|
    ]),
 | 
						|
  );
 | 
						|
  // });
 | 
						|
} catch (e) {
 | 
						|
  console.error("couldn't load jinxes", e);
 | 
						|
}
 | 
						|
 | 
						|
// base definition for custom roles
 | 
						|
const customRole = {
 | 
						|
  id: "",
 | 
						|
  name: "",
 | 
						|
  image: "",
 | 
						|
  ability: "",
 | 
						|
  edition: "custom",
 | 
						|
  firstNight: 0,
 | 
						|
  firstNightReminder: "",
 | 
						|
  otherNight: 0,
 | 
						|
  otherNightReminder: "",
 | 
						|
  reminders: [],
 | 
						|
  remindersGlobal: [],
 | 
						|
  setup: false,
 | 
						|
  team: "townsfolk",
 | 
						|
  isCustom: true,
 | 
						|
};
 | 
						|
 | 
						|
export default new Vuex.Store({
 | 
						|
  modules: {
 | 
						|
    players,
 | 
						|
    session,
 | 
						|
  },
 | 
						|
  state: {
 | 
						|
    grimoire: {
 | 
						|
      isNight: false,
 | 
						|
      isNightOrder: false,
 | 
						|
      isRinging: false,
 | 
						|
      isPublic: true,
 | 
						|
      isMenuOpen: false,
 | 
						|
      isStatic: false,
 | 
						|
      isMuted: false,
 | 
						|
      isImageOptIn: false,
 | 
						|
      isStreamerMode: false,
 | 
						|
      isOrganVoteMode: false,
 | 
						|
      zoom: 0,
 | 
						|
      background: "",
 | 
						|
      timer: {
 | 
						|
        name: "",
 | 
						|
        duration: 0,
 | 
						|
      },
 | 
						|
    },
 | 
						|
    modals: {
 | 
						|
      edition: false,
 | 
						|
      fabled: false,
 | 
						|
      gameState: false,
 | 
						|
      nightOrder: false,
 | 
						|
      reference: false,
 | 
						|
      reminder: false,
 | 
						|
      role: false,
 | 
						|
      roles: false,
 | 
						|
      voteHistory: false,
 | 
						|
    },
 | 
						|
    edition: editionJSONbyId.get("tb"),
 | 
						|
    editions: editionJSON,
 | 
						|
    roles: getRolesByEdition(),
 | 
						|
    otherTravelers: getTravelersNotInEdition(),
 | 
						|
    fabled,
 | 
						|
    jinxes,
 | 
						|
    locale,
 | 
						|
  },
 | 
						|
  getters: {
 | 
						|
    /**
 | 
						|
     * Return all custom roles, with default values and non-essential data stripped.
 | 
						|
     * Role object keys will be replaced with a numerical index to conserve bandwidth.
 | 
						|
     * @param roles
 | 
						|
     * @returns {[]}
 | 
						|
     */
 | 
						|
    customRolesStripped: ({ roles }) => {
 | 
						|
      const customRoles = [];
 | 
						|
      const customKeys = Object.keys(customRole);
 | 
						|
      const strippedProps = [
 | 
						|
        "firstNightReminder",
 | 
						|
        "otherNightReminder",
 | 
						|
        "isCustom",
 | 
						|
      ];
 | 
						|
      roles.forEach((role) => {
 | 
						|
        if (!role.isCustom) {
 | 
						|
          customRoles.push({ id: role.id });
 | 
						|
        } else {
 | 
						|
          const strippedRole = {};
 | 
						|
          for (let prop in role) {
 | 
						|
            if (strippedProps.includes(prop)) {
 | 
						|
              continue;
 | 
						|
            }
 | 
						|
            const value = role[prop];
 | 
						|
            if (customKeys.includes(prop) && value !== customRole[prop]) {
 | 
						|
              strippedRole[customKeys.indexOf(prop)] = value;
 | 
						|
            }
 | 
						|
          }
 | 
						|
          customRoles.push(strippedRole);
 | 
						|
        }
 | 
						|
      });
 | 
						|
      return customRoles;
 | 
						|
    },
 | 
						|
    rolesJSONbyId: () => rolesJSONbyId,
 | 
						|
  },
 | 
						|
  mutations: {
 | 
						|
    setZoom: set("zoom"),
 | 
						|
    setBackground: set("background"),
 | 
						|
    toggleMuted: toggle("isMuted"),
 | 
						|
    toggleMenu: toggle("isMenuOpen"),
 | 
						|
    toggleNightOrder: toggle("isNightOrder"),
 | 
						|
    toggleStatic: toggle("isStatic"),
 | 
						|
    toggleNight: toggle("isNight"),
 | 
						|
    toggleRinging: toggle("isRinging"),
 | 
						|
    toggleGrimoire: toggle("isPublic"),
 | 
						|
    toggleImageOptIn: toggle("isImageOptIn"),
 | 
						|
    toggleStreamerMode: toggle("isStreamerMode"),
 | 
						|
    toggleOrganVoteMode: toggle("isOrganVoteMode"),
 | 
						|
    setTimer(state, timer) {
 | 
						|
      state.grimoire.timer = timer;
 | 
						|
    },
 | 
						|
    toggleModal({ modals }, name) {
 | 
						|
      if (name) {
 | 
						|
        modals[name] = !modals[name];
 | 
						|
      }
 | 
						|
      for (let modal in modals) {
 | 
						|
        if (modal === name) continue;
 | 
						|
        modals[modal] = false;
 | 
						|
      }
 | 
						|
    },
 | 
						|
    /**
 | 
						|
     * Store custom roles
 | 
						|
     * @param state
 | 
						|
     * @param roles Array of role IDs or full role definitions
 | 
						|
     */
 | 
						|
    setCustomRoles(state, roles) {
 | 
						|
      const processedRoles = roles
 | 
						|
        // replace numerical role object keys with matching key names
 | 
						|
        .map((role) => {
 | 
						|
          if (role[0]) {
 | 
						|
            const customKeys = Object.keys(customRole);
 | 
						|
            const mappedRole = {};
 | 
						|
            for (let prop in role) {
 | 
						|
              if (customKeys[prop]) {
 | 
						|
                mappedRole[customKeys[prop]] = role[prop];
 | 
						|
              }
 | 
						|
            }
 | 
						|
            return mappedRole;
 | 
						|
          } else {
 | 
						|
            return role;
 | 
						|
          }
 | 
						|
        })
 | 
						|
        // clean up role.id
 | 
						|
        .map((role) => {
 | 
						|
          role.id = clean(role.id);
 | 
						|
          return role;
 | 
						|
        })
 | 
						|
        // map existing roles to base definition or pre-populate custom roles to ensure all properties
 | 
						|
        .map(
 | 
						|
          (role) =>
 | 
						|
            rolesJSONbyId.get(role.id) ||
 | 
						|
            state.roles.get(role.id) ||
 | 
						|
            Object.assign({}, customRole, role),
 | 
						|
        )
 | 
						|
        // default empty icons and placeholders, clean up firstNight / otherNight
 | 
						|
        .map((role) => {
 | 
						|
          if (rolesJSONbyId.get(role.id)) return role;
 | 
						|
          role.imageAlt = // map team to generic icon
 | 
						|
            {
 | 
						|
              townsfolk: "good",
 | 
						|
              outsider: "outsider",
 | 
						|
              minion: "minion",
 | 
						|
              demon: "evil",
 | 
						|
              fabled: "bootlegger",
 | 
						|
              traveler: "traveler",
 | 
						|
            }[role.team] || "custom";
 | 
						|
          role.firstNight = Math.abs(role.firstNight);
 | 
						|
          role.otherNight = Math.abs(role.otherNight);
 | 
						|
          return role;
 | 
						|
        })
 | 
						|
        // filter out roles that don't match an existing role and also don't have name/ability/team
 | 
						|
        .filter((role) => role.name && role.ability && role.team)
 | 
						|
        // sort by team
 | 
						|
        .sort((a, b) => b.team.localeCompare(a.team));
 | 
						|
      // convert to Map without Fabled
 | 
						|
      state.roles = new Map(
 | 
						|
        processedRoles
 | 
						|
          .filter((role) => role.team !== "fabled")
 | 
						|
          .map((role) => [role.id, role]),
 | 
						|
      );
 | 
						|
      // update Fabled to include custom Fabled from this script
 | 
						|
      state.fabled = new Map([
 | 
						|
        ...processedRoles
 | 
						|
          .filter((r) => r.team === "fabled")
 | 
						|
          .map((r) => [r.id, r]),
 | 
						|
        ...fabledJSON.map((role) => [role.id, role]),
 | 
						|
      ]);
 | 
						|
      // update extraTravelers map to only show travelers not in this script
 | 
						|
      state.otherTravelers = new Map(
 | 
						|
        rolesJSON
 | 
						|
          .filter(
 | 
						|
            (r) => r.team === "traveler" && !roles.some((i) => i.id === r.id),
 | 
						|
          )
 | 
						|
          .map((role) => [role.id, role]),
 | 
						|
      );
 | 
						|
    },
 | 
						|
    setEdition(state, edition) {
 | 
						|
      if (editionJSONbyId.has(edition.id)) {
 | 
						|
        state.edition = editionJSONbyId.get(edition.id);
 | 
						|
        state.roles = getRolesByEdition(state.edition);
 | 
						|
        state.otherTravelers = getTravelersNotInEdition(state.edition);
 | 
						|
      } else {
 | 
						|
        state.edition = edition;
 | 
						|
      }
 | 
						|
      state.modals.edition = false;
 | 
						|
    },
 | 
						|
  },
 | 
						|
  plugins: [persistence, socket],
 | 
						|
});
 |