From 1e19be207ae151e0f7472d3d3fb9c971d3247453 Mon Sep 17 00:00:00 2001 From: SabreDartStudios <108193207+SabreDartStudios@users.noreply.github.com> Date: Mon, 27 May 2024 16:42:30 -0400 Subject: [PATCH 1/4] Fixed PostgreSQL update scripts. Moved all update scripts for PostgreSQL into setup.sql. Fixed an issue for all DB types where MapInstances were no longer being removed for a World Server when OWS Instance Launcher shuts down. --- ... 20220801UpdateFrom20220613to20220801.sql} | 0 ... 20230304UpdateFrom20220801to20230304.sql} | 3 + .../CummulativeUpdateToCurrentVersion.sql | 530 ++++++++++++++++++ src/.docker/matchmakingcache.yml | 20 + src/.docker/postgres/setup.sql | 526 ++++++++++++++++- src/.env | 9 +- .../Postgres/InstanceManagementRepository.cs | 4 + src/OWSData/SQL/GenericQueries.cs | 8 +- .../ServerLauncherHealthMonitoring.cs | 4 +- src/docker-compose.yml | 7 + 10 files changed, 1102 insertions(+), 9 deletions(-) rename Databases/Postgres/UpdateScripts/{20220801UpdateFrom20220424to20220801.sql => 20220801UpdateFrom20220613to20220801.sql} (100%) rename Databases/Postgres/UpdateScripts/{20230304UpdateFrom20220424to20230304.sql => 20230304UpdateFrom20220801to20230304.sql} (99%) create mode 100644 Databases/Postgres/UpdateScripts/CummulativeUpdateToCurrentVersion.sql create mode 100644 src/.docker/matchmakingcache.yml diff --git a/Databases/Postgres/UpdateScripts/20220801UpdateFrom20220424to20220801.sql b/Databases/Postgres/UpdateScripts/20220801UpdateFrom20220613to20220801.sql similarity index 100% rename from Databases/Postgres/UpdateScripts/20220801UpdateFrom20220424to20220801.sql rename to Databases/Postgres/UpdateScripts/20220801UpdateFrom20220613to20220801.sql diff --git a/Databases/Postgres/UpdateScripts/20230304UpdateFrom20220424to20230304.sql b/Databases/Postgres/UpdateScripts/20230304UpdateFrom20220801to20230304.sql similarity index 99% rename from Databases/Postgres/UpdateScripts/20230304UpdateFrom20220424to20230304.sql rename to Databases/Postgres/UpdateScripts/20230304UpdateFrom20220801to20230304.sql index cf0715bd8..eb2663947 100644 --- a/Databases/Postgres/UpdateScripts/20230304UpdateFrom20220424to20230304.sql +++ b/Databases/Postgres/UpdateScripts/20230304UpdateFrom20220801to20230304.sql @@ -5,6 +5,7 @@ WHERE OWSDBVersion IS NOT NULL; SELECT OWSDBVersion FROM OWSVersion; + CREATE TABLE DefaultCharacterValues ( CustomerGUID UUID NOT NULL, @@ -21,6 +22,7 @@ CREATE TABLE DefaultCharacterValues PRIMARY KEY (DefaultCharacterValuesID, CustomerGUID) ); + CREATE TABLE DefaultCustomCharacterData ( CustomerGUID UUID NOT NULL, @@ -33,3 +35,4 @@ CREATE TABLE DefaultCustomCharacterData CONSTRAINT FK_DefaultCustomCharacterData_DefaultCharacterValueID FOREIGN KEY (DefaultCharacterValuesID, CustomerGUID) REFERENCES DefaultCharacterValues (DefaultCharacterValuesID, CustomerGUID) ); + diff --git a/Databases/Postgres/UpdateScripts/CummulativeUpdateToCurrentVersion.sql b/Databases/Postgres/UpdateScripts/CummulativeUpdateToCurrentVersion.sql new file mode 100644 index 000000000..562fa8ffd --- /dev/null +++ b/Databases/Postgres/UpdateScripts/CummulativeUpdateToCurrentVersion.sql @@ -0,0 +1,530 @@ +UPDATE OWSVersion +SET OWSDBVersion='20230304' +WHERE OWSDBVersion IS NOT NULL; + +SELECT OWSDBVersion +FROM OWSVersion; + +ALTER TABLE WorldServers +ADD ZoneServerGUID UUID NULL; + +ALTER TABLE WorldServers +ADD CONSTRAINT AK_ZoneServers UNIQUE (CustomerGUID, ZoneServerGUID); + + +CREATE OR REPLACE PROCEDURE AddOrUpdateAbility(_CustomerGUID UUID, + _AbilityID INT, + _AbilityName VARCHAR(50), + _AbilityTypeID INT, + _TextureToUseForIcon VARCHAR(200), + _Class INT, + _Race INT, + _GameplayAbilityClassName VARCHAR(200), + _AbilityCustomJSON TEXT) + LANGUAGE PLPGSQL +AS +$$ +BEGIN + + IF + NOT EXISTS(SELECT + FROM Abilities AB + WHERE AB.CustomerGUID = _CustomerGUID + AND (AB.AbilityID = _AbilityID + OR AB.AbilityName = _AbilityName) + FOR UPDATE) THEN + INSERT INTO Abilities (CustomerGUID, AbilityName, AbilityTypeID, TextureToUseForIcon, Class, Race, + GameplayAbilityClassName, AbilityCustomJSON) + VALUES (_CustomerGUID, _AbilityName, _AbilityTypeID, _TextureToUseForIcon, _Class, _Race, + _GameplayAbilityClassName, _AbilityCustomJSON); + ELSE + UPDATE Abilities AB + SET AbilityName = _AbilityName, + AbilityTypeID = _AbilityTypeID, + TextureToUseForIcon = _TextureToUseForIcon, + Class = _Class, + Race = _Race, + GameplayAbilityClassName = _GameplayAbilityClassName, + AbilityCustomJSON = _AbilityCustomJSON + WHERE AB.CustomerGUID = _CustomerGUID + AND AB.AbilityID = _AbilityID; + END IF; +END +$$; + + +CREATE OR REPLACE PROCEDURE AddOrUpdateAbilityType(_CustomerGUID UUID, + _AbilityTypeID INT, + _AbilityTypeName VARCHAR(50)) + LANGUAGE PLPGSQL +AS +$$ +BEGIN + + IF + NOT EXISTS(SELECT + FROM AbilityTypes ABT + WHERE ABT.CustomerGUID = _CustomerGUID + AND (ABT.AbilityTypeID = _AbilityTypeID + OR ABT.AbilityTypeName = _AbilityTypeName) + FOR UPDATE) THEN + INSERT INTO AbilityTypes (CustomerGUID, AbilityTypeName) + VALUES (_CustomerGUID, _AbilityTypeName); + ELSE + UPDATE AbilityTypes ABT + SET AbilityTypeName = _AbilityTypeName + WHERE ABT.CustomerGUID = _CustomerGUID + AND ABT.AbilityTypeID = _AbilityTypeID; + END IF; +END +$$; + + +CREATE OR REPLACE FUNCTION GetAbilityTypes(_CustomerGUID UUID) + RETURNS TABLE + ( + AbilityTypeID INT, + AbilityTypeName VARCHAR(50), + CustomerGUID UUID, + NumberOfAbilities INT + ) + LANGUAGE SQL +AS +$$ +SELECT * + , (SELECT COUNT(*) FROM Abilities AB WHERE AB.AbilityTypeID = ABT.AbilityTypeID) AS NumberOfAbilities +FROM AbilityTypes ABT +WHERE ABT.CustomerGUID = _CustomerGUID +ORDER BY AbilityTypeName; +$$; + + +CREATE OR REPLACE PROCEDURE AddAbilityToCharacter(_CustomerGUID UUID, + _AbilityName VARCHAR(50), + _CharacterName VARCHAR(50), + _AbilityLevel INT, + _CharHasAbilitiesCustomJSON TEXT) + LANGUAGE PLPGSQL +AS +$$ +BEGIN + IF NOT EXISTS(SELECT + FROM CharHasAbilities CHA + INNER JOIN Characters C + ON C.CharacterID = CHA.CharacterID + AND C.CustomerGUID = CHA.CustomerGUID + INNER JOIN Abilities A + ON A.AbilityID = CHA.AbilityID + AND A.CustomerGUID = CHA.CustomerGUID + WHERE CHA.CustomerGUID = _CustomerGUID + AND C.CharName = _CharacterName + AND A.AbilityName = _AbilityName FOR UPDATE) THEN + INSERT INTO CharHasAbilities (CustomerGUID, CharacterID, AbilityID, AbilityLevel, CharHasAbilitiesCustomJSON) + SELECT _CustomerGUID AS CustomerGUID, + (SELECT C.CharacterID + FROM Characters C + WHERE C.CharName = _CharacterName AND C.CustomerGUID = _CustomerGUID + LIMIT 1), + (SELECT A.AbilityID + FROM Abilities A + WHERE A.AbilityName = _AbilityName AND A.CustomerGUID = _CustomerGUID + LIMIT 1), + _AbilityLevel, + _CharHasAbilitiesCustomJSON; + END IF; +END +$$; + + +CREATE OR REPLACE PROCEDURE AddNewCustomer(_CustomerName VARCHAR(50), + _FirstName VARCHAR(50), + _LastName VARCHAR(50), + _Email VARCHAR(256), + _Password VARCHAR(256), + _CustomerGuid UUID) + LANGUAGE PLPGSQL +AS +$$ +DECLARE + _UserGUID UUID; + _ClassID INT; + _CharacterName VARCHAR(50) := 'Test'; + _CharacterID INT; +BEGIN + IF _CustomerGUID IS NULL THEN + _CustomerGUID := gen_random_uuid(); + END IF; + + IF NOT EXISTS(SELECT + FROM Customers + WHERE CustomerGUID = _CustomerGUID) + THEN + + INSERT INTO Customers (CustomerGUID, CustomerName, CustomerEmail, CustomerPhone, CustomerNotes, EnableDebugLogging) + VALUES (_CustomerGUID, _CustomerName, _Email, '', '', TRUE); + + INSERT INTO WorldSettings (CustomerGUID, StartTime) + SELECT _CustomerGUID, CAST(EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) AS BIGINT) + FROM Customers C + WHERE C.CustomerGUID = _CustomerGUID; + + SELECT UserGUID FROM AddUser(_CustomerGUID, _FirstName, _LastName, _Email, _Password, 'Developer') INTO _UserGUID; + + INSERT INTO Maps (CustomerGUID, MapName, ZoneName, MapData, Width, Height) + VALUES (_CustomerGUID, 'ThirdPersonExampleMap', 'ThirdPersonExampleMap', NULL, 1, 1); + INSERT INTO Maps (CustomerGUID, MapName, ZoneName, MapData, Width, Height) + VALUES (_CustomerGUID, 'Map2', 'Map2', NULL, 1, 1); + INSERT INTO Maps (CustomerGUID, MapName, ZoneName, MapData, Width, Height) + VALUES (_CustomerGUID, 'DungeonMap', 'DungeonMap', NULL, 1, 1); + INSERT INTO Maps (CustomerGUID, MapName, ZoneName, MapData, Width, Height) + VALUES (_CustomerGUID, 'FourZoneMap', 'Zone1', NULL, 1, 1); + INSERT INTO Maps (CustomerGUID, MapName, ZoneName, MapData, Width, Height) + VALUES (_CustomerGUID, 'FourZoneMap', 'Zone2', NULL, 1, 1); + + INSERT INTO CLASS (CustomerGUID, ClassName, StartingMapName, X, Y, Z, Perception, Acrobatics, Climb, Stealth, RX, + RY, RZ, Spirit, Magic, TeamNumber, Thirst, Hunger, Gold, Score, CharacterLevel, Gender, XP, + HitDie, Wounds, Size, weight, MaxHealth, Health, HealthRegenRate, MaxMana, Mana, ManaRegenRate, + MaxEnergy, Energy, EnergyRegenRate, MaxFatigue, Fatigue, FatigueRegenRate, MaxStamina, Stamina, + StaminaRegenRate, MaxEndurance, Endurance, EnduranceRegenRate, Strength, Dexterity, Constitution, + Intellect, Wisdom, Charisma, Agility, Fortitude, Reflex, Willpower, BaseAttack, BaseAttackBonus, + AttackPower, AttackSpeed, CritChance, CritMultiplier, Haste, SpellPower, SpellPenetration, + Defense, Dodge, Parry, Avoidance, Versatility, Multishot, Initiative, NaturalArmor, + PhysicalArmor, BonusArmor, ForceArmor, MagicArmor, Resistance, ReloadSpeed, RANGE, Speed, Silver, + Copper, FreeCurrency, PremiumCurrency, Fame, ALIGNMENT, Description) + VALUES (_CustomerGUID, 'MaleWarrior', 'ThirdPersonExampleMap', 0, 0, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 1, 1, 0, 10, 0, 1, 0, 100, 50, 1, 100, 0, 1, 100, 0, 5, 100, 0, 1, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, + 0, 1, 1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ''); + + _ClassID := CURRVAL(PG_GET_SERIAL_SEQUENCE('class', 'classid')); + + INSERT INTO Characters (CustomerGUID, ClassID, UserGUID, Email, CharName, MapName, X, Y, Z, Perception, Acrobatics, + Climb, Stealth, ServerIP, LastActivity, + RX, RY, RZ, Spirit, Magic, TeamNumber, Thirst, Hunger, Gold, Score, CharacterLevel, Gender, + XP, HitDie, Wounds, Size, weight, MaxHealth, Health, + HealthRegenRate, MaxMana, Mana, ManaRegenRate, MaxEnergy, Energy, EnergyRegenRate, + MaxFatigue, Fatigue, FatigueRegenRate, MaxStamina, Stamina, + StaminaRegenRate, MaxEndurance, Endurance, EnduranceRegenRate, Strength, Dexterity, + Constitution, Intellect, Wisdom, Charisma, Agility, Fortitude, + Reflex, Willpower, BaseAttack, BaseAttackBonus, AttackPower, AttackSpeed, CritChance, + CritMultiplier, Haste, SpellPower, SpellPenetration, Defense, + Dodge, Parry, Avoidance, Versatility, Multishot, Initiative, NaturalArmor, PhysicalArmor, + BonusArmor, ForceArmor, MagicArmor, Resistance, ReloadSpeed, + RANGE, Speed, Silver, Copper, FreeCurrency, PremiumCurrency, Fame, ALIGNMENT, Description) + SELECT _CustomerGUID, + _ClassID, + _UserGUID, + '', + _CharacterName, + StartingMapName, + X, + Y, + Z, + Perception, + Acrobatics, + Climb, + Stealth, + '', + NOW(), + RX, + RY, + RZ, + Spirit, + Magic, + TeamNumber, + Thirst, + Hunger, + Gold, + Score, + CharacterLevel, + Gender, + XP, + HitDie, + Wounds, + Size, + weight, + MaxHealth, + Health, + HealthRegenRate, + MaxMana, + Mana, + ManaRegenRate, + MaxEnergy, + Energy, + EnergyRegenRate, + MaxFatigue, + Fatigue, + FatigueRegenRate, + MaxStamina, + Stamina, + StaminaRegenRate, + MaxEndurance, + Endurance, + EnduranceRegenRate, + Strength, + Dexterity, + Constitution, + Intellect, + Wisdom, + Charisma, + Agility, + Fortitude, + Reflex, + Willpower, + BaseAttack, + BaseAttackBonus, + AttackPower, + AttackSpeed, + CritChance, + CritMultiplier, + Haste, + SpellPower, + SpellPenetration, + Defense, + Dodge, + Parry, + Avoidance, + Versatility, + Multishot, + Initiative, + NaturalArmor, + PhysicalArmor, + BonusArmor, + ForceArmor, + MagicArmor, + Resistance, + ReloadSpeed, + RANGE, + Speed, + Silver, + Copper, + FreeCurrency, + PremiumCurrency, + Fame, + ALIGNMENT, + Description + FROM CLASS + WHERE ClassID = _ClassID; + + _CharacterID := CURRVAL(PG_GET_SERIAL_SEQUENCE('characters', 'characterid')); + + INSERT INTO CharInventory (CustomerGUID, CharacterID, InventoryName, InventorySize) + VALUES (_CustomerGUID, _CharacterID, 'Bag', 16); + ELSE + RAISE 'Duplicate Customer GUID: %', _CustomerGUID USING ERRCODE = 'unique_violation'; + END IF; +END +$$; + + + +CREATE OR REPLACE FUNCTION JoinMapByCharName(_CustomerGUID UUID, + _CharName VARCHAR(50), + _ZoneName VARCHAR(50), + _PlayerGroupType INT) + RETURNS TABLE + ( + ServerIP VARCHAR(50), + WorldServerID INT, + WorldServerIP VARCHAR(50), + WorldServerPort INT, + Port INT, + MapInstanceID INT, + MapNameToStart VARCHAR(50), + MapInstanceStatus INT, + NeedToStartUpMap BOOLEAN, + EnableAutoLoopBack BOOLEAN, + NoPortForwarding BOOLEAN + ) + LANGUAGE PLPGSQL +AS +$$ +DECLARE + _MapID INT; + _MapNameToStart VARCHAR(50); + _CharacterID INT; + _Email VARCHAR(255); + _SoftPlayerCap INT; + _PlayerGroupID INT; + _ServerIP VARCHAR(50); + _WorldServerID INT; + _WorldServerIP VARCHAR(50); + _WorldServerPort INT; + _Port INT; + _MapInstanceID INT; + _MapInstanceStatus INT; + _NeedToStartUpMap BOOLEAN; + _EnableAutoLoopBack BOOLEAN; + _NoPortForwarding BOOLEAN; + _IsInternalNetworkTestUser BOOLEAN := FALSE; + _ErrorRaised BOOLEAN := FALSE; +BEGIN + CREATE TEMP TABLE IF NOT EXISTS temp_table + ( + ServerIP VARCHAR(50), + WorldServerID INT, + WorldServerIP VARCHAR(50), + WorldServerPort INT, + Port INT, + MapInstanceID INT, + MapNameToStart VARCHAR(50), + MapInstanceStatus INT, + NeedToStartUpMap BOOLEAN, + EnableAutoLoopBack BOOLEAN, + NoPortForwarding BOOLEAN + ) ON COMMIT DROP; + + --Run Cleanup here for now. Later this can get moved to a scheduler to run periodically. + CALL CleanUp(_CustomerGUID); + + INSERT INTO DebugLog (DebugDate, DebugDesc, CustomerGUID) + VALUES (NOW(), 'JoinMapByCharName: ' || _ZoneName || ' - ' || _CharName, _CustomerGUID); + + SELECT M.MapID, M.MapName, M.SoftPlayerCap + INTO _MapID, _MapNameToStart, _SoftPlayerCap + FROM Maps M + WHERE M.ZoneName = _ZoneName + AND M.CustomerGUID = _CustomerGUID; + + SELECT C.CharacterID, C.IsInternalNetworkTestUser, C.Email + INTO _CharacterID, _IsInternalNetworkTestUser, _Email + FROM Characters C + WHERE C.CharName = _CharName + AND C.CustomerGUID = _CustomerGUID; + + IF (_CharacterID IS NULL) THEN + INSERT INTO DebugLog (DebugDate, DebugDesc, CustomerGUID) + VALUES (NOW(), 'JoinMapByCharName: CharacterID is NULL!', _CustomerGUID); + + _NeedToStartUpMap := 0; + _ErrorRaised := TRUE; + END IF; + + IF _ErrorRaised = FALSE THEN + SELECT C.EnableAutoLoopBack, C.NoPortForwarding + INTO _EnableAutoLoopBack, _NoPortForwarding + FROM Customers C + WHERE C.CustomerGUID = _CustomerGUID; + END IF; + + IF _ErrorRaised = FALSE AND (_PlayerGroupType > 0) THEN + SELECT COALESCE(PG.PlayerGroupID, 0) + FROM PlayerGroupCharacters PGC + INNER JOIN PlayerGroup PG + ON PG.PlayerGroupID = PGC.PlayerGroupID + WHERE PGC.CustomerGUID = _CustomerGUID + AND PGC.CharacterID = _CharacterID + AND PG.PlayerGroupTypeID = _PlayerGroupType + INTO _PlayerGroupID; + END IF; + + IF _ErrorRaised = FALSE THEN + SELECT (CASE + WHEN _IsInternalNetworkTestUser = TRUE THEN WS.InternalServerIP + ELSE WS.ServerIP END) AS ServerIp, + WS.InternalServerIP, + WS.Port AS WSPort, + MI.Port AS MIPort, + MI.MapInstanceID, + WS.WorldServerID, + MI.Status + INTO _ServerIP, _WorldServerIP, _WorldServerPort, _Port, _MapInstanceID, _WorldServerID, _MapInstanceStatus + FROM WorldServers WS + LEFT JOIN MapInstances MI + ON MI.WorldServerID = WS.WorldServerID + AND MI.CustomerGUID = WS.CustomerGUID + LEFT JOIN CharOnMapInstance CMI + ON CMI.MapInstanceID = MI.MapInstanceID + AND CMI.CustomerGUID = MI.CustomerGUID + WHERE MI.MapID = _MapID + AND WS.ActiveStartTime IS NOT NULL + AND WS.CustomerGUID = _CustomerGUID + AND MI.NumberOfReportedPlayers < _SoftPlayerCap + AND (MI.PlayerGroupID = _PlayerGroupID OR COALESCE(_PlayerGroupID,0) = 0) --Only lookup map instances that match the player group fro this Player Group Type or lookup all if zero + AND MI.Status = 2 + GROUP BY MI.MapInstanceID, WS.ServerIP, MI.Port, WS.WorldServerID, WS.InternalServerIP, WS.Port, MI.Status + ORDER BY COUNT(DISTINCT CMI.CharacterID); + + + --There is a map already running to connect to + IF _MapInstanceID IS NOT NULL THEN + /*IF (POSITION('\@localhost' IN _Email) > 0) THEN + _ServerIP := '127.0.0.1'; + END IF;*/ + + _NeedToStartUpMap := FALSE; + + INSERT INTO DebugLog (DebugDate, DebugDesc, CustomerGUID) + VALUES (NOW(), 'Joined Existing Map: ' || COALESCE(_ZoneName, '') || ' - ' || COALESCE(_CharName, '') || + ' - ' || COALESCE(_ServerIP, ''), + _CustomerGUID); + ELSE --Spin up a new map + + SELECT * + FROM SpinUpMapInstance(_CustomerGUID, _ZoneName, _PlayerGroupID) + INTO _ServerIP , _WorldServerID , _WorldServerIP , _WorldServerPort , _Port, _MapInstanceID; + + /*IF (POSITION('@localhost' IN _Email) > 0 OR _IsInternalNetworkTestUser = TRUE) THEN + _ServerIP := '127.0.0.1'; + END IF;*/ + + _NeedToStartUpMap := TRUE; + + INSERT INTO DebugLog (DebugDate, DebugDesc, CustomerGUID) + VALUES (NOW(), + 'SpinUpMapInstance returned: ' || COALESCE(_ZoneName, '') || ' CharName: ' || + COALESCE(_CharName, '') || ' ServerIP: ' || + COALESCE(_ServerIP, '') || + ' WorldServerPort: ' || CAST(COALESCE(_WorldServerPort, -1) AS VARCHAR), _CustomerGUID); + + + INSERT INTO DebugLog (DebugDate, DebugDesc, CustomerGUID) + VALUES (NOW(), + 'JoinMapByCharName returned: ' || COALESCE(_MapNameToStart, '[NoMapName]') || ' MapInstanceID: ' || + CAST(COALESCE(_MapInstanceID, -1) AS VARCHAR) || ' MapInstanceStatus: ' || + CAST(COALESCE(_MapInstanceStatus, -1) AS VARCHAR) || ' NeedToStartUpMap: ' || + CAST(_NeedToStartUpMap AS VARCHAR) || ' EnableAutoLoopBack: ' || + CAST(_EnableAutoLoopBack AS VARCHAR) || + ' ServerIP: ' || COALESCE(_ServerIP, '') || ' WorldServerIP: ' || COALESCE(_WorldServerIP, ''), + _CustomerGUID); + END IF; + END IF; + INSERT INTO temp_table(ServerIP, WorldServerID, WorldServerIP, WorldServerPort, Port, MapInstanceID, MapNameToStart, + MapInstanceStatus, NeedToStartUpMap, EnableAutoLoopBack, NoPortForwarding) + VALUES (_ServerIP, _WorldServerID, _WorldServerIP, _WorldServerPort, _Port, _MapInstanceID, _MapNameToStart, + _MapInstanceStatus, _NeedToStartUpMap, _EnableAutoLoopBack, _NoPortForwarding); + RETURN QUERY SELECT * FROM temp_table; +END; +$$; + + +CREATE TABLE DefaultCharacterValues +( + CustomerGUID UUID NOT NULL, + DefaultCharacterValuesID SERIAL NOT NULL, + DefaultSetName VARCHAR(50) NOT NULL, + StartingMapName VARCHAR(50) NOT NULL, + X FLOAT NOT NULL, + Y FLOAT NOT NULL, + Z FLOAT NOT NULL, + RX FLOAT DEFAULT 0 NOT NULL, + RY FLOAT DEFAULT 0 NOT NULL, + RZ FLOAT DEFAULT 0 NOT NULL, + CONSTRAINT PK_DefaultCharacterValues + PRIMARY KEY (DefaultCharacterValuesID, CustomerGUID) +); + + +CREATE TABLE DefaultCustomCharacterData +( + CustomerGUID UUID NOT NULL, + DefaultCustomCharacterDataID SERIAL NOT NULL, + DefaultCharacterValuesID INT NOT NULL, + CustomFieldName VARCHAR(50) NOT NULL, + FieldValue TEXT NOT NULL, + CONSTRAINT PK_DefaultCustomCharacterData + PRIMARY KEY (DefaultCustomCharacterDataID, CustomerGUID), + CONSTRAINT FK_DefaultCustomCharacterData_DefaultCharacterValueID + FOREIGN KEY (DefaultCharacterValuesID, CustomerGUID) REFERENCES DefaultCharacterValues (DefaultCharacterValuesID, CustomerGUID) +); + diff --git a/src/.docker/matchmakingcache.yml b/src/.docker/matchmakingcache.yml new file mode 100644 index 000000000..a8069fbcb --- /dev/null +++ b/src/.docker/matchmakingcache.yml @@ -0,0 +1,20 @@ +version: '3.7' + +services: + matchmakingcache: + hostname: 'ows2' + container_name: matchmakingcache + image: redis:alpine3.19 + restart: always + command: redis-server --save 20 1 --loglevel warning --requirepass ${MatchmakingCacheRedisPassword} + ports: + - "6379:6379" + volumes: + - matchmakingcache:/data + environment: + REDIS_HOST: matchmakingcache + REDIS_PORT: 6379 + REDIS_PASSWORD: ${MatchmakingCacheRedisPassword} + volumes: + matchmakingcache: + driver: local diff --git a/src/.docker/postgres/setup.sql b/src/.docker/postgres/setup.sql index e1df1dcdf..c4eae6563 100644 --- a/src/.docker/postgres/setup.sql +++ b/src/.docker/postgres/setup.sql @@ -3244,4 +3244,528 @@ BEGIN END $$; -INSERT INTO OWSVersion (OWSDBVersion) VALUES('20210829'); +ALTER TABLE WorldServers +ADD ZoneServerGUID UUID NULL; + +ALTER TABLE WorldServers +ADD CONSTRAINT AK_ZoneServers UNIQUE (CustomerGUID, ZoneServerGUID); + + +CREATE OR REPLACE PROCEDURE AddOrUpdateAbility(_CustomerGUID UUID, + _AbilityID INT, + _AbilityName VARCHAR(50), + _AbilityTypeID INT, + _TextureToUseForIcon VARCHAR(200), + _Class INT, + _Race INT, + _GameplayAbilityClassName VARCHAR(200), + _AbilityCustomJSON TEXT) + LANGUAGE PLPGSQL +AS +$$ +BEGIN + + IF + NOT EXISTS(SELECT + FROM Abilities AB + WHERE AB.CustomerGUID = _CustomerGUID + AND (AB.AbilityID = _AbilityID + OR AB.AbilityName = _AbilityName) + FOR UPDATE) THEN + INSERT INTO Abilities (CustomerGUID, AbilityName, AbilityTypeID, TextureToUseForIcon, Class, Race, + GameplayAbilityClassName, AbilityCustomJSON) + VALUES (_CustomerGUID, _AbilityName, _AbilityTypeID, _TextureToUseForIcon, _Class, _Race, + _GameplayAbilityClassName, _AbilityCustomJSON); + ELSE + UPDATE Abilities AB + SET AbilityName = _AbilityName, + AbilityTypeID = _AbilityTypeID, + TextureToUseForIcon = _TextureToUseForIcon, + Class = _Class, + Race = _Race, + GameplayAbilityClassName = _GameplayAbilityClassName, + AbilityCustomJSON = _AbilityCustomJSON + WHERE AB.CustomerGUID = _CustomerGUID + AND AB.AbilityID = _AbilityID; + END IF; +END +$$; + + +CREATE OR REPLACE PROCEDURE AddOrUpdateAbilityType(_CustomerGUID UUID, + _AbilityTypeID INT, + _AbilityTypeName VARCHAR(50)) + LANGUAGE PLPGSQL +AS +$$ +BEGIN + + IF + NOT EXISTS(SELECT + FROM AbilityTypes ABT + WHERE ABT.CustomerGUID = _CustomerGUID + AND (ABT.AbilityTypeID = _AbilityTypeID + OR ABT.AbilityTypeName = _AbilityTypeName) + FOR UPDATE) THEN + INSERT INTO AbilityTypes (CustomerGUID, AbilityTypeName) + VALUES (_CustomerGUID, _AbilityTypeName); + ELSE + UPDATE AbilityTypes ABT + SET AbilityTypeName = _AbilityTypeName + WHERE ABT.CustomerGUID = _CustomerGUID + AND ABT.AbilityTypeID = _AbilityTypeID; + END IF; +END +$$; + + +CREATE OR REPLACE FUNCTION GetAbilityTypes(_CustomerGUID UUID) + RETURNS TABLE + ( + AbilityTypeID INT, + AbilityTypeName VARCHAR(50), + CustomerGUID UUID, + NumberOfAbilities INT + ) + LANGUAGE SQL +AS +$$ +SELECT * + , (SELECT COUNT(*) FROM Abilities AB WHERE AB.AbilityTypeID = ABT.AbilityTypeID) AS NumberOfAbilities +FROM AbilityTypes ABT +WHERE ABT.CustomerGUID = _CustomerGUID +ORDER BY AbilityTypeName; +$$; + + +CREATE OR REPLACE PROCEDURE AddAbilityToCharacter(_CustomerGUID UUID, + _AbilityName VARCHAR(50), + _CharacterName VARCHAR(50), + _AbilityLevel INT, + _CharHasAbilitiesCustomJSON TEXT) + LANGUAGE PLPGSQL +AS +$$ +BEGIN + IF NOT EXISTS(SELECT + FROM CharHasAbilities CHA + INNER JOIN Characters C + ON C.CharacterID = CHA.CharacterID + AND C.CustomerGUID = CHA.CustomerGUID + INNER JOIN Abilities A + ON A.AbilityID = CHA.AbilityID + AND A.CustomerGUID = CHA.CustomerGUID + WHERE CHA.CustomerGUID = _CustomerGUID + AND C.CharName = _CharacterName + AND A.AbilityName = _AbilityName FOR UPDATE) THEN + INSERT INTO CharHasAbilities (CustomerGUID, CharacterID, AbilityID, AbilityLevel, CharHasAbilitiesCustomJSON) + SELECT _CustomerGUID AS CustomerGUID, + (SELECT C.CharacterID + FROM Characters C + WHERE C.CharName = _CharacterName AND C.CustomerGUID = _CustomerGUID + LIMIT 1), + (SELECT A.AbilityID + FROM Abilities A + WHERE A.AbilityName = _AbilityName AND A.CustomerGUID = _CustomerGUID + LIMIT 1), + _AbilityLevel, + _CharHasAbilitiesCustomJSON; + END IF; +END +$$; + + +CREATE OR REPLACE PROCEDURE AddNewCustomer(_CustomerName VARCHAR(50), + _FirstName VARCHAR(50), + _LastName VARCHAR(50), + _Email VARCHAR(256), + _Password VARCHAR(256), + _CustomerGuid UUID) + LANGUAGE PLPGSQL +AS +$$ +DECLARE + _UserGUID UUID; + _ClassID INT; + _CharacterName VARCHAR(50) := 'Test'; + _CharacterID INT; +BEGIN + IF _CustomerGUID IS NULL THEN + _CustomerGUID := gen_random_uuid(); + END IF; + + IF NOT EXISTS(SELECT + FROM Customers + WHERE CustomerGUID = _CustomerGUID) + THEN + + INSERT INTO Customers (CustomerGUID, CustomerName, CustomerEmail, CustomerPhone, CustomerNotes, EnableDebugLogging) + VALUES (_CustomerGUID, _CustomerName, _Email, '', '', TRUE); + + INSERT INTO WorldSettings (CustomerGUID, StartTime) + SELECT _CustomerGUID, CAST(EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) AS BIGINT) + FROM Customers C + WHERE C.CustomerGUID = _CustomerGUID; + + SELECT UserGUID FROM AddUser(_CustomerGUID, _FirstName, _LastName, _Email, _Password, 'Developer') INTO _UserGUID; + + INSERT INTO Maps (CustomerGUID, MapName, ZoneName, MapData, Width, Height) + VALUES (_CustomerGUID, 'ThirdPersonExampleMap', 'ThirdPersonExampleMap', NULL, 1, 1); + INSERT INTO Maps (CustomerGUID, MapName, ZoneName, MapData, Width, Height) + VALUES (_CustomerGUID, 'Map2', 'Map2', NULL, 1, 1); + INSERT INTO Maps (CustomerGUID, MapName, ZoneName, MapData, Width, Height) + VALUES (_CustomerGUID, 'DungeonMap', 'DungeonMap', NULL, 1, 1); + INSERT INTO Maps (CustomerGUID, MapName, ZoneName, MapData, Width, Height) + VALUES (_CustomerGUID, 'FourZoneMap', 'Zone1', NULL, 1, 1); + INSERT INTO Maps (CustomerGUID, MapName, ZoneName, MapData, Width, Height) + VALUES (_CustomerGUID, 'FourZoneMap', 'Zone2', NULL, 1, 1); + + INSERT INTO CLASS (CustomerGUID, ClassName, StartingMapName, X, Y, Z, Perception, Acrobatics, Climb, Stealth, RX, + RY, RZ, Spirit, Magic, TeamNumber, Thirst, Hunger, Gold, Score, CharacterLevel, Gender, XP, + HitDie, Wounds, Size, weight, MaxHealth, Health, HealthRegenRate, MaxMana, Mana, ManaRegenRate, + MaxEnergy, Energy, EnergyRegenRate, MaxFatigue, Fatigue, FatigueRegenRate, MaxStamina, Stamina, + StaminaRegenRate, MaxEndurance, Endurance, EnduranceRegenRate, Strength, Dexterity, Constitution, + Intellect, Wisdom, Charisma, Agility, Fortitude, Reflex, Willpower, BaseAttack, BaseAttackBonus, + AttackPower, AttackSpeed, CritChance, CritMultiplier, Haste, SpellPower, SpellPenetration, + Defense, Dodge, Parry, Avoidance, Versatility, Multishot, Initiative, NaturalArmor, + PhysicalArmor, BonusArmor, ForceArmor, MagicArmor, Resistance, ReloadSpeed, RANGE, Speed, Silver, + Copper, FreeCurrency, PremiumCurrency, Fame, ALIGNMENT, Description) + VALUES (_CustomerGUID, 'MaleWarrior', 'ThirdPersonExampleMap', 0, 0, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 1, 1, 0, 10, 0, 1, 0, 100, 50, 1, 100, 0, 1, 100, 0, 5, 100, 0, 1, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, + 0, 1, 1, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ''); + + _ClassID := CURRVAL(PG_GET_SERIAL_SEQUENCE('class', 'classid')); + + INSERT INTO Characters (CustomerGUID, ClassID, UserGUID, Email, CharName, MapName, X, Y, Z, Perception, Acrobatics, + Climb, Stealth, ServerIP, LastActivity, + RX, RY, RZ, Spirit, Magic, TeamNumber, Thirst, Hunger, Gold, Score, CharacterLevel, Gender, + XP, HitDie, Wounds, Size, weight, MaxHealth, Health, + HealthRegenRate, MaxMana, Mana, ManaRegenRate, MaxEnergy, Energy, EnergyRegenRate, + MaxFatigue, Fatigue, FatigueRegenRate, MaxStamina, Stamina, + StaminaRegenRate, MaxEndurance, Endurance, EnduranceRegenRate, Strength, Dexterity, + Constitution, Intellect, Wisdom, Charisma, Agility, Fortitude, + Reflex, Willpower, BaseAttack, BaseAttackBonus, AttackPower, AttackSpeed, CritChance, + CritMultiplier, Haste, SpellPower, SpellPenetration, Defense, + Dodge, Parry, Avoidance, Versatility, Multishot, Initiative, NaturalArmor, PhysicalArmor, + BonusArmor, ForceArmor, MagicArmor, Resistance, ReloadSpeed, + RANGE, Speed, Silver, Copper, FreeCurrency, PremiumCurrency, Fame, ALIGNMENT, Description) + SELECT _CustomerGUID, + _ClassID, + _UserGUID, + '', + _CharacterName, + StartingMapName, + X, + Y, + Z, + Perception, + Acrobatics, + Climb, + Stealth, + '', + NOW(), + RX, + RY, + RZ, + Spirit, + Magic, + TeamNumber, + Thirst, + Hunger, + Gold, + Score, + CharacterLevel, + Gender, + XP, + HitDie, + Wounds, + Size, + weight, + MaxHealth, + Health, + HealthRegenRate, + MaxMana, + Mana, + ManaRegenRate, + MaxEnergy, + Energy, + EnergyRegenRate, + MaxFatigue, + Fatigue, + FatigueRegenRate, + MaxStamina, + Stamina, + StaminaRegenRate, + MaxEndurance, + Endurance, + EnduranceRegenRate, + Strength, + Dexterity, + Constitution, + Intellect, + Wisdom, + Charisma, + Agility, + Fortitude, + Reflex, + Willpower, + BaseAttack, + BaseAttackBonus, + AttackPower, + AttackSpeed, + CritChance, + CritMultiplier, + Haste, + SpellPower, + SpellPenetration, + Defense, + Dodge, + Parry, + Avoidance, + Versatility, + Multishot, + Initiative, + NaturalArmor, + PhysicalArmor, + BonusArmor, + ForceArmor, + MagicArmor, + Resistance, + ReloadSpeed, + RANGE, + Speed, + Silver, + Copper, + FreeCurrency, + PremiumCurrency, + Fame, + ALIGNMENT, + Description + FROM CLASS + WHERE ClassID = _ClassID; + + _CharacterID := CURRVAL(PG_GET_SERIAL_SEQUENCE('characters', 'characterid')); + + INSERT INTO CharInventory (CustomerGUID, CharacterID, InventoryName, InventorySize) + VALUES (_CustomerGUID, _CharacterID, 'Bag', 16); + ELSE + RAISE 'Duplicate Customer GUID: %', _CustomerGUID USING ERRCODE = 'unique_violation'; + END IF; +END +$$; + + + +CREATE OR REPLACE FUNCTION JoinMapByCharName(_CustomerGUID UUID, + _CharName VARCHAR(50), + _ZoneName VARCHAR(50), + _PlayerGroupType INT) + RETURNS TABLE + ( + ServerIP VARCHAR(50), + WorldServerID INT, + WorldServerIP VARCHAR(50), + WorldServerPort INT, + Port INT, + MapInstanceID INT, + MapNameToStart VARCHAR(50), + MapInstanceStatus INT, + NeedToStartUpMap BOOLEAN, + EnableAutoLoopBack BOOLEAN, + NoPortForwarding BOOLEAN + ) + LANGUAGE PLPGSQL +AS +$$ +DECLARE + _MapID INT; + _MapNameToStart VARCHAR(50); + _CharacterID INT; + _Email VARCHAR(255); + _SoftPlayerCap INT; + _PlayerGroupID INT; + _ServerIP VARCHAR(50); + _WorldServerID INT; + _WorldServerIP VARCHAR(50); + _WorldServerPort INT; + _Port INT; + _MapInstanceID INT; + _MapInstanceStatus INT; + _NeedToStartUpMap BOOLEAN; + _EnableAutoLoopBack BOOLEAN; + _NoPortForwarding BOOLEAN; + _IsInternalNetworkTestUser BOOLEAN := FALSE; + _ErrorRaised BOOLEAN := FALSE; +BEGIN + CREATE TEMP TABLE IF NOT EXISTS temp_table + ( + ServerIP VARCHAR(50), + WorldServerID INT, + WorldServerIP VARCHAR(50), + WorldServerPort INT, + Port INT, + MapInstanceID INT, + MapNameToStart VARCHAR(50), + MapInstanceStatus INT, + NeedToStartUpMap BOOLEAN, + EnableAutoLoopBack BOOLEAN, + NoPortForwarding BOOLEAN + ) ON COMMIT DROP; + + --Run Cleanup here for now. Later this can get moved to a scheduler to run periodically. + CALL CleanUp(_CustomerGUID); + + INSERT INTO DebugLog (DebugDate, DebugDesc, CustomerGUID) + VALUES (NOW(), 'JoinMapByCharName: ' || _ZoneName || ' - ' || _CharName, _CustomerGUID); + + SELECT M.MapID, M.MapName, M.SoftPlayerCap + INTO _MapID, _MapNameToStart, _SoftPlayerCap + FROM Maps M + WHERE M.ZoneName = _ZoneName + AND M.CustomerGUID = _CustomerGUID; + + SELECT C.CharacterID, C.IsInternalNetworkTestUser, C.Email + INTO _CharacterID, _IsInternalNetworkTestUser, _Email + FROM Characters C + WHERE C.CharName = _CharName + AND C.CustomerGUID = _CustomerGUID; + + IF (_CharacterID IS NULL) THEN + INSERT INTO DebugLog (DebugDate, DebugDesc, CustomerGUID) + VALUES (NOW(), 'JoinMapByCharName: CharacterID is NULL!', _CustomerGUID); + + _NeedToStartUpMap := 0; + _ErrorRaised := TRUE; + END IF; + + IF _ErrorRaised = FALSE THEN + SELECT C.EnableAutoLoopBack, C.NoPortForwarding + INTO _EnableAutoLoopBack, _NoPortForwarding + FROM Customers C + WHERE C.CustomerGUID = _CustomerGUID; + END IF; + + IF _ErrorRaised = FALSE AND (_PlayerGroupType > 0) THEN + SELECT COALESCE(PG.PlayerGroupID, 0) + FROM PlayerGroupCharacters PGC + INNER JOIN PlayerGroup PG + ON PG.PlayerGroupID = PGC.PlayerGroupID + WHERE PGC.CustomerGUID = _CustomerGUID + AND PGC.CharacterID = _CharacterID + AND PG.PlayerGroupTypeID = _PlayerGroupType + INTO _PlayerGroupID; + END IF; + + IF _ErrorRaised = FALSE THEN + SELECT (CASE + WHEN _IsInternalNetworkTestUser = TRUE THEN WS.InternalServerIP + ELSE WS.ServerIP END) AS ServerIp, + WS.InternalServerIP, + WS.Port AS WSPort, + MI.Port AS MIPort, + MI.MapInstanceID, + WS.WorldServerID, + MI.Status + INTO _ServerIP, _WorldServerIP, _WorldServerPort, _Port, _MapInstanceID, _WorldServerID, _MapInstanceStatus + FROM WorldServers WS + LEFT JOIN MapInstances MI + ON MI.WorldServerID = WS.WorldServerID + AND MI.CustomerGUID = WS.CustomerGUID + LEFT JOIN CharOnMapInstance CMI + ON CMI.MapInstanceID = MI.MapInstanceID + AND CMI.CustomerGUID = MI.CustomerGUID + WHERE MI.MapID = _MapID + AND WS.ActiveStartTime IS NOT NULL + AND WS.CustomerGUID = _CustomerGUID + AND MI.NumberOfReportedPlayers < _SoftPlayerCap + AND (MI.PlayerGroupID = _PlayerGroupID OR COALESCE(_PlayerGroupID,0) = 0) --Only lookup map instances that match the player group fro this Player Group Type or lookup all if zero + AND MI.Status = 2 + GROUP BY MI.MapInstanceID, WS.ServerIP, MI.Port, WS.WorldServerID, WS.InternalServerIP, WS.Port, MI.Status + ORDER BY COUNT(DISTINCT CMI.CharacterID); + + + --There is a map already running to connect to + IF _MapInstanceID IS NOT NULL THEN + /*IF (POSITION('\@localhost' IN _Email) > 0) THEN + _ServerIP := '127.0.0.1'; + END IF;*/ + + _NeedToStartUpMap := FALSE; + + INSERT INTO DebugLog (DebugDate, DebugDesc, CustomerGUID) + VALUES (NOW(), 'Joined Existing Map: ' || COALESCE(_ZoneName, '') || ' - ' || COALESCE(_CharName, '') || + ' - ' || COALESCE(_ServerIP, ''), + _CustomerGUID); + ELSE --Spin up a new map + + SELECT * + FROM SpinUpMapInstance(_CustomerGUID, _ZoneName, _PlayerGroupID) + INTO _ServerIP , _WorldServerID , _WorldServerIP , _WorldServerPort , _Port, _MapInstanceID; + + /*IF (POSITION('@localhost' IN _Email) > 0 OR _IsInternalNetworkTestUser = TRUE) THEN + _ServerIP := '127.0.0.1'; + END IF;*/ + + _NeedToStartUpMap := TRUE; + + INSERT INTO DebugLog (DebugDate, DebugDesc, CustomerGUID) + VALUES (NOW(), + 'SpinUpMapInstance returned: ' || COALESCE(_ZoneName, '') || ' CharName: ' || + COALESCE(_CharName, '') || ' ServerIP: ' || + COALESCE(_ServerIP, '') || + ' WorldServerPort: ' || CAST(COALESCE(_WorldServerPort, -1) AS VARCHAR), _CustomerGUID); + + + INSERT INTO DebugLog (DebugDate, DebugDesc, CustomerGUID) + VALUES (NOW(), + 'JoinMapByCharName returned: ' || COALESCE(_MapNameToStart, '[NoMapName]') || ' MapInstanceID: ' || + CAST(COALESCE(_MapInstanceID, -1) AS VARCHAR) || ' MapInstanceStatus: ' || + CAST(COALESCE(_MapInstanceStatus, -1) AS VARCHAR) || ' NeedToStartUpMap: ' || + CAST(_NeedToStartUpMap AS VARCHAR) || ' EnableAutoLoopBack: ' || + CAST(_EnableAutoLoopBack AS VARCHAR) || + ' ServerIP: ' || COALESCE(_ServerIP, '') || ' WorldServerIP: ' || COALESCE(_WorldServerIP, ''), + _CustomerGUID); + END IF; + END IF; + INSERT INTO temp_table(ServerIP, WorldServerID, WorldServerIP, WorldServerPort, Port, MapInstanceID, MapNameToStart, + MapInstanceStatus, NeedToStartUpMap, EnableAutoLoopBack, NoPortForwarding) + VALUES (_ServerIP, _WorldServerID, _WorldServerIP, _WorldServerPort, _Port, _MapInstanceID, _MapNameToStart, + _MapInstanceStatus, _NeedToStartUpMap, _EnableAutoLoopBack, _NoPortForwarding); + RETURN QUERY SELECT * FROM temp_table; +END; +$$; + + +CREATE TABLE DefaultCharacterValues +( + CustomerGUID UUID NOT NULL, + DefaultCharacterValuesID SERIAL NOT NULL, + DefaultSetName VARCHAR(50) NOT NULL, + StartingMapName VARCHAR(50) NOT NULL, + X FLOAT NOT NULL, + Y FLOAT NOT NULL, + Z FLOAT NOT NULL, + RX FLOAT DEFAULT 0 NOT NULL, + RY FLOAT DEFAULT 0 NOT NULL, + RZ FLOAT DEFAULT 0 NOT NULL, + CONSTRAINT PK_DefaultCharacterValues + PRIMARY KEY (DefaultCharacterValuesID, CustomerGUID) +); + + +CREATE TABLE DefaultCustomCharacterData +( + CustomerGUID UUID NOT NULL, + DefaultCustomCharacterDataID SERIAL NOT NULL, + DefaultCharacterValuesID INT NOT NULL, + CustomFieldName VARCHAR(50) NOT NULL, + FieldValue TEXT NOT NULL, + CONSTRAINT PK_DefaultCustomCharacterData + PRIMARY KEY (DefaultCustomCharacterDataID, CustomerGUID), + CONSTRAINT FK_DefaultCustomCharacterData_DefaultCharacterValueID + FOREIGN KEY (DefaultCharacterValuesID, CustomerGUID) REFERENCES DefaultCharacterValues (DefaultCharacterValuesID, CustomerGUID) +); + + +INSERT INTO OWSVersion (OWSDBVersion) VALUES('20230304'); diff --git a/src/.env b/src/.env index 09c670e99..907a55cd0 100644 --- a/src/.env +++ b/src/.env @@ -27,7 +27,7 @@ KIBANA_SYSTEM_PASSWORD='changeme' # - mssql # - postgres # - mysql -DATABASE='mssql' +DATABASE='postgres' # Database Password DATABASE_PASSWORD='yourStrong(!)Password' @@ -35,9 +35,9 @@ DATABASE_PASSWORD='yourStrong(!)Password' # Database Connection String # MSSQL -DATABASE_CONNECTION_STRING="Server=host.docker.internal;Database=OpenWorldServer;User Id=SA;Password=${DATABASE_PASSWORD};ConnectRetryCount=0" +# DATABASE_CONNECTION_STRING="Server=host.docker.internal;Database=OpenWorldServer;User Id=SA;Password=${DATABASE_PASSWORD};ConnectRetryCount=0" # Postgres -# DATABASE_CONNECTION_STRING="Host=host.docker.internal;Port=5432;Database=openworldserver;Username=postgres;Password=${DATABASE_PASSWORD};" +DATABASE_CONNECTION_STRING="Host=host.docker.internal;Port=5432;Database=openworldserver;Username=postgres;Password=${DATABASE_PASSWORD};" # Mysql # DATABASE_CONNECTION_STRING="server=host.docker.internal;user=root;database=openworldserver;port=3306;password=${DATABASE_PASSWORD};Allow User Variables=True;SslMode=None" @@ -52,3 +52,6 @@ RabbitMQHostName="host.docker.internal" RabbitMQPort=5672 RabbitMQUserName="dev" RabbitMQPassword="test" + +# Matchmaking Cache Redis +# MatchmakingCacheRedisPassword='YourRedi$Pa$$word' \ No newline at end of file diff --git a/src/OWSData/Repositories/Implementations/Postgres/InstanceManagementRepository.cs b/src/OWSData/Repositories/Implementations/Postgres/InstanceManagementRepository.cs index 3bcb9100e..4793f850f 100644 --- a/src/OWSData/Repositories/Implementations/Postgres/InstanceManagementRepository.cs +++ b/src/OWSData/Repositories/Implementations/Postgres/InstanceManagementRepository.cs @@ -135,6 +135,10 @@ await Connection.ExecuteAsync(GenericQueries.RemoveAllCharactersFromAllInstances parameter, commandType: CommandType.Text); + await Connection.ExecuteAsync(GenericQueries.RemoveAllMapInstancesForWorldServer, + parameter, + commandType: CommandType.Text); + await Connection.ExecuteAsync(GenericQueries.UpdateWorldServerStatus, parameter, commandType: CommandType.Text); diff --git a/src/OWSData/SQL/GenericQueries.cs b/src/OWSData/SQL/GenericQueries.cs index 49f0782ce..2b4a419d0 100644 --- a/src/OWSData/SQL/GenericQueries.cs +++ b/src/OWSData/SQL/GenericQueries.cs @@ -334,16 +334,18 @@ FROM Maps WHERE CustomerGUID = @CustomerGUID AND ZoneName = @ZoneName"; - public static readonly string GetZoneName = @"SELECT M.ZoneName + public static readonly string GetZoneName = @"SELECT M.ZoneName FROM Maps M INNER JOIN MapInstances MI ON MI.CustomerGUID = M.CustomerGUID AND MI.MapID = M.MapID WHERE M.CustomerGUID = @CustomerGUID AND MI.MapInstanceID = @MapInstanceID"; - public static readonly string RemoveMapInstances = @"DELETE FROM MapInstances WHERE CustomerGUID = @CustomerGUID AND MapInstanceID IN @MapInstances"; + public static readonly string RemoveMapInstances = @"DELETE FROM MapInstances WHERE CustomerGUID = @CustomerGUID AND MapInstanceID IN @MapInstances"; - public static readonly string UpdateMapInstanceStatus = @"UPDATE MapInstances + public static readonly string RemoveAllMapInstancesForWorldServer = @"DELETE FROM MapInstances WHERE CustomerGUID = @CustomerGUID AND WorldServerId = @WorldServerId"; + + public static readonly string UpdateMapInstanceStatus = @"UPDATE MapInstances SET Status = @MapInstanceStatus WHERE CustomerGUID = @CustomerGUID AND MapInstanceID = @MapInstanceID"; diff --git a/src/OWSInstanceLauncher/Services/ServerLauncherHealthMonitoring.cs b/src/OWSInstanceLauncher/Services/ServerLauncherHealthMonitoring.cs index 4e76d5b78..3a16c74c0 100644 --- a/src/OWSInstanceLauncher/Services/ServerLauncherHealthMonitoring.cs +++ b/src/OWSInstanceLauncher/Services/ServerLauncherHealthMonitoring.cs @@ -77,9 +77,9 @@ private List GetZoneInstancesForWorldServer(int } }; - var shutDownInstanceLauncherRequest = new StringContent(JsonSerializer.Serialize(worldServerIDRequestPayload), Encoding.UTF8, "application/json"); + var getZoneInstancesForWorldServerRequest = new StringContent(JsonSerializer.Serialize(worldServerIDRequestPayload), Encoding.UTF8, "application/json"); - var responseMessageTask = instanceManagementHttpClient.PostAsync("api/Instance/GetZoneInstancesForWorldServer", shutDownInstanceLauncherRequest); + var responseMessageTask = instanceManagementHttpClient.PostAsync("api/Instance/GetZoneInstancesForWorldServer", getZoneInstancesForWorldServerRequest); var responseMessage = responseMessageTask.Result; if (responseMessage.IsSuccessStatusCode) diff --git a/src/docker-compose.yml b/src/docker-compose.yml index 12ef9d52d..17c5e6a32 100644 --- a/src/docker-compose.yml +++ b/src/docker-compose.yml @@ -50,6 +50,11 @@ services: file: .docker/messaging.yml service: rabbitmq + #matchmakingcache: + # extends: + # file: .docker/matchmakingcache.yml + # service: matchmakingcache + # OWS Public Api owspublicapi: image: ${REGISTRY:-ows}/owspublicapi:${PLATFORM:-linux}-${TAG:-latest} @@ -169,3 +174,5 @@ volumes: name: "ows2-database" messaging: name: "ows2-messaging" + matchmakingcache: + name: "ows2-matchmakingcache" From fbb3e9d514840bd79fb2edf66fa914746ac67ceb Mon Sep 17 00:00:00 2001 From: Sabre Dart Studios <7533636+Dartanlla@users.noreply.github.com> Date: Mon, 27 May 2024 16:54:24 -0400 Subject: [PATCH 2/4] Update mssql.md Fixed MSSQL docs now that MSSQL is no longer the default DB tech. --- docs/getting-started/database-setup/mssql.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/getting-started/database-setup/mssql.md b/docs/getting-started/database-setup/mssql.md index 1faaf61c1..fd4b77ea3 100644 --- a/docs/getting-started/database-setup/mssql.md +++ b/docs/getting-started/database-setup/mssql.md @@ -10,6 +10,21 @@ nav_order: 1 [MSSQL](https://www.microsoft.com/de-de/sql-server/sql-server-2019) is a suite of database software published by Microsoft. It includes a relational database engine, which stores data in tables, columns and rows. +## Using MSSQL as an alternative to PostgreSQL +Open src/.env. +Rem out this line using # like this: +``` +# DATABASE_CONNECTION_STRING="Host=host.docker.internal;Port=5432;Database=openworldserver;Username=postgres;Password=${DATABASE_PASSWORD};" +``` +Unrem out this line by removing the # in front of the line like this: +``` +DATABASE_CONNECTION_STRING="Server=host.docker.internal;Database=OpenWorldServer;User Id=SA;Password=${DATABASE_PASSWORD};ConnectRetryCount=0" +``` +Replace the value in DATABASE with mssql like this: +``` +DATABASE='mssql' +``` + ## Update database version 1. Open SQL Server Management Studio or Azure Data Studio and connect to localhost with the following credentials. @@ -71,4 +86,4 @@ nav_order: 1 SELECT TOP 1 CustomerGUID FROM Customers ``` -[Next: OWS Starter Project](starter-project){: .btn .btn-outline } \ No newline at end of file +[Next: OWS Starter Project](starter-project){: .btn .btn-outline } From 4bdd41b6e838d1bd9084406d80490c823391ccd0 Mon Sep 17 00:00:00 2001 From: Sabre Dart Studios <7533636+Dartanlla@users.noreply.github.com> Date: Mon, 27 May 2024 17:01:07 -0400 Subject: [PATCH 3/4] Update index.md Updated Database Technology page to show that PostgreSQL is the new default. --- docs/getting-started/database-setup/index.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/getting-started/database-setup/index.md b/docs/getting-started/database-setup/index.md index ac5296b5b..cd2917d9b 100644 --- a/docs/getting-started/database-setup/index.md +++ b/docs/getting-started/database-setup/index.md @@ -8,12 +8,15 @@ has_children: true # Choose a Database Technology +OWS uses repository interfaces and can be used with multiple Database technologies. At this time there are implementations for PostgreSQL, MSSQL, and MySQL. **PostgeSQL is the default.** Support my vary by database technology. + ## Requirements +* If you are using PostgreSQL (the default), download one of the following SQL Management tools: + * [pgAdmin](https://www.pgadmin.org/) + * [dBeaver](https://dbeaver.io/download/) * If you are using MSSQL, download and install one of the following SQL Management tools: * [SQL Server Management Studio](https://docs.microsoft.com/en-us/sql/ssms/download-sql-server-management-studio-ssms?view=sql-server-ver15) (Windows Only) * [Azure Data Studio](https://docs.microsoft.com/en-us/sql/azure-data-studio/download-azure-data-studio?view=sql-server-ver15) (Windows/macOS/Linux) -OWS uses repository interfaces and can be used with multiple Database technologies. At this time there are implementations for MSSQL, Postgre SQL, and MySQL. MSSQL is the default. Support my vary by database technology. - > **Warning** -> Only MSSQL is officially supported. All other database technologies are community supported and may or may not work at any given time. Community supported database technologies may also be removed at any time without notice if the community stops supporting them. +> Only PostgeSQL and MSSQL are officially supported. All other database technologies are community supported and may or may not work at any given time. Community supported database technologies may also be removed at any time without notice if the community stops supporting them. From d2cc2e4848087c7183a0ce7fcb561beda67cd9dc Mon Sep 17 00:00:00 2001 From: Sabre Dart Studios <7533636+Dartanlla@users.noreply.github.com> Date: Mon, 27 May 2024 17:10:52 -0400 Subject: [PATCH 4/4] Update postgres.md Added additional setup information for PostgreSQL. --- .../database-setup/postgres.md | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/docs/getting-started/database-setup/postgres.md b/docs/getting-started/database-setup/postgres.md index 97db285f7..83b002755 100644 --- a/docs/getting-started/database-setup/postgres.md +++ b/docs/getting-started/database-setup/postgres.md @@ -10,35 +10,38 @@ nav_order: 1 [PostgreSQL](https://www.postgresql.org/) is a powerful, open source object-relational database system with over 30 years of active development that has earned it a strong reputation for reliability, feature robustness, and performance. -## Using Postgres as an alternative to MSSQL -1. Open `src/.env.` -2. Replace the `OWSDBConnectionString` with `Host=host.docker.internal;Port=5432;Database=openworldserver;Username=postgres;Password=yourStrong(!)Password;` -3. Replace the value in **OWSDBBackend** with **postgres** +## PostgreSQL is the now the default database technology for OWS +No configuration changes are required to use PostgreSQL ## Update database version +All database updates are applied by default now. If you need to update an existing database, the update scripts are in the ./Databases/Postgres/UpdateScripts folder. -1. Open PGAdmin3/4 and connect to localhost using the following credentials: - -
-
Username
-
postgres
-
Password
-
yourStrong(!)Password
-
- -2. Run the following SQL statement against the Open World Server database. - - ```sql - SELECT * FROM OWSVersion - ``` - -3. Compare the **OWSDBVersion** returned with the SQL update scripts in the `Databases\MSSQL\UpdateScriptsFolder` to see which scripts to run on your database. - -4. The update scripts have **From[SomeVersion]To[SomeVersion]** in the name. Run them in order starting with the **From[SomeVersion]** that matches your **OWSDBVersion** from the previous step. To run the script open the file, copy the contents from it and run it as a SQL statement against the Open World Server database like you did in step 2. If you are not familiar with SQL please refer to the [SQL Tutorial](https://www.w3schools.com/sql/) to learn more about it. +## Warning! If you already have PostgreSQL installed on your development PC running on port 5432, then OWS 2 will not work +Please adjust the OWS 2 configuration to run PostgreSQL on an alternate port. Here is an example of what to change to switch OWS copy of PostgreSQL to port 15432: +1. Open the .env file and go to the Postgres connection string and edit it like this: +``` +# Postgres +DATABASE_CONNECTION_STRING="Host=host.docker.internal;Port=15432;Database=openworldserver;Username=postgres;Password=${DATABASE_PASSWORD};" +``` +2. Open databases.yml and edit it like this: +``` +### PostgreSQL Database + postgres: + build: + context: postgres/ + environment: + - POSTGRES_PASSWORD=${DATABASE_PASSWORD} + ports: + - "15432:5432" + volumes: + - database:/var/lib/postgresql/data + container_name: PostgreSQL +``` +Notice that we ONLY change the first number to 15432 and not the second number. The second number has to stay 5432. ## Create API Key -1. Open PGAdmin3/4 and connect to localhost using the following credentials: +1. Open PGAdmin3/4 or dBeaver and connect to localhost using the following credentials:
Username
@@ -75,5 +78,7 @@ nav_order: 1 ```sql SELECT CustomerGUID FROM Customers LIMIT 1; ``` + The above query only works if this is a new OWS 2 installation. A common issue during setup is when you have more than one API key (CustomerGUID). + -[Next: OWS Starter Project](starter-project){: .btn .btn-outline } \ No newline at end of file +[Next: OWS Starter Project](starter-project){: .btn .btn-outline }