├── 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 | ![](ss.png) 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; --------------------------------------------------------------------------------