diff --git a/linux_run_server.sh b/linux_run_server.sh index 69692575..a4c57057 100755 --- a/linux_run_server.sh +++ b/linux_run_server.sh @@ -34,6 +34,7 @@ rm server/database/treasures.sqlite3 rm server/database/spawns.sqlite3 rm server/database/prettify.sqlite3 rm server/database/tmapsolutions.sqlite3 +rm server/database/areadistances.sqlite3 mkdir server/override mkdir server/config @@ -48,6 +49,7 @@ cp seeded_database/treasures.sqlite3 server/database/treasures.sqlite3 cp seeded_database/randspellbooks.sqlite3 server/database/randspellbooks.sqlite3 cp seeded_database/prettify.sqlite3 server/database/prettify.sqlite3 cp seeded_database/tmapsolutions.sqlite3 server/database/tmapsolutions.sqlite3 +cp seeded_database/areadistances.sqlite3 server/database/areadistances.sqlite3 cp server/env/env.2da server/override/env.2da cp server/env/env_dm.2da server/override/env_dm.2da diff --git a/linux_run_server_dev.sh b/linux_run_server_dev.sh index c487f977..60e40054 100755 --- a/linux_run_server_dev.sh +++ b/linux_run_server_dev.sh @@ -5,12 +5,14 @@ rm database/treasures.sqlite3 rm database/spawns.sqlite3 rm database/prettify.sqlite3 rm database/tmapsolutions.sqlite3 +rm database/areadistances.sqlite3 cp seeded_database/spawns.sqlite3 database/spawns.sqlite3 cp seeded_database/treasures.sqlite3 database/treasures.sqlite3 cp seeded_database/randspellbooks.sqlite3 database/randspellbooks.sqlite3 cp seeded_database/prettify.sqlite3 database/prettify.sqlite3 cp seeded_database/tmapsolutions.sqlite3 database/tmapsolutions.sqlite3 +cp seeded_database/areadistances.sqlite3 database/areadistances.sqlite3 docker-compose -f docker-compose-dev.yml down docker-compose -f docker-compose-dev.yml up --no-recreate -d \ No newline at end of file diff --git a/linux_run_server_dev_seed.sh b/linux_run_server_dev_seed.sh index 3d412e5a..e86eba5f 100755 --- a/linux_run_server_dev_seed.sh +++ b/linux_run_server_dev_seed.sh @@ -7,10 +7,12 @@ rm seeded_database/randspellbooks.sqlite3 rm seeded_database/treasures.sqlite3 rm seeded_database/spawns.sqlite3 rm seeded_database/prettify.sqlite3 +rm seeded_database/areadistances.sqlite3 rm seeded_database/tmapsolutions.sqlite3 cp database/spawns.sqlite3 seeded_database/spawns.sqlite3 cp database/treasures.sqlite3 seeded_database/treasures.sqlite3 cp database/randspellbooks.sqlite3 seeded_database/randspellbooks.sqlite3 cp database/prettify.sqlite3 seeded_database/prettify.sqlite3 +cp database/areadistances.sqlite3 seeded_database/areadistances.sqlite3 cp database/tmapsolutions.sqlite3 seeded_database/tmapsolutions.sqlite3 \ No newline at end of file diff --git a/seeded_database/areadistances.sqlite3 b/seeded_database/areadistances.sqlite3 new file mode 100644 index 00000000..53b29086 Binary files /dev/null and b/seeded_database/areadistances.sqlite3 differ diff --git a/seeded_database/prettify.sqlite3 b/seeded_database/prettify.sqlite3 index 79c0b735..4ad77312 100644 Binary files a/seeded_database/prettify.sqlite3 and b/seeded_database/prettify.sqlite3 differ diff --git a/seeded_database/randspellbooks.sqlite3 b/seeded_database/randspellbooks.sqlite3 index 29cba00c..efdff3b5 100644 Binary files a/seeded_database/randspellbooks.sqlite3 and b/seeded_database/randspellbooks.sqlite3 differ diff --git a/seeded_database/spawns.sqlite3 b/seeded_database/spawns.sqlite3 index 01f4e7cf..7d66944c 100644 Binary files a/seeded_database/spawns.sqlite3 and b/seeded_database/spawns.sqlite3 differ diff --git a/seeded_database/tmapsolutions.sqlite3 b/seeded_database/tmapsolutions.sqlite3 index 79c5cda3..9bb26bf9 100644 Binary files a/seeded_database/tmapsolutions.sqlite3 and b/seeded_database/tmapsolutions.sqlite3 differ diff --git a/seeded_database/treasures.sqlite3 b/seeded_database/treasures.sqlite3 index 8e1446c1..89b9cb85 100644 Binary files a/seeded_database/treasures.sqlite3 and b/seeded_database/treasures.sqlite3 differ diff --git a/src/git/icelakes.git.json b/src/git/icelakes.git.json index 2e4776f2..c0727ff1 100644 --- a/src/git/icelakes.git.json +++ b/src/git/icelakes.git.json @@ -13225,6 +13225,21 @@ "type": "cexostring", "value": "polar" } + }, + { + "__struct_id": 0, + "Name": { + "type": "cexostring", + "value": "connection1" + }, + "Type": { + "type": "dword", + "value": 3 + }, + "Value": { + "type": "cexostring", + "value": "river_mirar" + } } ] }, diff --git a/src/git/lith.git.json b/src/git/lith.git.json index f499e4ae..065afc13 100644 --- a/src/git/lith.git.json +++ b/src/git/lith.git.json @@ -19454,7 +19454,7 @@ }, "Cost": { "type": "dword", - "value": 7 + "value": 0 }, "Cursed": { "type": "byte", @@ -20544,7 +20544,7 @@ }, "Cost": { "type": "dword", - "value": 7 + "value": 0 }, "Cursed": { "type": "byte", @@ -21804,7 +21804,7 @@ }, "Cost": { "type": "dword", - "value": 7 + "value": 0 }, "Cursed": { "type": "byte", @@ -22894,7 +22894,7 @@ }, "Cost": { "type": "dword", - "value": 7 + "value": 0 }, "Cursed": { "type": "byte", @@ -24154,7 +24154,7 @@ }, "Cost": { "type": "dword", - "value": 7 + "value": 0 }, "Cursed": { "type": "byte", @@ -25244,7 +25244,7 @@ }, "Cost": { "type": "dword", - "value": 7 + "value": 0 }, "Cursed": { "type": "byte", @@ -26473,7 +26473,7 @@ }, "Cost": { "type": "dword", - "value": 7 + "value": 0 }, "Cursed": { "type": "byte", @@ -27563,7 +27563,7 @@ }, "Cost": { "type": "dword", - "value": 7 + "value": 0 }, "Cursed": { "type": "byte", @@ -98474,6 +98474,21 @@ "type": "int", "value": 1 } + }, + { + "__struct_id": 0, + "Name": { + "type": "cexostring", + "value": "connection1" + }, + "Type": { + "type": "dword", + "value": 3 + }, + "Value": { + "type": "cexostring", + "value": "ud_maker1" + } } ] }, diff --git a/src/git/river_mirar.git.json b/src/git/river_mirar.git.json index 2468d83f..44006ed1 100644 --- a/src/git/river_mirar.git.json +++ b/src/git/river_mirar.git.json @@ -10327,6 +10327,21 @@ "type": "cexostring", "value": "highland" } + }, + { + "__struct_id": 0, + "Name": { + "type": "cexostring", + "value": "connection1" + }, + "Type": { + "type": "dword", + "value": 3 + }, + "Value": { + "type": "cexostring", + "value": "icelakes" + } } ] }, diff --git a/src/git/ud_maker1.git.json b/src/git/ud_maker1.git.json index 765fa1fd..7b3d6cc8 100644 --- a/src/git/ud_maker1.git.json +++ b/src/git/ud_maker1.git.json @@ -429,7 +429,7 @@ }, "Cost": { "type": "dword", - "value": 1579 + "value": 105299 }, "Cursed": { "type": "byte", @@ -1941,7 +1941,7 @@ }, "Cost": { "type": "dword", - "value": 1579 + "value": 105299 }, "Cursed": { "type": "byte", @@ -53709,6 +53709,21 @@ "type": "cexostring", "value": "ud_maker4" } + }, + { + "__struct_id": 0, + "Name": { + "type": "cexostring", + "value": "connection1" + }, + "Type": { + "type": "dword", + "value": 3 + }, + "Value": { + "type": "cexostring", + "value": "lith" + } } ] }, diff --git a/src/nss/0e_load_menu.nss b/src/nss/0e_load_menu.nss index 848e536d..fadc1485 100644 --- a/src/nss/0e_load_menu.nss +++ b/src/nss/0e_load_menu.nss @@ -7,7 +7,7 @@ #include "0i_win_layouts" #include "0i_database" -void main () +void main() { string sKey = GetPCPublicCDKey(OBJECT_SELF, TRUE); if (GetCampaignInt(sKey, "pc_menu_disabled") != 1) diff --git a/src/nss/area_hb.nss b/src/nss/area_hb.nss index 71f4b290..65dc38a7 100644 --- a/src/nss/area_hb.nss +++ b/src/nss/area_hb.nss @@ -95,8 +95,8 @@ void main() if (nFurthestLinkedAreaOffRefresh > 0 && nFurthestLinkedAreaOffRefresh < REFRESH_HEARTBEAT_COUNT) { // Waiting for another area doesn't affect our refresh counter, so don't spam the log too hard with this - //if (nRefresh % 20 == 0) - if (1) + if (nRefresh % 20 == 0) + //if (1) { SendDebugMessage("Can't refresh " + sResRef + " due to linked area not being ready, furthest count = " + IntToString(nFurthestLinkedAreaOffRefresh), TRUE); } diff --git a/src/nss/area_init.nss b/src/nss/area_init.nss index d777573b..12da4e5c 100644 --- a/src/nss/area_init.nss +++ b/src/nss/area_init.nss @@ -1,3 +1,4 @@ + #include "inc_debug" #include "util_i_csvlists" #include "nwnx_area" @@ -265,12 +266,32 @@ void main() int nHighGold = GetLocalInt(oModule, "placeable_value_high_" + sACR); json jKeepTreasures = JsonArray(); + int nNextTransitionIndex = 1; + while (1) + { + if (GetLocalString(oArea, "connection" + IntToString(nNextTransitionIndex)) == "") + { + break; + } + nNextTransitionIndex++; + } + int nThisTreasureGold; // Loop through all objects in the area and do something special with them while (GetIsObjectValid(oObject)) { nType = GetObjectType(oObject); + + // The warden tele script is used... for more things that just wardens now + // Doors and placeables can have it too! + string sWardenTeleWP = GetLocalString(oObject, "warden_tele_wp"); + if (sWardenTeleWP != "" && GetIsObjectValid(GetObjectByTag(sWardenTeleWP))) + { + object oTargetArea = GetArea(GetObjectByTag(sWardenTeleWP)); + SetLocalString(oArea, "connection" + IntToString(nNextTransitionIndex), GetResRef(oTargetArea)); + nNextTransitionIndex++; + } // tag merchants/quest NPCs that are plot/immortal as dm_immune // these types should never be skipped @@ -365,6 +386,11 @@ void main() // all doors are plot SetPlotFlag(oObject, TRUE); + if (GetIsObjectValid(GetTransitionTarget(oObject))) + { + SetLocalString(oArea, "connection" + IntToString(nNextTransitionIndex), GetResRef(GetArea(GetTransitionTarget(oObject)))); + nNextTransitionIndex++; + } if (GetStringLeft(GetResRef(oObject), 5) != "_home") { @@ -552,6 +578,15 @@ void main() SetPlotFlag(oObject, TRUE); } break; + case OBJECT_TYPE_TRIGGER: + { + if (GetIsObjectValid(GetTransitionTarget(oObject))) + { + SetLocalString(oArea, "connection" + IntToString(nNextTransitionIndex), GetResRef(GetArea(GetTransitionTarget(oObject)))); + nNextTransitionIndex++; + } + break; + } } oObject = GetNextObjectInArea(oArea); } @@ -616,6 +651,26 @@ void main() } SendDebugMessage(sResRef+" loaded trap spawn points: "+IntToString(nTrapSpawnPoints), TRUE); } + + // Connections: rewrite these to make sure there aren't any duplicates + // The area distance graph calculation is already bad enough, don't make it worse! + json jConnections = JsonArray(); + for (i=1; i dist[i][k] + dist[k][j] + dist[i][j] ← dist[i][k] + dist[k][j] + end if + */ + + int k; + for (k=0; k nDistik + nDistkj) + { + int nNewVal = nDistik + nDistkj; + jSubArrayi = JsonArraySet(jSubArrayi, j, JsonInt(nNewVal)); + jDist = JsonArraySet(jDist, i, jSubArrayi); + } + } + } + } + + WriteTimestampedLogEntry("PrepareAreaTransitionDB: Saving connections to db..."); + + sql = SqlPrepareQueryCampaign("areadistances", + "CREATE TABLE IF NOT EXISTS areadists (" + + "areasource TEXT, " + + "areadest TEXT, " + + "distance INTEGER);"); + SqlStep(sql); + if (_ShouldAbortAreaDistOnError(SqlGetError(sql))) return; + + WriteTimestampedLogEntry("Make indexes"); + + // Attempting to speed up the lookup of this thing + sql = SqlPrepareQueryCampaign("areadistances", + "CREATE INDEX IF NOT EXISTS idx_areadists_source_distance ON areadists(areasource, distance);"); + SqlStep(sql); + if (_ShouldAbortAreaDistOnError(SqlGetError(sql))) return; + + sql = SqlPrepareQueryCampaign("areadistances", + "CREATE INDEX IF NOT EXISTS idx_areadists_source_dest ON areadists(areasource, areadest);"); + SqlStep(sql); + if (_ShouldAbortAreaDistOnError(SqlGetError(sql))) return; + + sql = SqlPrepareQueryCampaign("areadistances", "DELETE FROM areadists;"); + SqlStep(sql); + if (_ShouldAbortAreaDistOnError(SqlGetError(sql))) return; + + sql = SqlPrepareQueryCampaign("areadistances", "BEGIN TRANSACTION"); + SqlStep(sql); + if (_ShouldAbortAreaDistOnError(SqlGetError(sql))) return; + + for (i=0; i is a lot slower +// than rolling your own DB table and writing Set/Get for it +#include "inc_sql" +#include "inc_sqlite_time" + +// This also supports having individual tables for different things in case messing with them in the future +// is something that is useful + +// It turned out this also reduced to an implementation that just replaces the basic Bioware campaign variable functions +// for arbitrary database names. Maybe that is useful? + +// Get an variable from oPC's cdkey db, looking up sVariable in sTable. +// Returns 0 if not set. +// sTable should contain alphabetical characters only. +int GetCdkeyInt(object oPC, string sTable, string sVariable); +// Get an variable from oPC's cdkey db, looking up sVariable in sTable. +// Returns "" if not set. +// sTable should contain alphabetical characters only. +string GetCdkeyString(object oPC, string sTable, string sVariable); +// Get an variable from oPC's cdkey db, looking up sVariable in sTable. +// Returns Vector(-1.0,-1.0,-1.0) if not set. +// sTable should contain alphabetical characters only. +vector GetCdkeyVector(object oPC, string sTable, string sVariable); +// Get an variable from oPC's cdkey db, looking up sVariable in sTable. +// Returns 0.0 if not set. +// sTable should contain alphabetical characters only. +float GetCdkeyFloat(object oPC, string sTable, string sVariable); +// Get an variable from oPC's cdkey db, looking up sVariable in sTable. +// Returns LOCATION_INVALID if not set. +// sTable should contain alphabetical characters only. +location GetCdkeyLocation(object oPC, string sTable, string sVariable); +// Get an variable from oPC's cdkey db, looking up sVariable in sTable. +// Returns JsonNull() if not set. +// sTable should contain alphabetical characters only. +json GetCdkeyJson(object oPC, string sTable, string sVariable); + +// Normally passing the PC object is easier, but maybe not always + +// Get an variable from the sDatabase campaign database, looking up sVariable in sTable. +// Returns 0 if not set. +// sTable should contain alphabetical characters only. +int GetCampaignDBTableInt(string sDatabase, string sTable, string sVariable); +// Get an variable from the sDatabase campaign database, looking up sVariable in sTable. +// Returns "" if not set. +// sTable should contain alphabetical characters only. +string GetCampaignDBTableString(string sDatabase, string sTable, string sVariable); +// Get an variable from the sDatabase campaign database, looking up sVariable in sTable. +// Returns Vector(-1.0,-1.0,-1.0) if not set. +// sTable should contain alphabetical characters only. +vector GetCampaignDBTableVector(string sDatabase, string sTable, string sVariable); +// Get an variable from the sDatabase campaign database, looking up sVariable in sTable. +// Returns 0.0 if not set. +// sTable should contain alphabetical characters only. +float GetCampaignDBTableFloat(string sDatabase, string sTable, string sVariable); +// Get an variable from the sDatabase campaign database, looking up sVariable in sTable. +// Returns LOCATION_INVALID if not set. +// sTable should contain alphabetical characters only. +location GetCampaignDBTableLocation(string sDatabase, string sTable, string sVariable); +// Get an variable from the sDatabase campaign database, looking up sVariable in sTable. +// Returns JsonNull() if not set. +// sTable should contain alphabetical characters only. +json GetCampaignDBTableJson(string sDatabase, string sTable, string sVariable); + +// Set the value of sVariable in oPC's cdkey db. +// sTable should contain alphabetical characters only. +void SetCdkeyInt(object oPC, string sTable, string sVariable, int nValue); +// Set the value of sVariable in oPC's cdkey db. +// sTable should contain alphabetical characters only. +void SetCdkeyString(object oPC, string sTable, string sVariable, string sValue); +// Set the value of sVariable in oPC's cdkey db. +// sTable should contain alphabetical characters only. +void SetCdkeyVector(object oPC, string sTable, string sVariable, vector vValue); +// Set the value of sVariable in oPC's cdkey db. +// sTable should contain alphabetical characters only. +void SetCdkeyFloat(object oPC, string sTable, string sVariable, float fValue); +// Set the value of sVariable in oPC's cdkey db. +// sTable should contain alphabetical characters only. +void SetCdkeyLocation(object oPC, string sTable, string sVariable, location lValue); +// Set the value of sVariable in oPC's cdkey db. +// sTable should contain alphabetical characters only. +void SetCdkeyJson(object oPC, string sTable, string sVariable, json jValue); + +// Set the value of sVariable in the sDatabase campaign database. +// sTable should contain alphabetical characters only. +void SetCampaignDBTableInt(string sDatabase, string sTable, string sVariable, int nValue); +// Set the value of sVariable in the sDatabase campaign database. +// sTable should contain alphabetical characters only. +void SetCampaignDBTableString(string sDatabase, string sTable, string sVariable, string sValue); +// Set the value of sVariable in the sDatabase campaign database. +// sTable should contain alphabetical characters only. +void SetCampaignDBTableVector(string sDatabase, string sTable, string sVariable, vector vValue); +// Set the value of sVariable in the sDatabase campaign database. +// sTable should contain alphabetical characters only. +void SetCampaignDBTableFloat(string sDatabase, string sTable, string sVariable, float fValue); +// Set the value of sVariable in the sDatabase campaign database. +// sTable should contain alphabetical characters only. +void SetCampaignDBTableLocation(string sDatabase, string sTable, string sVariable, location lValue); +// Set the value of sVariable in the sDatabase campaign database. +// sTable should contain alphabetical characters only. +void SetCampaignDBTableJson(string sDatabase, string sTable, string sVariable, json jValue); + +// Delete sVariable in oPC's cdkey db. +// sTable should contain alphabetical characters only. +void DeleteCdkeyVariable(object oPC, string sTable, string sVariable); +// Delete sVariable in the sDatabase campaign database. +// sTable should contain alphabetical characters only. +void DeleteCampaignDBTableVariable(string sDatabase, string sTable, string sVariable); + + +// Statistic writing is still too slow. +// The solution seems to be to write changes into memory db +// and periodically save them to the disk database +int GetCachedCdkeyInt(object oPC, string sTable, string sVariable); +float GetCachedCdkeyFloat(object oPC, string sTable, string sVariable); +string GetCachedCdkeyString(object oPC, string sTable, string sVariable); +vector GetCachedCdkeyVector(object oPC, string sTable, string sVariable); +location GetCachedCdkeyLocation(object oPC, string sTable, string sVariable); +json GetCachedCdkeyJson(object oPC, string sTable, string sVariable); + +void SetCachedCdkeyInt(object oPC, string sTable, string sVariable, int nValue); +void SetCachedCdkeyFloat(object oPC, string sTable, string sVariable, float fValue); +void SetCachedCdkeyString(object oPC, string sTable, string sVariable, string sValue); +void SetCachedCdkeyVector(object oPC, string sTable, string sVariable, vector vValue); +void SetCachedCdkeyLocation(object oPC, string sTable, string sVariable, location lValue); +void SetCachedCdkeyJson(object oPC, string sTable, string sVariable, json jValue); + +// Copies changed records from the cache to the campaign db. +// Does this only for the cached database that has gone the longest since being updated. +void UpdateOldestCachedCdkeyDB(); + +// Copies all changed records from the cache to their campaign dbs. +// I expect this to be used on module shutdown only. +void UpdateAllCachedCdkeyDBs(); + +/////////////////////////////////// + +// Since you apparently cannot bind table names, this is my best attempt +// of preventing injection if anyone ever uses non-static table names for some reason +int _WarnOnBadCdkeyTableString(string sTable) +{ + json jMatch = RegExpMatch("[A-Za-z0-9]*", sTable); + if (jMatch == JsonNull()) + { + WriteTimestampedLogEntry("ERROR: inc_cdkeyvars attempted operation with nonalphabetical table name " + sTable + ". DB operation failed."); + return 1; + } + if (JsonGetString(JsonArrayGet(jMatch, 0)) != sTable) + { + WriteTimestampedLogEntry("ERROR: inc_cdkeyvars attempted operation with nonalphabetical table name " + sTable + ". DB operation failed."); + return 1; + } + return 0; +} + +// These internal functions ASSUME THE CALLER CHECKED sTable with _WarnOnBadCdkeyTableString +void _CreateCdkeyTable(string sDatabase, string sTable) +{ + sqlquery sql = SqlPrepareQueryCampaign(sDatabase, "CREATE TABLE IF NOT EXISTS " + sTable + " (" + + "var TEXT PRIMARY KEY, " + + "value TEXT);"); + SqlStep(sql); + sql = SqlPrepareQueryCampaign(sDatabase, "CREATE INDEX IF NOT EXISTS idx_var ON " + sTable + "(var);"); + SqlStep(sql); +} +// These internal functions ASSUME THE CALLER CHECKED sTable with _WarnOnBadCdkeyTableString +sqlquery _CdkeyPrepareSelect(string sDatabase, string sTable, string sVariable) +{ + _CreateCdkeyTable(sDatabase, sTable); + sqlquery sql = SqlPrepareQueryCampaign(sDatabase, "SELECT value from " + sTable + " where var=@var;"); + SqlBindString(sql, "@var", sVariable); + return sql; +} +// These internal functions ASSUME THE CALLER CHECKED sTable with _WarnOnBadCdkeyTableString +sqlquery _CdkeyPrepareInsert(string sDatabase, string sTable, string sVariable) +{ + _CreateCdkeyTable(sDatabase, sTable); + sqlquery sql = SqlPrepareQueryCampaign(sDatabase, + "INSERT INTO " + sTable + + " (var, value) VALUES (@var, @value) " + + " ON CONFLICT (var) DO UPDATE SET value = @value;"); + SqlBindString(sql, "@var", sVariable); + return sql; +} +// These internal functions ASSUME THE CALLER CHECKED sTable with _WarnOnBadCdkeyTableString +sqlquery _CdkeyPrepareDelete(string sDatabase, string sTable, string sVariable) +{ + _CreateCdkeyTable(sDatabase, sTable); + sqlquery sql = SqlPrepareQueryCampaign(sDatabase, + "DELETE FROM " + sTable + + " WHERE var=@var;"); + SqlBindString(sql, "@var", sVariable); + return sql; +} + +// All the gets + +int GetCdkeyInt(object oPC, string sTable, string sVariable) +{ + oPC = GetMasterFromPossessedFamiliar(oPC); + if (!GetIsPC(oPC)) return 0; + return GetCampaignDBTableInt(GetPCPublicCDKey(oPC, TRUE), sTable, sVariable); +} + +string GetCdkeyString(object oPC, string sTable, string sVariable) +{ + oPC = GetMasterFromPossessedFamiliar(oPC); + if (!GetIsPC(oPC)) return ""; + return GetCampaignDBTableString(GetPCPublicCDKey(oPC, TRUE), sTable, sVariable); +} + +float GetCdkeyFloat(object oPC, string sTable, string sVariable) +{ + oPC = GetMasterFromPossessedFamiliar(oPC); + if (!GetIsPC(oPC)) return 0.0; + return GetCampaignDBTableFloat(GetPCPublicCDKey(oPC, TRUE), sTable, sVariable); +} + +vector GetCdkeyVector(object oPC, string sTable, string sVariable) +{ + oPC = GetMasterFromPossessedFamiliar(oPC); + if (!GetIsPC(oPC)) return Vector(-1.0,-1.0,-1.0); + return GetCampaignDBTableVector(GetPCPublicCDKey(oPC, TRUE), sTable, sVariable); +} + +location GetCdkeyLocation(object oPC, string sTable, string sVariable) +{ + oPC = GetMasterFromPossessedFamiliar(oPC); + if (!GetIsPC(oPC)) return LOCATION_INVALID; + return GetCampaignDBTableLocation(GetPCPublicCDKey(oPC, TRUE), sTable, sVariable); +} + +json GetCdkeyJson(object oPC, string sTable, string sVariable) +{ + oPC = GetMasterFromPossessedFamiliar(oPC); + if (!GetIsPC(oPC)) return JsonNull(); + return GetCampaignDBTableJson(GetPCPublicCDKey(oPC, TRUE), sTable, sVariable); +} + +int GetCampaignDBTableInt(string sDatabase, string sTable, string sVariable) +{ + if (_WarnOnBadCdkeyTableString(sTable) || sDatabase == "" || sTable == "" || sVariable == "") { return 0; } + + sqlquery sql = _CdkeyPrepareSelect(sDatabase, sTable, sVariable); + if (SqlStep(sql)) + { + return SqlGetInt(sql, 0); + } + return 0; +} + +string GetCampaignDBTableString(string sDatabase, string sTable, string sVariable) +{ + if (_WarnOnBadCdkeyTableString(sTable) || sDatabase == "" || sTable == "" || sVariable == "") { return ""; } + + sqlquery sql = _CdkeyPrepareSelect(sDatabase, sTable, sVariable); + if (SqlStep(sql)) + { + return SqlGetString(sql, 0); + } + return ""; +} + +float GetCampaignDBTableFloat(string sDatabase, string sTable, string sVariable) +{ + if (_WarnOnBadCdkeyTableString(sTable) || sDatabase == "" || sTable == "" || sVariable == "") { return 0.0; } + + sqlquery sql = _CdkeyPrepareSelect(sDatabase, sTable, sVariable); + if (SqlStep(sql)) + { + return SqlGetFloat(sql, 0); + } + return 0.0; +} + +vector GetCampaignDBTableVector(string sDatabase, string sTable, string sVariable) +{ + if (_WarnOnBadCdkeyTableString(sTable) || sDatabase == "" || sTable == "" || sVariable == "") { return Vector(-1.0,-1.0,-1.0); } + + sqlquery sql = _CdkeyPrepareSelect(sDatabase, sTable, sVariable); + if (SqlStep(sql)) + { + return SqlGetVector(sql, 0); + } + return Vector(-1.0,-1.0,-1.0); +} + +location GetCampaignDBTableLocation(string sDatabase, string sTable, string sVariable) +{ + if (_WarnOnBadCdkeyTableString(sTable) || sDatabase == "" || sTable == "" || sVariable == "") { return LOCATION_INVALID; } + + sqlquery sql = _CdkeyPrepareSelect(sDatabase, sTable, sVariable); + if (SqlStep(sql)) + { + return SQLocalsPlayer_StringToLocation(SqlGetString(sql, 0)); + } + return LOCATION_INVALID; +} + +json GetCampaignDBTableJson(string sDatabase, string sTable, string sVariable) +{ + if (_WarnOnBadCdkeyTableString(sTable) || sDatabase == "" || sTable == "" || sVariable == "") { return JsonNull(); } + + sqlquery sql = _CdkeyPrepareSelect(sDatabase, sTable, sVariable); + if (SqlStep(sql)) + { + return SqlGetJson(sql, 0); + } + return JsonNull(); +} + + +// All the sets + +void SetCdkeyInt(object oPC, string sTable, string sVariable, int nValue) +{ + oPC = GetMasterFromPossessedFamiliar(oPC); + if (!GetIsPC(oPC)) return; + SetCampaignDBTableInt(GetPCPublicCDKey(oPC, TRUE), sTable, sVariable, nValue); +} + +void SetCdkeyString(object oPC, string sTable, string sVariable, string sValue) +{ + oPC = GetMasterFromPossessedFamiliar(oPC); + if (!GetIsPC(oPC)) return; + SetCampaignDBTableString(GetPCPublicCDKey(oPC, TRUE), sTable, sVariable, sValue); +} + +void SetCdkeyFloat(object oPC, string sTable, string sVariable, float fValue) +{ + oPC = GetMasterFromPossessedFamiliar(oPC); + if (!GetIsPC(oPC)) return; + SetCampaignDBTableFloat(GetPCPublicCDKey(oPC, TRUE), sTable, sVariable, fValue); +} + +void SetCdkeyVector(object oPC, string sTable, string sVariable, vector vValue) +{ + oPC = GetMasterFromPossessedFamiliar(oPC); + if (!GetIsPC(oPC)) return; + SetCampaignDBTableVector(GetPCPublicCDKey(oPC, TRUE), sTable, sVariable, vValue); +} + +void SetCdkeyLocation(object oPC, string sTable, string sVariable, location lValue) +{ + oPC = GetMasterFromPossessedFamiliar(oPC); + if (!GetIsPC(oPC)) return; + SetCampaignDBTableLocation(GetPCPublicCDKey(oPC, TRUE), sTable, sVariable, lValue); +} + +void SetCdkeyJson(object oPC, string sTable, string sVariable, json jValue) +{ + oPC = GetMasterFromPossessedFamiliar(oPC); + if (!GetIsPC(oPC)) return; + SetCampaignDBTableJson(GetPCPublicCDKey(oPC, TRUE), sTable, sVariable, jValue); +} + +void SetCampaignDBTableInt(string sDatabase, string sTable, string sVariable, int nValue) +{ + if (_WarnOnBadCdkeyTableString(sTable) || sDatabase == "" || sTable == "" || sVariable == "") { return; } + + sqlquery sql = _CdkeyPrepareInsert(sDatabase, sTable, sVariable); + SqlBindInt(sql, "@value", nValue); + SqlStep(sql); + + sql = SqlPrepareQueryCampaign(sDatabase, + "INSERT INTO " + sTable + + " (var, value) VALUES ('a', 'b');"); + SqlBindString(sql, "@var", sVariable); + SqlBindInt(sql, "@value", nValue); + SqlStep(sql); +} + +void SetCampaignDBTableString(string sDatabase, string sTable, string sVariable, string sValue) +{ + if (_WarnOnBadCdkeyTableString(sTable) || sDatabase == "" || sTable == "" || sVariable == "") { return; } + + sqlquery sql = _CdkeyPrepareInsert(sDatabase, sTable, sVariable); + SqlBindString(sql, "@value", sValue); + SqlStep(sql); +} + +void SetCampaignDBTableFloat(string sDatabase, string sTable, string sVariable, float fValue) +{ + if (_WarnOnBadCdkeyTableString(sTable) || sDatabase == "" || sTable == "" || sVariable == "") { return; } + + sqlquery sql = _CdkeyPrepareInsert(sDatabase, sTable, sVariable); + SqlBindFloat(sql, "@value", fValue); + SqlStep(sql); +} + +void SetCampaignDBTableVector(string sDatabase, string sTable, string sVariable, vector vValue) +{ + if (_WarnOnBadCdkeyTableString(sTable) || sDatabase == "" || sTable == "" || sVariable == "") { return; } + + sqlquery sql = _CdkeyPrepareInsert(sDatabase, sTable, sVariable); + SqlBindVector(sql, "@value", vValue); + SqlStep(sql); +} + +void SetCampaignDBTableLocation(string sDatabase, string sTable, string sVariable, location lValue) +{ + if (_WarnOnBadCdkeyTableString(sTable) || sDatabase == "" || sTable == "" || sVariable == "") { return; } + + sqlquery sql = _CdkeyPrepareInsert(sDatabase, sTable, sVariable); + SqlBindString(sql, "@value", SQLocalsPlayer_LocationToString(lValue)); + SqlStep(sql); +} + +void SetCampaignDBTableJson(string sDatabase, string sTable, string sVariable, json jValue) +{ + if (_WarnOnBadCdkeyTableString(sTable) || sDatabase == "" || sTable == "" || sVariable == "") { return; } + + sqlquery sql = _CdkeyPrepareInsert(sDatabase, sTable, sVariable); + SqlBindJson(sql, "@value", jValue); + SqlStep(sql); +} + +// All the deletes + +void DeleteCdkeyVariable(object oPC, string sTable, string sVariable) +{ + oPC = GetMasterFromPossessedFamiliar(oPC); + if (!GetIsPC(oPC)) return; + DeleteCampaignDBTableVariable(GetPCPublicCDKey(oPC, TRUE), sTable, sVariable); +} + +void DeleteCampaignDBTableVariable(string sDatabase, string sTable, string sVariable) +{ + if (_WarnOnBadCdkeyTableString(sTable) || sDatabase == "" || sTable == "" || sVariable == "") { return; } + sqlquery sql = _CdkeyPrepareDelete(sDatabase, sTable, sVariable); + SqlStep(sql); +} + +// INTERNAL. Assumes both sDatabase contains only safe characters +void _CreateCdkeyCacheTable(string sDatabase) +{ + if (sDatabase != "") + { + sqlquery sql = SqlPrepareQueryObject(GetModule(), "CREATE TABLE IF NOT EXISTS cdkeycache_" + sDatabase + " (" + + "var TEXT PRIMARY KEY, " + + "value TEXT, " + + "tablename TEXT);"); + SqlStep(sql); + sql = SqlPrepareQueryObject(GetModule(), "CREATE INDEX IF NOT EXISTS idx_var ON cdkeycache_" + sDatabase + "(tablename, var);"); + SqlStep(sql); + } + + sqlquery sql = SqlPrepareQueryObject(GetModule(), "CREATE TABLE IF NOT EXISTS cdkeycache__update (" + + "db TEXT PRIMARY KEY, " + + "lastupdate INTEGER);"); + SqlStep(sql); +} + +// INTERNAL. Assumes both sDatabase and sTable contain only safe characters +sqlquery _CdkeyCachePrepareSelect(string sDatabase, string sTable, string sVariable) +{ + _CreateCdkeyCacheTable(sDatabase); + sqlquery sql = SqlPrepareQueryObject(GetModule(), "SELECT value from cdkeycache_" + sDatabase + " where var=@var and tablename=@tablename;"); + SqlBindString(sql, "@var", sVariable); + SqlBindString(sql, "@tablename", sTable); + return sql; +} + +// These internal functions ASSUME THE CALLER CHECKED sTable with _WarnOnBadCdkeyTableString +sqlquery _CdkeyCachePrepareInsert(string sDatabase, string sTable, string sVariable) +{ + _CreateCdkeyCacheTable(sDatabase); + sqlquery sql = SqlPrepareQueryObject(GetModule(), "INSERT INTO cdkeycache__update (db, lastupdate) VALUES (@db, @update) " + + "ON CONFLICT (db) DO NOTHING;"); + SqlBindInt(sql, "@update", 0); + SqlBindString(sql, "@db", sDatabase); + SqlStep(sql); + + + sql = SqlPrepareQueryObject(GetModule(), + "INSERT INTO cdkeycache_" + sDatabase + " (var, tablename, value) VALUES (@var, @tablename, @value) " + + " ON CONFLICT (var) DO UPDATE SET value = @value;"); + SqlBindString(sql, "@var", sVariable); + SqlBindString(sql, "@tablename", sTable); + return sql; +} + +int _UpdateCachedCdkeyDB(string sDatabase) +{ + int nStart = GetMicrosecondCounter(); + sqlquery sql = SqlPrepareQueryObject(GetModule(), "SELECT var, tablename, value FROM cdkeycache_"+sDatabase+ ";"); + sqlquery sqlCampaign = SqlPrepareQueryCampaign(sDatabase, "BEGIN TRANSACTION"); + + SqlStep(sqlCampaign); + int bAction = 0; + while (SqlStep(sql)) + { + bAction = 1; + string sVar = SqlGetString(sql, 0); + string sTable = SqlGetString(sql, 1); + string sValue = SqlGetString(sql, 2); + + //WriteTimestampedLogEntry("Write " + sVar + " -> " + sValue); + SetCampaignDBTableString(sDatabase, sTable, sVar, sValue); + } + sqlCampaign = SqlPrepareQueryCampaign(sDatabase, "COMMIT"); + SqlStep(sqlCampaign); + + sql = SqlPrepareQueryObject(GetModule(), "DELETE FROM cdkeycache_"+sDatabase+ ";"); + SqlStep(sql); + + // If we did something, update the last updated time accordingly + // If we didn't do anything, remove this database's entry as the PC behind it probably logged out + if (bAction) + { + int nNow = SQLite_GetTimeStamp(); + sqlquery sql = SqlPrepareQueryObject(GetModule(), "INSERT INTO cdkeycache__update (db, lastupdate) VALUES (@db, @update) " + + "ON CONFLICT (db) DO UPDATE SET lastupdate=@update;"); + SqlBindInt(sql, "@update", nNow); + SqlBindString(sql, "@db", sDatabase); + SqlStep(sql); + } + else + { + sqlquery sql = SqlPrepareQueryObject(GetModule(), "DELETE FROM cdkeycache__update where db=@db;"); + SqlBindString(sql, "@db", sDatabase); + SqlStep(sql); + WriteTimestampedLogEntry("Clear database cache for " + sDatabase + " as there have been no writes since it was copied to disk"); + } + //WriteTimestampedLogEntry("_UpdateCachedCdkeyDB in " + IntToString(GetMicrosecondCounter() - nStart)); + return bAction; + +} + +void UpdateOldestCachedCdkeyDB() +{ + _CreateCdkeyCacheTable(""); + sqlquery sql = SqlPrepareQueryObject(GetModule(), "SELECT db from cdkeycache__update ORDER BY lastupdate ASC LIMIT 1;"); + if (SqlStep(sql)) + { + string sDatabase = SqlGetString(sql, 0); + // Todo: remove this once sure it works with multiple players online + WriteTimestampedLogEntry("Oldest cached cdkeydb = " + sDatabase); + // If this returns 1, it updated the database + // If it didn't, it removed the database from cdkeycache__update + // and doing this lookup again will find something different (or nothing) + if (_UpdateCachedCdkeyDB(sDatabase)) + { + return; + } + else + { + UpdateOldestCachedCdkeyDB(); + } + } +} + +void UpdateAllCachedCdkeyDBs() +{ + int nNow = SQLite_GetTimeStamp(); + sqlquery sql = SqlPrepareQueryObject(GetModule(), "SELECT db from cdkeycache__update;"); + json jDatabases = JsonArray(); + while (SqlStep(sql)) + { + string sDatabase = SqlGetString(sql, 0); + jDatabases = JsonArrayInsert(jDatabases, JsonString(sDatabase)); + } + int nLength = JsonGetLength(jDatabases); + int i; + for (i=0; i TREASUREMAP_HIGHEST_DIFFICULTY) { return; } - if (nACR > TREASUREMAP_ACR_MAX) - { - nACR = TREASUREMAP_ACR_MAX; - } - nACR = (nACR/TREASUREMAP_ACR_STEP)*TREASUREMAP_ACR_STEP; - string sJsonData = "json" + IntToString(nACR); + + string sJsonData = "difficulty" + IntToString(nDifficulty); sqlquery sql = SqlPrepareQueryCampaign("tmapsolutions", "SELECT " + sJsonData + " FROM treasuremaps WHERE puzzleid = @puzzleid;"); @@ -1345,14 +1386,31 @@ void CreateNewTreasureMapPuzzleAtLocation(location lLoc) "tilehash INTEGER, " + "minacr INTEGER"; - for (i=TREASUREMAP_ACR_MIN; i<=TREASUREMAP_ACR_MAX; i++) + for (i=TREASUREMAP_DIFFICULTY_EASY; i<=TREASUREMAP_HIGHEST_DIFFICULTY; i++) { - sQuery = sQuery + ", json" + IntToString(i) + " TEXT"; + sQuery = sQuery + ", difficulty" + IntToString(i) + " TEXT"; } sQuery = sQuery + ");"; - + + // Indexing: we do the following: + // select jsondata TREASUREMAP_ACR_MAX) { nAreaCR = TREASUREMAP_ACR_MAX; } - // Go to the first possible step - nAreaCR = (nAreaCR/TREASUREMAP_ACR_STEP)*TREASUREMAP_ACR_STEP; - - int nCurrentACR; - for (nCurrentACR=nAreaCR; nCurrentACR<=TREASUREMAP_ACR_MAX; nCurrentACR+=TREASUREMAP_ACR_STEP) + int nCurrentDifficulty; + for (nCurrentDifficulty=TREASUREMAP_DIFFICULTY_EASY; nCurrentDifficulty<=TREASUREMAP_HIGHEST_DIFFICULTY; nCurrentDifficulty++) { - float fCRDiff = IntToFloat(nCurrentACR - TREASUREMAP_ACR_MIN); - int nNumFewerScansForThisACR = FloatToInt(fCRDiff * TREASUREMAP_PROPORTION_DISTANCE_REMOVED_PER_ACR * IntToFloat(nRealScanCount)); - int nScanCountForThisACR = nRealScanCount - nNumFewerScansForThisACR; - //WriteTimestampedLogEntry("Scancount: " + IntToString(nRealScanCount) + " - " + IntToString(nNumFewerScansForThisACR) + " -> " + IntToString(nScanCountForThisACR)); + float fProportionRemoved = 0.0; + if (nCurrentDifficulty == TREASUREMAP_DIFFICULTY_EASY) { fProportionRemoved = TREASUREMAP_PROPORTION_DISTANCE_REMOVED_EASY; } + else if (nCurrentDifficulty == TREASUREMAP_DIFFICULTY_MEDIUM) { fProportionRemoved = TREASUREMAP_PROPORTION_DISTANCE_REMOVED_MEDIUM; } + else if (nCurrentDifficulty == TREASUREMAP_DIFFICULTY_HARD) { fProportionRemoved = TREASUREMAP_PROPORTION_DISTANCE_REMOVED_HARD; } + else if (nCurrentDifficulty == TREASUREMAP_DIFFICULTY_MASTER) { fProportionRemoved = TREASUREMAP_PROPORTION_DISTANCE_REMOVED_MASTER; } + int nNumFewerScansForThisDifficulty = FloatToInt(fProportionRemoved * IntToFloat(nRealScanCount)); + int nScanCountForThisDifficulty = nRealScanCount - nNumFewerScansForThisDifficulty; + //WriteTimestampedLogEntry("Scancount: " + IntToString(nRealScanCount) + " - " + IntToString(nNumFewerScansForThisDifficulty) + " -> " + IntToString(nScanCountForThisDifficulty)); // We calculated the offset for the scan based on the MAXIMUM scan distance - // there is no guarantee that it's the same with the reduced scan count due to ACR increase... - vector vOffsetForThisACR = GetVectorToMoveScanLocationWithinAreaBounds(lLoc, nScanCountForThisACR, nScanCountForThisACR, fInterval); - vector vOffsetFromScanCentreForThisACR = vPosScan - (vLoc + vOffsetForThisACR); + // there is no guarantee that it's the same with the reduced scan count due to difficulty increase... + vector vOffsetForThisDifficulty = GetVectorToMoveScanLocationWithinAreaBounds(lLoc, nScanCountForThisDifficulty, nScanCountForThisDifficulty, fInterval); + vector vOffsetFromScanCentreForThisDifficulty = vPosScan - (vLoc + vOffsetForThisDifficulty); // ProcessMapData takes two variables per axis // 1) Ending interval index. @@ -1490,31 +1546,31 @@ void CalculateTreasureMaps(int nPuzzleID) // The negation here in the x axis seems to fix some left/right mirroring issues. // I'm assuming has to do with the fact that y axis was inverted at some point scanning but x wasn't // but my brain is fried and my mathematical reasoning is failing me - int nOffsetIntervalsX = FloatToInt(-vOffsetFromScanCentreForThisACR.x/fInterval); - int nOffsetIntervalsY = FloatToInt(vOffsetFromScanCentreForThisACR.y/fInterval); + int nOffsetIntervalsX = FloatToInt(-vOffsetFromScanCentreForThisDifficulty.x/fInterval); + int nOffsetIntervalsY = FloatToInt(vOffsetFromScanCentreForThisDifficulty.y/fInterval); - int nNonOffsetIntervalCount = nNumFewerScansForThisACR/2; + int nNonOffsetIntervalCount = nNumFewerScansForThisDifficulty/2; - //WriteTimestampedLogEntry("vOffsetFromScanCentreForThisACR " + tmVectorToString(vOffsetFromScanCentreForThisACR)); + //WriteTimestampedLogEntry("vOffsetFromScanCentreForThisDifficulty " + tmVectorToString(vOffsetFromScanCentreForThisDifficulty)); // Same deal with negating the x axis adjustment - mpSettings.fCrossOffsetX = -(vScanAdjustment.x - vOffsetFromScanCentreForThisACR.x)/fInterval; - mpSettings.fCrossOffsetY = (vScanAdjustment.y - vOffsetFromScanCentreForThisACR.y)/fInterval; + mpSettings.fCrossOffsetX = -(vScanAdjustment.x - vOffsetFromScanCentreForThisDifficulty.x)/fInterval; + mpSettings.fCrossOffsetY = (vScanAdjustment.y - vOffsetFromScanCentreForThisDifficulty.y)/fInterval; - mpSettings.MAPDATA_MINIMUM_POLYGON_AREA = 0.02 / IntToFloat(nScanCountForThisACR); + mpSettings.MAPDATA_MINIMUM_POLYGON_AREA = 0.02 / IntToFloat(nScanCountForThisDifficulty); int nStartIntervalX = nNonOffsetIntervalCount+nOffsetIntervalsX; int nStartIntervalY = nNonOffsetIntervalCount+nOffsetIntervalsY; - //WriteTimestampedLogEntry("Process map data: X = " + IntToString(nStartIntervalX) + " to " + IntToString(nStartIntervalX+nScanCountForThisACR) + ", Y = " + IntToString(nStartIntervalY) + " to " + IntToString(nStartIntervalY+nScanCountForThisACR)); + //WriteTimestampedLogEntry("Process map data: X = " + IntToString(nStartIntervalX) + " to " + IntToString(nStartIntervalX+nScanCountForThisDifficulty) + ", Y = " + IntToString(nStartIntervalY) + " to " + IntToString(nStartIntervalY+nScanCountForThisDifficulty)); // This will be very very prone to TMI NWNX_Util_SetInstructionsExecuted(0); - json jDrawListElements = ProcessTreasureMapData(nStartIntervalX+nScanCountForThisACR, nStartIntervalY+nScanCountForThisACR, nStartIntervalX, nStartIntervalY, mpSettings); + json jDrawListElements = ProcessTreasureMapData(nStartIntervalX+nScanCountForThisDifficulty, nStartIntervalY+nScanCountForThisDifficulty, nStartIntervalX, nStartIntervalY, mpSettings); - string sJsonFieldName = "json" + IntToString(nCurrentACR); + string sJsonFieldName = "difficulty" + IntToString(nCurrentDifficulty); sql = SqlPrepareQueryCampaign("tmapsolutions", "UPDATE treasuremaps " + "SET " + sJsonFieldName + " = @value " + @@ -1545,11 +1601,21 @@ int GetTreasureMapGoldValue(int nACR) else if (nACR >= 6) { nValue += (nACR * 2); } // Then multiply up by a bit... - nValue *= 4; + nValue *= 6; return nValue; } -void InitialiseTreasureMap(object oMap, int nACR, string sLocation="") +void SetTreasureMapDifficulty(object oMap, int nDifficulty) +{ + SetLocalInt(oMap, "difficulty", nDifficulty); +} + +int GetTreasureMapDifficulty(object oMap) +{ + return GetLocalInt(oMap, "difficulty"); +} + +void InitialiseTreasureMap(object oMap, int nACR, string sExtraDesc="") { if (nACR > TREASUREMAP_ACR_MAX) { @@ -1566,18 +1632,109 @@ void InitialiseTreasureMap(object oMap, int nACR, string sLocation="") NWNX_Item_SetAddGoldPieceValue(oMap, nValue - nBaseValue); SetLocalInt(oMap, "acr", nACR); SetDescription(oMap, ""); - if (sLocation != "") + if (sExtraDesc != "") { string sDesc = GetDescription(oMap); - sDesc += "\n\nThis map was obtained in " + sLocation + "."; + sDesc += "\n\n" + sExtraDesc; SetDescription(oMap, sDesc); } + int nRoll = d100(); + if (nRoll <= 31) { SetTreasureMapDifficulty(oMap, TREASUREMAP_DIFFICULTY_EASY); } + else if (nRoll <= 62) { SetTreasureMapDifficulty(oMap, TREASUREMAP_DIFFICULTY_MEDIUM); } + else if (nRoll <= 93) { SetTreasureMapDifficulty(oMap, TREASUREMAP_DIFFICULTY_HARD); } + else { SetTreasureMapDifficulty(oMap, TREASUREMAP_DIFFICULTY_MASTER); } +} + +void AssignNewPuzzleToMap(object oMap, object oNearbyArea, int bNearby) +{ + int nACR = GetLocalInt(oMap, "acr"); + if (nACR < TREASUREMAP_ACR_MIN) + { + SetLocalInt(oMap, "acr", nACR); + nACR = TREASUREMAP_ACR_MIN; + } + + if (bNearby && GetIsObjectValid(oNearbyArea)) + { + int nDist = TREASUREMAP_NEARBY_START_DISTANCE; + while (Random(100) < TREASUREMAP_START_DISTANCE_INCREASE_CHANCE) + { + nDist += TREASUREMAP_START_DISTANCE_INCREASE_AMOUNT; + } + json jAreaTags = GetAreasWithinDistance(oNearbyArea, nDist); + int nLength = JsonGetLength(jAreaTags); + int nValidAreas = 0; + int i; + json jValid = JsonArray(); + string sAreas = ""; + for (i=0; i 10) { nDifficulty = TREASUREMAP_DIFFICULTY_HARD; } + else if (nACR > 6) { nDifficulty = TREASUREMAP_DIFFICULTY_MEDIUM; } + else { nDifficulty = TREASUREMAP_DIFFICULTY_EASY; } + SetLocalInt(oMap, "difficulty", nDifficulty); + + } + int nPuzzleID = GetLocalInt(oMap, "puzzleid"); string sAreaTag = ""; int nAssignPuzzle = 1; if (nPuzzleID > 0) @@ -1601,16 +1758,10 @@ void UseTreasureMap(object oMap) } if (nAssignPuzzle) { - string sQuery = "SELECT puzzleid " + - "FROM treasuremaps WHERE minacr <= @minacr"; - sQuery = sQuery + " ORDER BY RANDOM() LIMIT 1;"; - sqlquery sql = SqlPrepareQueryCampaign("tmapsolutions", sQuery); - SqlBindInt(sql, "@minacr", nACR); - SqlStep(sql); - nPuzzleID = SqlGetInt(sql, 0); - SetLocalInt(oMap, "puzzleid", nPuzzleID); + AssignNewPuzzleToMap(oMap, OBJECT_INVALID, 0); + nPuzzleID = GetLocalInt(oMap, "puzzleid"); } - DisplayTreasureMapUI(GetItemPossessor(oMap), nPuzzleID, nACR, oMap); + DisplayTreasureMapUI(GetItemPossessor(oMap), nPuzzleID, nDifficulty, oMap); } location GetPuzzleSolutionLocation(int nPuzzleID) @@ -1694,31 +1845,37 @@ int RollForTreasureMap(object oSource=OBJECT_SELF) int DoesLocationCompleteMap(object oMap, location lTest) { int nPuzzleID = GetLocalInt(oMap, "puzzleid"); - location lSolution = GetPuzzleSolutionLocation(nPuzzleID); - if (GetIsObjectValid(GetAreaFromLocation(lSolution))) + if (nPuzzleID >= 0) { - if (GetAreaFromLocation(lSolution) == GetAreaFromLocation(lTest)) + if (GetTreasureMapDifficulty(oMap) > 0) { - float fDist = TREASUREMAP_LOCATION_TOLERANCE; - object oPC = GetItemPossessor(oMap); - float fSearch = IntToFloat(GetSkillRank(SKILL_SEARCH, oPC)); - fDist = fDist + (fSearch * 0.05 * fDist); - if (GetDistanceBetweenLocations(lSolution, lTest) <= fDist) + location lSolution = GetPuzzleSolutionLocation(nPuzzleID); + if (GetIsObjectValid(GetAreaFromLocation(lSolution))) { - if (!CanSavePCInfo(oPC)) + if (GetAreaFromLocation(lSolution) == GetAreaFromLocation(lTest)) { - FloatingTextStringOnCreature("You find some treasure, but cannot dig it up while polymorphed.", oPC, FALSE); - DelayCommand(6.0, FloatingTextStringOnCreature("You find some treasure, but cannot dig it up while polymorphed.", oPC, FALSE)); - return 0; + float fDist = TREASUREMAP_LOCATION_TOLERANCE; + object oPC = GetItemPossessor(oMap); + float fSearch = IntToFloat(GetSkillRank(SKILL_SEARCH, oPC)); + fDist = fDist + (fSearch * 0.05 * fDist); + if (GetDistanceBetweenLocations(lSolution, lTest) <= fDist) + { + if (!CanSavePCInfo(oPC)) + { + FloatingTextStringOnCreature("You find some treasure, but cannot dig it up while polymorphed.", oPC, FALSE); + DelayCommand(6.0, FloatingTextStringOnCreature("You find some treasure, but cannot dig it up while polymorphed.", oPC, FALSE)); + return 0; + } + return 1; + } } - return 1; } } } return 0; } -object SetupProgenitorTreasureMap(int nObjectACR, string sLocation="") +object SetupProgenitorTreasureMap(int nObjectACR, object oNearbyArea, int bNearby, string sExtraDesc="") { // Treasure generation expects a valid OID to copy that's in a system area // I guess we give it one @@ -1735,7 +1892,12 @@ object SetupProgenitorTreasureMap(int nObjectACR, string sLocation="") WriteTimestampedLogEntry("ERROR: couldn't make seed treasure map"); } } - InitialiseTreasureMap(oProgenitor, nObjectACR, sLocation); + if (GetIsObjectValid(oNearbyArea)) + { + sExtraDesc = "This map was obtained in " + GetName(oNearbyArea) + ". " + sExtraDesc; + } + InitialiseTreasureMap(oProgenitor, nObjectACR, sExtraDesc); + AssignNewPuzzleToMap(oProgenitor, oNearbyArea, bNearby); return oProgenitor; } @@ -1751,7 +1913,7 @@ object MaybeGenerateTreasureMap(int nObjectACR) string sLocation = GetName(GetArea(OBJECT_SELF)); - return SetupProgenitorTreasureMap(nObjectACR, sLocation); + return SetupProgenitorTreasureMap(nObjectACR, GetArea(OBJECT_SELF), Random(100) < TREASUREMAP_CHANCE_FOR_RANDOM_LOCATION ? 0 : 1); } void CompleteTreasureMap(object oMap) diff --git a/src/nss/nui_config_evt.nss b/src/nss/nui_config_evt.nss index f0aa0b45..1c892c70 100644 --- a/src/nss/nui_config_evt.nss +++ b/src/nss/nui_config_evt.nss @@ -29,7 +29,7 @@ void ProcessConfigChange(object oPC, string sElement, int nToken, string sParent sConfig = _NuiDataDB_GetString(sChangedWindowName + "_config_" + IntToString(nChangedConfigID)); // Change it back to the old one! json jValue = GetNuiConfigValue(oPC, sChangedWindowName, sConfig); - SetCampaignJson(sKey, "nuicfg" + sChangedWindowName + "_" + sConfig, jValue); + SetCdkeyJson(oPC, "nuiconfig", "nuicfg" + sChangedWindowName + "_" + sConfig, jValue); NuiSetBind(oPC, nParentToken, "_config_" + sConfig, jValue); WriteTimestampedLogEntry("Tried to change config " + sConfig + " of " + sChangedWindowName + " while visible window was " + sParentWindowName); return; @@ -67,7 +67,7 @@ void ProcessConfigChange(object oPC, string sElement, int nToken, string sParent } - SetCampaignJson(sKey, "nuicfg" + sParentWindowName + "_" + sConfigName, jValue); + SetCdkeyJson(oPC, "nuiconfig", "nuicfg" + sParentWindowName + "_" + sConfigName, jValue); NuiSetBind(oPC, nParentToken, "_config_" + sConfigName, jValue); //WriteTimestampedLogEntry("ConfigChange " + sConfigName + " (id " + IntToString(nConfigID) + " -> " + JsonDump(jValue)); if (GetStringLeft(sConfigName, 8) == "finemove") @@ -153,7 +153,7 @@ void main() string sWindowToReset = GetSubString(sElement, 10, 99); if (sWindowToReset == sParentWindowName) { - json jGeom = GetCampaignJson(sKey, "nui_geom_" + sWindowToReset); + json jGeom = GetCdkeyJson(oPC, "nuiconfig", "nui_geom_" + sWindowToReset); json jDefault = _NuiDataDB_GetJson(sWindowToReset + "_default_geom"); jGeom = JsonObjectSet(jGeom, "w", JsonObjectGet(jDefault, "w")); jGeom = JsonObjectSet(jGeom, "h", JsonObjectGet(jDefault, "h")); @@ -165,7 +165,7 @@ void main() string sWindowToReset = GetSubString(sElement, 9, 99); if (sWindowToReset == sParentWindowName) { - json jGeom = GetCampaignJson(sKey, "nui_geom_" + sWindowToReset); + json jGeom = GetCdkeyJson(oPC, "nuiconfig", "nui_geom_" + sWindowToReset); json jDefault = _NuiDataDB_GetJson(sWindowToReset + "_default_geom"); jGeom = JsonObjectSet(jGeom, "x", JsonObjectGet(jDefault, "x")); jGeom = JsonObjectSet(jGeom, "y", JsonObjectGet(jDefault, "y")); diff --git a/src/nss/on_mod_heartb.nss b/src/nss/on_mod_heartb.nss index cc788c8b..2e4f9994 100644 --- a/src/nss/on_mod_heartb.nss +++ b/src/nss/on_mod_heartb.nss @@ -341,4 +341,7 @@ void main() SetLocalInt(oModule, "yesgar_count", nYesgarCount + 1); } } + + // Writes one PC's updated stats database to disk + UpdateOldestCachedCdkeyDB(); } diff --git a/src/nss/on_mod_load.nss b/src/nss/on_mod_load.nss index 2b7ed7b8..1e65241f 100644 --- a/src/nss/on_mod_load.nss +++ b/src/nss/on_mod_load.nss @@ -17,16 +17,18 @@ #include "util_i_csvlists" #include "inc_prettify" #include "inc_loot" +#include "inc_areadist" #include "nwnx_damage" const int SEED_SPAWNS = 1; const int SEED_TREASURES = 1; const int SEED_SPELLBOOKS = 0; // These two check to see if stuff needs updating before doing it -// Turning them off might shave off 30s to a minute from a seeding run but that is about it +// Turning them off might shave off 1-2mins from a seeding run but that is about it // When run from scratch they are by far the slowest of the bunch +const int SEED_AREA_CONNECTIONS = 1; // ~7 minutes if changed else just ~1 minute of area_init loop const int SEED_PRETTIFY_PLACEABLES = 1; // ~40 minutes -const int SEED_TREASUREMAPS = 1; // ~3 hours +const int SEED_TREASUREMAPS = 1; // ~4 hours void LoadTreasureContainer(string sTag, float x = 1.0, float y = 1.0, float z = 1.0) { @@ -98,6 +100,33 @@ void EnsureAreaBilateralLinkages() } } +void InitialiseAreas() +{ + object oArea = GetFirstArea(); + while (GetIsObjectValid(oArea)) + { + string sAreaResRef = GetResRef(oArea); +// Skip the system areas or copied areas. They are prepended with an underscore. + if (GetStringLeft(sAreaResRef, 1) == "_") + { + oArea = GetNextArea(); + continue; + } + + // Skip prefab areas + if (GetStringLeft(GetName(oArea), 8) == "_PREFAB_") + { + oArea = GetNextArea(); + continue; + } + + ExecuteScript("area_init", oArea); + + oArea = GetNextArea(); + } + +} + void SeedMonitor() { int nStage = GetLocalInt(GetModule(), "seed_stage"); @@ -222,6 +251,15 @@ void SeedMonitor() ExecuteScript("seed_treasuremap"); } } + else if (nStage == 5) + { + if (SEED_AREA_CONNECTIONS) + { + // This is needed to set up connection lists for all areas + InitialiseAreas(); + PrepareAreaTransitionDB(); + } + } else { // Done! @@ -229,6 +267,7 @@ void SeedMonitor() WriteTimestampedLogEntry("Spawns were " + (SEED_SPAWNS ? "" : "NOT ") + "seeded."); WriteTimestampedLogEntry("Treasures were " + (SEED_TREASURES ? "" : "NOT ") + "seeded."); WriteTimestampedLogEntry("Random spellbooks were " + (SEED_SPELLBOOKS ? "" : "NOT ") + "seeded."); + WriteTimestampedLogEntry("Area connections were " + (SEED_AREA_CONNECTIONS ? "" : "NOT ") + "seeded."); WriteTimestampedLogEntry("Prettify placeables were " + (SEED_PRETTIFY_PLACEABLES ? "" : "NOT ") + "seeded."); WriteTimestampedLogEntry("Treasure map locations were " + (SEED_TREASUREMAPS ? "" : "NOT ") + "seeded."); NWNX_Administration_ShutdownServer(); @@ -712,36 +751,12 @@ void main() BuildItemNamesToObjectsDB(); SetLocalInt(GetModule(), "treasure_ready", 1); CalculatePlaceableLootValues(); - - object oArea = GetFirstArea(); - string sAreaResRef; - location lBaseLocation = Location(GetObjectByTag("_BASE"), Vector(1.0, 1.0, 1.0), 0.0); LoadAllPrettifyPlaceables(); // Loop through all objects in the module. - while (GetIsObjectValid(oArea)) - { - sAreaResRef = GetResRef(oArea); -// Skip the system areas or copied areas. They are prepended with an underscore. - if (GetStringLeft(sAreaResRef, 1) == "_") - { - oArea = GetNextArea(); - continue; - } - - // Skip prefab areas - if (GetStringLeft(GetName(oArea), 8) == "_PREFAB_") - { - oArea = GetNextArea(); - continue; - } - - ExecuteScript("area_init", oArea); - - oArea = GetNextArea(); - } + InitialiseAreas(); EnsureAreaBilateralLinkages(); // Add quests that don't have variables set on any creature here diff --git a/src/nss/on_mod_shutdown.nss b/src/nss/on_mod_shutdown.nss index 8e7471f8..308ae9e7 100644 --- a/src/nss/on_mod_shutdown.nss +++ b/src/nss/on_mod_shutdown.nss @@ -1,6 +1,8 @@ #include "inc_webhook" +#include "inc_cdkeyvars" void main() { ServerWebhook("The Frozen North is shutting down!", "The Frozen North server is shutting down for maintenance. Once the module is ready for play again, which will be shortly, we'll let you know."); + UpdateAllCachedCdkeyDBs(); } diff --git a/src/nss/seed_treasuremap.nss b/src/nss/seed_treasuremap.nss index 92d115da..e9be1bc1 100644 --- a/src/nss/seed_treasuremap.nss +++ b/src/nss/seed_treasuremap.nss @@ -160,7 +160,7 @@ void main() } } nAreaCount++; - //if (nAreaCount >= 5) { break; } + //if (nAreaCount >= 15) { break; } oArea = GetNextArea(); } } diff --git a/src/nss/tmap_complete.nss b/src/nss/tmap_complete.nss index 8ad58beb..2a72c9a9 100644 --- a/src/nss/tmap_complete.nss +++ b/src/nss/tmap_complete.nss @@ -15,12 +15,14 @@ void main() } object oReward = CreateObject(OBJECT_TYPE_PLACEABLE, "treasuremap_loot", GetLocation(oOwner)); + ApplyEffectToObject(DURATION_TYPE_PERMANENT, SupernaturalEffect(EffectCutsceneGhost()), oReward); SetName(oReward, sTreasureName); SetObjectVisualTransform(oReward, OBJECT_VISUAL_TRANSFORM_SCALE, 0.5); SetLocalString(oReward, "owner", GetPCPublicCDKey(oOwner)); - int nLootLevel = (GetLocalInt(oMap, "acr")*3)/2; + int nLootLevel = GetLocalInt(oMap, "acr"); SetLocalInt(oReward, "cr", nLootLevel); SetLocalInt(oReward, "area_cr", nLootLevel); + SetLocalInt(oReward, "parent_map_difficulty", GetTreasureMapDifficulty(OBJECT_SELF)); SetPlotFlag(oReward, 1); AssignCommand(GetModule(), DelayCommand(280.0, SetPlotFlag(oReward, 0))); DestroyObject(oReward, 300.0); diff --git a/src/nss/tmapreward_open.nss b/src/nss/tmapreward_open.nss index 468ae2c5..0331fb63 100644 --- a/src/nss/tmapreward_open.nss +++ b/src/nss/tmapreward_open.nss @@ -14,10 +14,20 @@ void main() int nGenerated = GetLocalInt(OBJECT_SELF, "doneloot"); if (!nGenerated) { - int nACR = GetLocalInt(OBJECT_SELF, "area_cr"); int nNewMapACR = 1 + nACR + (nACR / 6); if (nNewMapACR > TREASUREMAP_ACR_MAX) { nNewMapACR = TREASUREMAP_ACR_MAX; } + int nParentDifficulty = GetLocalInt(OBJECT_SELF, "parent_map_difficulty"); + int nNewDifficulty = nParentDifficulty + 1; + if (nNewDifficulty > TREASUREMAP_HIGHEST_DIFFICULTY) + { + nNewDifficulty = TREASUREMAP_HIGHEST_DIFFICULTY; + } + WriteTimestampedLogEntry(GetName(oUser) + " completed a treasure map difficulty " + IntToString(nParentDifficulty) + " and acr " + IntToString(nACR)); + int nDowngradeChance = TREASUREMAP_REWARD_DOWNGRADE_EASY; + if (nParentDifficulty == TREASUREMAP_DIFFICULTY_MEDIUM) { nDowngradeChance = TREASUREMAP_REWARD_DOWNGRADE_MEDIUM; } + else if (nParentDifficulty == TREASUREMAP_DIFFICULTY_HARD) { nDowngradeChance = TREASUREMAP_REWARD_DOWNGRADE_HARD; } + else if (nParentDifficulty == TREASUREMAP_DIFFICULTY_MASTER) { nDowngradeChance = TREASUREMAP_REWARD_DOWNGRADE_MASTER; } int i; int nTries = d2(2); for (i=0; i {newvarname}") + else: + print(f"Migrated variable {varname}") + + con.commit() + return + print(f"{dbfilepath} seems not to have a bioware db table or isn't a player's cdkey db, ignored") + +def main(): + # For now, this is what we want to do + #dbpath = "../server/database" + dbpath = "../database" + dbs = os.listdir(dbpath) + for item in dbs: + if not item.endswith(".sqlite3"): + continue + realfp = os.path.join(dbpath, item) + + migratedb(realfp, "stat_*", "playerstats", stat_migration_variable_truncations) + migratedb(realfp, "nui*", "nuiconfig") + +if __name__ == "__main__": + main() + \ No newline at end of file diff --git a/tools/treasuremaploot.py b/tools/treasuremaploot.py index d7552bf0..392fa15c 100644 --- a/tools/treasuremaploot.py +++ b/tools/treasuremaploot.py @@ -24,13 +24,13 @@ def treasuremaploots(downgradechance, trials = 1e6): return (numhigh/trials, numlow/trials) def main(): - results = treasuremaploots(0.95) + results = treasuremaploots(0.93) print(f"Low: {results[0]} boss, {results[1]} downgraded") - results = treasuremaploots(0.85) + results = treasuremaploots(0.81) print(f"Medium: {results[0]} boss, {results[1]} downgraded") - results = treasuremaploots(0.7) + results = treasuremaploots(0.64) print(f"High: {results[0]} boss, {results[1]} downgraded") - results = treasuremaploots(0.5) + results = treasuremaploots(0.42) print(f"Master: {results[0]} boss, {results[1]} downgraded") if __name__ == "__main__": diff --git a/win_nasher_install.bat b/win_nasher_install.bat index 277600da..ba10ed35 100644 --- a/win_nasher_install.bat +++ b/win_nasher_install.bat @@ -13,7 +13,7 @@ rd modules del /f TFN.mod -"%CD%/tools/win/nasher/nasher.exe" install --verbose --erfUtil:"%CD%/tools/win/neverwinter64/nwn_erf.exe" --gffUtil:"%CD%/tools/win/neverwinter64/nwn_gff.exe" --tlkUtil:"%CD%/tools/win/neverwinter64/nwn_tlk.exe" --nssCompiler:"%CD%/tools/win/nwnsc/nwnsc.exe" --installDir:"%CD%" --nssFlags:"-oe -i ""%CD%/nwn-base-scripts""" --yes +"%CD%/tools/win/nasher/nasher.exe" install --verbose --erfUtil:"%CD%/tools/win/neverwinter64/nwn_erf.exe" --gffUtil:"%CD%/tools/win/neverwinter64/nwn_gff.exe" --tlkUtil:"%CD%/tools/win/neverwinter64/nwn_tlk.exe" --nssCompiler:"%CD%/tools/win/nwnsc/nwnsc.exe" --installDir:"%CD%" --nssFlags:"-oe -i ""%CD%/nwn-base-scripts""" --no del /f TFN.mod pause diff --git a/win_nasher_install_clean.bat b/win_nasher_install_clean.bat new file mode 100644 index 00000000..a56f0220 --- /dev/null +++ b/win_nasher_install_clean.bat @@ -0,0 +1,19 @@ +@echo off +echo. +echo Checking to see if there is a previous module. If prompted, type 'Y' to delete the current module and module folder. +echo If you are canceling, you can close the window and exit. nasher will not continue if there is a module file and folder. +echo It will automatically continue if you do not have module built (clean slate) +echo. +echo WARNING: Continuing will rebuild the module from source, deleting all unsaved changes! Commit or stash your changes, or exit out. +@echo on + +del modules /S +rd modules\TFN +rd modules + +del /f TFN.mod + +"%CD%/tools/win/nasher/nasher.exe" install --verbose --erfUtil:"%CD%/tools/win/neverwinter64/nwn_erf.exe" --gffUtil:"%CD%/tools/win/neverwinter64/nwn_gff.exe" --tlkUtil:"%CD%/tools/win/neverwinter64/nwn_tlk.exe" --nssCompiler:"%CD%/tools/win/nwnsc/nwnsc.exe" --installDir:"%CD%" --nssFlags:"-oe -i ""%CD%/nwn-base-scripts""" --no --clean + +del /f TFN.mod +pause diff --git a/win_run_server.bat b/win_run_server.bat index f30afe9b..162abbcc 100644 --- a/win_run_server.bat +++ b/win_run_server.bat @@ -43,12 +43,14 @@ del /f server\database\treasures.sqlite3 del /f server\database\randspellbooks.sqlite3 del /f server\database\prettify.sqlite3 del /f server\database\tmapsolutions.sqlite3 +del /f server\database\areadistances.sqlite3 del /f server\settings.tml rmdir /s /q server\override copy modules\TFN.mod server\modules\TFN.mod copy config\common.env server\config\common.env copy settings.tml server\settings.tml copy seeded_database\tmapsolutions.sqlite3 server\database\tmapsolutions.sqlite3 +copy seeded_database\areadistances.sqlite3 server\database\areadistances.sqlite3 copy seeded_database\spawns.sqlite3 server\database\spawns.sqlite3 copy seeded_database\treasures.sqlite3 server\database\treasures.sqlite3 copy seeded_database\randspellbooks.sqlite3 server\database\randspellbooks.sqlite3 diff --git a/win_run_server_dev.bat b/win_run_server_dev.bat index 94c9b9dc..aff4ef1c 100644 --- a/win_run_server_dev.bat +++ b/win_run_server_dev.bat @@ -3,8 +3,10 @@ del /f database\treasures.sqlite3 del /f database\randspellbooks.sqlite3 del /f database\prettify.sqlite3 del /f database\tmapsolutions.sqlite3 +del /f database\areadistances.sqlite3 copy seeded_database\tmapsolutions.sqlite3 database\tmapsolutions.sqlite3 +copy seeded_database\areadistances.sqlite3 database\areadistances.sqlite3 copy seeded_database\spawns.sqlite3 database\spawns.sqlite3 copy seeded_database\treasures.sqlite3 database\treasures.sqlite3 copy seeded_database\randspellbooks.sqlite3 database\randspellbooks.sqlite3 diff --git a/win_run_server_dev_seed.bat b/win_run_server_dev_seed.bat index 02a1369b..7707150f 100644 --- a/win_run_server_dev_seed.bat +++ b/win_run_server_dev_seed.bat @@ -6,9 +6,11 @@ del /f seeded_database\treasures.sqlite3 del /f seeded_database\randspellbooks.sqlite3 del /f seeded_database\prettify.sqlite3 del /f seeded_database\tmapsolutions.sqlite3 +del /f seeded_database\areadistances.sqlite3 copy database\tmapsolutions.sqlite3 seeded_database\tmapsolutions.sqlite3 copy database\spawns.sqlite3 seeded_database\spawns.sqlite3 copy database\treasures.sqlite3 seeded_database\treasures.sqlite3 copy database\randspellbooks.sqlite3 seeded_database\randspellbooks.sqlite3 -copy database\prettify.sqlite3 seeded_database\prettify.sqlite3 \ No newline at end of file +copy database\prettify.sqlite3 seeded_database\prettify.sqlite3 +copy database\areadistances.sqlite3 seeded_database\areadistances.sqlite3 \ No newline at end of file