From 00793fd42d693bcbf8d19a75d86901f720348341 Mon Sep 17 00:00:00 2001 From: Felix Date: Tue, 6 Sep 2016 20:26:38 +0200 Subject: [PATCH] Update - Spawn cycle - Fixed avatar gender change - Basic wild pokemon - Basic map encounter spawns - Store cells inside array instead of object, to improve performance - Increased map visibility range dramatically - Lint game master parsing --- src/cycle.js | 9 +++ src/index.js | 1 + src/models/GameMaster/index.js | 25 +++++- src/models/Player/Avatar/index.js | 15 +++- src/models/Player/index.js | 2 + src/models/Player/packets/SetAvatar.js | 13 ++++ src/models/Pokemon/WildPokemon/index.js | 87 +++++++++++++++++++-- src/models/Pokemon/index.js | 11 ++- src/models/World/Cell/index.js | 94 +++++++++++++++++++++-- src/models/World/forts.js | 4 +- src/models/World/index.js | 48 +++++++++--- src/models/World/packets/Encounter.js | 14 ++++ src/models/World/packets/GetMapObjects.js | 13 +--- src/models/World/packets/index.js | 1 + src/modes/index.js | 7 +- src/response.js | 1 + src/setup.js | 21 +---- 17 files changed, 299 insertions(+), 67 deletions(-) create mode 100644 src/models/World/packets/Encounter.js diff --git a/src/cycle.js b/src/cycle.js index b10b86c..0979b5a 100644 --- a/src/cycle.js +++ b/src/cycle.js @@ -1,6 +1,9 @@ import print from "./print"; import CFG from "../cfg"; +import Settings from "./modes"; +const MAP_REFRESH_RATE = Settings.GAME_SETTINGS.map_settings.get_map_objects_max_refresh_seconds; + export function startCycle() { this.cycleInstance = setTimeout(() => this.cycle(), CFG.TICK_INTERVAL); } @@ -51,6 +54,12 @@ export function resetTimers() { //this.saveAllPlayers(); this.saveTick = 0; } + this.spawnTick++; + // Pkmn spawn interval + if (this.spawnTick >= MAP_REFRESH_RATE * 1e3) { + this.world.spawnEncounters(); + this.spawnTick = 0; + } return void 0; } diff --git a/src/index.js b/src/index.js index 3687055..ce46257 100644 --- a/src/index.js +++ b/src/index.js @@ -63,6 +63,7 @@ export default class GameServer { this.time = 0; this.fullTick = 0; this.saveTick = 0; + this.spawnTick = 0; this.timeoutTick = 0; this.passedTicks = 0; diff --git a/src/models/GameMaster/index.js b/src/models/GameMaster/index.js index c25a45b..b0e5557 100644 --- a/src/models/GameMaster/index.js +++ b/src/models/GameMaster/index.js @@ -1,28 +1,34 @@ +import fs from "fs"; import POGOProtos from "pokemongo-protobuf"; import { idToPkmnBundleName } from "../../utils"; +import CFG from "../../../cfg"; import ENUM from "../../enum"; +import print from "../../print"; + /** * @class GameMaster */ export default class GameMaster { /** - * @param {Object} decode + * @param {GameServer} instance * @constructor */ - constructor(decode) { + constructor(instance) { + + this.instance = instance; this.settings = this.buildSettings(); - this.decode = decode; + this.decode = this.parse(); this.buffer = this.encode(); - this.parse(); + this.parseItemTemplates(); } @@ -36,6 +42,17 @@ export default class GameMaster { } parse() { + let master = null; + try { + let data = fs.readFileSync(CFG.DUMP_ASSET_PATH + "game_master"); + master = this.instance.parseProtobuf(data, "POGOProtos.Networking.Responses.DownloadItemTemplatesResponse"); + } catch (e) { + print(e, 31); + } + return (master); + } + + parseItemTemplates() { let item = null; let items = this.decode.item_templates; diff --git a/src/models/Player/Avatar/index.js b/src/models/Player/Avatar/index.js index 220a19b..1b4ff2d 100644 --- a/src/models/Player/Avatar/index.js +++ b/src/models/Player/Avatar/index.js @@ -70,7 +70,7 @@ export default class Avatar { return (this._pants); } set pants(value) { - if (this.between(value, 0, 2)) { + if (this.between(value, 0, 5)) { this._pants = value; } } @@ -125,6 +125,17 @@ export default class Avatar { } } + resetOutfit() { + this.skin = 0; + this.hair = 0; + this.shirt = 0; + this.pants = 0; + this.hat = 0; + this.shoes = 0; + this.eyes = 0; + this.backpack = 0; + } + /** * @return {Object} */ @@ -149,7 +160,7 @@ export default class Avatar { let obj = JSON.parse(str); for (let key in obj) { if (this.hasOwnProperty("_" + key)) { - this[key] = obj[key]; + this[key] = obj[key] << 0; } }; } diff --git a/src/models/Player/index.js b/src/models/Player/index.js index 2edc1df..6545fff 100644 --- a/src/models/Player/index.js +++ b/src/models/Player/index.js @@ -61,6 +61,8 @@ export default class Player extends MapObject { this.remoteAddress = null; + this.currentEncounter = null; + this.bag = new Bag(this); this.candyBag = new CandyBag(this); diff --git a/src/models/Player/packets/SetAvatar.js b/src/models/Player/packets/SetAvatar.js index dc1cd04..50fd525 100644 --- a/src/models/Player/packets/SetAvatar.js +++ b/src/models/Player/packets/SetAvatar.js @@ -6,6 +6,19 @@ import POGOProtos from "pokemongo-protobuf"; */ export default function SetAvatar(msg) { + if (!msg.player_avatar.hasOwnProperty("gender")) { + msg.player_avatar.gender = "MALE"; + } + + let gender = msg.player_avatar.gender; + let genderChange = this.avatar.gender !== gender; + + msg.player_avatar.gender = gender === "MALE" ? 0 : 1; + + if (genderChange) { + this.avatar.resetOutfit(); + } + for (let key in msg.player_avatar) { this.avatar[key] = msg.player_avatar[key]; }; diff --git a/src/models/Pokemon/WildPokemon/index.js b/src/models/Pokemon/WildPokemon/index.js index 95693cf..028cf99 100644 --- a/src/models/Pokemon/WildPokemon/index.js +++ b/src/models/Pokemon/WildPokemon/index.js @@ -1,10 +1,19 @@ +import s2 from "s2-geometry"; import Pokemon from "../index"; -//import CaptureProbability from "CaptureProbability"; + +import Settings from "../../../modes"; + +import { getHashCodeFrom } from "../../../utils"; + +const S2Geo = s2.S2; + +const MAP_REFRESH_RATE = Settings.GAME_SETTINGS.map_settings.get_map_objects_max_refresh_seconds; +const EXPIRE_MULTIPLIER = Settings.PKMN_SETTINGS.EXPIRE_MULTIPLIER; /** * @class WildPokemon */ -class WildPokemon extends Pokemon { +export default class WildPokemon extends Pokemon { /** * @param {Object} obj @@ -15,14 +24,80 @@ class WildPokemon extends Pokemon { super(obj); this.encounterId = 0; + this.spawnPointId = 0; - this.latitude = 0; - this.longitude = 0; + this.creation = +new Date(); - this.pokeball = 0; + this.expiration = ~~(Math.random() * (MAP_REFRESH_RATE * 1e3) * EXPIRE_MULTIPLIER) + (MAP_REFRESH_RATE * 1e3); - this.spawnPoint = 0; + this.setRandomPosition(); + + this.uid = Math.random() * 1e5 << 0; } + setRandomPosition() { + let pos = S2Geo.idToLatLng(this.cellId); + this.latitude = pos.lat + (Math.random() * .009) + .0002; + this.longitude = pos.lng + (Math.random() * .009) + .0002; + } + + /** + * @return {Boolean} + */ + isExpired() { + return ( + +new Date() >= this.creation + this.expiration + ); + } + + getEncounterId() { + return (getHashCodeFrom(this.cellId + "" + this.uid)); + } + + getPkmnId() { + return ( + this.getPkmnName().toUpperCase() + ); + } + + /** + * @return {Object} + */ + serializeWild() { + return ({ + encounter_id: this.getEncounterId(), + last_modified_timestamp_ms: +new Date(), + latitude: this.latitude, + longitude: this.longitude, + pokemon_data: { + pokemon_id: this.getPkmnId() + }, + time_till_hidden_ms: this.expiration + }); + } + + /** + * @return {Object} + */ + serializeCatchable() { + return ({ + encounter_id: this.getEncounterId(), + pokemon_id: this.getPkmnId(), + expiration_timestamp_ms: this.creation + this.expiration, + latitude: this.latitude, + longitude: this.longitude + }); + } + + /** + * @return {Object} + */ + serializeNearby() { + return ({ + pokemon_id: this.getPkmnId(), + encounter_id: this.getEncounterId() + }); + } + } \ No newline at end of file diff --git a/src/models/Pokemon/index.js b/src/models/Pokemon/index.js index 21fe9d4..a9050f6 100644 --- a/src/models/Pokemon/index.js +++ b/src/models/Pokemon/index.js @@ -62,6 +62,8 @@ export default class Pokemon extends MapObject { this.nickname = null; this.pokeball = null; + this.isWild = false; + this.init(obj); } @@ -84,8 +86,13 @@ export default class Pokemon extends MapObject { this[key] = obj[key]; } }; - this.calcStats(); - this.calcMoves(); + if (obj.isWild) { + this.isWild = true; + } + else { + this.calcStats(); + this.calcMoves(); + } } /** diff --git a/src/models/World/Cell/index.js b/src/models/World/Cell/index.js index 68993e6..3ce03ad 100644 --- a/src/models/World/Cell/index.js +++ b/src/models/World/Cell/index.js @@ -1,8 +1,11 @@ import s2 from "s2-geometry"; +import rare from "pokerare"; import Gym from "../Fort/Gym"; import Pokestop from "../Fort/Pokestop"; +import WildPokemon from "../../Pokemon/WildPokemon"; + import MapObject from "../MapObject"; import Settings from "../../../modes"; import CFG from "../../../../cfg"; @@ -25,14 +28,26 @@ export default class Cell extends MapObject { super(obj); + this._uPkmnId = 0; + this.synced = false; this.forts = []; + this.encounters = []; + this.type = obj.type; } + get uPkmnId() { + return (this._uPkmnId); + } + set uPkmnId(value) { + if (this.uPkmnId < Number.MAX_SAFE_INTEGER) this._uPkmnId += value; + else this._uPkmnId = 0; + } + /** * @param {Number} lat * @param {Number} lng @@ -44,6 +59,63 @@ export default class Cell extends MapObject { ); } + /** + * @param {Object} obj + * @return {Fort} + */ + addFort(obj) { + obj.world = this.world; + let fort = null; + fort = obj.type === "CHECKPOINT" ? new Pokestop(obj) : new Gym(obj); + this.forts.push(fort); + return (fort); + } + + /** + * @return {WildPokemon} + */ + addEncounter() { + let pkmn = this.getRandomEncounter(); + print(`Spawned 1x ${pkmn.getPkmnName()} at ${this.cellId}`); + this.encounters.push(pkmn); + return (pkmn); + } + + getRandomEncounter() { + let ids = rare.getPkmnByRarity(255, 255); + let index = Math.floor(Math.random() * ids.length); + return new WildPokemon({ + dexNumber: ids[index].id, + pokeball: "ITEM_POKE_BALL", + favorite: 0, + isWild: true, + uid: this.uPkmnId++, + cellId: this.cellId + }); + } + + /** + * @param {WildPokemon} encounter + */ + removeEncounter(encounter) { + let index = 0; + this.encounters.map((pkmn) => { + if (pkmn.uid === encounter.uid) { + print(`Killed 1x ${pkmn.getPkmnName()} at ${this.cellId}`, 33); + this.encounters.splice(index, 1); + } + index++; + }); + } + + refreshEncounters() { + this.encounters.map((encounter) => { + if (encounter.isExpired()) { + this.removeEncounter(encounter); + } + }); + } + loadForts() { return new Promise((resolve) => { if (this.synced) { @@ -165,15 +237,21 @@ export default class Cell extends MapObject { } /** - * @param {Object} obj - * @return {Fort} + * @return {Object} */ - addFort(obj) { - obj.world = this.world; - let fort = null; - fort = obj.type === "CHECKPOINT" ? new Pokestop(obj) : new Gym(obj); - this.forts.push(fort); - return (fort); + serialize() { + return ({ + s2_cell_id: this.cellId, + current_timestamp_ms: +new Date(), + forts: this.forts.map((fort) => { return fort.serialize(); }), + spawn_points: [], + deleted_objects: [], + fort_summaries: [], + decimated_spawn_points: [], + wild_pokemons: this.encounters.map((pkmn) => { return pkmn.serializeWild(); }), + catchable_pokemons: this.encounters.map((pkmn) => { return pkmn.serializeCatchable(); }), + nearby_pokemons: this.encounters.map((pkmn) => { return pkmn.serializeNearby(); }) + }); } } \ No newline at end of file diff --git a/src/models/World/forts.js b/src/models/World/forts.js index 8512b94..4c8bb6c 100644 --- a/src/models/World/forts.js +++ b/src/models/World/forts.js @@ -46,7 +46,7 @@ export function getFortsByCells(cells, out, index) { */ export function getFortsByCellId(cellId) { return new Promise((resolve) => { - if (!this.cellRegistered(cellId)) { + if (!this.cellAlreadyRegistered(cellId)) { this.registerCell(cellId).then((cell) => { resolve(cell); }); @@ -68,7 +68,7 @@ export function registerCell(cellId) { world: this, cellId: cellId }); - this.cells[cellId] = cell; + this.cells.push(cell); return new Promise((resolve) => { cell.loadForts().then(() => { resolve(this.getCellById(cellId)); diff --git a/src/models/World/index.js b/src/models/World/index.js index cd21140..3cae790 100644 --- a/src/models/World/index.js +++ b/src/models/World/index.js @@ -22,7 +22,7 @@ export default class World { this.players = []; - this.cells = {}; + this.cells = []; } @@ -30,15 +30,27 @@ export default class World { return (this.players.length); } + /** + * @param {String} cellId + * @return {Cell} + */ + getCellByCellId(cellId) { + let ii = 0; + let length = this.cells.length; + for (; ii < length; ++ii) { + if (this.cells[ii].cellId === cellId) return (this.cells[ii]); + }; + return (null); + } + /** * @param {String} cellId * @return {Boolean} */ - cellRegistered(cellId) { + cellAlreadyRegistered(cellId) { + let cell = this.getCellByCellId(cellId); return ( - this.cells[cellId] !== null && - this.cells[cellId] !== void 0 && - this.cells[cellId] + cell !== null ); } @@ -47,15 +59,24 @@ export default class World { * @return {Cell} */ getCellById(cellId) { - return (this.cells[cellId]); + return (this.getCellByCellId(cellId)); } - addGym() { - - } + spawnEncounters() { + + let ii = 0; + let length = this.cells.length; + + let cell = null; + + for (; ii < length; ++ii) { + cell = this.cells[ii]; + if (Math.random() < .25 && cell.encounters.length <= 3) { + cell.addEncounter(); + } + cell.refreshEncounters(); + }; - addWildPokemon() { - } /** @@ -65,6 +86,11 @@ export default class World { getPacket(type, msg) { return new Promise((resolve) => { switch (type) { + case "ENCOUNTER": + this.Encounter(msg).then((result) => { + resolve(result); + }); + break; case "FORT_SEARCH": this.FortSearch(msg).then((result) => { resolve(result); diff --git a/src/models/World/packets/Encounter.js b/src/models/World/packets/Encounter.js new file mode 100644 index 0000000..eff1ef4 --- /dev/null +++ b/src/models/World/packets/Encounter.js @@ -0,0 +1,14 @@ +import POGOProtos from "pokemongo-protobuf"; + +/** + * @param {Object} msg + */ +export default function Encounter(msg) { + + let buffer = {}; + + return ( + POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.EncounterResponse") + ); + +} \ No newline at end of file diff --git a/src/models/World/packets/GetMapObjects.js b/src/models/World/packets/GetMapObjects.js index bdbf1a7..3ddcdf2 100644 --- a/src/models/World/packets/GetMapObjects.js +++ b/src/models/World/packets/GetMapObjects.js @@ -30,18 +30,7 @@ export default function GetMapObjects(msg) { else ids.push(id); }); } - mapCells.push({ - s2_cell_id: cell.cellId, - current_timestamp_ms: +new Date(), - forts: cell.forts.map((fort) => { return fort.serialize() }), - spawn_points: [], - deleted_objects: [], - fort_summaries: [], - decimated_spawn_points: [], - wild_pokemons: [], - catchable_pokemons: [], - nearby_pokemons: [] - }); + mapCells.push(cell.serialize()); }); resolve( POGOProtos.serialize(buffer, "POGOProtos.Networking.Responses.GetMapObjectsResponse") diff --git a/src/models/World/packets/index.js b/src/models/World/packets/index.js index c07be2d..79efaf6 100644 --- a/src/models/World/packets/index.js +++ b/src/models/World/packets/index.js @@ -1,3 +1,4 @@ +export Encounter from "./Encounter"; export FortSearch from "./FortSearch"; export FortDetails from "./FortDetails"; export GetMapObjects from "./GetMapObjects"; diff --git a/src/modes/index.js b/src/modes/index.js index d0aea5d..f7aebe8 100644 --- a/src/modes/index.js +++ b/src/modes/index.js @@ -11,9 +11,9 @@ export default { far_interaction_range_meters: 1000.0156862745098 }, map_settings: { - pokemon_visible_range: 70.00196078431372, + pokemon_visible_range: 999.00196078431372, poke_nav_range_meters: 751.0156862745098, - encounter_range_meters: 50.25098039215686, + encounter_range_meters: 999.25098039215686, get_map_objects_min_refresh_seconds: 16, get_map_objects_max_refresh_seconds: 16, get_map_objects_min_distance_meters: 10.007843017578125, @@ -30,6 +30,7 @@ export default { }, PKMN_SETTINGS: { MIN_IV: 1, - MAX_IV: 15 + MAX_IV: 15, + EXPIRE_MULTIPLIER: 5 } } \ No newline at end of file diff --git a/src/response.js b/src/response.js index 087e0ca..7cee67c 100644 --- a/src/response.js +++ b/src/response.js @@ -56,6 +56,7 @@ export function processResponse(player, req) { return void 0; break; // Global + case "ENCOUNTER": case "FORT_SEARCH": case "FORT_DETAILS": case "GET_MAP_OBJECTS": diff --git a/src/setup.js b/src/setup.js index a02b262..ac37fda 100644 --- a/src/setup.js +++ b/src/setup.js @@ -39,8 +39,7 @@ export function setup() { print(`Downloaded assets are valid! Proceeding..`); - let parsedMaster = this.parseGameMaster(); - shared.GAME_MASTER = new GameMaster(parsedMaster); + shared.GAME_MASTER = new GameMaster(this); this.setupDatabaseConnection().then(() => { if (CFG.PORT < 1) { @@ -93,9 +92,8 @@ export function validateModels() { return new Promise((resolve, reject) => { const validate = (index) => { - let platform = pogo.platforms[index]; - let name = platform.name; - let path = CFG.DUMP_ASSET_PATH + name + "/"; + let platform = pogo.platforms[index].name; + let path = CFG.DUMP_ASSET_PATH + platform + "/"; // ups, validate asset_digest's too if (!this.fileExists(path + "asset_digest")) { @@ -103,7 +101,7 @@ export function validateModels() { } else { let buffer = fs.readFileSync(path + "asset_digest"); - shared.GAME_ASSETS[name] = { + shared.GAME_ASSETS[platform] = { buffer: buffer, decode: this.parseProtobuf(buffer, "POGOProtos.Networking.Responses.GetAssetDigestResponse") }; @@ -130,17 +128,6 @@ export function validateModels() { } -export function parseGameMaster() { - let master = null; - try { - let data = fs.readFileSync(CFG.DUMP_ASSET_PATH + "game_master"); - master = this.parseProtobuf(data, "POGOProtos.Networking.Responses.DownloadItemTemplatesResponse"); - } catch (e) { - print(e, 31); - } - return (master); -} - export function onFirstRun(resolve) { print(`Attempt to login with ${_toCC(CFG.DOWNLOAD_PROVIDER)}..`, 33); pogo.login({