diff --git a/Arrowgene.Ddon.Database/IDatabase.cs b/Arrowgene.Ddon.Database/IDatabase.cs index 65df8404..f532e90a 100644 --- a/Arrowgene.Ddon.Database/IDatabase.cs +++ b/Arrowgene.Ddon.Database/IDatabase.cs @@ -184,8 +184,7 @@ public interface IDatabase List SelectEquipItemByCharacter(uint characterCommonId); // Job Items - bool InsertEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo); - bool ReplaceEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo); + bool UpsertEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo, DbConnection? connectionIn = null); bool DeleteEquipJobItem(uint commonId, JobId job, ushort slotNo); // CustomSkills diff --git a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquipJobItem.cs b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquipJobItem.cs index 799d177e..faee5974 100644 --- a/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquipJobItem.cs +++ b/Arrowgene.Ddon.Database/Sql/Core/DdonSqlDbEquipJobItem.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Data.Common; using Arrowgene.Ddon.Shared.Model; @@ -5,79 +6,35 @@ namespace Arrowgene.Ddon.Database.Sql.Core; public partial class DdonSqlDb : SqlDb { - private const string SqlDeleteEquipJobItem = - "DELETE FROM \"ddon_equip_job_item\" WHERE \"character_common_id\"=@character_common_id AND \"job\"=@job AND \"equip_slot\"=@equip_slot;"; - protected static readonly string[] CDataEquipJobItemFields = new[] { "item_uid", "character_common_id", "job", "equip_slot" }; - private static readonly string SqlUpdateEquipJobItem = - $"UPDATE \"ddon_equip_job_item\" SET {BuildQueryUpdate(CDataEquipJobItemFields)} WHERE \"character_common_id\" = @character_common_id AND \"job\" = @job AND \"equip_slot\"=@equip_slot;"; - private static readonly string SqlSelectEquipJobItems = $"SELECT {BuildQueryField(CDataEquipJobItemFields)} FROM \"ddon_equip_job_item\" WHERE \"character_common_id\" = @character_common_id AND \"job\" = @job;"; private static readonly string SqlSelectEquipJobItemsByCharacter = $"SELECT {BuildQueryField(CDataEquipJobItemFields)} FROM \"ddon_equip_job_item\" WHERE \"character_common_id\" = @character_common_id;"; - private readonly string SqlInsertEquipJobItem = - $"INSERT INTO \"ddon_equip_job_item\" ({BuildQueryField(CDataEquipJobItemFields)}) VALUES ({BuildQueryInsert(CDataEquipJobItemFields)});"; + private readonly string SqlUpsertEquipJobItem = + $@"INSERT INTO ""ddon_equip_job_item"" ({BuildQueryField(CDataEquipJobItemFields)}) VALUES ({BuildQueryInsert(CDataEquipJobItemFields)}) ON CONFLICT (character_common_id, job, equip_slot) DO UPDATE SET item_uid = EXCLUDED.item_uid;"; - private readonly string SqlInsertIfNotExistsEquipJobItem = - $"INSERT INTO \"ddon_equip_job_item\" ({BuildQueryField(CDataEquipJobItemFields)}) SELECT {BuildQueryInsert(CDataEquipJobItemFields)} WHERE NOT EXISTS (SELECT 1 FROM \"ddon_equip_job_item\" WHERE \"character_common_id\" = @character_common_id AND \"job\" = @job);"; + private const string SqlDeleteEquipJobItem = + "DELETE FROM \"ddon_equip_job_item\" WHERE \"character_common_id\"=@character_common_id AND \"job\"=@job AND \"equip_slot\"=@equip_slot;"; - public bool InsertIfNotExistsEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo) + public override bool UpsertEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo, DbConnection? connectionIn = null) { - using DbConnection connection = OpenNewConnection(); - return InsertIfNotExistsEquipJobItem(connection, itemUId, commonId, job, slotNo); - } - - public bool InsertIfNotExistsEquipJobItem(DbConnection conn, string itemUId, uint commonId, JobId job, ushort slotNo) - { - return ExecuteNonQuery(conn, SqlInsertIfNotExistsEquipJobItem, command => + return ExecuteQuerySafe(connectionIn, connection => { - AddParameter(command, "item_uid", itemUId); - AddParameter(command, "character_common_id", commonId); - AddParameter(command, "job", (byte)job); - AddParameter(command, "equip_slot", slotNo); - }) == 1; - } - - public override bool InsertEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo) - { - using DbConnection connection = OpenNewConnection(); - return InsertEquipJobItem(connection, itemUId, commonId, job, slotNo); - } - - public bool InsertEquipJobItem(DbConnection conn, string itemUId, uint commonId, JobId job, ushort slotNo) - { - return ExecuteNonQuery(conn, SqlInsertEquipJobItem, command => - { - AddParameter(command, "item_uid", itemUId); - AddParameter(command, "character_common_id", commonId); - AddParameter(command, "job", (byte)job); - AddParameter(command, "equip_slot", slotNo); - }) == 1; - } - - public override bool ReplaceEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo) - { - using DbConnection connection = OpenNewConnection(); - return ReplaceEquipJobItem(connection, itemUId, commonId, job, slotNo); - } - - public bool ReplaceEquipJobItem(DbConnection conn, string itemUId, uint commonId, JobId job, ushort slotNo) - { - Logger.Debug("Inserting equp job item."); - if (!InsertIfNotExistsEquipJobItem(conn, itemUId, commonId, job, slotNo)) - { - Logger.Debug("Equip job item already exists, replacing."); - return UpdateEquipJobItem(conn, itemUId, commonId, job, slotNo); - } - - return true; + return ExecuteNonQuery(connection, SqlUpsertEquipJobItem, command => + { + AddParameter(command, "item_uid", itemUId); + AddParameter(command, "character_common_id", commonId); + AddParameter(command, "job", (byte)job); + AddParameter(command, "equip_slot", slotNo); + }) == 1; + }); } public override bool DeleteEquipJobItem(uint commonId, JobId job, ushort slotNo) @@ -89,21 +46,4 @@ public partial class DdonSqlDb : SqlDb AddParameter(command, "equip_slot", slotNo); }) == 1; } - - public bool UpdateEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo) - { - using DbConnection connection = OpenNewConnection(); - return UpdateEquipJobItem(connection, itemUId, commonId, job, slotNo); - } - - public bool UpdateEquipJobItem(DbConnection connection, string itemUId, uint commonId, JobId job, ushort slotNo) - { - return ExecuteNonQuery(connection, SqlUpdateEquipJobItem, command => - { - AddParameter(command, "item_uid", itemUId); - AddParameter(command, "character_common_id", commonId); - AddParameter(command, "job", (byte)job); - AddParameter(command, "equip_slot", slotNo); - }) == 1; - } } diff --git a/Arrowgene.Ddon.Database/Sql/SqlDb.cs b/Arrowgene.Ddon.Database/Sql/SqlDb.cs index 4b6d08c8..dbea5161 100644 --- a/Arrowgene.Ddon.Database/Sql/SqlDb.cs +++ b/Arrowgene.Ddon.Database/Sql/SqlDb.cs @@ -374,8 +374,7 @@ public abstract class SqlDb : IDatabase public abstract bool DeleteEquipItem(uint commonId, JobId job, EquipType equipType, byte equipSlot, DbConnection? connectionIn = null); public abstract void DeleteAllEquipItems(uint commonId, DbConnection? connectionIn = null); public abstract List SelectEquipItemByCharacter(uint characterCommonId); - public abstract bool InsertEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo); - public abstract bool ReplaceEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo); + public abstract bool UpsertEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo, DbConnection? connectionIn = null); public abstract bool DeleteEquipJobItem(uint commonId, JobId job, ushort slotNo); public abstract bool InsertLearnedCustomSkill(uint commonId, CustomSkill skill, DbConnection? connectionIn = null); public abstract bool UpdateLearnedCustomSkill(uint commonId, CustomSkill updatedSkill, DbConnection? connectionIn = null); diff --git a/Arrowgene.Ddon.GameServer/Characters/EquipManager.cs b/Arrowgene.Ddon.GameServer/Characters/EquipManager.cs index a27d9c2c..c146da4a 100644 --- a/Arrowgene.Ddon.GameServer/Characters/EquipManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/EquipManager.cs @@ -80,7 +80,7 @@ namespace Arrowgene.Ddon.GameServer.Characters // EQUIP Item item = server.Database.SelectStorageItemByUId(changeEquipJobItem.EquipJobItemUId); characterToEquipTo.EquipmentTemplate.SetJobItem(item, characterToEquipTo.Job, changeEquipJobItem.EquipSlotNo); - server.Database.ReplaceEquipJobItem(item.UId, characterToEquipTo.CommonId, characterToEquipTo.Job, changeEquipJobItem.EquipSlotNo); + server.Database.UpsertEquipJobItem(item.UId, characterToEquipTo.CommonId, characterToEquipTo.Job, changeEquipJobItem.EquipSlotNo); } } diff --git a/Arrowgene.Ddon.GameServer/Characters/JobManager.cs b/Arrowgene.Ddon.GameServer/Characters/JobManager.cs index 4c59f092..9fc542eb 100644 --- a/Arrowgene.Ddon.GameServer/Characters/JobManager.cs +++ b/Arrowgene.Ddon.GameServer/Characters/JobManager.cs @@ -142,7 +142,6 @@ namespace Arrowgene.Ddon.GameServer.Characters changeJobNotice.EquipJobItemList = jobItems; // TODO: Unk0 - updateCharacterItemNtc.UpdateType = ItemNoticeType.ChangeJob; S2CJobChangeJobRes changeJobResponse = new S2CJobChangeJobRes(); @@ -158,7 +157,7 @@ namespace Arrowgene.Ddon.GameServer.Characters .FirstOrDefault(new CDataPlayPointData()); changeJobResponse.Unk0.Unk0 = (byte)jobId; changeJobResponse.Unk0.Unk1 = character.Storage.GetAllStoragesAsCDataCharacterItemSlotInfoList(); - + client.Enqueue(changeJobResponse, queue); client.Enqueue(updateCharacterItemNtc, queue); foreach (GameClient otherClient in Server.ClientLookup.GetAll()) @@ -167,8 +166,6 @@ namespace Arrowgene.Ddon.GameServer.Characters } queue.AddRange(Server.CharacterManager.UpdateCharacterExtendedParamsNtc(client, common)); - - return queue; } else if (common is Pawn) { @@ -209,12 +206,32 @@ namespace Arrowgene.Ddon.GameServer.Characters queue.AddRange(Server.CharacterManager.UpdateCharacterExtendedParamsNtc(client, common)); - return queue; } else { throw new Exception("Unknown character type"); } + + var equippedJobEmblems = client.Character.JobEmblems + .GetValueOrDefault(common.Job) + ?.UIDs + .Select(x => common.Equipment.Storage.FindItemByUId(x)) + .Where(x => x is not null); + if (equippedJobEmblems is not null && equippedJobEmblems.Any()) + { + var extraUpdateItemNtc = new S2CItemUpdateCharacterItemNtc() + { + UpdateType = ItemNoticeType.EmblemStatUpdate // ??? + }; + foreach (var (slot, item, _) in equippedJobEmblems) + { + extraUpdateItemNtc.UpdateItemList.Add(Server.ItemManager.CreateItemUpdateResult(common, item, common.Equipment.Storage.Type, slot, 1, 1)); + } + + client.Enqueue(extraUpdateItemNtc, queue); + } + + return queue; } private List SwapEquipmentAndStorage(GameClient client, CharacterCommon common, JobId oldJobId, JobId newJobId, EquipType equipType, DbConnection? connectionIn = null) diff --git a/Arrowgene.Ddon.GameServer/Chat/ChatManager.cs b/Arrowgene.Ddon.GameServer/Chat/ChatManager.cs index da9c61c8..bd45438b 100644 --- a/Arrowgene.Ddon.GameServer/Chat/ChatManager.cs +++ b/Arrowgene.Ddon.GameServer/Chat/ChatManager.cs @@ -193,6 +193,12 @@ namespace Arrowgene.Ddon.GameServer.Chat } case LobbyChatMsgType.Shout: response.Recipients.AddRange(_Server.ClientLookup.GetAll()); + + if (_Server.GameSettings.ChatCommandsSettings.CrossChannelShout) + { + _Server.RpcManager.AnnounceShoutChat(client, response); + } + break; case LobbyChatMsgType.Party: PartyGroup party = client.Party; diff --git a/Arrowgene.Ddon.GameServer/Handler/CharacterSwitchGameModeHandler.cs b/Arrowgene.Ddon.GameServer/Handler/CharacterSwitchGameModeHandler.cs index d8af6b6b..d7559486 100644 --- a/Arrowgene.Ddon.GameServer/Handler/CharacterSwitchGameModeHandler.cs +++ b/Arrowgene.Ddon.GameServer/Handler/CharacterSwitchGameModeHandler.cs @@ -99,6 +99,15 @@ namespace Arrowgene.Ddon.GameServer.Handler VisualEquipItemList = client.Character.Equipment.AsCDataEquipItemInfo(EquipType.Visual), }); + client.Send(new S2CItemSortGetItemSortdataBinNtc() + { + SortData = [.. ItemManager.ItemBagStorageTypes.Select(x => new CDataItemSort() + { + StorageType = x, + Bin = client.Character.Storage.GetStorage(x).SortData + })] + }); + return new S2CCharacterSwitchGameModeRes() { GameMode = packet.GameMode, diff --git a/Arrowgene.Ddon.GameServer/RpcManager.cs b/Arrowgene.Ddon.GameServer/RpcManager.cs index 9d3b74d8..bfacc0ae 100644 --- a/Arrowgene.Ddon.GameServer/RpcManager.cs +++ b/Arrowgene.Ddon.GameServer/RpcManager.cs @@ -183,11 +183,16 @@ namespace Arrowgene.Ddon.GameServer } } + public void AnnounceAsync(ushort channelId, string route, RpcInternalCommand command, object data) + { + Task.Run(() => Announce(channelId, route, command, data)); + } + public void AnnounceAll(string route, RpcInternalCommand command, object data) { foreach (var channel in Server.AssetRepository.ServerList) { - Announce(channel.Id, route, command, data); + AnnounceAsync(channel.Id, route, command, data); } } @@ -196,7 +201,7 @@ namespace Arrowgene.Ddon.GameServer foreach (var channel in Server.AssetRepository.ServerList) { if (channel.Id == Server.Id) continue; - Announce(channel.Id, route, command, data); + AnnounceAsync(channel.Id, route, command, data); } } @@ -211,7 +216,7 @@ namespace Arrowgene.Ddon.GameServer if (channel.Value.Any(x => x.Value.ClanId == clanId)) { - Announce(channel.Key, route, command, data); + AnnounceAsync(channel.Key, route, command, data); } } } @@ -327,6 +332,23 @@ namespace Arrowgene.Ddon.GameServer #endregion #region Chat + public void AnnounceShoutChat(GameClient client, ChatResponse chatResponse) + { + RpcChatData chatData = new RpcChatData() + { + HandleId = 0, + Type = LobbyChatMsgType.Shout, + MessageFlavor = chatResponse.MessageFlavor, + PhrasesCategory = chatResponse.PhrasesCategory, + PhrasesIndex = chatResponse.PhrasesIndex, + Message = chatResponse.Message, + Deliver = false, + SourceData = new RpcCharacterData(client.Character) + }; + + AnnounceOthers("internal/chat", RpcInternalCommand.SendShoutMessage, chatData); + } + public void AnnounceClanChat(GameClient client, ChatResponse chatResponse) { if (client.Character.ClanId == 0) return; @@ -372,7 +394,7 @@ namespace Arrowgene.Ddon.GameServer } }; - Announce(targetServer, "internal/chat", RpcInternalCommand.SendTellMessage, chatData); + AnnounceAsync(targetServer, "internal/chat", RpcInternalCommand.SendTellMessage, chatData); } #endregion diff --git a/Arrowgene.Ddon.Rpc.Web/Route/Internal/InternalChatRoute.cs b/Arrowgene.Ddon.Rpc.Web/Route/Internal/InternalChatRoute.cs index 1216e758..d0a58bf6 100644 --- a/Arrowgene.Ddon.Rpc.Web/Route/Internal/InternalChatRoute.cs +++ b/Arrowgene.Ddon.Rpc.Web/Route/Internal/InternalChatRoute.cs @@ -7,7 +7,6 @@ using Arrowgene.Logging; using Arrowgene.WebServer; using System.Linq; using System.Threading.Tasks; -using static Arrowgene.Ddon.GameServer.RpcManager; namespace Arrowgene.Ddon.Rpc.Web.Route.Internal { @@ -103,6 +102,36 @@ namespace Arrowgene.Ddon.Rpc.Web.Route.Internal Message = $"SendTellMessage ID {data.SourceData.CharacterId} -> {data.TargetData.CharacterId}" }; } + case RpcInternalCommand.SendShoutMessage: + { + RpcChatData data = _entry.GetData(); + + ChatResponse response = new() + { + HandleId = 0, + Deliver = false, + FirstName = data.SourceData.FirstName, + LastName = data.SourceData.LastName, + ClanName = data.SourceData.ClanName, + CharacterId = data.SourceData.CharacterId, + Type = LobbyChatMsgType.Shout, + Message = data.Message, + MessageFlavor = data.MessageFlavor, + PhrasesCategory = data.PhrasesCategory, + PhrasesIndex = data.PhrasesIndex + }; + + response.Recipients.AddRange(gameServer.ClientLookup + .GetAll() + .Where(x => x.Character != null) + ); + gameServer.ChatManager.Send(response); + + return new RpcCommandResult(this, true) + { + Message = $"SendShoutMessage {data.SourceData.FirstName} {data.SourceData.LastName}: {data.Message}" + }; + } default: return new RpcCommandResult(this, false); } diff --git a/Arrowgene.Ddon.Server/Settings/ChatCommandSettings.cs b/Arrowgene.Ddon.Server/Settings/ChatCommandSettings.cs index b57f646a..80058816 100644 --- a/Arrowgene.Ddon.Server/Settings/ChatCommandSettings.cs +++ b/Arrowgene.Ddon.Server/Settings/ChatCommandSettings.cs @@ -25,5 +25,22 @@ namespace Arrowgene.Ddon.Server.Settings } } private const bool _DisableAccountTypeCheck = false; + + /// + /// Controls whether Shout is sent to all channels or not. + /// + [DefaultValue(_CrossChannelShout)] + public bool CrossChannelShout + { + set + { + SetSetting("CrossChannelShout", value); + } + get + { + return TryGetSetting("CrossChannelShout", _CrossChannelShout); + } + } + private const bool _CrossChannelShout = true; } } diff --git a/Arrowgene.Ddon.Shared/Model/Rpc/RpcInternalCommand.cs b/Arrowgene.Ddon.Shared/Model/Rpc/RpcInternalCommand.cs index 600e0db7..9ae1e840 100644 --- a/Arrowgene.Ddon.Shared/Model/Rpc/RpcInternalCommand.cs +++ b/Arrowgene.Ddon.Shared/Model/Rpc/RpcInternalCommand.cs @@ -18,6 +18,7 @@ namespace Arrowgene.Ddon.Shared.Model.Rpc //InternalChatRoute SendTellMessage, // RpcChatData SendClanMessage, // RpcChatData + SendShoutMessage, // RpcChatData //PacketRoute AnnouncePacketAll, // RpcPacketData diff --git a/Arrowgene.Ddon.Shared/Model/Storages.cs b/Arrowgene.Ddon.Shared/Model/Storages.cs index e8fa03be..c1f217fc 100644 --- a/Arrowgene.Ddon.Shared/Model/Storages.cs +++ b/Arrowgene.Ddon.Shared/Model/Storages.cs @@ -327,7 +327,7 @@ namespace Arrowgene.Ddon.Shared.Model { ItemId = (ushort) x.ItemId, ColorNo = x.Color, - PlusValue = x.SafetySetting, + PlusValue = x.PlusValue, EquipElementParamList = x.EquipElementParamList, AddStatusParamList = x.AddStatusParamList, }) diff --git a/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs b/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs index aa4d5bf4..20050c48 100644 --- a/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs +++ b/Arrowgene.Ddon.Test/Database/DatabaseMigratorTest.cs @@ -242,7 +242,6 @@ namespace Arrowgene.Ddon.Test.Database public bool InsertConnection(Connection connection) { return true; } public int InsertContact(uint requestingCharacterId, uint requestedCharacterId, ContactListStatus status, ContactListType type, bool requesterFavorite, bool requestedFavorite) { return 1; } public bool InsertEquipItem(uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId) { return true; } - public bool InsertEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo) { return true; } public bool InsertEquippedAbility(uint commonId, JobId equipptedToJob, byte slotNo, Ability ability) { return true; } public bool InsertEquippedCustomSkill(uint commonId, byte slotNo, CustomSkill skill) { return true; } public bool InsertGainExtendParam(uint commonId, CDataOrbGainExtendParam Param) { return true; } @@ -273,7 +272,6 @@ namespace Arrowgene.Ddon.Test.Database public bool ReplaceCharacterJobData(uint commonId, CDataCharacterJobData replacedCharacterJobData, DbConnection? connectionIn = null) { return true; } public bool ReplaceCommunicationShortcut(uint characterId, CDataCommunicationShortCut communicationShortcut, DbConnection? connectionIn = null) { return true; } public bool ReplaceEquipItem(uint commonId, JobId job, EquipType equipType, byte equipSlot, string itemUId, DbConnection? connectionIn = null) { return true; } - public bool ReplaceEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo) { return true; } public bool ReplaceEquippedAbilities(uint commonId, JobId equippedToJob, List abilities) { return true; } public bool ReplaceEquippedAbility(uint commonId, JobId equipptedToJob, byte slotNo, Ability ability) { return true; } public bool ReplaceEquippedCustomSkill(uint commonId, byte slotNo, CustomSkill skill) { return true; } @@ -539,6 +537,8 @@ namespace Arrowgene.Ddon.Test.Database public List SelectPawnHistory(uint pawnId, DbConnection? connectionIn = null) { return []; } public CDataPawnTotalScore SelectPawnTotalScore(uint pawnId, DbConnection? connectionIn = null) { return new(); } + public bool UpsertEquipJobItem(string itemUId, uint commonId, JobId job, ushort slotNo, DbConnection? connectionIn = null) { return true; } + public void AddParameter(DbCommand command, string name, object? value, DbType type) { } public void AddParameter(DbCommand command, string name, string value) { } public void AddParameter(DbCommand command, string name, Int32 value) { } diff --git a/README.adoc b/README.adoc index 5d4c5798..79ea1c38 100644 --- a/README.adoc +++ b/README.adoc @@ -37,6 +37,17 @@ The project is intended for educational purpose only. . Debug the Project .. Run the `Ddon.Cli`-Project with arguments `server start` +== User Setup +. Clone the repository +.. `git clone https://github.com/sebastian-heinz/Arrowgene.DragonsDogmaOnline.git` +. Install .NET 9.0 SDK or later https://dotnet.microsoft.com/download +. Run `publish.cmd` or `publish.sh` to build the server files. +. If updating from a previous build of the server... +.. Make sure that your database file `db.sqlite` is in the proper location, usually `Arrowgene.DragonsDogmaOnline\publish\win-x64-#.#.#.#\Server\Files\Database`. +.. You may have to migrate your database schema by running `MigrateDatabase.cmd`, which can be found in `Arrowgene.DragonsDogmaOnline\publish\win-x64-#.#.#.#`. +. Launch the server with `StartServer.cmd`, which can be found in `Arrowgene.DragonsDogmaOnline\publish\win-x64-#.#.#.#`. +. For frequently asked questions, see https://github.com/sebastian-heinz/Arrowgene.DragonsDogmaOnline/blob/develop/docs/faq.md + == Deployment The application (server) requires the (ASP).NET runtime 9 to function. Alternatively the SDK also works and is required when building from source. @@ -96,46 +107,7 @@ Launch the client with the following args: `"DDO.exe" "addr=localhost port=52100 token=00000000000000000000 DL=http://127.0.0.1:52099/win/ LVer=03.04.003.20181115.0 RVer=3040008"` == Progress - -=== Login Server -* [x] Account -* [x] Character Creation - -=== Game Server -==== Party Management (Party List) -* [ ] Party Members -** [x] View Arisen Profile -** [ ] Send Tell -** [ ] Send Friend Request -** [ ] View Status and Equipment -** [x] Promote to Party Leader -** [x] Kick from Party -** [ ] Invite to Group Chat -** [x] Disband Party -** [ ] Invite to Entryboard -** [ ] Follow with Autorun -** [ ] Cancel Party Invite -** [ ] Decline Party Invite -** [ ] View Party List -** [x] Leave -** [ ] Invite Directly to Clan -* [ ] Main Pawns -** [ ] View Pawn Profile -** [x] Invite to Party -** [x] Kick from Party -** [ ] View Status and Equipment -* [ ] Support Pawns -* [ ] Party Search -** [ ] Search -** [ ] Simple Request -* [ ] Player Search -** [ ] View Arisen Profile -** [x] Invite to Party -** [ ] Send Tell -** [ ] Send Friend Request -** [ ] Invite to Group Chat -** [ ] Invite to Entryboard -** [x] Search +See the https://github.com/sebastian-heinz/Arrowgene.DragonsDogmaOnline/wiki/What-Works%3F[wiki]. == Guidelines === Git diff --git a/ReleaseFiles/MigrateDatabase.cmd b/ReleaseFiles/MigrateDatabase.cmd new file mode 100644 index 00000000..3ea6d47b --- /dev/null +++ b/ReleaseFiles/MigrateDatabase.cmd @@ -0,0 +1,3 @@ +pushd "%~dp0 " +cd ./Server. +start Arrowgene.Ddon.Cli.exe dbmigration \ No newline at end of file diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 00000000..ea61e8aa --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,32 @@ +# Frequently Asked Questions + +## When I launch the server, it says "Please update the database..." +As features are developed, we must often change the structure of the database. To stay up to date with these changes, you'll need to migrate your database. If you built the server using `publish.cmd` or `publish.sh`, you can use the provided `MigrateDatabase.cmd` to do this. If you built the server using VS or another tool, you can navigate to `Arrowgene.Ddon.Cli` and run `dotnet run dbmigration` from the command line or PowerShell. + +## How do I update the translation? +* Option 1: Use the new and improved [DDON Launcher](https://github.com/D00MK1D/DDON-Launcher/releases). Place this in your DDON directory (near `ddon.exe`), run the launcher, and click the `大A` button. By default, this downloads and installs the most recent *English* translation, or you can provide the github URL for another translation project. +* Option 2: Use the legacy local patcher. Place a translation file (`gmd.csv`) in `[Your DDON Directory]/nativePC/Server/Files/Client`, then drag the folder `[Your DDON Directory]/nativePC/rom` onto `pack_gmd_english.cmd` in the same directory. + +## How do I use admin commands? +You'll need to modify your account status in the server's database. If you're running locally and using the default SQLite DB, [DB4S](https://sqlitebrowser.org/) is a convenient tool for this. Make sure you've logged in at least once, open up the DB (`Server\Files\Database\db.sqlite`) in DB4S, navigate to the `account` table, and set the `state` column for your account to `100`. + +Alternatively, you can disable the account status check, by editing the server setting scripts. See below for details. + +## How do I place monsters and loot? +The most convenient tool for this is [DDONTools](https://github.com/alborrajo/DDOn-Tools). + +## How do I change settings like EXP multipliers? +See the [README](https://github.com/sebastian-heinz/Arrowgene.DragonsDogmaOnline/blob/develop/Arrowgene.Ddon.Scripts/scripts/settings/README.md). + +## How do I open my game up for multiplayer? +* Ensure that the IP addresses and ports in `Server/Files/Arrowgene.Ddon.config.json` are publicly accessible. +* Update `Server/Files/Assets/GameServerList.csv` with your publicly facing IP address/ports. +* Players can add your server to their launcher by clicking the gear icon in the bottom left. + +## How do I run multiple channels at once? +Each channel is a separately running server process. One server acts as the leader, managing login and some periodic tasks, as well as its own GameServer instance, while also directing players to the subordinate channels. In general, to prevent issues, all channels should run from the same asset folder and same build of the server. +* Make a copy of your config file, then change the `Port` and `Id` values so that they do not overlap with any other channel's ports and IDs. Web, login, and game servers must all have unique ids. The copy can be in the same folder as the original. +* Add the new channel in a new row of `GameServerList.csv`. The values here should reflect the config files. + * `IsHide` will prevent players from seeing this channel in the channel list. + * `PreventLogin` will prevent automatic load balancing from placing new characters on this channel. +* Launch each separate instance with `dotnet run server start --config=[PATH TO CONFIG FILE]`. \ No newline at end of file