/*:
* @author Coelocanth & Ellie
* @plugindesc HB Games high score integration
* @target MZ
* @url https://www.arpgmaker.com
*
* @help This plugin lets you post high scores to your game page on HB Games
*
* Use the plugin command "postHighScore" to send a score.
* The first character of a score type is invisible and just used for sorting,
* so use "1Score" and not "Score" or it'll appear as "core" on the site.
*
* Use the plugin command "readHighScore" to pull the high score table into variables
* to display yourself.
*
* Use the plugin command "showHighScore" to just show a window with the scores in.
*
* RPG Maker MV:
* postHighScore 1 3 1Score
* 1st argument is the actor ID who has the player name as their name
* 2nd argument is the variable ID containing the score
* 3rd argument is the type of the score (if omitted, "1Score")
*
* readHighScore 1 2 3
* 1st argument is the actor ID whose name is set to the high score's name
* 2nd argument is the variable ID set to the high score's value
* 3rd argument is the index of the high score table (0 = highest)
*
* showHighScore 0
* 1st argument is window type (0 = normal, 1 = dim, 2 = transparent)
*
* RPG Maker MZ:
* Use the plugin commands from this plugin
*
* Script calls (use with conditional branch, control variables, etc.):
* CC.HBHS.lastSuccess() -> returns whether the last high score post worked.
* CC.HBHS.highscoreCount() -> returns the number of high score entries
* CC.HBHS.highscoreName(index) -> returns the name of high score entry at index (0 is highest score)
* CC.HBHS.highscoreValue(index) -> returns the score of high score entry at index (0 is highest score)
*
* ISC License
*
* Copyright (c) 2021 Coelocanth
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*
* @param gameId
* @text Game ID
* @desc Your Game ID from HB Games (number at end of URL)
*
* @param highscoresToken
* @text Highscores Token
* @desc Your Game's high scores token from HB Games (on game settings)
*
* @param highscoreRows
* @text Highscore Rows
* @desc max number of top scores to show in the high score window
* @type number
* @default 10
* @min 1
*
* @param spacer
* @text --- ignore below here (MV compatibility hack)
*
* @param nothing
* @text This does nothing (MV compatibility hack)
*
* @command postHighScore
* @arg name
* @text Actor ID who has the player name as their name
* @type actor
* @default 1
* @arg score
* @text Variable ID containing the high score
* @type variable
* @default 1
* @arg type
* @text type of score (1st char is invisible for)
* @type string
* @default 1Score
*
* @command readHighScore
* @arg name
* @text Actor ID to store the name in
* @type actor
* @default 2
* @arg score
* @text Variable ID to store the high score in
* @type variable
* @default 1
* @arg index
* @text Variable ID containing the index to read (0 is highest score)
* @type variables
* @default 2
*
* @command showHighScore
* @arg bgType
* @text background type
* @type select
* @option normal
* @value 0
* @option dim
* @value 1
* @option transparent
* @value 2
*/
var Imported = Imported || {};
Imported.HBHS = true;
var CC = CC || {};
class HBHighScores {
constructor() {
const params = PluginManager.parameters("HBHS");
this.gameId = params["gameId"];
this.token = params["highscoresToken"];
this.rows = parseInt(params["highscoreRows"] || "10");
this._lastSuccess = false;
this._highscores = [];
}
postHighScore(interpreter, args) {
const user = $gameActors.actor(JSON.parse(args.name)).name();
const score = $gameVariables.value(JSON.parse(args.score));
this.sendHighScore(interpreter, user, score, args.type);
}
readHighScore(interpreter, args) {
const index = JSON.parse(args.index);
$gameActors.actor(JSON.parse(args.name)).setName(this.highscoreName(index));
$gameVariables.setValue(JSON.parse(args.score), this.highscoreValue(index));
}
sendHighScore(interpreter, user, score, type) {
// block interpreter
interpreter.setWaitMode("CC.HBHS.fetch");
let qs = new URLSearchParams({
"h": this.token + "@" + score + "@" + user + "@" + this.gameId + "@" + type
})
let url = new URL("https://www.arpgmaker.com/highscores.php?" + qs.toString());
fetch(url, {"method": "GET", "mode": "cors"})
.then(response => {
this._lastSuccess = response.ok;
return response.text()
})
.then(text => {
this.processHighScores(text);
// unblock interpreter
interpreter.setWaitMode("");
})
.catch(error => {
console.error(error);
this._lastSuccess = false;
// unblock interpreter
interpreter.setWaitMode("");
});
}
processHighScores(text) {
try {
let data = JSON.parse(text);
let scores = [];
if(data["highscores"]) {
for (const [key, value] of Object.entries(data.highscores)) {
scores.push({"u": key, "s": value});
}
// sort descending by score
scores.sort((a,b) => b.s - a.s);
this._highscores = scores;
}
} catch(error) {
console.warn(error);
this._lastSuccess = false;
}
}
lastSuccess() { return this._lastSuccess; }
highscoreCount() { return this._highscores.length; }
highscoreName(index) { return this._highscores[index].u || ""; }
highscoreValue(index) { return this._highscores[index].s || 0; }
}
class Window_HBHighScores extends Window_Base {
constructor() {
if(Utils.RPGMAKER_NAME === "MV") {
super(0,0,0,0);
} else {
// MZ (+?)
super(new Rectangle(0,0,0,0));
}
this.hide();
}
refresh() {
let c = Math.min(CC.HBHS.rows, CC.HBHS.highscoreCount());
let w = Math.max(this.textWidth("High Scores"), Graphics.boxWidth / 2);
let h = this.fittingHeight(CC.HBHS.rows + 2);
for(let i=0; i<c; i++) {
w = Math.max(w, this.textWidth(CC.HBHS.highscoreName(i) + " " + CC.HBHS.highscoreValue(i)));
}
if(Utils.RPGMAKER_NAME === "MV") {
w += this.standardPadding() * 2;
} else {
// MZ (+?)
w += $gameSystem.windowPadding() * 2;
}
this.move(Math.floor((Graphics.boxWidth - w) / 2),
Math.floor((Graphics.boxHeight - h) / 2),
w, h);
this.createContents();
this.resetFontSettings();
this.drawText("High Scores", 0, 0, this.contentsWidth(), "center");
for(let i=0; i<c; i++) {
this.drawText(CC.HBHS.highscoreName(i), 0, this.lineHeight() * (2 + i), this.contentsWidth());
this.drawText(CC.HBHS.highscoreValue(i), 0, this.lineHeight() * (2 + i), this.contentsWidth(), "right");
}
}
update() {
if(!this.active) return;
if(Input.isTriggered("ok")) {
this.hide();
this.deactivate();
}
}
}
CC.HBHS = new HBHighScores();
CC.HBHS.Game_Interpreter_updateWaitMode = Game_Interpreter.prototype.updateWaitMode;
Game_Interpreter.prototype.updateWaitMode = function() {
if(this._waitMode === "CC.HBHS.fetch") {
return true; // blocked on http request
}
if(this._waitMode === "CC.HBHS.show") {
const w = SceneManager._scene._highscoreWindow;
return w && w.active; // block until window dismissed
}
return CC.HBHS.Game_Interpreter_updateWaitMode.call(this);
}
CC.HBHS.Scene_Map_createAllWindows = Scene_Map.prototype.createAllWindows;
Scene_Map.prototype.createAllWindows = function() {
CC.HBHS.Scene_Map_createAllWindows.call(this);
this._highscoreWindow = new Window_HBHighScores();
this.addWindow(this._highscoreWindow);
}
Scene_Map.prototype.showHighScore = function(bgType) {
this._highscoreWindow.refresh();
this._highscoreWindow.setBackgroundType(bgType);
this._highscoreWindow.show();
this._highscoreWindow.activate();
}
if(Utils.RPGMAKER_NAME === "MV") {
// MV - bind plugin commands via Game_Interpreter
CC.HBHS.Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
Game_Interpreter.prototype.pluginCommand = function(command, args) {
// MV only
if(command === "postHighScore") {
return CC.HBHS.postHighScore(this, {"name": args[0], "score": args[1], "type": args[2] || "1Score"});
}
if(command === "readHighScore") {
return CC.HBHS.readHighScore(this, {"name": args[0], "score": args[1], "index": args[2]});
}
if(command === "showHighScore") {
SceneManager._scene.showHighScore(parseInt(args[0]));
this._waitMode = "CC.HBHS.show";
}
return CC.HBHS.Game_Interpreter_pluginCommand.call(this, command, args);
};
} else {
// MZ (+?) - bind plugin commands via plugin manager
PluginManager.registerCommand("HBHS", "postHighScore", function(args) {
CC.HBHS.postHighScore(this, args);
});
PluginManager.registerCommand("HBHS", "readHighScore", function(args) {
CC.HBHS.readHighScore(this, args);
});
PluginManager.registerCommand("HBHS", "showHighScore", function(args) {
SceneManager._scene.showHighScore(parseInt(args.bgType));
this._waitMode = "CC.HBHS.show";
});
}