summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgumi <git@gumi.ca>2020-07-23 20:36:49 -0400
committergumi <git@gumi.ca>2020-07-27 08:47:41 -0400
commitb899412192d02f5f36d693a92d97b8cf6863f72f (patch)
tree30f0dff6fb5d4a856621411e55d49f459f93312c
parent0d06b85ee4fc385c2d15057f8d8fc59f77f526ed (diff)
downloadserverdata-b899412192d02f5f36d693a92d97b8cf6863f72f.tar.gz
serverdata-b899412192d02f5f36d693a92d97b8cf6863f72f.tar.bz2
serverdata-b899412192d02f5f36d693a92d97b8cf6863f72f.tar.xz
serverdata-b899412192d02f5f36d693a92d97b8cf6863f72f.zip
add the player cache system
-rw-r--r--npc/functions/player-cache.txt353
-rw-r--r--npc/functions/vault.txt22
-rw-r--r--npc/scripts.conf1
3 files changed, 376 insertions, 0 deletions
diff --git a/npc/functions/player-cache.txt b/npc/functions/player-cache.txt
new file mode 100644
index 00000000..7c281af9
--- /dev/null
+++ b/npc/functions/player-cache.txt
@@ -0,0 +1,353 @@
+// Player cache system
+//
+// Holds a cache of online players in-memory even after logout. This greatly
+// reduces the need to query the SQL database for data that is frequently
+// accessed.
+//
+// NOTE: This NPC uses the new public/private function call system to avoid
+// polluting the global function namespace
+//
+// Example usage:
+// .@account_id = "playerCache"::name2account("Reid");
+//
+
+- script playerCache FAKE_NPC,{
+ /**
+ * "playerCache"::name2account("char name") => account id
+ *
+ * Searches through the player cache to convert a char name to a account ID.
+ * If the player is not found, checks the SQL table. If the player doesn't
+ * exist, returns false.
+ *
+ * @param 0 - the char name to search
+ * @return the account id of the char
+ */
+ public function name2account {
+ if (.@acc = getcharid(CHAR_ID_ACCOUNT, getarg(0))) {
+ // player is currently online
+ return .@acc;
+ }
+
+ if (.@acc = htget(.name_to_account, getarg(0), 0)) {
+ // player found in the hash table
+ return .@acc;
+ }
+
+ // player still not found: now we try SQL
+ .@name$ = escape_sql(getarg(0));
+ query_sql(sprintf("SELECT account_id, char_id FROM `char` WHERE `name`='%s' LIMIT 1;", .@name$), .@acc, .@char);
+
+ if (.@acc > 0) {
+ // player found: add to our cache
+ htput(.name_to_account, getarg(0), .@acc);
+ .account_to_name$[.@acc] = getarg(0);
+ htput(.name_to_char, getarg(0), .@char);
+ .char_to_name$[.@char] = getarg(0);
+
+ return .@acc;
+ }
+
+ // player doesn't exist
+ return false;
+ }
+
+ /**
+ * "playerCache"::name2char("char name") => char id
+ *
+ * Searches through the player cache to convert a char name to a char ID.
+ * If the player is not found, checks the SQL table. If the player doesn't
+ * exist, returns false.
+ *
+ * @param 0 - the char name to search
+ * @return the char id of the char
+ */
+ public function name2char {
+ if (.@char = getcharid(CHAR_ID_CHAR, getarg(0))) {
+ // player is currently online
+ return .@char;
+ }
+
+ if (.@char = htget(.name_to_char, getarg(0), 0)) {
+ // player found in the hash table
+ return .@char;
+ }
+
+ // player still not found: now we try SQL
+ .@name$ = escape_sql(getarg(0));
+ query_sql(sprintf("SELECT account_id, char_id FROM `char` WHERE `name`='%s' LIMIT 1;", .@name$), .@acc, .@char);
+
+ if (.@acc > 0) {
+ // player found: add to our cache
+ htput(.name_to_account, getarg(0), .@acc);
+ .account_to_name$[.@acc] = getarg(0);
+ htput(.name_to_char, getarg(0), .@char);
+ .char_to_name$[.@char] = getarg(0);
+
+ return .@char;
+ }
+
+ // player doesn't exist
+ return false;
+ }
+
+ /**
+ * "playerCache"::char2account(char id) => account id
+ *
+ * Searches through the player cache to convert a char ID to an account ID.
+ * If the player is not found, checks the SQL table. If the player doesn't
+ * exist, returns false.
+ *
+ * @param 0 - the char ID to search
+ * @return the account id of the char
+ */
+ public function char2account {
+ .@name$ = .char_to_name$[getarg(0)];
+
+ if (.@name$ != "") {
+ if (.@acc = getcharid(CHAR_ID_ACCOUNT, .@name$)) {
+ // player is currently online
+ return .@acc;
+ }
+
+ if (.@acc = htget(.name_to_account, .@name$, 0)) {
+ // player found in the hash table
+ return .@acc;
+ }
+ }
+
+ // player still not found: now we try SQL
+ query_sql(sprintf("SELECT account_id, name FROM `char` WHERE `char_id`='%d' LIMIT 1;", getarg(0)), .@acc, .@name$);
+
+ if (.@acc > 0) {
+ // player found: add to our cache
+ htput(.name_to_account, .@name$, .@acc);
+ .account_to_name$[.@acc] = .@name$;
+ htput(.name_to_char, .@name$, getarg(0));
+ .char_to_name$[getarg(0)] = .@name$;
+
+ return .@acc;
+ }
+
+ // player doesn't exist
+ return false;
+ }
+
+ /**
+ * "playerCache"::account2char(account id) => char id
+ *
+ * Searches through the player cache to convert an account ID into a
+ * char ID
+ *
+ * NOTE: this is a weak reference; an account ID does not uniquely identify
+ * a character
+ *
+ * @param 0 - the account ID to search for
+ * @return the char ID of the char
+ */
+ public function account2char {
+ if ((.@name$ = strcharinfo(PC_NAME, "", getarg(0))) != "") {
+ // player is online
+ return getcharid(CHAR_ID_CHAR, .@name$);
+ } else if ((.@name$ = .account_to_name$[getarg(0)]) != "") {
+ // found in our cache
+ return htget(.name_to_char, .@name$, false);
+ }
+
+ // not found
+ return false;
+ }
+
+ // TODO: char2name
+
+ /**
+ * "playerCache"::vault2account(vault id) => account id
+ *
+ * Searches through the player cache to convert a Vault account ID into a
+ * game account ID
+ *
+ * NOTE: this is a weak reference; a Vault ID does not uniquely identify
+ * a game account
+ *
+ * @param 0 - the Vault ID to search for
+ * @return the account id of the char
+ */
+ public function vault2account {
+ if (!SERVER_USES_VAULT) {
+ return false;
+ } else if (.@acc = .vault_to_account[getarg(0)]) {
+ // found in the cache
+ return .@acc;
+ }
+ return false;
+ }
+
+ // TODO: vault2char (weak reference)
+ // TODO: vault2name (weak reference)
+
+ /**
+ * "playerCache"::account2vault(account id) => vault id
+ *
+ * Searches through the player cache to convert a game account ID into a
+ * Vault account ID
+ *
+ * @param 0 - the account ID to search for
+ * @return the Vault ID associated with the account
+ */
+ public function account2vault {
+ if (!SERVER_USES_VAULT) {
+ return false;
+ } else if (.@vault = .account_to_vault[getarg(0)]) {
+ return .@vault;
+ } else {
+ // TODO: find in SQL
+ return false;
+ }
+ return false;
+ }
+
+ // TODO: char2vault
+ // TODO: name2vault
+
+ /**
+ * Registers a public local function that will be called when the char name
+ * associated with an account changes. The previous account ID will be
+ * passed as first argument to the provided function.
+ *
+ * @param 0 - the public local function
+ */
+ public function addNameHandler {
+ htput(.name_handlers, "" + getnpcid(), getarg(0));
+ return;
+ }
+
+ /**
+ * Registers a public local function that will be called when the account
+ * associated with a Vault account changes. The previous account id will be
+ * passed as first argument to the provided function and the previous
+ * char name will be passed as second argument.
+ *
+ * @param 0 - the public local function
+ */
+ public function addVaultHandler {
+ htput(.vault_handlers, "" + getnpcid(), getarg(0));
+ return;
+ }
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * updates the char name cache and fires event handlers when the name
+ * associated with an account changes (such as when switching char)
+ */
+ private function updateName {
+ // get the cached name:
+ .@old$ = .account_to_name$[playerattached()];
+
+ // now update the cache:
+ htput(.name_to_account, strcharinfo(PC_NAME), playerattached());
+ .account_to_name$[playerattached()] = strcharinfo(PC_NAME);
+ htput(.name_to_char, strcharinfo(PC_NAME), getcharid(CHAR_ID_CHAR));
+ .char_to_name$[getcharid(CHAR_ID_CHAR)] = strcharinfo(PC_NAME);
+
+ if (.@old$ != "" && .@old$ != .account_to_name$[playerattached()]) {
+ // fire event handlers
+ .@it = htiterator(.name_handlers);
+ for (.@npc$ = htinextkey(.@it); hticheck(.@it); .@npc$ = htinextkey(.@it)) {
+ if (.@npc$ == "") {
+ continue;
+ }
+
+ .@func$ = htget(.name_handlers, .@npc$, "");
+ callfunctionofnpc(atoi(.@npc$), .@func$, .@old$);
+ }
+ htidelete(.@it);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * updates the Vault account cache and fires event handlers when the account
+ * associated with a Vault account changes (different game account on same
+ * Vault account)
+ */
+ private function updateVault {
+ // get the cached id:
+ .@old = .vault_to_account[getvaultid()];
+ .@old$ = .account_to_name$[.@old];
+
+ // now update the cache:
+ .vault_to_account[getvaultid()] = playerattached();
+ .account_to_vault[playerattached()] = getvaultid();
+
+ if (.@old > 0 && .@old != .vault_to_account[getvaultid()]) {
+ // fire event handlers
+ .@it = htiterator(.vault_handlers);
+ for (.@npc$ = htinextkey(.@it); hticheck(.@it); .@npc$ = htinextkey(.@it)) {
+ if (.@npc$ == "") {
+ continue;
+ }
+
+ .@func$ = htget(.vault_handlers, .@npc$, "");
+ callfunctionofnpc(atoi(.@npc$), .@func$, .@old, .@old$);
+ }
+ htidelete(.@it);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * adds/updates the player to the player cache
+ */
+ private function updateCache {
+ updateName();
+
+ if (SERVER_USES_VAULT) {
+ updateVault();
+ }
+
+ return;
+ }
+
+ /**
+ * force-add all online players to the cache (used on init and reload)
+ */
+ private function forceCache {
+ .@count = getunits(BL_PC, .@units, false);
+
+ for (.@i = 0; .@i < .@count; ++.@i) {
+ attachrid(.@units[.@i]);
+ updateCache();
+ detachrid();
+ }
+
+ return;
+ }
+
+OnPCLoginEvent:
+ updateCache();
+ end;
+
+OnInit:
+ .name_to_account = htnew(); // Map<char name, account id>
+ .account_to_name$[0] = ""; // sparse Array<char name> indexed by account id
+ .name_to_char = htnew(); // Map<char name, char id>
+ .char_to_name$[0] = ""; // sparse Array<char name> indexed by char id
+
+ if (SERVER_USES_VAULT) {
+ .vault_to_account[0] = 0; // sparse Array<vault id> indexed by account id
+ .account_to_vault[0] = 0; // sparse Array<account id> indexed by vault id
+ }
+
+ // event handlers:
+ .vault_handlers = htnew();
+ .name_handlers = htnew();
+
+ // reloadscript handler:
+ forceCache();
+}
diff --git a/npc/functions/vault.txt b/npc/functions/vault.txt
index 05d65886..87ee50df 100644
--- a/npc/functions/vault.txt
+++ b/npc/functions/vault.txt
@@ -72,3 +72,25 @@ function script getvaultvar {
function script setvaultvar {
return set(getvaultvar(getarg(0), getarg(2, "")), getarg(1));
}
+
+/**
+ * handles Vault hooks on player login
+ */
+- script VaultHandler NPC_HIDDEN,{
+ public function OnDualLogin {
+ .@toKick$ = strcharinfo(PC_NAME, "", getarg(0, 0));
+
+ if (.@toKick$ != "") {
+ .@msg$ = sprintf("Kicking player %s (Vault dual-login)", .@toKick$);
+ consolemes(CONSOLEMES_INFO, .@msg$); // log this
+ dispbottom(.@msg$); // tell the player
+
+ return kick(.@toKick$, 2); // reason 2 is dual-login
+ }
+
+ return false;
+ }
+
+OnInit:
+ "playerCache"::addVaultHandler("OnDualLogin");
+}
diff --git a/npc/scripts.conf b/npc/scripts.conf
index edc58478..9714b492 100644
--- a/npc/scripts.conf
+++ b/npc/scripts.conf
@@ -19,6 +19,7 @@
"npc/functions/main.txt",
"npc/functions/legacy.txt",
"npc/functions/vault.txt",
+"npc/functions/player-cache.txt",
"npc/functions/asleep.txt",
"npc/functions/barber.txt",
"npc/functions/clientversion.txt",