├── public
├── logo.png
├── favicon.ico
├── assets
│ ├── ana-portrait.png
│ ├── d-va-portrait.png
│ ├── genji-portrait.png
│ ├── hanzo-portrait.png
│ ├── lucio-portrait.png
│ ├── mei-portrait.png
│ ├── mercy-portrait.png
│ ├── orisa-portrait.png
│ ├── zarya-portrait.png
│ ├── bastion-portrait.png
│ ├── junkrat-portrait.png
│ ├── mccree-portrait.png
│ ├── pharah-portrait.png
│ ├── reaper-portrait.png
│ ├── roadhog-portrait.png
│ ├── sombra-portrait.png
│ ├── tracer-portrait.png
│ ├── winston-portrait.png
│ ├── reinhardt-portrait.png
│ ├── soldier-76-portrait.png
│ ├── symmetra-portrait.png
│ ├── torbjorn-portrait.png
│ ├── widowmaker-portrait.png
│ └── zenyatta-portrait.png
└── index.html
├── src
├── styles
│ ├── HeroSet.css
│ ├── TeamSelect.css
│ ├── HoverInfo.css
│ ├── TeamStats.css
│ ├── TeamRoster.css
│ ├── HeroSlot.css
│ └── HeroButton.css
├── App.test.js
├── index.css
├── components
│ ├── HeroSlot.jsx
│ ├── HoverInfo.jsx
│ ├── TeamRoster.jsx
│ ├── HeroButton.jsx
│ ├── HeroSet.jsx
│ ├── TeamSelect.jsx
│ └── TeamStats.jsx
├── index.js
├── App.css
├── reducer.js
├── events.js
├── App.jsx
├── core.js
└── heroes.js
├── README.md
├── .gitignore
└── package.json
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/logo.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/favicon.ico
--------------------------------------------------------------------------------
/public/assets/ana-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/ana-portrait.png
--------------------------------------------------------------------------------
/public/assets/d-va-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/d-va-portrait.png
--------------------------------------------------------------------------------
/public/assets/genji-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/genji-portrait.png
--------------------------------------------------------------------------------
/public/assets/hanzo-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/hanzo-portrait.png
--------------------------------------------------------------------------------
/public/assets/lucio-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/lucio-portrait.png
--------------------------------------------------------------------------------
/public/assets/mei-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/mei-portrait.png
--------------------------------------------------------------------------------
/public/assets/mercy-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/mercy-portrait.png
--------------------------------------------------------------------------------
/public/assets/orisa-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/orisa-portrait.png
--------------------------------------------------------------------------------
/public/assets/zarya-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/zarya-portrait.png
--------------------------------------------------------------------------------
/public/assets/bastion-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/bastion-portrait.png
--------------------------------------------------------------------------------
/public/assets/junkrat-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/junkrat-portrait.png
--------------------------------------------------------------------------------
/public/assets/mccree-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/mccree-portrait.png
--------------------------------------------------------------------------------
/public/assets/pharah-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/pharah-portrait.png
--------------------------------------------------------------------------------
/public/assets/reaper-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/reaper-portrait.png
--------------------------------------------------------------------------------
/public/assets/roadhog-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/roadhog-portrait.png
--------------------------------------------------------------------------------
/public/assets/sombra-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/sombra-portrait.png
--------------------------------------------------------------------------------
/public/assets/tracer-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/tracer-portrait.png
--------------------------------------------------------------------------------
/public/assets/winston-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/winston-portrait.png
--------------------------------------------------------------------------------
/public/assets/reinhardt-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/reinhardt-portrait.png
--------------------------------------------------------------------------------
/public/assets/soldier-76-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/soldier-76-portrait.png
--------------------------------------------------------------------------------
/public/assets/symmetra-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/symmetra-portrait.png
--------------------------------------------------------------------------------
/public/assets/torbjorn-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/torbjorn-portrait.png
--------------------------------------------------------------------------------
/public/assets/widowmaker-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/widowmaker-portrait.png
--------------------------------------------------------------------------------
/public/assets/zenyatta-portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CaptainStack/overcomp/master/public/assets/zenyatta-portrait.png
--------------------------------------------------------------------------------
/src/styles/HeroSet.css:
--------------------------------------------------------------------------------
1 | .hero-set h2 {
2 | text-align: center;
3 | }
4 | .hero-set > span {
5 | width: 100%;
6 | height: 2px;
7 | opacity: .2;
8 | background: #fff;
9 | }
10 |
--------------------------------------------------------------------------------
/src/styles/TeamSelect.css:
--------------------------------------------------------------------------------
1 | .TeamSelect {
2 | display: flex;
3 | justify-content: space-around;
4 | }
5 | .stat-bar {
6 | display: inline-block;
7 | transition: 0.5s;
8 | height: 1.2em;
9 | }
10 |
--------------------------------------------------------------------------------
/src/styles/HoverInfo.css:
--------------------------------------------------------------------------------
1 | .hover-info {
2 | border: 1px solid black;
3 | position: absolute;
4 | background-color: lightgrey;
5 | border-radius: 10px;
6 | padding: 1em;
7 | pointer-events: none;
8 | }
9 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | });
9 |
--------------------------------------------------------------------------------
/src/styles/TeamStats.css:
--------------------------------------------------------------------------------
1 | .TeamStats h2 {
2 | text-align: center;
3 | }
4 | .left-column {
5 | width: 10%;
6 | }
7 | .center-column {
8 | width: 50%;
9 | }
10 | .right-column {
11 | width: 25%;
12 | }
13 | table td:nth-child(1) {
14 | text-align: right;
15 | }
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [Overcomp](https://captainstack.github.io/overcomp/)
2 | A team composition tool for Overwatch built in ReactJS/Redux and bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app). App prototype hosted on [GitHub Pages](https://captainstack.github.io/overcomp/).
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
19 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 1em;
3 | padding-left: 2em;
4 | padding-right: 2em;
5 | font-family: sans-serif;
6 | background-color: #8b99a7;
7 | max-width: 1800px;
8 | margin-left: auto;
9 | margin-right: auto;
10 | }
11 | .App {
12 | display: flex;
13 | flex-direction: column;
14 | flex-wrap: wrap;
15 | }
16 |
--------------------------------------------------------------------------------
/src/styles/TeamRoster.css:
--------------------------------------------------------------------------------
1 | #hero-slots {
2 | display: flex;
3 | flex-wrap: wrap;
4 | flex-direction: row;
5 | justify-content: center;
6 | align-items: center;
7 | width: 35%;
8 | min-width: 380px;
9 | }
10 | #team-roster {
11 | display: flex;
12 | flex-wrap: wrap;
13 | justify-content: space-around;
14 | }
15 | .TeamStats {
16 | width: 40%;
17 | min-width: 575px;
18 | }
19 | @media (max-width: 720px) {
20 | .TeamStats {
21 | width: 100%;
22 | }
23 | #hero-slots {
24 | width: 100%;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/components/HeroSlot.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '../styles/HeroSlot.css';
3 |
4 | const HeroSlot = ({hero, clickAction}) => {
5 | let filled_class = hero.name !== "Empty" ? 'selected' : 'unselected';
6 | let portrait = hero.portrait ?
: null;
7 | return (
8 |
9 |
10 | {portrait}
11 |
12 |
{hero.name}
13 |
14 | );
15 | };
16 |
17 | export default HeroSlot;
18 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import './index.css';
5 | import { createStore } from 'redux';
6 | import reducer from './reducer';
7 | import {trackMouse} from './events';
8 |
9 | document.addEventListener('DOMContentLoaded', () => {
10 | document.querySelector('body').addEventListener('mousemove', (e) => {
11 | trackMouse(e);
12 | });
13 | }, false);
14 |
15 | export const store = createStore(reducer);
16 |
17 | const render = () => ReactDOM.render(, document.getElementById('root'));
18 | render();
19 | store.subscribe(render);
20 |
--------------------------------------------------------------------------------
/src/components/HoverInfo.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '../styles/HoverInfo.css';
3 |
4 | const HoverInfo = ({hero, left, top}) => {
5 | return (
6 |
7 |
8 | | {hero.name} |
9 | | Hitpoints: | {hero.hitpoints} |
10 | | Armor: | {hero.armor} |
11 | | Shields: | {hero.shields} |
12 | | Barriers: | {hero.barrier} |
13 |
14 |
15 | );
16 | };
17 |
18 | export default HoverInfo;
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "overcomp",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "immutable": "^3.8.1",
7 | "react": "15.4.2",
8 | "react-dom": "15.4.2",
9 | "redux": "^3.6.0"
10 | },
11 | "devDependencies": {
12 | "gh-pages": "^0.12.0",
13 | "react-scripts": "0.9.3"
14 | },
15 | "homepage": "https://captainstack.github.io/overcomp",
16 | "scripts": {
17 | "start": "react-scripts start",
18 | "build": "react-scripts build",
19 | "predeploy": "npm run build",
20 | "deploy": "gh-pages -d build",
21 | "test": "react-scripts test --env=jsdom",
22 | "eject": "react-scripts eject"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/TeamRoster.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import HeroSlot from './HeroSlot';
3 | import TeamStats from './TeamStats';
4 | import '../styles/TeamRoster.css';
5 | import { slotClick } from '../events';
6 |
7 | const TeamRoster = ({selected_heroes, clickAction}) => {
8 | let hero_slots = selected_heroes.map((selected_hero) =>
9 | );
10 | return (
11 |
12 |
{hero_slots}
13 |
14 |
15 | );
16 | };
17 |
18 | export default TeamRoster;
19 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .row {
2 | display: flex;
3 | flex-direction: row;
4 | flex-wrap: wrap;
5 | justify-content: center;
6 | align-items: top;
7 | }
8 | #logo {
9 | margin-left: auto;
10 | margin-right: auto;
11 | width: 25em;
12 | margin-bottom: 2em;
13 | }
14 | footer {
15 | display: flex;
16 | justify-content: space-between;
17 | font-size: 0.75em;
18 | border-top: 1px solid rgba(51, 51, 51, 0.5);
19 | margin-top: 1em;
20 | padding-top: 0.5em;
21 | }
22 | footer a {
23 | text-decoration: none;
24 | color: rgba(51, 51, 51, 1);
25 | }
26 | footer a:hover {
27 | color: rgba(51, 51, 51, 0.6);
28 | }
29 | @media (max-width: 500px) {
30 | #logo {
31 | width: 100%;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/styles/HeroSlot.css:
--------------------------------------------------------------------------------
1 | .hero-slot {
2 | padding: 0.5em;
3 | display: inline-block;
4 | width: 112px;
5 | }
6 | .hero-slot h3 {
7 | font: italic normal normal 10px Big Noodle Too,impact,sans-serif;
8 | font-size: 1.2rem;
9 | line-height: normal;
10 | letter-spacing: .08rem;
11 | margin: 0;
12 | text-align: center;
13 | }
14 | .slot-portrait {
15 | border: 1px solid black;
16 | width: 112px;
17 | height: 100px;
18 | background-color: rgb(200, 199, 207);
19 | border-radius: 0.4em;
20 | overflow: hidden;
21 | }
22 | .slot-portrait.selected {
23 | background-color: lightblue;
24 | }
25 | .selected.slot-portrait:hover {
26 | cursor: pointer;
27 | border: 1px solid #f9ab4f;
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/HeroButton.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '../styles/HeroButton.css';
3 |
4 | const HeroButton = ({hero, roster, clickAction, hoverAction, hoverExitAction}) => {
5 | let selected_class = roster.findIndex((roster_hero) => hero.id === roster_hero.id) !== -1 ? 'selected' : 'unselected';
6 | return (
7 |
8 |
11 |
{hero.name}
12 |
13 | );
14 | };
15 |
16 | export default HeroButton;
17 |
--------------------------------------------------------------------------------
/src/components/HeroSet.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import HeroButton from './HeroButton';
3 | import '../styles/HeroSet.css';
4 | import { buttonClick, heroHover, hoverExit } from '../events';
5 |
6 | const HeroSet = ({heroes_set, selected_heroes, category}) => {
7 | let hero_buttons = heroes_set.map((hero) =>
8 | );
14 | return (
15 |
16 |
{category}
17 | {hero_buttons}
18 |
19 | );
20 | };
21 |
22 | export default HeroSet;
23 |
--------------------------------------------------------------------------------
/src/reducer.js:
--------------------------------------------------------------------------------
1 | import {add_hero_to_roster, remove_hero_from_roster,
2 | add_hover_hero, remove_hover_hero, update_mouse_position, INITIAL_STATE} from './core';
3 |
4 | export default function reducer(state = INITIAL_STATE, action) {
5 | switch (action.type) {
6 | case 'ADD_HERO_TO_ROSTER':
7 | return add_hero_to_roster(state, action.hero);
8 | case 'REMOVE_HERO_FROM_ROSTER':
9 | return remove_hero_from_roster(state, action.hero);
10 | case 'ADD_HOVER_HERO':
11 | return add_hover_hero(state, action.hero);
12 | case 'REMOVE_HOVER_HERO':
13 | return remove_hover_hero(state);
14 | case 'UPDATE_MOUSE_POSITION':
15 | return update_mouse_position(state, action.event);
16 | default:
17 | return state;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/events.js:
--------------------------------------------------------------------------------
1 | import { store } from './index'
2 |
3 | export const buttonClick = clicked_hero => e => {
4 | let roster = store.getState().get('team');
5 | if (roster.indexOf(clicked_hero) === -1) {
6 | store.dispatch({ type: 'ADD_HERO_TO_ROSTER', hero: clicked_hero });
7 | } else {
8 | store.dispatch({ type: 'REMOVE_HERO_FROM_ROSTER', hero: clicked_hero });
9 | }
10 | }
11 |
12 | export const slotClick = slot_id => e => {
13 | let roster = store.getState().get('team');
14 | let clicked_index = roster.findIndex((slot) => slot.id === slot_id);
15 | if (roster.get(clicked_index).name !== "Empty") {
16 | store.dispatch({ type: 'REMOVE_HERO_FROM_ROSTER', hero: roster.get(clicked_index) });
17 | }
18 | }
19 |
20 | export const heroHover = hero => e => {
21 | store.dispatch({ type: 'ADD_HOVER_HERO', hero: hero });
22 | }
23 |
24 | export const hoverExit = hero => e => {
25 | store.dispatch({ type: 'REMOVE_HOVER_HERO' });
26 | }
27 |
28 | export const trackMouse = e => {
29 | store.dispatch({ type: 'UPDATE_MOUSE_POSITION', event: e });
30 | }
31 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './App.css';
3 | import TeamRoster from './components/TeamRoster';
4 | import TeamSelect from './components/TeamSelect';
5 | import HoverInfo from './components/HoverInfo';
6 |
7 | const App = ({state}) => {
8 |
9 | let hover_info;
10 | if (state.get('hover_hero')) {
11 | hover_info =
12 | }
13 |
14 | return (
15 |
16 |

17 |
18 |
19 | {hover_info}
20 |
24 |
25 | );
26 | };
27 |
28 | export default App;
29 |
--------------------------------------------------------------------------------
/src/components/TeamSelect.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import HeroSet from './HeroSet';
3 | import '../styles/TeamSelect.css';
4 | import { heroes } from '../heroes';
5 |
6 | const TeamSelect = ({selected_heroes, buttonClick}) => {
7 | let offense_heroes = heroes.filter((hero) => hero.category === 'offense');
8 | let defense_heroes = heroes.filter((hero) => hero.category === 'defense');
9 | let tank_heroes = heroes.filter((hero) => hero.category === 'tank');
10 | let support_heroes = heroes.filter((hero) => hero.category === 'support');
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | export default TeamSelect;
22 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
16 | Overcomp
17 |
18 |
19 |
20 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/styles/HeroButton.css:
--------------------------------------------------------------------------------
1 | .hero-button {
2 | cursor: pointer;
3 | display: inline-block;
4 | }
5 | .clipping-mask.selected {
6 | background-color: lightblue;
7 | }
8 | .hero-image {
9 | display: inline-block;
10 | width: calc(100% + 10px);
11 | height: 100%;
12 | background-size: auto 100%;
13 | background-position: 50% 50%;
14 | position: relative;
15 | -webkit-transform: skew(-25rad) translateX(-5px);
16 | -ms-transform: skew(-25rad) translateX(-5px);
17 | transform: skew(-25rad) translateX(-5px);
18 | }
19 | .clipping-mask {
20 | display: block;
21 | position: relative;
22 | width: 70px;
23 | height: 84px;
24 | margin: 2.4px 6px;
25 | border-top: 3px solid #f0edf2;
26 | border-bottom: 3px solid #f0edf2;
27 | border-left: 3px solid transparent;
28 | border-right: 3px solid transparent;
29 | background-color: rgba(51,51,51,.9);
30 | background-clip: padding-box;
31 | overflow: hidden;
32 | border-radius: 7px;
33 | transform: skew(25rad);
34 | }
35 | .clipping-mask:hover {
36 | background-color: #f9ab4f;
37 | border: 3px solid #f0edf2;
38 | }
39 | .hero-portrait-label {
40 | position: relative;
41 | display: inline-block;
42 | top: -4.2px;
43 | margin-left: -10px;
44 | padding: 1px 10px;
45 | font-size: 1.3rem;
46 | color: #333;
47 | white-space: nowrap;
48 | font: italic normal normal 14px Big Noodle Too,impact,sans-serif;
49 | }
50 |
--------------------------------------------------------------------------------
/src/core.js:
--------------------------------------------------------------------------------
1 | import { List, Map } from 'immutable';
2 |
3 | let empty_hero = id => { return {id: id, name: "Empty", hitpoints: 0, armor: 0, shields: 0, barrier: 0, healing: 0, tank: 0} }
4 | export const INITIAL_STATE = Map({
5 | team: List([empty_hero(25), empty_hero(26), empty_hero(27), empty_hero(28), empty_hero(29), empty_hero(30)]),
6 | hover_hero: null,
7 | mouse: Map({x: null, y: null})
8 | });
9 |
10 | export const add_hero_to_roster = (state, hero) => {
11 | let empty_index = state.get('team').findIndex((roster_hero) => roster_hero.name === "Empty");
12 | if (empty_index !== -1) {
13 | return state.set('team', state.get('team').set(empty_index, hero));
14 | } else {
15 | return state;
16 | }
17 | }
18 |
19 | export const remove_hero_from_roster = (state, hero) => {
20 | let hero_index = state.get('team').findIndex((roster_hero) => roster_hero === hero);
21 | if (hero_index !== -1) {
22 | return state.set('team', state.get('team').set(hero_index, empty_hero(hero_index + 25)));
23 | } else {
24 | return state;
25 | }
26 | }
27 |
28 | export const add_hover_hero = (state, hero) => {
29 | return state.set('hover_hero', hero);
30 | }
31 |
32 | export const remove_hover_hero = (state) => {
33 | return state.set('hover_hero', null);
34 | }
35 |
36 | export const update_mouse_position = (state, event) => {
37 | let mouse = state.get('mouse');
38 | let updateX = mouse.set('x', event.pageX);
39 | let updateY = updateX.set('y', event.pageY);
40 | return state.set('mouse', updateY);
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/TeamStats.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '../styles/TeamStats.css';
3 |
4 | const TeamStats = ({team}) => {
5 | let hitpoints = team.reduce((acc, hero) => acc + hero.hitpoints, 0);
6 | let armor = team.reduce((acc, hero) => acc + hero.armor, 0);
7 | let shields = team.reduce((acc, hero) => acc + hero.shields, 0);
8 | let healing = team.reduce((acc, hero) => acc + hero.healing, 0);
9 | let tank = team.reduce((acc, hero) => acc + hero.tank, 0);
10 | let barrier = team.reduce((acc, hero) => acc + hero.barrier, 0);
11 |
12 | let healing_message;
13 | let tank_message;
14 | let healing_color;
15 | let tank_color;
16 |
17 | if (healing === 0) { healing_message = 'No healer'; healing_color = 'indianred'; }
18 | else if (healing > 0 && healing < 4) { healing_message = 'Need more healing'; healing_color = 'indianred'; }
19 | else if (healing > 3 && healing < 6) { healing_message = 'Low healing'; healing_color = 'palegoldenrod'; }
20 | else { healing_color = 'darkseagreen'; healing_message = 'Good healing'; }
21 |
22 | if (tank === 0) { tank_message = 'No tank'; tank_color = 'indianred'; }
23 | else if (tank === 1) { tank_message = 'Need more tank'; tank_color = 'indianred'; }
24 | else if (tank < 3) { tank_message = 'Low tank'; tank_color = 'palegoldenrod'; }
25 | else { tank_color = 'darkseagreen'; tank_message = 'Good tank'; }
26 |
27 | return (
28 |
29 |
Team Stats
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | | HP/Armor/Shields: |
39 |
40 |
41 |
42 |
43 | |
44 | {hitpoints + '/' + armor + '/' + shields} |
45 |
46 |
47 | | Barriers: |
48 | |
49 | {barrier} |
50 |
51 |
52 | | Healing: |
53 | |
54 | {healing_message} |
55 |
56 |
57 | | Tank: |
58 | |
59 | {tank_message} |
60 |
61 |
62 |
63 |
64 | );
65 | };
66 |
67 | export default TeamStats;
68 |
--------------------------------------------------------------------------------
/src/heroes.js:
--------------------------------------------------------------------------------
1 | export const heroes = [{
2 | id: 1,
3 | name: "Genji",
4 | hitpoints: 200,
5 | armor: 0,
6 | shields: 0,
7 | barrier: 0,
8 | portrait: "genji-portrait.png",
9 | category: "offense",
10 | healing: 0,
11 | tank: 0
12 | },
13 | {
14 | id: 2,
15 | name: "McCree",
16 | hitpoints: 200,
17 | armor: 0,
18 | shields: 0,
19 | barrier: 0,
20 | portrait: "mccree-portrait.png",
21 | category: "offense",
22 | healing: 0,
23 | tank: 0
24 | },
25 | {
26 | id: 3,
27 | name: "Pharah",
28 | hitpoints: 200,
29 | armor: 0,
30 | shields: 0,
31 | barrier: 0,
32 | portrait: "pharah-portrait.png",
33 | category: "offense",
34 | healing: 0,
35 | tank: 0
36 | },
37 | {
38 | id: 4,
39 | name: "Reaper",
40 | hitpoints: 250,
41 | armor: 0,
42 | shields: 0,
43 | barrier: 0,
44 | portrait: "reaper-portrait.png",
45 | category: "offense",
46 | healing: 0,
47 | tank: 0
48 | },
49 | {
50 | id: 5,
51 | name: "Soldier: 76",
52 | hitpoints: 200,
53 | armor: 0,
54 | shields: 0,
55 | barrier: 0,
56 | portrait: "soldier-76-portrait.png",
57 | category: "offense",
58 | healing: 2,
59 | tank: 0
60 | },
61 | {
62 | id: 6,
63 | name: "Sombra",
64 | hitpoints: 200,
65 | armor: 0,
66 | shields: 0,
67 | barrier: 0,
68 | portrait: "sombra-portrait.png",
69 | category: "offense",
70 | healing: 0,
71 | tank: 0
72 | },
73 | {
74 | id: 7,
75 | name: "Tracer",
76 | hitpoints: 150,
77 | armor: 0,
78 | shields: 0,
79 | barrier: 0,
80 | portrait: "tracer-portrait.png",
81 | category: "offense",
82 | healing: 0,
83 | tank: 0
84 | },
85 | {
86 | id: 8,
87 | name: "Bastion",
88 | hitpoints: 200,
89 | armor: 100,
90 | shields: 0,
91 | barrier: 0,
92 | portrait: "bastion-portrait.png",
93 | category: "defense",
94 | healing: 1,
95 | tank: 0
96 | },
97 | {
98 | id: 9,
99 | name: "Hanzo",
100 | hitpoints: 200,
101 | armor: 0,
102 | shields: 0,
103 | barrier: 0,
104 | portrait: "hanzo-portrait.png",
105 | category: "defense",
106 | healing: 0,
107 | tank: 0
108 | },
109 | {
110 | id: 10,
111 | name: "Junkrat",
112 | hitpoints: 200,
113 | armor: 0,
114 | shields: 0,
115 | barrier: 0,
116 | portrait: "junkrat-portrait.png",
117 | category: "defense",
118 | healing: 0,
119 | tank: 0
120 | },
121 | {
122 | id: 11,
123 | name: "Mei",
124 | hitpoints: 250,
125 | armor: 0,
126 | shields: 0,
127 | barrier: 500,
128 | portrait: "mei-portrait.png",
129 | category: "defense",
130 | healing: 1,
131 | tank: 0
132 | },
133 | {
134 | id: 12,
135 | name: "Torbjörn",
136 | hitpoints: 200,
137 | armor: 0,
138 | shields: 0,
139 | barrier: 0,
140 | portrait: "torbjorn-portrait.png",
141 | category: "defense",
142 | healing: 0,
143 | tank: 0
144 | },
145 | {
146 | id: 13,
147 | name: "Widowmaker",
148 | hitpoints: 200,
149 | armor: 0,
150 | shields: 0,
151 | barrier: 0,
152 | portrait: "widowmaker-portrait.png",
153 | category: "defense",
154 | healing: 0,
155 | tank: 0
156 | },
157 | {
158 | id: 14,
159 | name: "D.Va",
160 | hitpoints: 400,
161 | armor: 200,
162 | shields: 0,
163 | barrier: 1500,
164 | portrait: "d-va-portrait.png",
165 | category: "tank",
166 | healing: 0,
167 | tank: 2
168 | },
169 | {
170 | id: 15,
171 | name: "Orisa",
172 | hitpoints: 200,
173 | armor: 200,
174 | shields: 0,
175 | barrier: 900,
176 | portrait: "orisa-portrait.png",
177 | category: "tank",
178 | healing: 0,
179 | tank: 1
180 | },
181 | {
182 | id: 16,
183 | name: "Reinhardt",
184 | hitpoints: 300,
185 | armor: 200,
186 | shields: 0,
187 | barrier: 2000,
188 | portrait: "reinhardt-portrait.png",
189 | category: "tank",
190 | healing: 0,
191 | tank: 2
192 | },
193 | {
194 | id: 17,
195 | name: "Roadhog",
196 | hitpoints: 600,
197 | armor: 0,
198 | shields: 0,
199 | barrier: 0,
200 | portrait: "roadhog-portrait.png",
201 | category: "tank",
202 | healing: 1,
203 | tank: 1
204 | },
205 | {
206 | id: 18,
207 | name: "Winston",
208 | hitpoints: 400,
209 | armor: 100,
210 | shields: 0,
211 | barrier: 600,
212 | portrait: "winston-portrait.png",
213 | category: "tank",
214 | healing: 0,
215 | tank: 1
216 | },
217 | {
218 | id: 19,
219 | name: "Zarya",
220 | hitpoints: 200,
221 | armor: 0,
222 | shields: 200,
223 | barrier: 400,
224 | portrait: "zarya-portrait.png",
225 | category: "tank",
226 | healing: 0,
227 | tank: 1
228 | },
229 | {
230 | id: 20,
231 | name: "Ana",
232 | hitpoints: 200,
233 | armor: 0,
234 | shields: 0,
235 | barrier: 0,
236 | portrait: "ana-portrait.png",
237 | category: "support",
238 | healing: 2,
239 | tank: 0
240 | },
241 | {
242 | id: 21,
243 | name: "Lúcio",
244 | hitpoints: 200,
245 | armor: 0,
246 | shields: 0,
247 | barrier: 0,
248 | portrait: "lucio-portrait.png",
249 | category: "support",
250 | healing: 4,
251 | tank: 0
252 | },
253 | {
254 | id: 22,
255 | name: "Mercy",
256 | hitpoints: 200,
257 | armor: 0,
258 | shields: 0,
259 | barrier: 0,
260 | portrait: "mercy-portrait.png",
261 | category: "support",
262 | healing: 4,
263 | tank: 0
264 | },
265 | {
266 | id: 23,
267 | name: "Symmetra",
268 | hitpoints: 100,
269 | armor: 0,
270 | shields: 100,
271 | barrier: 1025,
272 | portrait: "symmetra-portrait.png",
273 | category: "support",
274 | healing: 0,
275 | tank: 0
276 | },
277 | {
278 | id: 24,
279 | name: "Zenyatta",
280 | hitpoints: 50,
281 | armor: 0,
282 | shields: 150,
283 | barrier: 0,
284 | portrait: "zenyatta-portrait.png",
285 | category: "support",
286 | healing: 2,
287 | tank: 0
288 | }];
--------------------------------------------------------------------------------