Update, 0.5.5

- Run server by npm boot script
- Updated to version 0.5.5
- Sync player with db
- Various player model changes
- Save player position
- Better packet logging
- Set favorite pkmn and recycle item support
- Validate emails by regex
This commit is contained in:
Felix 2016-09-05 15:52:14 +02:00
parent 5d7519929e
commit 8be4111e94
22 changed files with 380 additions and 246 deletions

View File

@ -1,6 +1,6 @@
{
"name": "POGOServer",
"version": "0.5.0",
"version": "0.5.5",
"description": "",
"repository": {
"type": "git",
@ -9,7 +9,7 @@
"scripts": {
"test": "echo \"Error: no test specified\"",
"babel-node": "babel-node --presets=es2015",
"start": "npm run babel-node -- ./src/index.js"
"boot": "npm run babel-node -- ./src/index.js"
},
"engines": {
"node": ">= 6.x",

10
run-linux.sh vendored
View File

@ -1,10 +0,0 @@
#!/bin/bash
pause() {
read -p "Press [Enter] key to exit"
}
cd $(dirname $0)
npm install
npm run start

2
run-windows.bat vendored
View File

@ -1,2 +0,0 @@
npm run start
pause

View File

@ -19,51 +19,39 @@ export function getUserQuery(cmd, after) {
altitude=?,
send_marketing_emails=?,
send_push_notifications=?,
skin=?,
hair=?,
shirt=?,
pants=?,
hat=?,
shoes=?,
eyes=?,
gender=?,
backpack=?
candies=?,
items=?,
avatar=?,
tutorial=?
${after}
`);
}
/**
* @param {Object} obj
* @param {Player} player
* @return {Array}
*/
export function getUserQueryData(obj) {
export function getPlayerQueryData(player) {
return ([
obj.username,
obj.email,
obj.exp,
obj.level,
obj.stardust,
obj.pokecoins,
obj.team,
player.username,
player.email,
player.exp,
player.level,
player.stardust,
player.pokecoins,
player.team,
// position
obj.latitude,
obj.longitude,
obj.altitude,
player.latitude,
player.longitude,
player.altitude,
// contact settings
obj.send_marketing_emails,
obj.send_push_notifications,
// avatar
obj.skin,
obj.hair,
obj.shirt,
obj.pants,
obj.hat,
obj.shoes,
obj.eyes,
obj.gender,
obj.backpack,
// WHERE
obj.email
player.send_marketing_emails,
player.send_push_notifications,
// json
player.candies.serialize(),
player.items.serialize(),
player.avatar.serialize(),
player.tutorial.serialize()
]);
}
@ -143,85 +131,4 @@ export function getOwnedPkmnQueryData(obj) {
obj.nickname || "",
obj.from_fort || 0
]);
}
export function getUserItemQuery(cmd, after) {
return (`
${cmd} ${CFG.MYSQL_USERS_TABLE}
SET
item_poke_ball=?,
item_great_ball=?,
item_ultra_ball=?,
item_master_ball=?,
item_potion=?,
item_super_potion=?,
item_hyper_potion=?,
item_max_potion=?,
item_revive=?,
item_max_revive=?,
item_lucky_egg=?,
item_incense_ordinary=?,
item_incense_spicy=?,
item_incense_cool=?,
item_incense_floral=?,
item_troy_disk=?,
item_razz_berry=?,
item_bluk_berry=?,
item_nanab_berry=?,
item_wepar_berry=?,
item_pinap_berry=?,
item_incubator_basic=?,
item_incubator_basic_unlimited=?,
item_pokemon_storage_upgrade=?,
item_storage_upgrade=?
${after}
`);
}
/**
* @param {Object} obj
* @return {Array}
*/
export function getUserItemQueryData(obj) {
let items = [];
for (let key in obj.items) {
items.push(obj.items[key]);
};
let email = obj.email;
obj = obj.items;
return ([
obj.poke_ball,
obj.great_ball,
obj.ultra_ball,
obj.master_ball,
obj.potion,
obj.super_potion,
obj.hyper_potion,
obj.max_potion,
obj.revive,
obj.max_revive,
obj.lucky_egg,
obj.incense_ordinary,
obj.incense_spicy,
obj.incense_cool,
obj.incense_floral,
obj.troy_disk,
obj.razz_berry,
obj.bluk_berry,
obj.nanab_berry,
obj.wepar_berry,
obj.pinap_berry,
obj.incubator_basic,
obj.incubator_basic_unlimited,
obj.pokemon_storage_upgrade,
obj.storage_upgrade,
// WHERE
email
]);
}

View File

@ -4,6 +4,7 @@ export default {
TEAM: proto.Enums.TeamColor,
ITEMS: proto.Inventory.Item.ItemId,
GENDER: proto.Enums.Gender,
TUTORIAL: proto.Enums.TutorialState,
POKEMON_IDS: proto.Enums.PokemonId,
getNameById: (emu, id) => {
id <<= 0;

View File

@ -201,6 +201,16 @@ export default class GameServer {
return (true);
}
/**
* @return {String}
*/
getCurrentTime() {
let date = new Date();
return (
`${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`
);
}
greet() {
console.log(greetMessage);
}

View File

@ -18,8 +18,8 @@ export default class Avatar {
this._hat = 0;
this._shoes = 0;
this._eyes = 0;
this._gender = "MALE";
this._backpack = 0;
this._gender = "MALE";
}
@ -41,7 +41,7 @@ export default class Avatar {
}
set skin(value) {
if (this.between(value, 0, 3)) {
this.skin = value;
this._skin = value;
}
}
@ -105,16 +105,6 @@ export default class Avatar {
}
}
// gender
get gender() {
return (this._gender);
}
set gender(value) {
if (value === "MALE" || value === "FEMALE") {
this._gender = value;
}
}
// backpack
get backpack() {
return (this._backpack);
@ -125,6 +115,19 @@ export default class Avatar {
}
}
// gender
get gender() {
return (this._gender);
}
set gender(value) {
if (value === "MALE" || value === "FEMALE") {
this._gender = value;
}
}
/**
* @return {Object}
*/
serialize() {
return ({
skin: this.skin,
@ -134,9 +137,21 @@ export default class Avatar {
hat: this.hat,
shoes: this.shoes,
eyes: this.eyes,
gender: this.gender ? "FEMALE" : "MALE",
gender: this.gender,
backpack: this.backpack
});
}
/**
* @param {String} str
*/
parseJSON(str) {
let obj = JSON.parse(str);
for (let key in obj) {
if (this.hasOwnProperty("_" + key)) {
this[key] = obj[key];
}
};
}
}

View File

@ -13,38 +13,38 @@ export default class Bag {
this.player = player;
this.poke_ball = 150;
this.great_ball = 25;
this.ultra_ball = 5;
this.master_ball = 10;
this.poke_ball = 0;
this.great_ball = 0;
this.ultra_ball = 0;
this.master_ball = 0;
this.potion = 1;
this.super_potion = 1;
this.hyper_potion = 1;
this.max_potion = 1;
this.potion = 0;
this.super_potion = 0;
this.hyper_potion = 0;
this.max_potion = 0;
this.revive = 1;
this.max_revive = 1;
this.revive = 0;
this.max_revive = 0;
this.lucky_egg = 1;
this.troy_disk = 1;
this.lucky_egg = 0;
this.troy_disk = 0;
this.incense_ordinary = 1;
this.incense_spicy = 1;
this.incense_cool = 1;
this.incense_floral = 1;
this.incense_ordinary = 0;
this.incense_spicy = 0;
this.incense_cool = 0;
this.incense_floral = 0;
this.razz_berry = 1;
this.bluk_berry = 1;
this.nanab_berry = 1;
this.wepar_berry = 1;
this.pinap_berry = 1;
this.razz_berry = 0;
this.bluk_berry = 0;
this.nanab_berry = 0;
this.wepar_berry = 0;
this.pinap_berry = 0;
this.incubator_basic = 1;
this.incubator_basic_unlimited = 1;
this.incubator_basic = 0;
this.incubator_basic_unlimited = 0;
this.pokemon_storage_upgrade = 1;
this.storage_upgrade = 1;
this.pokemon_storage_upgrade = 0;
this.storage_upgrade = 0;
}
@ -58,6 +58,16 @@ export default class Bag {
);
}
/**
* @param {Number} id
* @return {String}
*/
getItemEnumName(id) {
return (
ENUM.getNameById(ENUM.ITEMS, id)
);
}
/**
* @param {String} name
* @return {String}
@ -68,13 +78,42 @@ export default class Bag {
);
}
/**
* @param {String} name
* @return {Boolean}
*/
isValidItemKey(name) {
return (
this.hasOwnProperty(name) && Number.isInteger(this[name])
);
}
/**
* @param {String} name
* @param {Number} amount
* @return {Number}
*/
updateItem(name, amount) {
let key = name.replace("ITEM_", "").toLowerCase();
if (!this.isValidItemKey(key)) return (-1);
let currentAmount = this[key] << 0;
if (amount < 0) {
if (currentAmount + amount < 0) this[key] = 0;
else this[key] += amount;
}
else {
this[key] += amount;
}
return (this[key]);
}
/**
* @return {Array}
*/
serialize() {
let out = [];
for (let key in this) {
if (this.hasOwnProperty(key) && Number.isInteger(this[key])) {
if (this.isValidItemKey(key)) {
let itemId = this.getItemName(key);
if (Number.isInteger(itemId) && this[key] > 0) {
out.push({
@ -98,7 +137,7 @@ export default class Bag {
querify() {
let buffer = {};
for (let key in this) {
if (this.hasOwnProperty(key) && Number.isInteger(this[key])) {
if (this.isValidItemKey(key)) {
let itemId = this.getItemName(key);
if (Number.isInteger(itemId)) {
buffer[itemId] = this[key];
@ -108,4 +147,17 @@ export default class Bag {
return (JSON.stringify(buffer));
}
/**
* @param {String} str
*/
parseJSON(str) {
let obj = JSON.parse(str);
for (let key in obj) {
let name = this.getItemEnumName(key).toLowerCase().replace("item_", "");
if (this.isValidItemKey(name)) {
this[name] = obj[key];
}
};
}
}

View File

@ -11,7 +11,7 @@ export default class CandyBag {
this.player = player;
this.candies = [];
this.candies = {};
}
@ -20,20 +20,9 @@ export default class CandyBag {
* @return {Object}
*/
getCandyByDexNumber(dex) {
let candies = this.candies;
let ii = 0;
let length = candies.length;
for (; ii < length; ++ii) {
if (candies[ii].dex === dex) {
return (candies[ii]);
}
};
return (null);
return (
this.candies[dex << 0] || null
);
}
/**
@ -42,7 +31,7 @@ export default class CandyBag {
*/
getCandy(dex) {
return (
this.getCandyByDexNumber(dex) || 0
this.getCandyByDexNumber(dex)
);
}
@ -65,47 +54,20 @@ export default class CandyBag {
if (candy.amount < 0) candy.amount = 0;
}
/**
* @param {String} str
*/
parseCandies(str) {
let result = null;
let seperator = ":";
let split = str.split(",");
let ii = 0;
let length = split.length;
for (; ii < length; ++ii) {
result = split[ii].split(seperator);
this.candies.push({
dex: result[0] << 0,
amount: result[1] << 0
});
};
}
/**
* @return {String}
*/
serialize() {
return (
JSON.stringify(this.candies)
);
}
let str = "";
let candies = this.candies;
let ii = 0;
let length = candies.length;
for (; ii < length; ++ii) {
str += candies[ii].dex + ":" + candies[ii].dex;
if (ii + 1 < length) str += ",";
};
return (str);
/**
* @param {String} str
*/
parseJSON(str) {
this.candies = JSON.parse(str);
}
}

View File

@ -1,5 +1,7 @@
import Pokemon from "../../Pokemon";
import print from "../../../print";
/**
* @class Party
*/
@ -21,6 +23,16 @@ export default class Party {
favorite: 0
});
this.fetchFromDatabase();
}
fetchFromDatabase() {
let instance = this.player.world.db;
this.player.world.db.query(`SELECT * FROM owned_pkmn WHERE owner_id=?`, [this.player.id], (e, rows) => {
if (e) return print(e, 31);
console.log("Pokemon:", rows);
});
}
/**

View File

@ -1,3 +1,5 @@
import ENUM from "../../../enum";
/**
* @class Tutorial
*/
@ -15,6 +17,16 @@ export default class Tutorial {
}
/**
* @param {String} name
* @return {String}
*/
getItemName(name) {
return (
ENUM.getNameById(ENUM.TUTORIAL, name)
);
}
skipTutorial() {
this.states = [
"LEGAL_SCREEN",
@ -29,7 +41,6 @@ export default class Tutorial {
* @return {Array}
*/
serialize() {
this.skipTutorial();
return (
this.states
);
@ -45,4 +56,18 @@ export default class Tutorial {
return (JSON.stringify(buffer));
}
/**
* @param {String} str
*/
parseJSON(str) {
this.states = [];
let obj = JSON.parse(str);
for (let key in obj) {
let name = this.getItemName(key);
if (obj[key] === 1) {
this.states.push(name.toUpperCase());
}
};
}
}

View File

@ -122,6 +122,9 @@ export default class Player extends MapObject {
getPacket(type, msg) {
return new Promise((resolve) => {
switch (type) {
case "SET_FAVORITE_POKEMON":
resolve(this.SetFavoritePokemon(msg));
break;
case "LEVEL_UP_REWARDS":
resolve(this.LevelUpRewards(msg));
break;
@ -146,6 +149,9 @@ export default class Player extends MapObject {
case "CHECK_AWARDED_BADGES":
resolve(this.CheckAwardedBadges(msg));
break;
case "RECYCLE_INVENTORY_ITEM":
resolve(this.RecycleInventoryItem(msg));
break;
};
});
}
@ -170,6 +176,55 @@ export default class Player extends MapObject {
});
}
inheritByObject(obj) {
for (let key in obj) {
// ignore
if (!(key !== "id")) continue;
if (!(key !== "email")) continue;
if (key === "candies") {
this.candyBag.parseJSON(obj[key]);
}
else if (key === "items") {
this.bag.parseJSON(obj[key]);
}
else if (key === "avatar") {
this.avatar.parseJSON(obj[key]);
}
else if (key === "tutorial") {
this.tutorial.parseJSON(obj[key]);
}
else {
if (this.hasOwnProperty(key)) {
this[key] = obj[key];
}
}
};
}
syncWithDatabase() {
return new Promise((resolve) => {
this.loadFromDatabase().then((row) => {
this.inheritByObject(row);
resolve();
});
});
}
loadFromDatabase() {
let query = `SELECT * from ${CFG.MYSQL_USERS_TABLE} WHERE email=? LIMIT 1`;
return new Promise((resolve) => {
this.world.db.query(query, [this.email], (e, rows) => {
if (e) return print(e, 31);
if (rows.length >= 1) resolve(rows[0]);
else print(`Failed to sync player ${this.username} with database!`, 31);
});
});
}
saveIntoDatabase() {
}
}
inherit(Player, _packets);

View File

@ -0,0 +1,22 @@
import POGOProtos from "pokemongo-protobuf";
/**
* @param {Object} msg
* @return {Buffer}
*/
export default function RecycleInventoryItem(msg) {
let buffer = null;
let success = this.bag.updateItem(msg.item_id, -(msg.count << 0));
buffer = {
result: success !== -1 ? "SUCCESS" : "ERROR_NOT_ENOUGH_COPIES",
new_count: success,
};
return (
POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.RecycleInventoryItemResponse")
);
}

View File

@ -0,0 +1,24 @@
import POGOProtos from "pokemongo-protobuf";
/**
* @param {Object} msg
* @return {Buffer}
*/
export default function SetFavoritePokemon(msg) {
let buffer = null;
let pkmn = this.party.getPkmnById(msg.pokemon_id);
console.log(this.party.party);
if (pkmn) {
pkmn.setFavorite(msg.is_favorite);
buffer = { result: "SUCCESS" };
}
else {
buffer = { result: "ERROR_POKEMON_NOT_FOUND" };
}
return (
POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.SetFavoritePokemonResponse")
);
}

View File

@ -6,4 +6,6 @@ export LevelUpRewards from "./LevelUpRewards";
export GetHatchedEggs from "./GetHatchedEggs";
export GetAssetDigest from "./GetAssetDigest";
export GetPlayerProfile from "./GetPlayerProfile";
export CheckAwardedBadges from "./CheckAwardedBadges";
export SetFavoritePokemon from "./SetFavoritePokemon";
export CheckAwardedBadges from "./CheckAwardedBadges";
export RecycleInventoryItem from "./RecycleInventoryItem";

View File

@ -160,7 +160,7 @@ export default class Pokemon extends MapObject {
*/
serialize() {
return ({
id: 1,
id: this.id,
pokemon_id: this.dexNumber,
cp: this.cp,
stamina: this.stamina,
@ -181,12 +181,12 @@ export default class Pokemon extends MapObject {
}
/**
* @return {Object}
* @return {Array}
*/
querify() {
return ({
return ([
});
]);
}
}

View File

@ -1,12 +1,8 @@
import POGOProtos from "pokemongo-protobuf";
import s2 from "s2-geometry";
import CFG from "../../../../cfg";
import print from "../../../print";
const S2Geo = s2.S2;
/**
* @param {Object} msg
* @return {Buffer}

View File

@ -73,6 +73,31 @@ export function getPlayerByIP(ip) {
}
/**
* @param {String} email
*/
export function playerIsRegistered(email) {
return new Promise((resolve) => {
this.db.query(`SELECT * FROM ${CFG.MYSQL_USERS_TABLE} WHERE email=?`, [email], (e, rows) => {
if (e) return print(e, 31);
resolve(rows.length >= 1);
});
});
}
/**
* @param {Player} player
*/
export function registerPlayer(player) {
return new Promise((resolve) => {
this.db.query(`INSERT INTO ${CFG.MYSQL_USERS_TABLE} SET email=? `, [player.email], (e, res) => {
if (e) return this.print(e, 31);
player.id = id;
resolve();
});
});
}
/**
* @param {Request} req
* @param {Response} res

View File

@ -11,7 +11,7 @@ export function processCommand(cmd, data) {
// How many active connections there are
case "/players":
var length = players.length;
print(`${length}:${CFG.MAX_CONNECTIONS} connected players!`, 33);
print(`${length}/${CFG.MAX_CONNECTIONS} connected players!`, 33);
break;
// Exit the server
case "/exit":

View File

@ -6,7 +6,11 @@ import POGOProtos from "pokemongo-protobuf";
import print from "./print";
import CFG from "../cfg";
import { deXOR, getHashCodeFrom } from "./utils";
import {
deXOR,
validEmail,
getHashCodeFrom
} from "./utils";
/**
* @param {Request} req
@ -89,13 +93,6 @@ export function onRequest(player) {
let request = player.request;
request.requests = request.requests || [];
if (CFG.DEBUG_LOG_REQUESTS) {
print("#####");
request.requests.map((request) => {
print(request.request_type, 35);
}).join(",");
}
if (!player.authenticated) {
this.authenticatePlayer(player);
return void 0;
@ -105,6 +102,15 @@ export function onRequest(player) {
player.getDevicePlatform();
}
// Update position
if (
request.latitude !== void 0 &&
request.longitude !== void 0
) {
player.latitude = request.latitude;
player.longitude = request.longitude;
}
if (!request.requests.length) {
// Dirty hack, appears when open pkmn stats in inventory
if (request.unknown6 && request.unknown6[1].request_type === 6) {
@ -122,6 +128,16 @@ export function onRequest(player) {
this.dumpTraffic(request, returns);
}
let msg = this.envelopResponse(returns, request, player);
if (CFG.DEBUG_LOG_REQUESTS) {
print(`##### ${this.getCurrentTime()}`);
let index = 0;
request.requests.map((request) => {
let reqSize = Buffer.byteLength(request.request_message, "utf8");
let resSize = Buffer.byteLength(returns[index], "utf8");
console.log(`[Packet]: ${request.request_type}`, `${reqSize} => ${resSize}`);
index++;
});
}
player.sendResponse(msg);
});
@ -137,8 +153,6 @@ export function envelopResponse(returns, request, player) {
let buffer = request;
delete buffer.requests;
buffer.returns = returns;
if (request.auth_ticket) {
@ -200,10 +214,27 @@ export function authenticatePlayer(player) {
return void 0;
}
if (!validEmail(player.email)) return void 0;
player.authenticated = (
deXOR(this.hash, getHashCodeFrom(this.claim)) === this.repository
);
player.sendResponse(msg);
// Register player
this.world.playerIsRegistered(player.email).then((truth) => {
if (!truth) {
this.world.registerPlayer(player).then((id) => {
player.syncWithDatabase().then(() => {
player.sendResponse(msg);
});
});
}
else {
player.syncWithDatabase().then(() => {
player.sendResponse(msg);
});
}
});
}

View File

@ -48,8 +48,9 @@ export function processResponse(player, req) {
case "LEVEL_UP_REWARDS":
case "GET_PLAYER_PROFILE":
case "CHECK_AWARDED_BADGES":
case "SET_FAVORITE_POKEMON":
case "RECYCLE_INVENTORY_ITEM":
player.getPacket(req.request_type, msg).then((result) => {
print(`Success: ${req.request_type} => ${result.toString().length}`);
resolve(result);
});
return void 0;
@ -65,7 +66,6 @@ export function processResponse(player, req) {
case "DOWNLOAD_ITEM_TEMPLATES":
msg.player = player;
player.world.getPacket(req.request_type, msg).then((result) => {
print(`Success: ${req.request_type} => ${result.toString().length}`);
resolve(result);
});
return void 0;

View File

@ -94,6 +94,13 @@ export function validUsername(str) {
);
}
let rx_email = /\S+@\S+\.\S+/;
export function validEmail(str) {
return (
!!rx_email.test(str)
);
}
/**
* @param {Request} req
*/