├── src
├── components
│ ├── map
│ │ ├── fields
│ │ │ ├── base.js
│ │ │ └── Poison
│ │ │ │ ├── visual
│ │ │ │ ├── sprite.png
│ │ │ │ └── index.js
│ │ │ │ └── index.js
│ │ ├── terrain
│ │ │ ├── base.js
│ │ │ └── GroundSpikes
│ │ │ │ ├── visual
│ │ │ │ ├── sprite.png
│ │ │ │ └── index.js
│ │ │ │ └── index.js
│ │ └── tiles
│ │ │ ├── Grass
│ │ │ ├── visual
│ │ │ │ ├── sprite.png
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ │ ├── Sand
│ │ │ ├── visual
│ │ │ │ ├── sprite.png
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ │ ├── Water
│ │ │ ├── visual
│ │ │ │ ├── item_629.png
│ │ │ │ ├── item_630.png
│ │ │ │ ├── item_631.png
│ │ │ │ ├── item_632.png
│ │ │ │ ├── item_633.png
│ │ │ │ ├── item_634.png
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ │ ├── GrassBorder
│ │ │ ├── visual
│ │ │ │ ├── item_304.png
│ │ │ │ ├── item_307.png
│ │ │ │ ├── item_308.png
│ │ │ │ ├── item_309.png
│ │ │ │ ├── item_310.png
│ │ │ │ ├── item_311.png
│ │ │ │ ├── item_312.png
│ │ │ │ ├── item_313.png
│ │ │ │ ├── item_314.png
│ │ │ │ ├── item_315.png
│ │ │ │ ├── item_407.png
│ │ │ │ ├── item_618.png
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ │ ├── SandBorder
│ │ │ ├── visual
│ │ │ │ ├── item_979.png
│ │ │ │ ├── item_980.png
│ │ │ │ ├── item_981.png
│ │ │ │ ├── item_983.png
│ │ │ │ ├── item_984.png
│ │ │ │ ├── item_985.png
│ │ │ │ ├── item_986.png
│ │ │ │ ├── item_987.png
│ │ │ │ ├── item_988.png
│ │ │ │ ├── item_989.png
│ │ │ │ ├── item_990.png
│ │ │ │ ├── item_991.png
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ │ └── base.js
│ ├── alive
│ │ ├── base.js
│ │ └── Player
│ │ │ ├── visual
│ │ │ ├── idle.png
│ │ │ ├── walking.png
│ │ │ └── index.js
│ │ │ └── index.js
│ ├── effects
│ │ └── Poisoned
│ │ │ ├── visual
│ │ │ ├── sprite.png
│ │ │ └── index.js
│ │ │ └── index.js
│ └── hud
│ │ ├── ScreenMessage
│ │ └── index.js
│ │ └── NameBar
│ │ └── index.js
├── store
│ ├── actions
│ │ ├── developer.js
│ │ ├── hud.js
│ │ └── player.js
│ ├── reducers
│ │ ├── hud.js
│ │ ├── developer.js
│ │ └── player.js
│ └── index.js
├── assets
│ └── outfits
│ │ ├── orc_warlord_idle.png
│ │ └── orc_warlord_walking.png
├── styles
│ ├── effect.js
│ ├── field.js
│ ├── tile.js
│ ├── borderedText.js
│ ├── alive.js
│ └── base.js
├── map
│ ├── index.js
│ └── functions.js
├── functions
│ └── gameUtils.js
├── hooks
│ └── useKeyPress.js
└── scss
│ └── index.scss
├── ss.png
├── index.html
├── package.json
├── README.md
├── index.js
├── .gitignore
└── map.js
/src/components/map/fields/base.js:
--------------------------------------------------------------------------------
1 | export default {
2 | effect: {}
3 | };
--------------------------------------------------------------------------------
/ss.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/ss.png
--------------------------------------------------------------------------------
/src/components/alive/base.js:
--------------------------------------------------------------------------------
1 | export default {
2 | squareSize: 64,
3 | effects: {},
4 | };
--------------------------------------------------------------------------------
/src/components/map/terrain/base.js:
--------------------------------------------------------------------------------
1 | export default {
2 | zIndex: 1,
3 | walkable: false
4 | };
--------------------------------------------------------------------------------
/src/store/actions/developer.js:
--------------------------------------------------------------------------------
1 | export const toggleDebugMode = () => ({
2 | type: 'TOGGLE_DEBUG_MODE',
3 | });
--------------------------------------------------------------------------------
/src/store/actions/hud.js:
--------------------------------------------------------------------------------
1 | export const setMessage = (text) => ({
2 | type: 'SET_MESSAGE',
3 | payload: text,
4 | });
--------------------------------------------------------------------------------
/src/assets/outfits/orc_warlord_idle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/assets/outfits/orc_warlord_idle.png
--------------------------------------------------------------------------------
/src/assets/outfits/orc_warlord_walking.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/assets/outfits/orc_warlord_walking.png
--------------------------------------------------------------------------------
/src/components/alive/Player/visual/idle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/alive/Player/visual/idle.png
--------------------------------------------------------------------------------
/src/components/alive/Player/visual/walking.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/alive/Player/visual/walking.png
--------------------------------------------------------------------------------
/src/components/effects/Poisoned/visual/sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/effects/Poisoned/visual/sprite.png
--------------------------------------------------------------------------------
/src/components/map/tiles/Grass/visual/sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/Grass/visual/sprite.png
--------------------------------------------------------------------------------
/src/components/map/tiles/Sand/visual/sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/Sand/visual/sprite.png
--------------------------------------------------------------------------------
/src/components/map/fields/Poison/visual/sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/fields/Poison/visual/sprite.png
--------------------------------------------------------------------------------
/src/components/map/tiles/Water/visual/item_629.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/Water/visual/item_629.png
--------------------------------------------------------------------------------
/src/components/map/tiles/Water/visual/item_630.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/Water/visual/item_630.png
--------------------------------------------------------------------------------
/src/components/map/tiles/Water/visual/item_631.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/Water/visual/item_631.png
--------------------------------------------------------------------------------
/src/components/map/tiles/Water/visual/item_632.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/Water/visual/item_632.png
--------------------------------------------------------------------------------
/src/components/map/tiles/Water/visual/item_633.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/Water/visual/item_633.png
--------------------------------------------------------------------------------
/src/components/map/tiles/Water/visual/item_634.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/Water/visual/item_634.png
--------------------------------------------------------------------------------
/src/components/map/tiles/GrassBorder/visual/item_304.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/GrassBorder/visual/item_304.png
--------------------------------------------------------------------------------
/src/components/map/tiles/GrassBorder/visual/item_307.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/GrassBorder/visual/item_307.png
--------------------------------------------------------------------------------
/src/components/map/tiles/GrassBorder/visual/item_308.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/GrassBorder/visual/item_308.png
--------------------------------------------------------------------------------
/src/components/map/tiles/GrassBorder/visual/item_309.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/GrassBorder/visual/item_309.png
--------------------------------------------------------------------------------
/src/components/map/tiles/GrassBorder/visual/item_310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/GrassBorder/visual/item_310.png
--------------------------------------------------------------------------------
/src/components/map/tiles/GrassBorder/visual/item_311.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/GrassBorder/visual/item_311.png
--------------------------------------------------------------------------------
/src/components/map/tiles/GrassBorder/visual/item_312.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/GrassBorder/visual/item_312.png
--------------------------------------------------------------------------------
/src/components/map/tiles/GrassBorder/visual/item_313.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/GrassBorder/visual/item_313.png
--------------------------------------------------------------------------------
/src/components/map/tiles/GrassBorder/visual/item_314.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/GrassBorder/visual/item_314.png
--------------------------------------------------------------------------------
/src/components/map/tiles/GrassBorder/visual/item_315.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/GrassBorder/visual/item_315.png
--------------------------------------------------------------------------------
/src/components/map/tiles/GrassBorder/visual/item_407.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/GrassBorder/visual/item_407.png
--------------------------------------------------------------------------------
/src/components/map/tiles/GrassBorder/visual/item_618.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/GrassBorder/visual/item_618.png
--------------------------------------------------------------------------------
/src/components/map/tiles/SandBorder/visual/item_979.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/SandBorder/visual/item_979.png
--------------------------------------------------------------------------------
/src/components/map/tiles/SandBorder/visual/item_980.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/SandBorder/visual/item_980.png
--------------------------------------------------------------------------------
/src/components/map/tiles/SandBorder/visual/item_981.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/SandBorder/visual/item_981.png
--------------------------------------------------------------------------------
/src/components/map/tiles/SandBorder/visual/item_983.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/SandBorder/visual/item_983.png
--------------------------------------------------------------------------------
/src/components/map/tiles/SandBorder/visual/item_984.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/SandBorder/visual/item_984.png
--------------------------------------------------------------------------------
/src/components/map/tiles/SandBorder/visual/item_985.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/SandBorder/visual/item_985.png
--------------------------------------------------------------------------------
/src/components/map/tiles/SandBorder/visual/item_986.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/SandBorder/visual/item_986.png
--------------------------------------------------------------------------------
/src/components/map/tiles/SandBorder/visual/item_987.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/SandBorder/visual/item_987.png
--------------------------------------------------------------------------------
/src/components/map/tiles/SandBorder/visual/item_988.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/SandBorder/visual/item_988.png
--------------------------------------------------------------------------------
/src/components/map/tiles/SandBorder/visual/item_989.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/SandBorder/visual/item_989.png
--------------------------------------------------------------------------------
/src/components/map/tiles/SandBorder/visual/item_990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/SandBorder/visual/item_990.png
--------------------------------------------------------------------------------
/src/components/map/tiles/SandBorder/visual/item_991.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/tiles/SandBorder/visual/item_991.png
--------------------------------------------------------------------------------
/src/components/map/terrain/GroundSpikes/visual/sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rikezenho/tibia-js-experiment/HEAD/src/components/map/terrain/GroundSpikes/visual/sprite.png
--------------------------------------------------------------------------------
/src/store/reducers/hud.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'SET_MESSAGE': (state, action) => {
3 | return { ...state, hud: { ...state.hud, message: action.payload } };
4 | }
5 | };
--------------------------------------------------------------------------------
/src/styles/effect.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import Base from './base';
3 |
4 | export default styled(Base)`
5 | z-index: ${props => props.zIndex ? props.zIndex : 3};
6 | `;
--------------------------------------------------------------------------------
/src/styles/field.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import Base from './base';
3 |
4 | export default styled(Base)`
5 | z-index: ${props => props.zIndex ? props.zIndex : 1};
6 | `;
--------------------------------------------------------------------------------
/src/styles/tile.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import Base from './base';
3 |
4 | export default styled(Base)`
5 | z-index: ${props => props.zIndex ? props.zIndex : 0};
6 | `;
--------------------------------------------------------------------------------
/src/store/reducers/developer.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'TOGGLE_DEBUG_MODE': (state, action) => {
3 | return { ...state, developer: { ...state.developer, debugMode: !state.developer.debugMode } };
4 | },
5 | };
--------------------------------------------------------------------------------
/src/styles/borderedText.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const rules = `
4 | text-shadow: 1px 1px 1px #000, -1px -1px 1px #000, 1px -1px 1px #000, -1px 1px 1px #000;
5 | `;
6 |
7 | export default styled.div`${rules}`;
--------------------------------------------------------------------------------
/src/components/map/terrain/GroundSpikes/visual/index.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import Base from '../../../../../styles/tile';
3 | import img from './sprite.png';
4 |
5 | export default styled(Base)`
6 | background-image: url(${img});
7 | `;
--------------------------------------------------------------------------------
/src/styles/alive.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import Base from './base';
3 |
4 | export default styled(Base)`
5 | z-index: ${props => props.zIndex ? props.zIndex : 2};
6 | transition: left ${({ speed }) => speed ? speed : '0.25'}s, top ${({ speed }) => speed ? speed : '0.25'}s;
7 | `;
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Tibia JS
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/components/map/terrain/GroundSpikes/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import VisualElement from "./visual";
3 |
4 | const GroundSpikes = (props = {}) => {
5 | const myProps = {
6 | ...props,
7 | walkable: false,
8 | };
9 | return ;
10 | };
11 |
12 | GroundSpikes.metadata = {};
13 |
14 | export default GroundSpikes;
15 |
--------------------------------------------------------------------------------
/src/components/map/tiles/Grass/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { getRandomTile } from "../base";
3 | import VisualElement, { options } from "./visual";
4 |
5 | const Grass = (props = {}) => {
6 | const randomTile = getRandomTile("Grass", options);
7 | return ;
8 | };
9 |
10 | Grass.metadata = {};
11 |
12 | export default Grass;
13 |
--------------------------------------------------------------------------------
/src/components/map/tiles/Sand/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { getRandomTile } from "../base";
3 | import VisualElement, { options } from "./visual";
4 |
5 | const Sand = (props = {}) => {
6 | const randomTile = getRandomTile("Sand", options);
7 | return ;
8 | };
9 |
10 | Sand.metadata = {
11 | slow: 0.5,
12 | };
13 |
14 | export default Sand;
15 |
--------------------------------------------------------------------------------
/src/components/map/tiles/Water/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { getRandomTile } from "../base";
3 | import VisualElement, { options } from "./visual";
4 |
5 | const Water = (props = {}) => {
6 | const randomTile = getRandomTile("Water", options);
7 | return ;
8 | };
9 |
10 | Water.metadata = {
11 | walkable: false,
12 | };
13 |
14 | export default Water;
15 |
--------------------------------------------------------------------------------
/src/components/map/fields/Poison/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import VisualElement from "./visual";
3 | import Poisoned, {
4 | metadata as effectMetadata,
5 | } from "../../../effects/Poisoned";
6 |
7 | const Poison = (props = {}) => {
8 | return ;
9 | };
10 |
11 | Poison.metadata = {
12 | effect: {
13 | name: "poison",
14 | component: Poisoned,
15 | metadata: effectMetadata,
16 | },
17 | };
18 |
19 | export default Poison;
20 |
--------------------------------------------------------------------------------
/src/styles/base.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export default styled.div`
4 | position: absolute;
5 | display: inline-block;
6 | width: ${props => props.width ? props.width : 32};
7 | height: ${props => props.height ? props.height : 32};
8 | z-index: ${props => props.zIndex ? props.zIndex : 0};
9 | top: ${props => typeof props.top === 'undefined' ? 'auto' : props.top};
10 | left: ${props => typeof props.left === 'undefined' ? 'auto' : props.left};
11 | `;
12 |
13 |
--------------------------------------------------------------------------------
/src/components/map/fields/Poison/visual/index.js:
--------------------------------------------------------------------------------
1 | import styled, { keyframes } from 'styled-components';
2 | import Base from '../../../../../styles/field';
3 | import img from './sprite.png';
4 |
5 | const animationKeyframes = keyframes`
6 | 0% {
7 | background-position: 0 0;
8 | }
9 | 100% {
10 | background-position: 0 -100%;
11 | }
12 | `;
13 |
14 | export default styled(Base)`
15 | background-image: url(${img});
16 | animation: ${animationKeyframes} 1s steps(5) infinite;
17 | `;
--------------------------------------------------------------------------------
/src/components/map/tiles/base.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const getRandomTile = (name, options = {}) => {
4 | const keys = Object.keys(options);
5 |
6 | if (!keys || !keys.length) {
7 | console.error(`WARNING: ${name} doesn't have more than one visual element!`);
8 | return null;
9 | }
10 |
11 | const randomKey = keys[Math.floor(Math.random() * keys.length)];
12 |
13 | return randomKey;
14 | };
15 |
16 | export default {
17 | zIndex: 0,
18 | walkable: true
19 | };
--------------------------------------------------------------------------------
/src/components/map/tiles/SandBorder/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import VisualElement, { options } from "./visual";
3 |
4 | const SandBorder = (props = {}) => {
5 | const { type } = props;
6 | if (typeof options[type] === "undefined") {
7 | console.error(`WARNING: SandBorder with type ${type} not found!`);
8 | return null;
9 | }
10 |
11 | return ;
12 | };
13 |
14 | SandBorder.metadata = {
15 | walkable: true,
16 | slow: 0.35,
17 | };
18 |
19 | export default SandBorder;
20 |
--------------------------------------------------------------------------------
/src/components/map/tiles/GrassBorder/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import VisualElement, { options } from "./visual";
3 |
4 | const GrassBorder = (props = {}) => {
5 | const { type } = props;
6 |
7 | if (typeof options[type] === "undefined") {
8 | console.error(`WARNING: GrassBorder with type ${type} not found!`);
9 | return null;
10 | }
11 |
12 | return ;
13 | };
14 |
15 | GrassBorder.metadata = {
16 | walkable: true,
17 | slow: 0.15,
18 | };
19 |
20 | export default GrassBorder;
21 |
--------------------------------------------------------------------------------
/src/map/index.js:
--------------------------------------------------------------------------------
1 | import { prepareStack } from './functions';
2 | import stringMap, { mapInfo } from '../../map';
3 |
4 | const [tileWidth, tileHeight] = [32, 32];
5 | const map = JSON.parse(stringMap);
6 |
7 | const mapParser = () => {
8 | const parsedMap = map;
9 |
10 | Object.keys(map).forEach((item, index) => {
11 | const [x, y, z] = item.split(':');
12 | const stack = map[item];
13 | parsedMap[item] = prepareStack(stack, { index, left: x * tileWidth, top: y * tileHeight, axis: item });
14 | });
15 |
16 | return {
17 | map: parsedMap,
18 | tileWidth,
19 | tileHeight,
20 | mapInfo,
21 | }
22 | };
23 |
24 | export default mapParser();
--------------------------------------------------------------------------------
/src/functions/gameUtils.js:
--------------------------------------------------------------------------------
1 | export const log = (type, ...message) => {
2 | const groupStyles = (obj = {}) => {
3 | const keys = Object.keys(obj);
4 | const values = Object.values(obj);
5 | return `font-size: 16px; padding: 3px; ${keys.map((item, index) => `${item}:${values[index]};`).join('')}`;
6 | };
7 | const styleConfig = {
8 | 'store': {
9 | background: '#000',
10 | color: '#FFF',
11 | },
12 | 'game': {
13 | background: '#B7FFDC',
14 | },
15 | 'player': {
16 | background: '#CCC',
17 | }
18 | };
19 |
20 | return console.log(`%c${type} log`, groupStyles(styleConfig[type]), ...message);
21 | };
--------------------------------------------------------------------------------
/src/components/map/tiles/Grass/visual/index.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 | import Base from '../../../../../styles/tile';
3 | import img from './sprite.png';
4 |
5 | export const options = {
6 | Grass1: css`
7 | background-position: 0 0;
8 | `,
9 | Grass2: css`
10 | background-position: 0 -32px;
11 | `,
12 | Grass3: css`
13 | background-position: -64px 0;
14 | `,
15 | Grass4: css`
16 | background-position: -64px -32px;
17 | `,
18 | };
19 |
20 | export default styled(Base)`
21 | background-image: url(${img});
22 | ${({ tile }) => (
23 | tile
24 | ? options[tile]
25 | : options[Object.keys(options).shift()]
26 | )}
27 | `;
--------------------------------------------------------------------------------
/src/store/actions/player.js:
--------------------------------------------------------------------------------
1 | export const setPlayerPos = (pos) => ({
2 | type: 'SET_PLAYER_POS',
3 | payload: pos,
4 | });
5 | export const loseHealth = (damage) => ({
6 | type: 'LOSE_HP',
7 | payload: damage,
8 | });
9 | export const loseMana = (mana) => ({
10 | type: 'LOSE_MANA',
11 | payload: mana,
12 | });
13 | export const addHealth = (health) => ({
14 | type: 'ADD_HP',
15 | payload: health,
16 | });
17 | export const addMana = (mana) => ({
18 | type: 'ADD_MANA',
19 | payload: mana,
20 | });
21 | export const addMaxHealth = (health) => ({
22 | type: 'PERMANENTLY_ADD_HP',
23 | payload: health,
24 | });
25 | export const addMaxMana = (mana) => ({
26 | type: 'PERMANENTLY_ADD_MANA',
27 | payload: mana,
28 | });
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tibia-js",
3 | "version": "1.0.0",
4 | "license": "MIT",
5 | "scripts": {
6 | "start": "cross-env NODE_ENV=development parcel index.html",
7 | "build": "cross-env NODE_ENV=production parcel build index.html"
8 | },
9 | "browserslist": "> 0.5%, not dead",
10 | "dependencies": {
11 | "@babel/plugin-syntax-dynamic-import": "^7.8.3",
12 | "@babel/polyfill": "^7.8.3",
13 | "cross-env": "^7.0.0",
14 | "react": ">= 16.8.0",
15 | "react-dom": ">= 16.8.0",
16 | "react-is": ">= 16.8.0",
17 | "styled-components": "^5.0.0"
18 | },
19 | "devDependencies": {
20 | "@babel/core": "^7.8.4",
21 | "@parcel/transformer-sass": "^2.10.0",
22 | "parcel": "^2.10.0",
23 | "process": "^0.11.10",
24 | "sass": "^1.25.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/hooks/useKeyPress.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | export default function useKeyPress(targetKey) {
4 | // State for keeping track of whether key is pressed
5 | const [keyPressed, setKeyPressed] = useState(false);
6 |
7 | // If pressed key is our target key then set to true
8 | function downHandler({ key }) {
9 | if (key === targetKey) {
10 | setKeyPressed(true);
11 | }
12 | }
13 |
14 | // If released key is our target key then set to false
15 | const upHandler = ({ key }) => {
16 | if (key === targetKey) {
17 | setKeyPressed(false);
18 | }
19 | };
20 |
21 | // Add event listeners
22 | useEffect(() => {
23 | window.addEventListener("keydown", downHandler);
24 | window.addEventListener("keyup", upHandler);
25 | // Remove event listeners on cleanup
26 | return () => {
27 | window.removeEventListener("keydown", downHandler);
28 | window.removeEventListener("keyup", upHandler);
29 | };
30 | }, []); // Empty array ensures that effect is only run on mount and unmount
31 |
32 | return keyPressed;
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/map/tiles/Sand/visual/index.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 | import Base from '../../../../../styles/tile';
3 | import img from './sprite.png';
4 |
5 | export const options = {
6 | Sand1: css`
7 | background-position: 0 0;
8 | `,
9 | Sand2: css`
10 | background-position: 0 -32px;
11 | `,
12 | Sand3: css`
13 | background-position: 0 -64px;
14 | `,
15 | Sand4: css`
16 | background-position: -32px 0;
17 | `,
18 | Sand5: css`
19 | background-position: -64px 0;
20 | `,
21 | Sand6: css`
22 | background-position: -32px -32px;
23 | `,
24 | Sand7: css`
25 | background-position: -32px -64px;
26 | `,
27 | Sand8: css`
28 | background-position: -64px -32px;
29 | `,
30 | Sand9: css`
31 | background-position: -64px -64px;
32 | `,
33 | };
34 |
35 | export default styled(Base)`
36 | background-image: url(${img});
37 | ${({ tile }) => (
38 | tile
39 | ? options[tile]
40 | : options[Object.keys(options).shift()]
41 | )}
42 | `;
--------------------------------------------------------------------------------
/src/components/hud/ScreenMessage/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import borderedText from '../../../styles/borderedText';
3 | import styled from 'styled-components';
4 | import { setMessage } from '../../../store/actions/hud';
5 | import { store } from '../../../store';
6 |
7 | const Container = styled(borderedText)`
8 | color: white;
9 | text-align: center;
10 | font-weight: bold;
11 | padding-bottom: 3px;
12 | position: absolute;
13 | bottom: 0;
14 | left: 0;
15 | right: 0;
16 | z-index: 9;
17 | `;
18 |
19 | const ScreenMessage = (props) => {
20 | const { state: globalState, dispatch } = React.useContext(store);
21 | const {
22 | hud: {
23 | message
24 | }
25 | } = globalState;
26 |
27 | React.useEffect(() => {
28 | if (message) {
29 | const timer = setTimeout(() => {
30 | dispatch(setMessage(''));
31 | }, 4000);
32 | return () => {
33 | clearTimeout(timer);
34 | };
35 | }
36 | }, [message]);
37 |
38 | return
39 | {message}
40 | ;
41 | };
42 |
43 | export default ScreenMessage;
--------------------------------------------------------------------------------
/src/store/reducers/player.js:
--------------------------------------------------------------------------------
1 | export default {
2 | 'LOSE_HP': (state, action) => {
3 | return { ...state, player: { ...state.player, currentHealth: state.player.currentHealth - action.payload } };
4 | },
5 | 'LOSE_MANA': (state, action) => {
6 | return { ...state, player: { ...state.player, currentMana: state.player.currentMana - action.payload } };
7 | },
8 | 'ADD_HP': (state, action) => {
9 | return { ...state, player: { ...state.player, currentHealth: state.player.currentHealth + action.payload } };
10 | },
11 | 'ADD_MANA': (state, action) => {
12 | return { ...state, player: { ...state.player, currentMana: state.player.currentMana + action.payload } };
13 | },
14 | 'PERMANENTLY_ADD_HP': (state, action) => {
15 | return { ...state, player: { ...state.player, maxHealth: state.player.maxHealth + action.payload } };
16 | },
17 | 'PERMANENTLY_ADD_MANA': (state, action) => {
18 | return { ...state, player: { ...state.player, maxMana: state.player.maxMana + action.payload } };
19 | },
20 | 'SET_PLAYER_POS': (state, action) => {
21 | return { ...state, player: { ...state.player, pos: action.payload } };
22 | },
23 | };
--------------------------------------------------------------------------------
/src/components/effects/Poisoned/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import VisualElement from './visual';
3 | import { loseHealth } from '../../../store/actions/player';
4 | import { setMessage } from '../../../store/actions/hud';
5 | import { store } from '../../../store';
6 |
7 | export const metadata = {
8 | damage: 20,
9 | interval: 5 * 1000,
10 | duration: 30 * 1000,
11 | animationDuration: 1000,
12 | message: `You are poisoned.`,
13 | };
14 |
15 | const Poisoned = (props = {}) => {
16 | const { state: globalState, dispatch } = React.useContext(store);
17 |
18 | const [times, setTimes] = React.useState(Math.ceil(metadata.duration / metadata.interval));
19 | const [doEffect, setDoEffect] = React.useState(false);
20 |
21 | React.useEffect(() => {
22 | if (times) {
23 | setTimeout(() => {
24 | setDoEffect(true);
25 | setTimes(times - 1);
26 | setTimeout(() => setDoEffect(false), metadata.animationDuration);
27 | dispatch(setMessage(`You lose ${metadata.damage} hitpoints.`));
28 | dispatch(loseHealth(metadata.damage));
29 | }, metadata.interval);
30 | }
31 | }, [times]);
32 |
33 | return ;
36 | };
37 |
38 | export default Poisoned;
--------------------------------------------------------------------------------
/src/components/effects/Poisoned/visual/index.js:
--------------------------------------------------------------------------------
1 | import styled, { keyframes, css } from 'styled-components';
2 | import Base from '../../../../styles/effect';
3 | import img from './sprite.png';
4 | import { rules as borderedText } from '../../../../styles/borderedText';
5 |
6 | const animationKeyframes = keyframes`
7 | 0% {
8 | background-position: 0 0;
9 | }
10 | 100% {
11 | background-position: 0 100%;
12 | }
13 | `;
14 |
15 | const damageKeyframes = keyframes`
16 | 0% {
17 | transform: translateY(0);
18 | }
19 | 100% {
20 | transform: translateY(-20px);
21 | }
22 | `;
23 |
24 | const animation = css`
25 | animation: ${animationKeyframes} ${({ animationDuration }) => animationDuration ? `${animationDuration / 1000}s` : 0} steps(11);
26 | &:before {
27 | content: "${({ damage }) => damage}";
28 | color: #00C103;
29 | position: absolute;
30 | left: 20%;
31 | bottom: 20%;
32 | font-weight: bold;
33 | ${borderedText}
34 | z-index: 3;
35 | animation: ${damageKeyframes} ${({ animationDuration }) => animationDuration ? `${animationDuration / 1000}s` : 0};
36 | }
37 | `;
38 |
39 | export default styled(Base)`
40 | background-image: url(${img});
41 | background-repeat: no-repeat;
42 | background-position: 0 32px;
43 | ${({ doEffect }) => doEffect ? animation : ''};
44 | `;
--------------------------------------------------------------------------------
/src/components/map/tiles/Water/visual/index.js:
--------------------------------------------------------------------------------
1 | import styled, { keyframes, css } from 'styled-components';
2 | import Base from '../../../../../styles/tile';
3 |
4 | import sprite1 from './item_629.png';
5 | import sprite2 from './item_630.png';
6 | import sprite3 from './item_631.png';
7 | import sprite4 from './item_632.png';
8 | import sprite5 from './item_633.png';
9 | import sprite6 from './item_634.png';
10 |
11 | const animationKeyframes = keyframes`
12 | 0% {
13 | background-position: 0 0;
14 | }
15 | 100% {
16 | background-position: 0 -100%;
17 | }
18 | `;
19 |
20 | const animationRule = css`
21 | animation: ${animationKeyframes} 2s steps(12) infinite;
22 | `;
23 |
24 | export const options = {
25 | Water1: `
26 | background-image: url(${sprite1});
27 | `,
28 | Water2: `
29 | background-image: url(${sprite2});
30 | `,
31 | Water3: `
32 | background-image: url(${sprite3});
33 | `,
34 | Water4: `
35 | background-image: url(${sprite4});
36 | `,
37 | Water5: `
38 | background-image: url(${sprite5});
39 | `,
40 | Water6: `
41 | background-image: url(${sprite6});
42 | `
43 | };
44 |
45 | export default styled(Base)`
46 | ${({ tile }) => (
47 | tile
48 | ? options[tile]
49 | : options[Object.keys(options).shift()]
50 | )}
51 | ${animationRule}
52 | `;
--------------------------------------------------------------------------------
/src/scss/index.scss:
--------------------------------------------------------------------------------
1 | *,
2 | *:before,
3 | *:after {
4 | box-sizing: border-box;
5 | }
6 |
7 | html,
8 | body {
9 | margin: 0;
10 | padding: 0;
11 | font-family: Tahoma;
12 | font-size: 12px;
13 | background: #000;
14 | }
15 |
16 | #root {
17 | padding: 30px;
18 | }
19 |
20 | .game {
21 | position: relative;
22 | color: #FFF;
23 |
24 | &:after {
25 | box-shadow: inset 0px 0px 160px rgba(0, 0, 0, 1);
26 | content: '';
27 | position: absolute;
28 | top: 0;
29 | right: 0;
30 | bottom: 0;
31 | left: 0;
32 | z-index: 3;
33 | pointer-events: none;
34 | }
35 |
36 | &.debugMode {
37 |
38 | .sqm {
39 | &:hover {
40 | &:before {
41 | border: 2px solid red;
42 | content: attr(data-axis);
43 | color: #FFF;
44 | font-size: 9px;
45 | text-shadow: 1px 1px 1px #000, -1px -1px 1px #000, 1px -1px 1px #000, -1px 1px 1px #000;
46 | line-height: 32px;
47 | text-align: center;
48 | position: absolute;
49 | top: 0;
50 | right: 0;
51 | left: 0;
52 | bottom: 0;
53 | z-index: 999;
54 | }
55 | }
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tibia JS Experiment
2 |
3 | A funny experiment recreating Tibia with JavaScript and React.
4 |
5 | ## Demo
6 |
7 | Use WASD keys to move character.
8 |
9 | 
10 |
11 | https://tibia-js.netlify.com
12 |
13 | ## Toggle debug mode
14 |
15 | With debug mode, you can see more messages and console information, and even view SQM information (x, y and z axis) when hovering mouse through the map.
16 |
17 | Simply execute on your DevTools console:
18 | ```
19 | utils.toggleDebugMode();
20 | ```
21 |
22 | ## Roadmap
23 |
24 | - [x] Walkable player (with CSS animation!)
25 | - [x] CSS field animation
26 | - [x] Name and life bars
27 | - [x] Map system with z-index
28 | - [x] Tibia things as components (with styled-components too!)
29 | - [x] More ground tiles
30 | - [x] Water tiles
31 | - [x] Animated water tiles
32 | - [x] Tile border system
33 | - [x] Tile "randomizer" (e.g.: for more than one sprite for grass)
34 | - [ ] Buildings
35 | - [x] Not walkable SQMs
36 | - [x] Field damage when walk into it
37 | - [x] Game messages on screen
38 | - [ ] Game HUD
39 | - [ ] More field types
40 | - [ ] First monster with basic AI
41 | - [ ] Monster attacking player
42 | - [ ] Attacking monsters
43 | - [ ] Store items in backpack
44 | - [ ] Use items
45 | - [ ] Looting system
46 | - [ ] Basic map editor
47 | - [ ] Dying system
48 | - [ ] Spells
49 | - [ ] Chat
50 | - [ ] Socket connection (for more than one player)
51 | - [ ] Light system
52 | - [ ] Map following character system
53 | - [ ] Scripting system
54 | - [ ] Leveling system
55 | - [ ] Skills upgrade system
56 | - [x] Walking speed depending on which SQM the character is going
57 | - [ ] On screen HUD for character information
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import "@babel/polyfill";
2 |
3 | import React from 'react';
4 | import ReactDOM from 'react-dom';
5 | import parsedMap from './src/map';
6 | import Player from './src/components/alive/Player';
7 | import { toggleDebugMode } from './src/store/actions/developer';
8 | import { store, StateProvider } from './src/store';
9 | import ScreenMessage from './src/components/hud/ScreenMessage';
10 |
11 | import './src/scss/index.scss';
12 |
13 | const App = () => {
14 | const {
15 | map = [],
16 | tileWidth,
17 | tileHeight,
18 | mapInfo: {
19 | maxX,
20 | maxY,
21 | },
22 | } = React.useMemo(() => parsedMap, []);
23 |
24 | const { state: globalState, dispatch } = React.useContext(store);
25 | const {
26 | developer: {
27 | debugMode
28 | },
29 | } = globalState;
30 |
31 | React.useEffect(() => {
32 | window.addEventListener('developer:debugMode', () => {
33 | dispatch(toggleDebugMode());
34 | });
35 | }, []);
36 |
37 | return (
38 |
39 |
40 |
41 | {
42 | Object.values(map)
43 | }
44 |
45 |
);
46 | };
47 |
48 | window.utils = {};
49 | window.utils.toggleDebugMode = () => {
50 | window.dispatchEvent(new Event('developer:debugMode'));
51 | };
52 |
53 | ReactDOM.render(
54 |
55 |
56 |
57 | , document.getElementById('root'));
58 |
59 | if (module.hot) {
60 | module.hot.accept();
61 | }
--------------------------------------------------------------------------------
/src/components/hud/NameBar/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import borderedText from '../../../styles/borderedText';
3 | import styled from 'styled-components';
4 |
5 | const Container = styled.div`
6 | width: 64px;
7 | position: absolute;
8 | bottom: 115%;
9 | left: -60%;
10 | text-align: center;
11 | `;
12 |
13 | const Name = styled(borderedText)`
14 | color: ${({ color }) => color};
15 | text-align: center;
16 | font-weight: bold;
17 | padding-bottom: 3px;
18 | `;
19 |
20 | const LifeBar = styled.div`
21 | width: 40px;
22 | background: #000;
23 | border: 1px solid #000;
24 | position: relative;
25 | display: inline-block;
26 | `;
27 |
28 | const LifeBarColor = styled.div`
29 | background: ${({ color }) => color};
30 | height: 4px;
31 | width: ${({ calculatedSize }) => calculatedSize ? `${calculatedSize}%` : '100%'};
32 | `;
33 |
34 | const NameBar = (props) => {
35 | const {
36 | name,
37 | currentHealth,
38 | maxHealth
39 | } = props;
40 |
41 | const percentage = currentHealth / maxHealth * 100;
42 | let lifeBarColor = '#00C103';
43 | if (percentage <= 90 && percentage >= 70) {
44 | lifeBarColor = '#89a17f';
45 | }
46 | if (percentage < 70 && percentage >= 20) {
47 | lifeBarColor = '#c4c400';
48 | }
49 | if (percentage < 20 && percentage >= 10) {
50 | lifeBarColor = '#8a463b';
51 | }
52 | if (percentage < 10) {
53 | lifeBarColor = '#230e0b';
54 | }
55 |
56 | return
57 | { name }
58 |
59 |
60 |
61 | ;
62 | };
63 |
64 | export default NameBar;
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useReducer } from 'react';
2 | import { log } from '../functions/gameUtils';
3 | import developerReducer from './reducers/developer';
4 | import hudReducer from './reducers/hud';
5 | import playerReducer from './reducers/player';
6 |
7 | const initialState = {
8 | player: {
9 | name: 'Player',
10 | maxHealth: 100,
11 | currentHealth: 100,
12 | maxMana: 100,
13 | currentMana: 100,
14 | baseSpeed: 300,
15 | currentSpeed: 0,
16 | walking: false,
17 | direction: 'down',
18 | pos: {
19 | x: 0,
20 | y: 0,
21 | },
22 | },
23 | hud: {
24 | message: '',
25 | },
26 | map: {
27 | tileWidth: 32,
28 | },
29 | developer: {
30 | debugMode: false,
31 | }
32 | };
33 |
34 | const store = createContext(initialState);
35 | const { Provider } = store;
36 |
37 | const StateProvider = ({ children }) => {
38 | const mappedCallbacks = {
39 | ...developerReducer,
40 | ...hudReducer,
41 | ...playerReducer,
42 | };
43 |
44 | const [state, dispatch] = useReducer((state, action) => {
45 | if (!action.type) {
46 | throw new Error('Action type missing in dispatch!');
47 | }
48 | if (mappedCallbacks[action.type]) {
49 | if (state.developer.debugMode) {
50 | log('store', `Action ${action.type} dispatched!`, 'Action payload:', action.payload);
51 | }
52 | return mappedCallbacks[action.type](state, action);
53 | } else {
54 | throw new Error('Action type not found!');
55 | }
56 | }, initialState);
57 |
58 | return {children};
59 | };
60 |
61 | export { store, StateProvider };
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .parcel-cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
--------------------------------------------------------------------------------
/src/components/map/tiles/SandBorder/visual/index.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 | import Base from '../../../../../styles/tile';
3 |
4 | import topBorder32 from './item_980.png';
5 | import leftBorder32 from './item_983.png';
6 | import bottomBorder32 from './item_979.png';
7 | import rightBorder32 from './item_981.png';
8 |
9 | import topLeftCorner32 from './item_988.png';
10 | import topRightCorner32 from './item_989.png';
11 | import bottomLeftCorner32 from './item_990.png';
12 | import bottomRightCorner32 from './item_991.png';
13 |
14 | import topLeftTip32 from './item_987.png';
15 | import topRightTip32 from './item_986.png';
16 | import bottomLeftTip32 from './item_984.png';
17 | import bottomRightTip32 from './item_985.png';
18 |
19 | export const options = {
20 | /* -- edges -- */
21 | TopBorder: css`
22 | background-image: url(${topBorder32});
23 | `,
24 | LeftBorder: css`
25 | background-image: url(${leftBorder32});
26 | `,
27 | BottomBorder: css`
28 | background-image: url(${bottomBorder32});
29 | `,
30 | RightBorder: css`
31 | background-image: url(${rightBorder32});
32 | `,
33 | /* -- corners -- */
34 | TopLeftCorner: css`
35 | background-image: url(${topLeftCorner32});
36 | `,
37 | TopRightCorner: css`
38 | background-image: url(${topRightCorner32});
39 | `,
40 | BottomLeftCorner: css`
41 | background-image: url(${bottomLeftCorner32});
42 | `,
43 | BottomRightCorner: css`
44 | background-image: url(${bottomRightCorner32});
45 | `,
46 | /* -- tips -- */
47 | TopLeftTip: css`
48 | background-image: url(${topLeftTip32});
49 | `,
50 | TopRightTip: css`
51 | background-image: url(${topRightTip32});
52 | `,
53 | BottomLeftTip: css`
54 | background-image: url(${bottomLeftTip32});
55 | `,
56 | BottomRightTip: css`
57 | background-image: url(${bottomRightTip32});
58 | `,
59 | };
60 |
61 | export default styled(Base)`
62 | ${({ type }) => (
63 | type
64 | ? options[type]
65 | : options[Object.keys(options).shift()]
66 | )}
67 | `;
--------------------------------------------------------------------------------
/src/components/alive/Player/visual/index.js:
--------------------------------------------------------------------------------
1 | import styled, { keyframes, css } from 'styled-components';
2 | import Base from '../../../../styles/alive';
3 | import idleImg from './idle.png';
4 | import walkingImg from './walking.png';
5 |
6 | const walkingKeyFramesMap = {
7 | up: {
8 | from: {
9 | left: '-32px',
10 | top: '-32px',
11 | },
12 | to: {
13 | left: '-32px',
14 | top: '-100%',
15 | },
16 | },
17 | down: {
18 | from: {
19 | left: '-288px',
20 | top: '-32px',
21 | },
22 | to: {
23 | left: '-288px',
24 | top: '-100%',
25 | },
26 | },
27 | left: {
28 | from: {
29 | left: '-416px',
30 | top: '-32px',
31 | },
32 | to: {
33 | left: '-416px',
34 | top: '-100%',
35 | },
36 | },
37 | right: {
38 | from: {
39 | left: '-160px',
40 | top: '-32px',
41 | },
42 | to: {
43 | left: '-160px',
44 | top: '-100%',
45 | },
46 | },
47 | };
48 |
49 | const walkingKeyFrameBuilder = (direction) => keyframes`
50 | 0% {
51 | background-position: ${walkingKeyFramesMap[direction].from.left} ${walkingKeyFramesMap[direction].from.top};
52 | }
53 | 100% {
54 | background-position: ${walkingKeyFramesMap[direction].to.left} ${walkingKeyFramesMap[direction].to.top};
55 | }
56 | `;
57 |
58 | const walkingAnimations = css`
59 | animation: ${({ direction }) => walkingKeyFrameBuilder(direction)} ${({ speed }) => speed ? speed : '0.25'}s steps(8);
60 | `;
61 |
62 | const backgrounds = css`
63 | background-position: ${({ direction }) => {
64 | if (direction === 'up') return `-32px -32px;`;
65 | if (direction === 'down') return `-288px -32px;`;
66 | if (direction === 'left') return `-416px -32px;`;
67 | if (direction === 'right') return `-160px -32px;`;
68 | }};
69 | `;
70 |
71 | export default styled(Base)`
72 | background-image: url(${({ walking }) => walking ? walkingImg : idleImg});
73 | ${backgrounds}
74 | ${({ walking }) => walking ? walkingAnimations : ''}
75 | `;
--------------------------------------------------------------------------------
/src/components/map/tiles/GrassBorder/visual/index.js:
--------------------------------------------------------------------------------
1 | import styled, { css } from 'styled-components';
2 | import Base from '../../../../../styles/tile';
3 |
4 | import topBorder64 from './item_304.png';
5 | import leftBorder64 from './item_307.png';
6 | import bottomBorder64 from './item_407.png';
7 | import rightBorder64 from './item_618.png';
8 |
9 | import topLeftCorner32 from './item_312.png';
10 | import topRightCorner32 from './item_313.png';
11 | import bottomLeftCorner32 from './item_314.png';
12 | import bottomRightCorner32 from './item_315.png';
13 |
14 | import topLeftTip32 from './item_308.png';
15 | import topRightTip32 from './item_309.png';
16 | import bottomLeftTip32 from './item_310.png';
17 | import bottomRightTip32 from './item_311.png';
18 |
19 | export const options = {
20 | /* -- edges -- */
21 | TopBorder1: css`
22 | background-image: url(${topBorder64});
23 | `,
24 | TopBorder2: css`
25 | background-image: url(${topBorder64});
26 | background-position: -32px 0;
27 | `,
28 | LeftBorder1: css`
29 | background-image: url(${leftBorder64});
30 | `,
31 | LeftBorder2: css`
32 | background-image: url(${leftBorder64});
33 | background-position: 0 -32px;
34 | `,
35 | BottomBorder1: css`
36 | background-image: url(${bottomBorder64});
37 | `,
38 | BottomBorder2: css`
39 | background-image: url(${bottomBorder64});
40 | background-position: -32px 0;
41 | `,
42 | RightBorder1: css`
43 | background-image: url(${rightBorder64});
44 | `,
45 | RightBorder2: css`
46 | background-image: url(${rightBorder64});
47 | background-position: 0 -32px;
48 | `,
49 | /* -- corners -- */
50 | TopLeftCorner: css`
51 | background-image: url(${topLeftCorner32});
52 | `,
53 | TopRightCorner: css`
54 | background-image: url(${topRightCorner32});
55 | `,
56 | BottomLeftCorner: css`
57 | background-image: url(${bottomLeftCorner32});
58 | `,
59 | BottomRightCorner: css`
60 | background-image: url(${bottomRightCorner32});
61 | `,
62 | /* -- tips -- */
63 | TopLeftTip: css`
64 | background-image: url(${topLeftTip32});
65 | `,
66 | TopRightTip: css`
67 | background-image: url(${topRightTip32});
68 | `,
69 | BottomLeftTip: css`
70 | background-image: url(${bottomLeftTip32});
71 | `,
72 | BottomRightTip: css`
73 | background-image: url(${bottomRightTip32});
74 | `,
75 | };
76 |
77 | export default styled(Base)`
78 | ${({ type }) => (
79 | type
80 | ? options[type]
81 | : options[Object.keys(options).shift()]
82 | )}
83 | `;
--------------------------------------------------------------------------------
/src/map/functions.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import FieldsBase from "../components/map/fields/base";
4 | import Poison from "../components/map/fields/Poison";
5 |
6 | import TilesBase from "../components/map/tiles/base";
7 | import Grass from "../components/map/tiles/Grass";
8 | import GrassBorder from "../components/map/tiles/GrassBorder";
9 | import Sand from "../components/map/tiles/Sand";
10 | import SandBorder from "../components/map/tiles/SandBorder";
11 | import Water from "../components/map/tiles/Water";
12 |
13 | import TerrainBase from "../components/map/terrain/base";
14 | import GroundSpikes from "../components/map/terrain/GroundSpikes";
15 |
16 | const components = {
17 | fields: {
18 | base: FieldsBase,
19 | Poison,
20 | },
21 | tiles: {
22 | base: TilesBase,
23 | Grass,
24 | GrassBorder,
25 | Sand,
26 | SandBorder,
27 | Water,
28 | },
29 | terrain: {
30 | base: TerrainBase,
31 | GroundSpikes,
32 | },
33 | };
34 |
35 | export const prepareStack = (stack = [], { index, top, left, axis }) =>
36 | stack
37 | .map((item, currIndex) => {
38 | let [type, name] = (item || "").split(":");
39 | if (!type || !name) {
40 | return null;
41 | }
42 | const hasComponentSubType = name.includes(".");
43 | const componentNames = hasComponentSubType ? name.split(".") : [name];
44 | const componentSubType = hasComponentSubType
45 | ? name.split(".").pop()
46 | : null;
47 |
48 | name = hasComponentSubType ? componentNames[0] : name;
49 |
50 | const component = components[type][name] ? components[type][name] : null;
51 |
52 | if (!component) {
53 | console.error(`WARNING: ${item} not found in axis ${axis}!`);
54 | return null;
55 | }
56 |
57 | const reactElement = React.createElement(component, {
58 | ...components[type].base,
59 | ...component.metadata,
60 | ...{
61 | key: `tile-${index}-${currIndex}`,
62 | zIndex: currIndex,
63 | top,
64 | left,
65 | type: componentSubType,
66 | "data-axis": axis,
67 | },
68 | });
69 | return reactElement;
70 | })
71 | .filter((item) => !!item);
72 |
73 | export const shouldNotCompleteMove = (map = {}, { x, y }) => {
74 | return (
75 | typeof map[`${x}:${y}:0`] === "undefined" ||
76 | (map[`${x}:${y}:0`] &&
77 | !map[`${x}:${y}:0`].some(
78 | (stackItem) => stackItem.props.walkable === true
79 | ) &&
80 | map[`${x}:${y}:0`].some((stackItem) => !stackItem.props.border))
81 | );
82 | };
83 |
84 | export const getFutureSqmInfo = (map = {}, { x, y }) => {
85 | const sqmEffects = map[`${x}:${y}:0`].filter(
86 | (stackItem) => stackItem.props.effect
87 | );
88 | const effects = sqmEffects.length
89 | ? sqmEffects.map((item) => item.props.effect)
90 | : [];
91 |
92 | const sqmSlow = map[`${x}:${y}:0`].reduce(
93 | (acc, curr) => (curr.props.slow > acc ? curr.props.slow : acc),
94 | 0
95 | );
96 |
97 | return {
98 | effects,
99 | slow: sqmSlow,
100 | };
101 | };
102 |
--------------------------------------------------------------------------------
/src/components/alive/Player/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import NameBar from "../../hud/NameBar";
3 | import VisualElement from "./visual";
4 | import {
5 | shouldNotCompleteMove,
6 | getFutureSqmInfo,
7 | } from "../../../map/functions";
8 | import parsedMap from "../../../map";
9 | import { setPlayerPos } from "../../../store/actions/player";
10 | import { setMessage } from "../../../store/actions/hud";
11 | import { store } from "../../../store";
12 | import { log } from "../../../functions/gameUtils";
13 |
14 | const calculateSpeed = (speed) => (1 / (speed / 100)) * 1;
15 |
16 | const useWalkWithKeyboard = () => {
17 | const { state: globalState, dispatch } = React.useContext(store);
18 | const {
19 | player: { baseSpeed, pos },
20 | developer: { debugMode },
21 | } = globalState;
22 |
23 | const [direction, setDirection] = React.useState("down");
24 | const [walking, setWalking] = React.useState(false);
25 | const [speed, setSpeed] = React.useState(baseSpeed);
26 | const [effects, setEffects] = React.useState({});
27 |
28 | const calculatedSpeed = React.useMemo(() => {
29 | const finalSpeed = calculateSpeed(speed);
30 | if (debugMode) {
31 | log(
32 | "player",
33 | `Changed speed from ${baseSpeed} to ${speed} - animation/transition time: ${finalSpeed}s`
34 | );
35 | }
36 | return finalSpeed;
37 | }, [speed]);
38 |
39 | React.useEffect(() => {
40 | const fn = (event) => {
41 | if (walking) {
42 | return;
43 | }
44 |
45 | let newDirection;
46 | let newPos = {};
47 |
48 | const keyPressed = `${event.key}`.toLowerCase();
49 | const isShiftKeyPressed = event.shiftKey;
50 |
51 | if (keyPressed === "w") {
52 | newDirection = "up";
53 | newPos = { x: pos.x, y: pos.y - 1 };
54 | } else if (keyPressed === "s") {
55 | newDirection = "down";
56 | newPos = { x: pos.x, y: pos.y + 1 };
57 | } else if (keyPressed === "a") {
58 | newDirection = "left";
59 | newPos = { x: pos.x - 1, y: pos.y };
60 | } else if (keyPressed === "d") {
61 | newDirection = "right";
62 | newPos = { x: pos.x + 1, y: pos.y };
63 | }
64 |
65 | if (newDirection) {
66 | setDirection(newDirection);
67 | setWalking(true);
68 |
69 | if (shouldNotCompleteMove(parsedMap.map, newPos) || isShiftKeyPressed) {
70 | setWalking(false);
71 | return;
72 | }
73 |
74 | const { effects: sqmEffects, slow } = getFutureSqmInfo(
75 | parsedMap.map,
76 | newPos
77 | );
78 | const finalEffects = { ...effects } || {};
79 | sqmEffects.forEach((item) => {
80 | dispatch(setMessage(item.metadata.message));
81 | finalEffects[item.name] = {
82 | component: item.component,
83 | wipeTimeout: setTimeout(() => {
84 | delete effects[item.name];
85 | setEffects(effects);
86 | }, item.metadata.duration),
87 | };
88 | }, {});
89 |
90 | const speedTime = slow ? baseSpeed - baseSpeed * slow : baseSpeed;
91 | setSpeed(speedTime);
92 | setEffects(finalEffects);
93 |
94 | dispatch(setPlayerPos(newPos));
95 | }
96 | };
97 |
98 | window.addEventListener("keypress", fn);
99 |
100 | return () => {
101 | window.removeEventListener("keypress", fn);
102 | };
103 | }, [pos, walking]);
104 |
105 | React.useEffect(() => {
106 | if (walking === true) {
107 | setTimeout(() => {
108 | setWalking(false);
109 | }, calculatedSpeed * 500);
110 | }
111 | }, [walking]);
112 |
113 | return {
114 | direction,
115 | walking,
116 | calculatedSpeed,
117 | effects,
118 | };
119 | };
120 |
121 | const Player = (props = {}) => {
122 | const { state: globalState } = React.useContext(store);
123 | const {
124 | player: { name, maxHealth, maxMana, currentHealth, currentMana, pos },
125 | map: { tileWidth },
126 | } = globalState;
127 |
128 | const { walking, calculatedSpeed, direction, effects } =
129 | useWalkWithKeyboard();
130 |
131 | const cssPosition = React.useMemo(
132 | () => ({
133 | left: pos.x * tileWidth,
134 | top: pos.y * tileWidth,
135 | }),
136 | [pos]
137 | );
138 |
139 | return (
140 |
141 |
150 | {Object.values(effects).length
151 | ? Object.values(effects).map((item, index) =>
152 | React.createElement(item.component, {
153 | key: `effect-${index}`,
154 | })
155 | )
156 | : null}
157 |
160 |
161 |
162 | );
163 | };
164 |
165 | export default Player;
166 |
--------------------------------------------------------------------------------
/map.js:
--------------------------------------------------------------------------------
1 | export const mapInfo = {
2 | minX: 0,
3 | minY: 0,
4 | maxX: 15,
5 | maxY: 15,
6 | minZ: -7,
7 | maxZ: 8,
8 | };
9 |
10 | const finalMap = {};
11 | const tileItems = {
12 | '2:3:0': {
13 | items: ['fields:Poison']
14 | },
15 | '6:4:0': {
16 | items: ['terrain:GroundSpikes']
17 | }
18 | };
19 | const lake = {
20 | '5:5:0': {
21 | items: [
22 | 'tiles:Water',
23 | 'tiles:GrassBorder.TopLeftCorner',
24 | ]
25 | },
26 | '5:6:0': {
27 | items: [
28 | 'tiles:Water',
29 | 'tiles:GrassBorder.LeftBorder1',
30 | ]
31 | },
32 | '5:7:0': {
33 | items: [
34 | 'tiles:Water',
35 | 'tiles:GrassBorder.BottomLeftCorner',
36 | ]
37 | },
38 | '6:5:0': {
39 | items: [
40 | 'tiles:Water',
41 | 'tiles:GrassBorder.TopBorder1',
42 | ]
43 | },
44 | '6:6:0': {
45 | items: ['tiles:Water',]
46 | },
47 | '6:7:0': {
48 | items: [
49 | 'tiles:Water',
50 | 'tiles:GrassBorder.BottomBorder1',
51 | ]
52 | },
53 | '7:5:0': {
54 | items: [
55 | 'tiles:Water',
56 | 'tiles:GrassBorder.TopRightCorner',
57 | ]
58 | },
59 | '7:6:0': {
60 | items: [
61 | 'tiles:Water',
62 | 'tiles:GrassBorder.TopRightTip',
63 | ]
64 | },
65 | '7:7:0': {
66 | items: [
67 | 'tiles:Water',
68 | 'tiles:GrassBorder.BottomBorder2',
69 | ]
70 | },
71 | '8:7:0': {
72 | items: [
73 | 'tiles:Water',
74 | 'tiles:GrassBorder.BottomRightCorner',
75 | ]
76 | },
77 | '8:6:0': {
78 | items: [
79 | 'tiles:Water',
80 | 'tiles:GrassBorder.TopRightCorner',
81 | ]
82 | },
83 | };
84 |
85 | const sand = {
86 | /* -- borders -- */
87 | '7:8:0': {
88 | items: [
89 | 'tiles:Grass',
90 | 'tiles:SandBorder.TopLeftTip',
91 | ]
92 | },
93 | '8:8:0': {
94 | items: [
95 | 'tiles:Grass',
96 | 'tiles:SandBorder.TopBorder',
97 | ]
98 | },
99 | '9:8:0': {
100 | items: [
101 | 'tiles:Grass',
102 | 'tiles:SandBorder.TopBorder',
103 | ]
104 | },
105 | '10:8:0': {
106 | items: [
107 | 'tiles:Grass',
108 | 'tiles:SandBorder.TopBorder',
109 | ]
110 | },
111 | '11:8:0': {
112 | items: [
113 | 'tiles:Grass',
114 | 'tiles:SandBorder.TopBorder',
115 | ]
116 | },
117 | '12:8:0': {
118 | items: [
119 | 'tiles:Grass',
120 | 'tiles:SandBorder.TopRightTip',
121 | ]
122 | },
123 | '12:9:0': {
124 | items: [
125 | 'tiles:Grass',
126 | 'tiles:SandBorder.RightBorder',
127 | ]
128 | },
129 | '12:10:0': {
130 | items: [
131 | 'tiles:Grass',
132 | 'tiles:SandBorder.BottomLeftTip',
133 | ]
134 | },
135 | '11:10:0': {
136 | items: [
137 | 'tiles:Grass',
138 | 'tiles:SandBorder.TopLeftCorner',
139 | ]
140 | },
141 | '11:11:0': {
142 | items: [
143 | 'tiles:Grass',
144 | 'tiles:SandBorder.BottomLeftTip',
145 | ]
146 | },
147 | '10:11:0': {
148 | items: [
149 | 'tiles:Grass',
150 | 'tiles:SandBorder.BottomBorder',
151 | ]
152 | },
153 | '9:11:0': {
154 | items: [
155 | 'tiles:Grass',
156 | 'tiles:SandBorder.BottomBorder',
157 | ]
158 | },
159 | '8:11:0': {
160 | items: [
161 | 'tiles:Grass',
162 | 'tiles:SandBorder.BottomBorder',
163 | ]
164 | },
165 | '7:11:0': {
166 | items: [
167 | 'tiles:Grass',
168 | 'tiles:SandBorder.BottomRightTip',
169 | ]
170 | },
171 | '7:10:0': {
172 | items: [
173 | 'tiles:Grass',
174 | 'tiles:SandBorder.LeftBorder',
175 | ]
176 | },
177 | '7:9:0': {
178 | items: [
179 | 'tiles:Grass',
180 | 'tiles:SandBorder.LeftBorder',
181 | ]
182 | },
183 | /* -- inside -- */
184 | '8:9:0': {
185 | items: [
186 | 'tiles:Sand',
187 | ]
188 | },
189 | '9:9:0': {
190 | items: [
191 | 'tiles:Sand',
192 | ]
193 | },
194 | '10:9:0': {
195 | items: [
196 | 'tiles:Sand',
197 | ]
198 | },
199 | '11:9:0': {
200 | items: [
201 | 'tiles:Sand',
202 | ]
203 | },
204 | '8:10:0': {
205 | items: [
206 | 'tiles:Sand',
207 | ]
208 | },
209 | '9:10:0': {
210 | items: [
211 | 'tiles:Sand',
212 | ]
213 | },
214 | '10:10:0': {
215 | items: [
216 | 'tiles:Sand',
217 | ]
218 | },
219 | };
220 |
221 | for (let x = mapInfo.minX; x < mapInfo.maxX; x++) {
222 | for (let y = mapInfo.minY; y < mapInfo.maxY; y++) {
223 | let stack = [
224 | 'tiles:Grass'
225 | ];
226 |
227 | const lakeSqm = lake[`${x}:${y}:0`];
228 | if (lakeSqm) {
229 | stack = lakeSqm.items;
230 | }
231 |
232 | const sandSqm = sand[`${x}:${y}:0`];
233 | if (sandSqm) {
234 | stack = sandSqm.items;
235 | }
236 |
237 | const item = tileItems[`${x}:${y}:0`];
238 | if (item) {
239 | stack = [].concat(stack, [...item.items]);
240 | }
241 |
242 | finalMap[`${x}:${y}:0`] = stack;
243 | }
244 | }
245 |
246 | const stringMap = JSON.stringify(finalMap);
247 |
248 | export default stringMap;
--------------------------------------------------------------------------------