baseline: OpenMU upstream b5a0961 (fresh source)

This commit is contained in:
Acentech Dev
2026-07-14 19:00:35 +03:00
parent 450402e47d
commit 36fc125d5c
11968 changed files with 748705 additions and 0 deletions

13
.gemini/config.yaml Normal file
View File

@@ -0,0 +1,13 @@
have_fun: false
memory_config:
disabled: false
code_review:
disable: false
comment_severity_threshold: MEDIUM
max_review_comments: -1
pull_request_opened:
help: false
summary: true
code_review: true
include_drafts: false
ignore_patterns: []

10
.gitattributes vendored Normal file
View File

@@ -0,0 +1,10 @@
# Auto detect text files and perform LF normalization
* text=auto
# Explicitly declare text files you want to always be normalized and converted
# to native line endings on checkout.
*.cpp text
*.h text
*.cs text
*.csproj text
*.sln text

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

10
.github/ISSUE_TEMPLATE/other-request.md vendored Normal file
View File

@@ -0,0 +1,10 @@
---
name: Other request
about: Ask questions or similar
title: ''
labels: question
assignees: ''
---

41
.github/workflows/claude.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
jobs:
claude:
if: |
github.actor == 'sven-n' && (
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
)
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: write
id-token: write
actions: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
additional_permissions: |
actions: read

17
.github/workflows/dotnetcore.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: .NET Core
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 10.0.x
- name: Build with dotnet
run: dotnet publish src/Startup/MUnique.OpenMU.Startup.csproj --configuration Release -p:ci=true

40
.github/workflows/publish.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: publish to nuget
on:
push:
branches:
- master
jobs:
publish:
name: Publish packages to nuget
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: 10.0.x
- name: publish MUnique.OpenMU.PlugIns on version change
uses: rohith/publish-nuget@5fdc86fb81a38b37521c9f153bfb60ac44e7ba93
with:
PROJECT_FILE_PATH: src/PlugIns/MUnique.OpenMU.PlugIns.csproj
TAG_COMMIT: false
NUGET_KEY: ${{secrets.NUGET_KEY}}
PACKAGE_NAME: munique.openmu.plugins
- name: publish MUnique.OpenMU.Network on version change
uses: rohith/publish-nuget@5fdc86fb81a38b37521c9f153bfb60ac44e7ba93
with:
PROJECT_FILE_PATH: src/Network/MUnique.OpenMU.Network.csproj
TAG_COMMIT: false
NUGET_KEY: ${{secrets.NUGET_KEY}}
PACKAGE_NAME: munique.openmu.network
- name: publish MUnique.OpenMU.Network.Packets on version change
uses: rohith/publish-nuget@5fdc86fb81a38b37521c9f153bfb60ac44e7ba93
with:
PROJECT_FILE_PATH: src/Network/Packets/MUnique.OpenMU.Network.Packets.csproj
TAG_COMMIT: true
TAG_FORMAT: v*
NUGET_KEY: ${{secrets.NUGET_KEY}}
PACKAGE_NAME: munique.openmu.network.packets

325
.gitignore vendored Normal file
View File

@@ -0,0 +1,325 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
**/content/bundles/*.*
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Typescript v1 declaration files
typings/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# VS Code
.vscode/
*.lscache
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# SubVersion
.svn/
# Pandoc html result files
doc/*.htm
# text files; for documentation etc. use markdown
*.txt
# bundles
src/AdminPanel/wwwroot/content/css/app.css
src/AdminPanel/wwwroot/content/css/app.min.css
src/AdminPanel/wwwroot/content/js/app.js
src/AdminPanel/wwwroot/content/js/app.js.map
# Data
.data/
/out/
# Mac OS filesystem files
.DS_Store
# AI
.opencode/

36
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,36 @@
# Contributions
Contributions are welcome if they meet the following criteria:
* Language is english.
* Code should be StyleCop compliant - this project uses the [StyleCop.Analyzers](https://www.nuget.org/packages/StyleCop.Analyzers/)
for VS2019 so you should see issues directly as warnings.
* Coding style (naming, etc.) and quality should fit to the current state.
* No code copied/converted from the well-known decompiled source of the
original server.
If you want to contribute, please create a new issue for the feature or bug (if
the issue doesn't exist yet) so we can see who is working on something and can
discuss possible solutions. If it's a small thing, you can also just send a
pull request without adding an issue.
Apart of that, contributions from non-developers are welcome as well. You can
test the server, submit issues or suggestions, packet descriptions or
documentations about the concepts and mechanics of the game itself. Please use
markdown files/syntax for this purpose.
If you have questions about that, don't hesitate to ask in our [discord channel](https://discord.gg/2u5Agkd)
or by submitting an issue.
## How to contribute code
If you want to contribute code, please do the following steps:
1. fork this project from the original MUnique OpenMU Project.
2. create a feature branch from the master branch
3. commit your changes to your feature branch
4. submit a pull request to the original master branch
5. lean back, wait for the code review and merge :)

7
LICENSE Normal file
View File

@@ -0,0 +1,7 @@
Copyright 2017 MUnique
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

145
QuickStart.md Normal file
View File

@@ -0,0 +1,145 @@
# Quick Start OpenMU
General requirements:
* Free TCP ports:
* 80 (admin panel)
* 55901 - 55906 (game servers)
* 44405 - 44406 (connect servers)
* 44405: default connection port for the original client
* 44406: connection port especially for the [open source client](https://github.com/sven-n/MuMain)
* 55980 (chat server)
* A game client (check [our Discord](https://discord.gg/2u5Agkd) FAQs)
* Knowledge or way to start the game client, so that it connects to the server. Our Launcher will do that.
* Launcher binaries: [MUnique.OpenMU.ClientLauncher v0.9.6.zip](https://github.com/MUnique/OpenMU/releases/download/v0.9.0/MUnique.OpenMU.ClientLauncher_0.9.6.zip)
* It requires the [.NET 10 runtime](https://dotnet.microsoft.com/download/dotnet/10.0) or higher
* If your server and client runs on your local host, use any IP of 127.x.x.x, except 127.0.0.1, because this one is blocked by the client. For example, you could use 127.127.127.127
This guide describes two ways of starting the server. Use Docker, if you just
want to play around. If you want to develop or debug the server, choose the
manual way.
As you can see on the connect server ports, the server is initialized for two different clients by default.
They can connect to the same game servers through different ports. However, if you connect to the wrong port,
it may currently still work all correctly, you'll just get warnings in the logs. However, as soon as
we change encryption keys or methods, this will change.
## Docker
Please take a look at the deploy-folder of this project. There you'll find a more
detailed guide about how to set up this project.
### Environment Variables
It's possible to define additional environment variables to influence the
postgres database connection strings.
| Name | Description |
|------|---------------------|
| DB_HOST | The hostname of the database. If the local configuration file is still configured to use 'localhost', the value of this variable replaces it |
| DB_ADMIN_USER | The user name of the postgres admin account. If the local configuration file is still configured to use 'postgres' for the user name of the admin (first entry in the ConnectionSettings.xml), the value of this variable replaces it. |
| DB_ADMIN_PW | The password of the postgres admin account. If the local configuration file is still configured to use 'admin' for the user password of the admin (first entry in the ConnectionSettings.xml), the value of this variable replaces it. |
## Manually
Use this way, if you want to develop or debug for OpenMU.
Requirements:
* Windows OS, 10 or higher
* It runs under Linux and MacOS, too. However, this guide describes it for
windows.
* PostgreSQL installed
* Visual Studio 2026 installed, with workloads for ASP.NET Web development
and .NET Desktop development. Please keep it up-to-date to prevent any issues.
* Visual Studio Extension "Web Compiler 2022+", if you plan to edit SCSS files
for the admin panel.
* https://marketplace.visualstudio.com/items?itemName=Failwyn.WebCompiler64
* [.NET SDK 10](https://dotnet.microsoft.com/download/dotnet/10.0)
(it should be included in Visual Studio 2026)
* `winget install Microsoft.DotNet.SDK.10`
* [NodeJS 16+](https://nodejs.org) installed
* `winget install OpenJS.NodeJS.LTS`
* This repository cloned
If you have that, you'll need to do:
* Open the solution of OpenMU with Visual Studio
* Right click the solution and 'Restore NuGet Packages'
* Edit src\Persistence\EntityFramework\ConnectionSettings.xml, so that the
connection strings are correct - however only the user/password of the first
and second connection string need to be correct. The server will try to create
the other roles specified by the settings.
* Build the solution
* Start MUnique.OpenMU.Startup
* If required, it will create the database schemas, the required roles and
gives permissions to this roles.
* Optional: You can reinitialize the database by adding a ```-reinit``` parameter.
* When the Admin Panel is initialized, go to <http://localhost/>. Then you
should see three gameservers, the chat server and two connect servers. Start
the connect servers and at least one gameserver.
* If you update to a newer state of the master-branch, it could be possible
that you have to update the database and configuration.
You can find updates in the admin panel.
* Then you can connect to the server through the game client.
## Helpful (optional) steps
* __Auto Start__: If you don't want to start each server listener after starting
the process, you can either activate "Auto Start"
* in the admin panel at ```Configuration -> System```,
* or with the start parameter ```-autostart```.
* __IP Resolving__: If you encounter disconnects after selecting a server, it's most
likely a wrong setting for the IP resolver. You can change it easily over the
admin panel at ```Configuration -> System``` as well.
You may also change the setting by providing start parameters or environment
parameters, however I just recommend this for experienced users. Look at this
[Readme](src/Startup/Readme.md) for more information.
* __Changing the game version__: If you want to player another version than
season 6, you may initialize the database with another game version.
You can do this over the admin panel as well, at the ```Setup``` page.
## Test Accounts
To test some features of the server, test accounts are created automatically
when the database is initialized.
These are the user names:
* test0 - test9: General test accounts, level 1 to 90, in 10 level steps
Season 6 only:
* test300: General test account with level 300
* test400: General test account with level 400, master characters
* testgm: Test account of a game master
* testgm2: Test account of a game master with summoner and rage fighter characters
* testunlock: Test account without characters, but unlocked character classes
* quest1: Test account for the level 150 quests
* quest2: Test account for the level 220 quests
* quest3: Test account for the level 400 quests
* ancient: Test account with ancient item sets, level 330 characters
* socket: Test account with socket item sets, level 380 characters
The __passwords__ of these accounts are the __same as the user name__.

131
README.md Normal file
View File

@@ -0,0 +1,131 @@
# OpenMU Project
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/d0f57e29e7524dadb677561389256d8b)](https://www.codacy.com/gh/MUnique/OpenMU/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=MUnique/OpenMU&amp;utm_campaign=Badge_Grade)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/MUnique/OpenMU)
[![Discord chat](https://img.shields.io/discord/669595902750490698?logo=discord)](https://discord.gg/2u5Agkd)
| Platform |Build Status |
|----------------|----------------------|
| Windows | ![Windows Build Status](https://dev.azure.com/MUnique/OpenMU/_apis/build/status/MUnique.OpenMU?branchName=master) |
| Linux (Docker) | [![Docker Build Status](https://dev.azure.com/MUnique/OpenMU/_apis/build/status/MUnique.OpenMU%20Docker?branchName=master)](https://hub.docker.com/r/munique/openmu) |
| NuGet Packages | |
|----------------|---|
| MUnique.OpenMU.Network | [![NuGet Badge](https://img.shields.io/nuget/v/MUnique.OpenMU.Network)](https://www.nuget.org/packages/MUnique.OpenMU.Network/) |
| MUnique.OpenMU.Network.Packets | [![NuGet Badge](https://img.shields.io/nuget/v/MUnique.OpenMU.Network.Packets)](https://www.nuget.org/packages/MUnique.OpenMU.Network.Packets/) |
This project aims to create an easy to use, extendable and customizable server
for a MMORPG called "MU Online".
The server supports multiple versions of the game, but the main focus is
version of Season 6 Episode 3 using the ENG (english) protocol. Additionally,
the long-term focus is on the [open source client](https://github.com/sven-n/MuMain)
which supports a slightly extended network protocol.
However, parts of the software can also be suitable for the development of
other games, even for other kind of games.
The code is a complete rewrite from scratch - it's not based on pre-existing
projects, and it's also explicitly not based on decompiled server sources or
their countless derivates.
There also exists a [blog](https://munique.net) which may contain some valuable
information about this development.
## Current project state
This project is currently under development without any release.
You can try the current state by using the available docker image, also
mentioned in the [quick start guide](QuickStart.md).
## Licensing
This project is released under the MIT license (see LICENSE file).
## Used technologies
The project is mainly written in C# and targets .NET 10.0.
The servers admin panel is hosted on an embedded ASP.NET Core webserver (Kestrel)
and implemented as Blazor Server App.
At the moment the persistence layer uses the [Entity Framework Core](https://github.com/aspnet/EntityFrameworkCore)
and [PostgreSQL](https://www.postgresql.org) as database. Additionally, it's
also possible to start it in a non-persistent in-memory mode.
The project is prepared to be hosted in a single process or distributed in multiple processes.
For the communication between the processes, we use [Dapr](https://dapr.io/).
## Deployment
We provide Docker images and docker-compose files for easy deployment.
Please take a look at the deploy-folder of this project.
## Contributions
Contributions are welcome if they meet the following criteria:
* Language is english.
* Code should be StyleCop compliant - this project uses the [StyleCop.Analyzers](https://www.nuget.org/packages/StyleCop.Analyzers/)
for VS2022 so you should see issues directly as warnings.
* Coding style (naming, etc.) and quality should fit to the current state.
* No code copied/converted from the well-known decompiled source of the
original server.
If you want to contribute, please create a new issue for the feature or bug (if
the issue doesn't exist yet) so we can see who is working on something and can
discuss possible solutions. If it's a small thing, you can also just send a
pull request without adding an issue.
Apart of that, contributions from non-developers are welcome as well. You can
test the server, submit issues or suggestions, packet descriptions or
documentations about the concepts and mechanics of the game itself. Please use
markdown files/syntax for this purpose.
If you have questions about that, don't hesitate to ask in our [discord channel](https://discord.gg/2u5Agkd)
or by submitting an issue.
## How to contribute code
If you want to contribute code, please do the following steps:
1. fork this project from the original MUnique OpenMU Project.
2. create a feature branch from the master branch
3. commit your changes to your feature branch
4. please test your changes, don't send AI generated code without testing it yourself
5. submit a pull request to the original master branch
6. wait for the code review and merge :)
## How to use
Please have a look at the [quick start guide](QuickStart.md).
## Gameplay differences to the original server
This project doesn't have the goal to copy the original MU Online server
behavior to 100 %. This is not entirely possible, because the original server
is written in another programming language and has a completely different
architecture.
With some points we make our life easier in this project, with other points we
try to improve the gameplay.
### Calculations
The calculations of attribute values (like character damage decrement etc.) are
done with 32 bit float numbers and without rounding off, like the original
server does at some places.
E.g. distributed stat points always have effect, while in the original server
effects might get rounded down. For example, when 4 points of strength gives 1
base damage, the original server doesn't calculate a fraction of 1 damage for
3 points, while OpenMU calculates 0.75 damage. This damage
has then an effect in further calculations.
### Countdown when changing character or sub-server
The original server uses a five second countdown when a player wants to change
his character or the sub-server. Maybe this was done for some performance
reasons, as the original server would then save the character/account data.
We think that's really annoying and see no real value in that, so we don't use
a countdown.

36
azure-pipelines.yml Normal file
View File

@@ -0,0 +1,36 @@
# ASP.NET Core
# Build and test ASP.NET Core projects targeting .NET Core.
# Add steps that run tests, create a NuGet package, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core
trigger:
- master
pool:
vmImage: 'windows-2022'
variables:
buildConfiguration: 'Release'
steps:
- task: UseDotNet@2
displayName: 'Install .NET Core SDK'
inputs:
version: 10.0.x
performMultiLevelLookup: true
includePreviewVersions: true # Required for preview versions
- script: dotnet restore
workingDirectory: src
displayName: 'dotnet restore'
- script: dotnet build --configuration $(buildConfiguration) -p:ci=true
workingDirectory: src
displayName: 'dotnet build $(buildConfiguration)'
- task: DotNetCoreCLI@2
displayName: 'dotnet test'
inputs:
command: test
projects: 'tests/*Tests/*.csproj'
arguments: '--configuration $(buildConfiguration) --no-build'

90
deploy/README.md Normal file
View File

@@ -0,0 +1,90 @@
# Deployment
The recommended way to deploy OpenMU is through Docker. Depending on the scale
you need, we have multiple ways to do that.
## All-in-one
The [all-in-one deployment](/all-in-one/) is recommended, if you want to host on
a small machine with a low amount of players.
In this case, all kinds of OpenMU subsystems (ConnectServer, GameServer, LoginServer,
AdminPanel, ...) are running in one process.
### Pros
* No communication overhead between subsystems, therefore slightly faster
* Simpler deployment
* Smaller memory footprint. Since we run all in one process, we don't have the
overhead of multiple processes, runtimes and can share data.
* Easier to observe and debug, no additional tools required
 
### Cons
* Harder to scale - only by scaling up your single machine
* Lower resiliency. If one subsystem crashes the process, the whole thing goes
down
* It's a more or less self-contained system which is harder to extend
## All-in-one with Traefik as Reverse Proxy
The [all-in-one with traefik deployment](/deploy/all-in-one-traefik/) is recommended,
if you want to host on a small machine with a low amount of players and want to host
your MuOnline Website on the same machine.
Once Traefik works as a Reverse Proxy, you can handle miltiple website without
change the default port to HTTP/HTTPS connections.
Addin a few labels to your container, you will tell Traefik how to handle incoming
requests and he will redirect to the correct website.
### Pros
* No communication overhead between subsystems, therefore slightly faster
* Simpler deployment
* Smaller memory footprint. Since we run all in one process, we don't have the
overhead of multiple processes, runtimes and can share data.
* Easier to observe and debug, no additional tools required
* You can have multi websites with auto renew SSL Certificates
* Expose only 80 and 443 ports for websites and admin panel.
Traefik knows what to do
 
### Cons
* Harder to scale - only by scaling up your single machine
* Lower resiliency. If one subsystem crashes the process, the whole thing goes
down
* It's a more or less self-contained system which is harder to extend
## Distributed
*!!! CURRENTLY BROKEN AND UNSUPPORTED, DOCS ARE OUT OF DATE !!!*
It's also possible to host OpenMU in a [distributed](/distributed/) way.
However, this introduces a lot more complexity and you should know what you're doing.
The communication between the subsystems is handled with Dapr.
Warnings:
* It's currently unsupported due to several issues we have to resolve first.
Feel free to contribute for the following issues: <https://github.com/MUnique/OpenMU/issues?q=is%3Aissue%20state%3Aopen%20label%3Adistributed-deployment>
* This way of hosting OpenMU is more complex and requires a good understanding
of distributed systems.
* This deployment requires more resources (CPU, RAM, Disk, Network).
### Pros
* Easier to scale. For example, if you need additional game servers you simply
add more containers.
* Higher resiliency. If one subsystem crashes, the others are not affected.
* It's easier to add more subsystems, even custom ones.
  For example, one could subscribe on already published events like guild messages
or letters.
Such a subsystem could forward messages to other systems (E-Mail, Discord, etc.).
### Cons
* Communication overhead between subsystems.
* Higher memory footprint, since we run multiple docker containers
  (each with their own .net runtime) which can't share some data.
* Harder to observe and debug. We added some stuff to compensate that (Loki,
Grafana, Prometheus, Zipkin), but they require additional resources, too.

View File

@@ -0,0 +1,25 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

View File

@@ -0,0 +1 @@
DOMAIN=domain.com

2
deploy/all-in-one-traefik/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
data-traefik/acme.json
.env

View File

@@ -0,0 +1,7 @@
# This file contains the passwords for the admin panel and tracing.
# Change these passwords in production!
# default is admin:openmu, hashed with bcrypt.
admin:$2y$10$xYL2d/QEukwGmX0uNZubsunL0qcANuTYkpapRVdlu5q3ymCpvOEh.
# this file should always end with an empty line!

View File

@@ -0,0 +1,140 @@
# All-in-one-traefik deployment
The all in one (with traefik) deployment is recommended, if you want to host
on a small machine with a low amount of players and want to host your
MuOnline Website on the same machine.
Once Traefik works as a Reverse Proxy, you can handle multiple websites
without changing the default port to HTTP/HTTPS connections.
Adding a few labels to your container, you will tell Traefik how to
handle incoming requests and it will redirect to the correct website.
```yaml
services:
admin-panel:
...
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.adm.entrypoints=websecure"
- "traefik.http.routers.adm.rule=Host(`admin.domain.com`)"
- "traefik.http.routers.adm.middlewares=auth"
- "traefik.http.middlewares.auth.basicauth.usersfile=.htpasswd"
muonline-website:
...
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.muonline.entrypoints=websecure"
- "traefik.http.routers.muonline.rule=Host(`muonline.domain.com`)"
```
You can even add multiple domains and/or subdomains to your host label
``` yaml
- "traefik.http.routers.muonline.rule=Host(`www.domain1.com`,`domain1.com`,`sub.domain1.com`)"
```
In this case, all kinds of OpenMU subsystems (ConnectServer, GameServer, LoginServer,
AdminPanel, ...) are running in one process, but you can run
AdminPanel and other websites separately.
## Deployment with docker-compose
### Install GIT
See [https://github.com/git-guides/install-git](https://github.com/git-guides/install-git).
### Clone the repository
``` bash
git clone https://github.com/MUnique/OpenMU.git
```
It will create a new folder OpenMU with the repository contents inside.
### Navigate to the docker-compose files
Navigate to the folder `deploy/all-in-one-traefik`
``` bash
cd deploy/all-in-one-traefik
```
### Create a new docker network
You need to have communication between containers from different docker compose files
``` bash
docker network create proxy
```
### Option A - for local testing
To run the official docker image on your local traefik server, just run:
``` bash
docker compose -f docker-compose.yml up -d
```
And that's it. It's then available on your local computer through a loopback
ip. Your AdminPanel URL is `http://admin.docker.localhost`. You can change it
in the `docker-compose.yml` file.
However, if you want to make it available through the internet, you should choose
Option B:
### Option B - with HTTPS
If you want to share your server with the world, it's recommended to set up HTTPS
and Traefik can handle it for you. Otherwise, traffic from and to the admin
panel is not encrypted.
#### Set your domain name as environment variable
Make a copy of `.env.example` called `.env` and edit the `.env` file with your
domain/subdomain to the AdminPanel URL.
``` bash
cp .env.example .env
```
Make a copy of `data-traefik/acme.example.json` called `data-traefik/acme.json`
and apply permission 600 to `acme.json`
``` bash
cp data-traefik/acme.example.json data-traefik/acme.json
chmod 600 data-traefik/acme.json
```
#### Run it
``` bash
docker compose -f docker-compose.prod.yml up -d
```
#### Important
Avoid editing the .htpasswd manually. Instead, access the admin panel
and add a new user. If you are using the _all-in-one-traefik_ you
need to restart Traefik after add a new user to it takes effect.
## What's next
The server is automatically started and initialized for Season 6. You can start
playing, if you want :-)
Additionally, take a look at the AdminPanel.
If your containers run on docker at your local machine, you can simply go to `http://admin.docker.localhost/`.
The default username is 'admin', the password 'openmu'. You should change that later.
If you want to run another game version, you can go to the setup page through
the navigation menu, where you can select your desired game version,
number of game servers (just the data of it), and if test accounts
should be created.
If you click on 'Install', wait a bit until the database is set up and
filled with the data and voila, OpenMU is ready to use.

View File

@@ -0,0 +1,26 @@
# Dynamic configuration
http:
middlewares:
nofloc:
headers:
customResponseHeaders:
Permissions-Policy: "interest-cohort=()"
secureHeaders:
headers:
sslRedirect: true
forceSTSHeader: true
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 31536000
tls:
options:
default:
cipherSuites:
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
minVersion: VersionTLS12

View File

@@ -0,0 +1,47 @@
api:
dashboard: true
entryPoints:
web:
address: :80
http:
redirections:
entryPoint:
to: websecure
websecure:
address: :443
http:
middlewares:
- secureHeaders@file
- nofloc@file
tls:
certResolver: letsencrypt
pilot:
dashboard: false
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
file:
filename: /configurations/dynamic.yml
certificatesResolvers:
letsencrypt:
acme:
email: admin@domain.com
storage: acme.json
keyType: EC384
httpChallenge:
entryPoint: web
buypass:
acme:
email: admin@domain.com
storage: acme.json
caServer: https://api.buypass.com/acme/directory
keyType: EC256
httpChallenge:
entryPoint: web

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" Sdk="Microsoft.Docker.Sdk">
<PropertyGroup Label="Globals">
<ProjectVersion>2.1</ProjectVersion>
<DockerTargetOS>Linux</DockerTargetOS>
<ProjectGuid>4430d6a8-54b5-412b-9dba-07b3e3580af7</ProjectGuid>
<DockerLaunchAction>None</DockerLaunchAction>
<DockerServiceUrl>{Scheme}://localhost:{ServicePort}</DockerServiceUrl>
</PropertyGroup>
<PropertyGroup>
<DockerServiceName>openmu-startup-traefik</DockerServiceName>
</PropertyGroup>
<ItemGroup>
<None Include=".env.example" />
<None Include="docker-compose.yml">
</None>
<None Include="docker-compose.prod.yml">
</None>
<None Include=".dockerignore" />
<None Include=".gitignore" />
<None Include=".htpasswd" />
<None Include="README.md" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,71 @@
services:
openmu-startup:
image: munique/openmu
container_name: openmu-startup
networks:
- proxy
ports:
- "55901:55901"
- "55902:55902"
- "55903:55903"
- "55904:55904"
- "55905:55905"
- "55906:55906"
- "44405:44405"
- "55980:55980"
environment:
DB_HOST: database
working_dir: /app/
volumes:
- ./.htpasswd:/etc/nginx/.htpasswd
depends_on:
- database
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.openmu.entrypoints=websecure"
- "traefik.http.routers.openmu.rule=Host(`${DOMAIN}`)"
- "traefik.http.routers.openmu.middlewares=auth"
- "traefik.http.middlewares.auth.basicauth.usersfile=.htpasswd"
database:
image: postgres
container_name: database
environment:
POSTGRES_PASSWORD: admin
POSTGRES_DB: openmu
POSTGRES_USER: postgres
networks:
- proxy
ports:
- "5432"
volumes:
- dbdata:/var/lib/postgresql #store data on volume
traefik:
image: traefik
container_name: traefik
restart: always
security_opt:
- no-new-privileges:true
ports:
- 80:80
- 443:443
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./data-traefik/traefik.yml:/traefik.yml:ro
- ./data-traefik/acme.json:/acme.json
- ./data-traefik/configurations:/configurations
- "./.htpasswd:/.htpasswd"
networks:
- proxy
labels:
- "traefik.docker.network=proxy"
networks:
proxy:
external: true
volumes:
dbdata:

View File

@@ -0,0 +1,68 @@
services:
openmu-startup:
image: munique/openmu
container_name: openmu-startup
networks:
- proxy
ports:
- "55901:55901"
- "55902:55902"
- "55903:55903"
- "55904:55904"
- "55905:55905"
- "55906:55906"
- "44405:44405"
- "55980:55980"
environment:
DB_HOST: database
working_dir: /app/
volumes:
- ./.htpasswd:/etc/nginx/.htpasswd
depends_on:
- database
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.openmu.entrypoints=web"
- "traefik.http.routers.openmu.rule=Host(`admin.docker.localhost`)"
- "traefik.http.routers.openmu.middlewares=auth"
- "traefik.http.middlewares.auth.basicauth.usersfile=.htpasswd"
database:
image: postgres
container_name: database
environment:
POSTGRES_PASSWORD: admin
POSTGRES_DB: openmu
POSTGRES_USER: postgres
networks:
- proxy
ports:
- "5432:5432"
volumes:
- dbdata:/var/lib/postgresql #store data on volume
traefik:
image: "traefik"
container_name: "traefik"
restart: always
command:
- "--log.level=DEBUG"
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
ports:
- "80:80"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./.htpasswd:/.htpasswd"
networks:
- proxy
networks:
proxy:
external: true
volumes:
dbdata:

View File

@@ -0,0 +1,25 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

View File

@@ -0,0 +1,7 @@
# This file contains the passwords for the admin panel and tracing.
# Change these passwords in production!
# default is admin:openmu, hashed with bcrypt.
admin:$2y$10$xYL2d/QEukwGmX0uNZubsunL0qcANuTYkpapRVdlu5q3ymCpvOEh.
# this file should always end with an empty line!

View File

@@ -0,0 +1,89 @@
# All-in-one deployment
The all in one deployment is recommended, if you want to host on a small machine
with a low amount of players.
In this case, all kinds of OpenMU subsystems (ConnectServer, GameServer, LoginServer,
AdminPanel, ...) are running in one process.
## Deployment with docker-compose
### Install GIT
See [https://github.com/git-guides/install-git](https://github.com/git-guides/install-git).
### Clone the repository
`git clone https://github.com/MUnique/OpenMU.git`
It will create a new folder OpenMU with the repository contents inside.
### Navigate to the docker-compose files
Navigate to the folder deploy/all-in-one
`cd deploy/all-in-one`
### Option A - for local testing
To use the official docker image, just run:
`docker compose up -d --no-build`
And that's it. It's then available on your local computer through a loopback ip.
However, if you want to make it available through the internet, you should choose
Option B:
### Option B - with HTTPS
If you want to share your server with the world, it's recommended to set up HTTPS
for nginx. Otherwise, traffic from and to the admin panel is not encrypted.
#### Set your domain name as environment variable
Specify the environment variable ```DOMAIN_NAME``` for docker compose.
This can be done in [various ways](https://docs.docker.com/compose/environment-variables/set-environment-variables/),
e.g. by editing the ```docker-compose.prod.yml``` or
[setting it in your shell](https://phoenixnap.com/kb/linux-set-environment-variable).
This variable is replaced in the nginx template config files which get included
in the other configuration files.
#### Run it
`docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d`
#### Run certbot explicitly
Hint: replace "example.org" with your domain.
`docker compose -f docker-compose.yml -f docker-compose.prod.yml run --rm certbot certonly --webroot --webroot-path /var/www/certbot/ -d example.org`
#### Set up certificate renewal
Because your certificates expire after 3 months, it's recommended to renew them regularly.
To renew it, run this command:
`docker compose -f docker-compose.yml -f docker-compose.prod.yml run --rm certbot renew`
Of course, it would make sense to add a cron job (e.g. once a week) on your host
machine for that.
## What's next
The server is automatically started and initialized for Season 6. You can start
playing, if you want :-)
Additionally, take a look at the AdminPanel.
If your containers run on docker at your local machine, you can simply go to `http://localhost/`.
The default username is 'admin', the password 'openmu'. You should change that later.
If you want to run another game version, you can go to the setup page through
the navigation menu, where you can select your desired game version,
number of game servers (just the data of it), and if test accounts
should be created.
If you click on 'Install', wait a bit until the database is set up and filled with the
data and voila, OpenMU is ready to use.

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" Sdk="Microsoft.Docker.Sdk">
<PropertyGroup Label="Globals">
<ProjectVersion>2.1</ProjectVersion>
<DockerTargetOS>Linux</DockerTargetOS>
<ProjectGuid>6518d58a-5f07-4341-87af-fcf3715d2554</ProjectGuid>
<DockerLaunchAction>None</DockerLaunchAction>
<DockerServiceUrl>{Scheme}://localhost:{ServicePort}</DockerServiceUrl>
</PropertyGroup>
<PropertyGroup>
<DockerServiceName>openmu-startup</DockerServiceName>
</PropertyGroup>
<ItemGroup>
<None Include="docker-compose.override.yml">
<DependentUpon>docker-compose.yml</DependentUpon>
</None>
<None Include="docker-compose.prod.yml">
<DependentUpon>docker-compose.yml</DependentUpon>
</None>
<None Include="docker-compose.yml" />
<None Include=".dockerignore" />
<None Include=".htpasswd" />
<None Include="nginx\nginx.dev.conf" />
<None Include="nginx\nginx.prod443.conf" />
<None Include="nginx\nginx.prod80.conf" />
<None Include="nginx\templates\nginx.prod.certificates.conf.template" />
<None Include="nginx\templates\nginx.server_name.conf.template" />
<None Include="README.md" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,13 @@
services:
openmu-startup:
build:
context: ../../src
dockerfile: Startup/Dockerfile
restart: "no"
ports:
- "8081:8080"
database:
ports:
- "5433:5432"

View File

@@ -0,0 +1,46 @@
services:
openmu-startup:
restart: "unless-stopped"
environment:
ASPNETCORE_ENVIRONMENT: Production
database:
restart: "unless-stopped"
nginx-80:
restart: "unless-stopped"
environment:
DOMAIN_NAME: ${DOMAIN_NAME}
volumes:
- ./nginx/nginx.prod80.conf:/etc/nginx/nginx.conf:ro
- ./.htpasswd:/etc/nginx/.htpasswd
- ./certbot/www:/var/www/certbot/:ro
- ./nginx/templates/nginx.server_name.conf.template:/etc/nginx/templates/nginx.server_name.conf.template:ro
# We add another nginx here, just for HTTPs.
# The reason is, that it's not starting when we combine port 80 and 443 until
# the certificates are created.
# So, when running the certbot the first time, we need to be able to access
# the port 80 - and we solve this by separating the server configs.
nginx-443:
image: nginx:alpine
container_name: nginx-443
restart: "unless-stopped"
environment:
DOMAIN_NAME: ${DOMAIN_NAME}
ports:
- "443:443"
volumes:
- ./nginx/nginx.prod443.conf:/etc/nginx/nginx.conf:ro
- ./.htpasswd:/etc/nginx/.htpasswd
- ./certbot/conf/:/etc/nginx/ssl/:ro
- ./nginx/templates/nginx.server_name.conf.template:/etc/nginx/templates/nginx.server_name.conf.template:ro
- ./nginx/templates/nginx.prod.certificates.conf.template:/etc/nginx/templates/nginx.prod.certificates.conf.template:ro
depends_on:
- openmu-startup
certbot:
image: certbot/certbot:latest
volumes:
- ./certbot/www/:/var/www/certbot/:rw
- ./certbot/conf/:/etc/letsencrypt/:rw

View File

@@ -0,0 +1,49 @@
services:
nginx-80:
image: nginx:alpine
container_name: nginx-80
ports:
- "80:80"
volumes:
- ./nginx/nginx.dev.conf:/etc/nginx/nginx.conf:ro
- ./.htpasswd:/etc/nginx/.htpasswd
depends_on:
- openmu-startup
openmu-startup:
image: munique/openmu
container_name: openmu-startup
ports:
- "8080"
- "55901:55901"
- "55902:55902"
- "55903:55903"
- "55904:55904"
- "55905:55905"
- "55906:55906"
- "44405:44405"
- "44406:44406"
- "55980:55980"
environment:
DB_HOST: database
ASPNETCORE_URLS: http://+:8080
working_dir: /app/
volumes:
- ./.htpasswd:/etc/nginx/.htpasswd
depends_on:
- database
database:
image: postgres
container_name: database
environment:
POSTGRES_PASSWORD: admin
POSTGRES_DB: openmu
POSTGRES_USER: postgres
ports:
- "5432"
volumes:
- dbdata:/var/lib/postgresql #store data on volume
volumes:
dbdata:

View File

@@ -0,0 +1,26 @@
events {
}
http {
# this is required to proxy Grafana Live WebSocket connections.
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
auth_basic "Protected Site";
auth_basic_user_file /etc/nginx/.htpasswd;
listen 80;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
resolver 127.0.0.11 ipv6=off;
location / {
proxy_pass http://openmu-startup:8080;
}
}
}

View File

@@ -0,0 +1,32 @@
events {
}
http {
# this is required to proxy Grafana Live WebSocket connections.
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 default_server ssl http2;
listen [::]:443 ssl http2;
include conf.d/nginx.server_name.conf;
include conf.d/nginx.prod.certificates.conf;
auth_basic "Protected Site";
auth_basic_user_file /etc/nginx/.htpasswd;
listen 80;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
resolver 127.0.0.11 ipv6=off;
location / {
proxy_pass http://openmu-startup:8080;
}
}
}

View File

@@ -0,0 +1,20 @@
events {
}
http {
server {
listen 80;
listen [::]:80;
include conf.d/nginx.server_name.conf;
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$server_name$request_uri;
}
}
}

View File

@@ -0,0 +1,2 @@
ssl_certificate /etc/nginx/ssl/live/$DOMAIN_NAME/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/live/$DOMAIN_NAME/privkey.pem;

View File

@@ -0,0 +1 @@
server_name $DOMAIN_NAME;

View File

@@ -0,0 +1,25 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

View File

@@ -0,0 +1,7 @@
# This file contains the passwords for the admin panel and tracing.
# Change these passwords in production!
# default is admin:openmu, hashed with bcrypt.
admin:$2y$10$xYL2d/QEukwGmX0uNZubsunL0qcANuTYkpapRVdlu5q3ymCpvOEh.
# this file should always end with an empty line!

View File

@@ -0,0 +1,121 @@
# Distributed
*!!! CURRENTLY BROKEN AND UNSUPPORTED, DOCS ARE OUT OF DATE !!!*
## Deployment with docker-compose
Currently, we just have a docker-compose file for the deployment.
docker-compose has the limitation, that all runs on the same physical machine.
For an even more distributed environment, with more machines, kubernetes can be
used. However, we don't have a finished configuration for kubernetes, yet. If you
are familiar with kubernetes, all contributions are welcome for kubernetes
configuration files.
So, these are the steps, if you want to deploy it with docker-compose:
### Install GIT
See [https://github.com/git-guides/install-git](https://github.com/git-guides/install-git).
### Clone the repository
`git clone https://github.com/MUnique/OpenMU.git`
It will create a new folder OpenMU with the repository contents inside.
### Navigate to the docker-compose files
Navigate to the folder deploy/distributed
`cd deploy/distributed`
### Option A - for local testing
To use the official docker images, just run:
`docker compose up -d --no-build`
And that's it. It's then available on your local computer through a loopback ip.
However, if you want to make it available through the internet, you should choose
Option B:
### Option B - with HTTPS
If you want to share your server with the world, it's recommended to set up HTTPS
for nginx. Otherwise, traffic from and to the admin panel is not encrypted.
#### Set your domain name as environment variable
Specify the environment variable ```DOMAIN_NAME``` for docker compose.
This can be done in [various ways](https://docs.docker.com/compose/environment-variables/set-environment-variables/),
e.g. by editing the ```docker-compose.prod.yml``` or
[setting it in your shell](https://phoenixnap.com/kb/linux-set-environment-variable).
This variable is replaced in the nginx template config files which get included
in the other configuration files.
#### Run it
`docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d`
#### Run certbot explicitly
Hint: replace "example.org" with your domain.
`docker compose -f docker-compose.yml -f docker-compose.prod.yml run --rm certbot certonly --webroot --webroot-path /var/www/certbot/ -d example.org`
#### Set up certificate renewal
Because your certificates expire after 3 months, it's recommended to renew them regularly.
To renew it, run this command:
`docker compose -f docker-compose.yml -f docker-compose.prod.yml run --rm certbot renew`
Of course, it would make sense to add a cron job (e.g. once a week) on your host
machine for that.
## What's next
Now, when you have deployed OpenMU, it's time to discover the AdminPanel.
If your containers run on docker at your local machine, you can simply go to `http://localhost/admin`.
The default username is 'admin', the password 'openmu'. You should change that later.
There you'll find a setup in the navigation menu, where you can select your desired
game version, number of game servers (just the data of it), and if test accounts
should be created.
Click on 'Install', wait a bit until the database is set up and filled with the
data.
OpenMU should now be ready to use.
## Environment variables
The openmu images which are used in this docker-compose consider the following
environment variables:
### ASPNETCORE_ENVIRONMENT
It's usually specified correctly in the docker-compose files. It has effect on
the ip resolver, see below.
### RESOLVE_IP
Similar to the -resolveIp starting parameter of the all-in-one startup project,
you're able to control the ip resolving with this variable. The defaults usually
work fine here, so you should try not to set this variable.
| Value | Description |
|-------|---------------------|
| local | Default value in a *Development* environment. Determines a local ip. If none is found, a loopback IP is used (127.127.127.127). |
| public | Default value in a *Production* environment. The public ip is automatically determined by an [external API](https://www.ipify.org/). |
| loopback | Returns *127.127.127.127*, usefully only if you run your server and client on the same machine. |
| [custom ip] | You can specify a custom ip, if needed. Example: *192.168.0.1* |
### GS_ID
It's usually specified correctly in the docker-compose files or each game server.
Specifies the id of a game server, and is used to retrieve the GameServerConfiguration
from the database.

View File

@@ -0,0 +1,11 @@
apiVersion: dapr.io/v1alpha1
kind: Configuration
metadata:
name: daprConfig
namespace: default
spec:
tracing:
enabled: true
samplingRate: "1"
zipkin:
endpointAddress: "http://zipkin:9411/api/v2/spans"

View File

@@ -0,0 +1,13 @@
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: secrets
namespace: default
spec:
type: secretstores.local.file
version: v1
metadata:
- name: secretsFile
value: components/secrets.json
- name: nestedSeparator
value: ":"

View File

@@ -0,0 +1,13 @@
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: login-state
spec:
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: redis-state:6379
#- name: ttlInSeconds
# value: <int> # Optional

View File

@@ -0,0 +1,20 @@
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub
spec:
type: pubsub.rabbitmq
version: v1
metadata:
- name: host
value: "amqp://rabbit:5672"
- name: durable
value: "true"
- name: deletedWhenUnused
value: "false"
- name: autoAck
value: "false"
- name: reconnectWait
value: "0"
- name: concurrency
value: parallel

View File

@@ -0,0 +1,11 @@
{
"connectionStrings": {
"MUnique.OpenMU.Persistence.EntityFramework.EntityDataContext": "Server=database;Port=5432;User Id=postgres;Password=admin;Database=openmu;Command Timeout=120;",
"MUnique.OpenMU.Persistence.EntityFramework.TypedContext": "Server=database;Port=5432;User Id=postgres;Password=admin;Database=openmu;Command Timeout=120;",
"MUnique.OpenMU.Persistence.EntityFramework.ConfigurationContext": "Server=database;Port=5432;User Id=config;Password=config;Database=openmu;Command Timeout=120;",
"MUnique.OpenMU.Persistence.EntityFramework.AccountContext": "Server=database;Port=5432;User Id=account;Password=account;Database=openmu;Command Timeout=120;",
"MUnique.OpenMU.Persistence.EntityFramework.TradeContext": "Server=database;Port=5432;User Id=account;Password=account;Database=openmu;Command Timeout=120;",
"MUnique.OpenMU.Persistence.EntityFramework.FriendContext": "Server=database;Port=5432;User Id=friend;Password=friend;Database=openmu;Command Timeout=120;",
"MUnique.OpenMU.Persistence.EntityFramework.GuildContext": "Server=database;Port=5432;User Id=guild;Password=guild;Database=openmu;Command Timeout=120;"
}
}

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" Sdk="Microsoft.Docker.Sdk">
<PropertyGroup Label="Globals">
<ProjectVersion>2.1</ProjectVersion>
<DockerTargetOS>Linux</DockerTargetOS>
<ProjectGuid>0dfecd55-7aa2-4263-b6fd-ec1c4e6a59b3</ProjectGuid>
<DockerLaunchAction>None</DockerLaunchAction>
<DockerServiceUrl>{Scheme}://localhost:{ServicePort}</DockerServiceUrl>
</PropertyGroup>
<PropertyGroup>
<DockerServiceName>openmu-distributed</DockerServiceName>
</PropertyGroup>
<ItemGroup>
<None Include="dapr-components\config.yaml" />
<None Include="dapr-components\localSecretStore.yaml" />
<None Include="dapr-components\login-state.yaml" />
<None Include="dapr-components\pubsub.yaml" />
<None Include="dapr-components\secrets.json" />
<None Include="docker-compose.override.yml">
<DependentUpon>docker-compose.yml</DependentUpon>
</None>
<None Include="docker-compose.prod.yml">
<DependentUpon>docker-compose.yml</DependentUpon>
</None>
<None Include="docker-compose.yml" />
<None Include=".dockerignore" />
<None Include=".htpasswd" />
<None Include="grafana.ini" />
<None Include="grafana_datasources.yaml" />
<None Include="loki-config.yaml" />
<None Include="nginx.prod.certificates.conf.template" />
<None Include="nginx.server_name.conf.template" />
<None Include="nginx.prod80.conf" />
<None Include="nginx.prod443.conf" />
<None Include="nginx.dev.conf" />
<None Include="prometheus.yml" />
<None Include="README.md" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,70 @@
services:
database:
ports:
- "5433:5432"
loki:
ports:
- "3100:3100"
prometheus:
ports:
- "9000:9090"
redis-state:
ports:
- "6379:6379"
gameServer0:
environment:
ASPNETCORE_ENVIRONMENT: Development
ports:
- "81:8080"
gameServer1:
environment:
ASPNETCORE_ENVIRONMENT: Development
ports:
- "82:8080"
connectServer:
build:
context: ../../src
dockerfile: Dapr/ConnectServer.Host/Dockerfile
environment:
ASPNETCORE_ENVIRONMENT: Development
loginServer:
build:
context: ../../src
dockerfile: Dapr/LoginServer.Host/Dockerfile
environment:
ASPNETCORE_ENVIRONMENT: Development
friendServer:
build:
context: ../../src
dockerfile: Dapr/FriendServer.Host/Dockerfile
environment:
ASPNETCORE_ENVIRONMENT: Development
guildServer:
build:
context: ../../src
dockerfile: Dapr/GuildServer.Host/Dockerfile
environment:
ASPNETCORE_ENVIRONMENT: Development
chatServer:
build:
context: ../../src
dockerfile: Dapr/ChatServer.Host/Dockerfile
environment:
ASPNETCORE_ENVIRONMENT: Development
adminPanel:
build:
context: ../../src
dockerfile: Dapr/AdminPanel.Host/Dockerfile
environment:
ASPNETCORE_ENVIRONMENT: Development
ports:
- "88:8080"

View File

@@ -0,0 +1,103 @@
volumes:
certificates:
certbot-www:
services:
nginx-80:
restart: "unless-stopped"
environment:
DOMAIN_NAME: ${DOMAIN_NAME}
volumes:
- ./nginx.server_name.conf.template:/etc/nginx/templates/nginx.server_name.conf.template
- ./nginx.prod80.conf:/etc/nginx/nginx.conf:ro
- ./.htpasswd:/etc/nginx/.htpasswd
- certbot-www:/var/www/certbot/:ro
# We add another nginx here, just for HTTPs.
# The reason is, that it's not starting when we combine port 80 and 443 until
# the certificates are created.
# So, when running the certbot the first time, we need to be able to access
# the port 80 - and we solve this by separating the server configs.
nginx-443:
image: nginx:alpine
container_name: nginx-443
restart: "unless-stopped"
ports:
- "443:443"
environment:
DOMAIN_NAME: ${DOMAIN_NAME}
volumes:
- ./nginx.server_name.conf.template:/etc/nginx/templates/nginx.server_name.conf.template
- ./nginx.prod.certificates.conf.template:/etc/nginx/templates/nginx.prod.certificates.conf.template
- ./nginx.prod443.conf:/etc/nginx/nginx.conf:ro
- ./.htpasswd:/etc/nginx/.htpasswd
- certificates:/etc/nginx/ssl/:ro
depends_on:
- grafana
- zipkin
- prometheus
certbot:
image: certbot/certbot:latest
volumes:
- certbot-www:/var/www/certbot/:rw
- certificates:/etc/letsencrypt/:rw
grafana:
restart: "unless-stopped"
prometheus:
restart: "unless-stopped"
loki:
restart: "unless-stopped"
minio:
restart: "unless-stopped"
redis-state:
restart: "unless-stopped"
dapr-placement:
restart: "unless-stopped"
rabbit:
restart: "unless-stopped"
database:
restart: "unless-stopped"
zipkin:
restart: "unless-stopped"
connectServer:
restart: "unless-stopped"
environment:
ASPNETCORE_ENVIRONMENT: Production
loginServer:
restart: "unless-stopped"
environment:
ASPNETCORE_ENVIRONMENT: Production
friendServer:
restart: "unless-stopped"
environment:
ASPNETCORE_ENVIRONMENT: Production
guildServer:
restart: "unless-stopped"
environment:
ASPNETCORE_ENVIRONMENT: Production
chatServer:
restart: "unless-stopped"
environment:
ASPNETCORE_ENVIRONMENT: Production
adminPanel:
restart: "unless-stopped"
environment:
ASPNETCORE_ENVIRONMENT: Production
gameServer0:
restart: "unless-stopped"
environment:
ASPNETCORE_ENVIRONMENT: Production
gameServer1:
restart: "unless-stopped"
environment:
ASPNETCORE_ENVIRONMENT: Production

View File

@@ -0,0 +1,352 @@
volumes:
dbdata:
prometheus-data:
minio-data:
services:
nginx-80:
image: nginx:alpine
container_name: nginx-80
restart: always
ports:
- "80:80"
volumes:
- ./nginx.dev.conf:/etc/nginx/nginx.conf
- ./.htpasswd:/etc/nginx/.htpasswd
depends_on:
- grafana
- zipkin
- prometheus
grafana:
image: grafana/grafana:9.5.14
container_name: grafana
volumes:
- ./grafana.ini:/etc/grafana/grafana.ini
- ./grafana_datasources.yaml:/etc/grafana/provisioning/datasources/ds.yaml
depends_on:
- loki
- prometheus
ports:
- 3000
prometheus:
image: prom/prometheus:v2.21.0
ports:
- 9090
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
command: --web.enable-lifecycle --config.file=/etc/prometheus/prometheus.yml
loki:
image: grafana/loki:2.5.0
container_name: loki
command: "-config.file=/etc/loki/config.yaml -target=all"
ports:
- 3100
- 7946
- 9095
volumes:
- ./loki-config.yaml:/etc/loki/config.yaml
depends_on:
- minio
minio:
image: minio/minio
entrypoint:
- sh
- -euc
- |
mkdir -p /data/loki-data && \
mkdir -p /data/loki-ruler && \
minio server /data
environment:
- MINIO_ACCESS_KEY=loki
- MINIO_SECRET_KEY=supersecret
- MINIO_PROMETHEUS_AUTH_TYPE=public
- MINIO_UPDATE=off
ports:
- 9000
volumes:
- minio-data:/data
redis-state:
image: redis
restart: always
ports:
- 6379
environment:
ALLOW_EMPTY_PASSWORD: "yes"
dapr-placement:
image: "daprio/dapr"
container_name: dapr_placement
command: ["./placement", "-port", "50006"]
ports:
- "50006"
rabbit:
image: rabbitmq:3-management-alpine
container_name: pubsub_rabbitmq
restart: always
ports:
- "5672"
database:
image: postgres
container_name: postgres
restart: always
environment:
POSTGRES_PASSWORD: admin
POSTGRES_DB: openmu
POSTGRES_USER: postgres
ports:
- "5432"
volumes:
- dbdata:/var/lib/postgresql
zipkin:
image: openzipkin/zipkin
container_name: tracing_zipkin
restart: always
ports:
- "9411:9411"
connectServer:
image: munique/openmu-connect
container_name: connectServer
ports:
- "50001"
- "44405:44405"
- "9464" # Prometheus
environment:
APPID: connectServer
ASPNETCORE_URLS: http://+:8080
depends_on:
- rabbit
- zipkin
- database
connectServer-dapr:
image: "daprio/daprd:latest"
container_name: connectServer_dapr
command: [ "./daprd", "-app-id", "connectServer", "-app-port", "8080",
"-components-path", "/components",
"-config", "/components/config.yaml",
"-placement-host-address", "dapr-placement:50006" ]
depends_on:
- connectServer
network_mode: "service:connectServer"
volumes:
- "./dapr-components/:/components"
loginServer:
image: munique/openmu-login
container_name: loginServer
ports:
- "50001"
- "9464" # Prometheus
environment:
ASPNETCORE_URLS: http://+:8080
depends_on:
- zipkin
- database
loginServer-dapr:
image: "daprio/daprd:latest"
container_name: loginServer_dapr
command: [ "./daprd", "-app-id", "loginServer", "-app-port", "8080",
"-components-path", "/components",
"-config", "/components/config.yaml",
"-placement-host-address", "dapr-placement:50006" ]
depends_on:
- loginServer
network_mode: "service:loginServer"
volumes:
- "./dapr-components/:/components"
friendServer:
image: munique/openmu-friend
container_name: friendServer
ports:
- "50001"
- "9464" # Prometheus
environment:
ASPNETCORE_URLS: http://+:8080
depends_on:
- chatServer-dapr
- database
- zipkin
friendServer-dapr:
image: "daprio/daprd:latest"
container_name: friendServer_dapr
command: [ "./daprd", "-app-id", "friendServer", "-app-port", "8080",
"-components-path", "/components",
"-config", "/components/config.yaml",
"-placement-host-address", "dapr-placement:50006" ]
depends_on:
- friendServer
network_mode: "service:friendServer"
volumes:
- "./dapr-components/:/components"
guildServer:
image: munique/openmu-guild
container_name: guildServer
ports:
- "50001"
- "9464" # Prometheus
environment:
ASPNETCORE_URLS: http://+:8080
depends_on:
- database
- zipkin
guildServer-dapr:
image: "daprio/daprd:latest"
container_name: guildServer_dapr
command: [ "./daprd", "-app-id", "guildServer", "-app-port", "8080",
"-components-path", "/components",
"-config", "/components/config.yaml",
"-placement-host-address", "dapr-placement:50006" ]
depends_on:
- guildServer
network_mode: "service:guildServer"
volumes:
- "./dapr-components/:/components"
chatServer:
image: munique/openmu-chat
container_name: chatServer
environment:
APPID: chatServer
ASPNETCORE_URLS: http://+:8080
ports:
- "55980:55980"
- "50001"
- "9464" # Prometheus
depends_on:
- database
- zipkin
chatServer-dapr:
image: "daprio/daprd:latest"
container_name: chatServer_dapr
command: [ "./daprd", "-app-id", "chatServer", "-app-port", "8080",
"-components-path", "/components",
"-config", "/components/config.yaml",
"-placement-host-address", "dapr-placement:50006" ]
depends_on:
- chatServer
network_mode: "service:chatServer"
volumes:
- "./dapr-components/:/components"
adminPanel:
image: munique/openmu-admin
container_name: adminPanel
depends_on:
- database
ports:
- "8080"
- "9464" # Prometheus
environment:
ASPNETCORE_URLS: http://+:8080
PATH_BASE: /admin/
volumes:
- ./.htpasswd:/etc/nginx/.htpasswd
adminPanel-dapr:
image: "daprio/daprd:latest"
container_name: adminPanel_dapr
command: [ "./daprd", "-app-id", "adminPanel", "-app-port", "8080",
"-components-path", "/components",
"-config", "/components/config.yaml",
"-placement-host-address", "dapr-placement:50006" ]
depends_on:
- adminPanel
network_mode: "service:adminPanel"
volumes:
- "./dapr-components/:/components"
gameServer0:
image: munique/openmu-game
container_name: gameServer0
build:
context: ../../src
dockerfile: Dapr/GameServer.Host/Dockerfile
ports:
- "8080"
- "50001"
- "55901:55901"
- "55902:55902"
- "9464" # Prometheus
environment:
GS_ID: 0
APPID: gameServer0
PATH_BASE: /gameServer/0/
ASPNETCORE_URLS: http://+:8080
depends_on:
- connectServer-dapr
- loginServer-dapr
- friendServer-dapr
- rabbit
- zipkin
- database
- loki
gameServer0-dapr:
image: "daprio/daprd:latest"
container_name: gameserver0_dapr
command: [ "./daprd", "-app-id", "gameServer0", "-app-port", "8080",
"-components-path", "/components",
"-config", "/components/config.yaml",
"-placement-host-address", "dapr-placement:50006" ]
depends_on:
- gameServer0
network_mode: "service:gameServer0"
volumes:
- "./dapr-components/:/components"
gameServer1:
image: munique/openmu-game
container_name: gameServer1
build:
context: ../../src
dockerfile: Dapr/GameServer.Host/Dockerfile
ports:
- "8080"
- "50001"
- "55903:55903"
- "55904:55904"
- "9464" # Prometheus
environment:
GS_ID: 1
APPID: gameServer1
PATH_BASE: /gameServer/1/
ASPNETCORE_URLS: http://+:8080
depends_on:
- connectServer-dapr
- loginServer-dapr
- friendServer-dapr
- rabbit
- zipkin
- database
- loki
gameServer1-dapr:
image: "daprio/daprd:latest"
container_name: gameserver1_dapr
command: [ "./daprd", "-app-id", "gameServer1", "-app-port", "8080",
"-components-path", "/components",
"-config", "/components/config.yaml",
"-placement-host-address", "dapr-placement:50006" ]
depends_on:
- gameServer1
network_mode: "service:gameServer1"
volumes:
- "./dapr-components/:/components"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,10 @@
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
url: http://loki:3100
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090

View File

@@ -0,0 +1,36 @@
{
"profiles": {
"Docker Compose": {
"commandName": "DockerCompose",
"commandVersion": "1.0",
"serviceActions": {
"connectServer": "StartDebugging",
"connectServer-dapr": "StartWithoutDebugging",
"dapr-placement": "StartWithoutDebugging",
"database": "StartWithoutDebugging",
"gameServer1": "StartDebugging",
"gameServer1-dapr": "StartWithoutDebugging",
"rabbit": "StartWithoutDebugging",
"friendServer": "StartDebugging",
"friendServer-dapr": "StartWithoutDebugging",
"loginServer": "StartDebugging",
"loginServer-dapr": "StartWithoutDebugging",
"guildServer": "StartDebugging",
"guildServer-dapr": "StartWithoutDebugging",
"chatServer": "StartDebugging",
"chatServer-dapr": "StartWithoutDebugging",
"adminPanel": "StartDebugging",
"adminPanel-dapr": "StartWithoutDebugging",
"grafana": "StartWithoutDebugging",
"loki": "StartWithoutDebugging",
"minio": "StartWithoutDebugging",
"nginx-80": "StartWithoutDebugging",
"prometheus": "StartWithoutDebugging",
"zipkin": "StartWithoutDebugging",
"gameServer0": "StartWithoutDebugging",
"gameServer0-dapr": "StartWithoutDebugging",
"redis-state": "StartWithoutDebugging"
}
}
}
}

View File

@@ -0,0 +1,36 @@
---
auth_enabled: false
server:
http_listen_port: 3100
memberlist:
join_members:
- loki:7946
schema_config:
configs:
- from: 2021-08-01
store: boltdb-shipper
object_store: s3
schema: v11
index:
prefix: index_
period: 24h
#limits_config:
# max_streams_per_user: 100000
common:
path_prefix: /loki
replication_factor: 1
storage:
s3:
endpoint: minio:9000
insecure: true
bucketnames: loki-data
access_key_id: loki
secret_access_key: supersecret
s3forcepathstyle: true
ring:
kvstore:
store: memberlist
ruler:
storage:
s3:
bucketnames: loki-ruler

View File

@@ -0,0 +1,65 @@
events {
}
http {
# this is required to proxy Grafana Live WebSocket connections.
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
auth_basic "Protected Site";
auth_basic_user_file /etc/nginx/.htpasswd;
listen 80;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
resolver 127.0.0.11 ipv6=off;
location /zipkin {
proxy_pass http://zipkin:9411/zipkin/;
}
location /grafana/ {
proxy_set_header Host $http_host;
proxy_pass http://grafana:3000/;
# Auth by nginx
proxy_set_header X-WEBAUTH-USER $remote_user;
proxy_set_header Authorization "";
}
# Proxy Grafana Live WebSocket connections.
location /grafana/api/live {
rewrite ^/grafana/(.*) /$1 break;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $http_host;
proxy_pass http://grafana:3000/;
# Auth by nginx
proxy_set_header X-WEBAUTH-USER $remote_user;
proxy_set_header Authorization "";
}
location ~ (/admin)(.*) {
proxy_pass http://adminPanel:8080/admin$2;
}
# Public API
location ~ (/serverInfo)(.*) {
proxy_pass http://connectServer:8080/serverInfo$2;
auth_basic off;
}
# Game Servers:
location ~ (/gameServer/(\d+)(.*)) {
proxy_pass http://gameServer$2:8080$1;
}
}
}

View File

@@ -0,0 +1,2 @@
ssl_certificate /etc/nginx/ssl/live/$DOMAIN_NAME/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/live/$DOMAIN_NAME/privkey.pem;

View File

@@ -0,0 +1,70 @@
events {
}
http {
# this is required to proxy Grafana Live WebSocket connections.
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 default_server ssl http2;
listen [::]:443 ssl http2;
include conf.d/nginx.server_name.conf;
include conf.d/nginx.prod.certificates.conf;
auth_basic "Protected Site";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
resolver 127.0.0.11 ipv6=off;
location /zipkin {
proxy_pass http://zipkin:9411/zipkin/;
}
location /grafana/ {
proxy_set_header Host $http_host;
proxy_pass http://grafana:3000/;
# Auth by nginx
proxy_set_header X-WEBAUTH-USER $remote_user;
proxy_set_header Authorization "";
}
# Proxy Grafana Live WebSocket connections.
location /grafana/api/live {
rewrite ^/grafana/(.*) /$1 break;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $http_host;
proxy_pass http://grafana:3000/;
# Auth by nginx
proxy_set_header X-WEBAUTH-USER $remote_user;
proxy_set_header Authorization "";
}
location ~ (/admin)(.*) {
proxy_pass http://adminPanel:8080/admin$2;
}
# Public API
location ~ (/serverInfo)(.*) {
proxy_pass http://connectServer:8080/serverInfo$2;
auth_basic off;
}
# Game Servers:
location ~ (/gameServer/(\d+)(.*)) {
proxy_pass http://gameServer$2:8080$1;
}
}
}

View File

@@ -0,0 +1,20 @@
events {
}
http {
server {
listen 80;
listen [::]:80;
include conf.d/nginx.server_name.conf;
server_tokens off;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$server_name$request_uri;
}
}
}

View File

@@ -0,0 +1 @@
server_name $DOMAIN_NAME;

View File

@@ -0,0 +1,14 @@
global:
scrape_interval: 10s
evaluation_interval: 10s
rule_files:
scrape_configs:
- job_name: prometheus
static_configs:
- targets: ['localhost:9090']
- job_name: openmu
static_configs:
- targets: ['gameServer1:9464', 'connectServer:9464', 'chatServer:9464']

13
dev.sh Normal file
View File

@@ -0,0 +1,13 @@
#!/bin/bash
function generate_certificates {
docker run --rm --entrypoint "" \
-v $PWD/certificates:/certificates \
mcr.microsoft.com/dotnet/sdk:5.0 \
sh -c "dotnet dev-certs https --clean && dotnet dev-certs https -ep /certificates/aspnetapp.pfx -p ${1}"
if [ $? == 0 ]; then
export CERTIFICATE_PASSWORD=${1}
echo "success"
fi
}

53
docs/GameMap.md Normal file
View File

@@ -0,0 +1,53 @@
# GameMap
I want to give a quick description about how the [game map](../src/GameLogic/GameMap.cs),
especially observing between players/npc works.
## AreaOfInterestManager
Each game map has an area of interest manager. It takes care of the event
subscriptions between objects, in case they were added, moved or removed at the
map.
The default implementation ([BucketAreaOfInterestManager](../src/GameLogic/BucketAreaOfInterestManager.cs))
creates a BucketMap, which is described below.
Other (simpler) implementations may be possible for the future for maps which
are more likely to be "empty" or require that all objects of a map need to know
all other objects (e.g. Duel map).
## BucketMap
The [bucket map](../src/GameLogic/BucketMap{T}.cs) is basically a two-dimensional
data structure.
We split the map of 256 x 256 possible coordinates into "[buckets](../src/GameLogic/Bucket{T}.cs)".
For example, each bucket covers 8 x 8 coordinates.
A player (or other "observers") can observe buckets, so that it gets informed
when objects (other players, npc, dropped items) enter or leave this bucket.
When a player moves and comes into the range of a bucket (defined by an
"info range"), it subscribes this enter/leave events and adds all existing
objects of this bucket to his view.
You may ask, why I introduced such a structure and not simply use a list of
objects and compare the distance to them when moving to decide if a object
should be sent (add/remove) to the game client.
As far as I know, the original server works like that and has a lot of lag when
a lot of players (more than thousand at castle siege) are on the same map, all
moving around.
This is a O(n²) problem where n is the number of objects on the same map - this
can get pretty complex very fast.
Now with my concept, this search is only happening when a player enters a new
bucket - and then it searches only for new buckets in range, not for other
individual objects.
So this is not as complex when you have many objects on your map. Of course,
this needs a bit more memory and is slower on empty maps, but IMHO that's
really worth it - usually, game maps are packed with monsters, players and a
lot of dropped items.
## Other ideas
Instead of partitioning a map into buckets (where a lot of them are empty), we
could try to use some other 2d index structures, like
[R-trees](https://en.wikipedia.org/wiki/R-tree) or [B-Trees](https://en.wikipedia.org/wiki/B-tree)
with [Z-Ordering](https://en.wikipedia.org/wiki/Z-order_curve).

116
docs/MasterSystem.md Normal file
View File

@@ -0,0 +1,116 @@
# Master System
## What is it?
When a character reached a certain level (usually level 400) and completes a quest,
its character class changes to a *master character class*.
This unlocks the *Master Skill Tree* which allows to distribute points to master
skills.
Points are given by each master level the player reaches (just like before, by
gaining experience).
## Types of master skills
Basically, there are two types of master skills in a skill tree:
### Passive skills
When these skills are learned, they give certain power-ups passively. For example,
there are master skills to increase the maximum health or to increase the
attack damage.
### Active skills
When learned, these skills appear in the skill list. Most skills of this type replace
previously existing skills. The replaced skills are staying in the background,
are not visible in the game client, but their internal value still apply to the
master skill as well.
The master skill just defines how much damage or buff power is added to the
replaced skill.
## Structure of the master skill tree
The master skill tree consists of three roots.
The skills are put into rows which defines the *rank* of a skill.
By default, there 5 ranks available. However, the game client would support up
to 9 ranks. Each skill usually can have up to 20 levels, some only have 10. The
client probably supports more than that.
A skill can be defined to depend on a skill of the same or the previous rank of
the same root.
Each character class is able to learn different skills, which are sometimes
exclusive to the character class. So, one skill can be available for multiple
character classes.
The root and rank for a skill is always the same for all character classes.
The visual appearance on the client may differ (more on that later).
## Requisitions of learning a skill
To learn a skill, the following checks are done by the server (and client).
### Character class
The skill must be defined for the class of the character.
### Rank
The skill must either be in the first rank, or a skill in the previous rank of
the same root must at least of level 10.
### Required Skill
If the skill has required skills defined (it's optional), these skills must be
at least of level 10.
## Client implementation
Some short notes about the client:
* There is a message for the master level etc. (F3 50)
* It contains health and mana, too. If this packet isn't sent, a master
character appears without health/mana.
* There is a message for the learned master skills (F3 53)
* The information about each skill contains:
* Skill Number
* Skill Index
* Defines were the skill is located in the clients interface
* I wonder why they need this in the message, since the client already
knows the index of a skill itself
* May differ between character classes
* Current Level
* Value of its effect at the current level
* Value of its effect at the next level
* Even if no skill was learned, this message needs to be sent - otherwise it
may still contain the master information about the previously played master
character.
## Server implementation
### Adding Points
* For code, see [AddMasterPointAction](https://github.com/MUnique/OpenMU/tree/master/src/GameLogic/PlayerActions/Character/AddMasterPointAction.cs).
* Request Packet: [C1F352 - Add Master Skill Point](Packets/C1-F3-52-AddMasterSkillPoint_by-client.md)
* Response Packet: [C1F352 - Master skill level update](Packets/C1-F3-52-MasterSkillLevelUpdate_by-server.md)
### Sending Master Stats (F3 50)
* For code, see [UpdateMasterStatsPlugIn](https://github.com/MUnique/OpenMU/tree/master/src/GameServer/RemoteView/Character/UpdateMasterStatsPlugIn.cs).
* Request Packet: [C2F350 - Master Stats Update](Packets/C1-F3-50-MasterStatsUpdate_by-server.md)
### Sending Master Skills (F3 53)
* For code, see [UpdateMasterSkillsPlugIn](https://github.com/MUnique/OpenMU/tree/master/src/GameServer/RemoteView/Character/UpdateMasterSkillsPlugIn.cs).
* Packet: [C2F353 - Master Skill List](Packets/C2-F3-53-MasterSkillList_by-server.md)

View File

@@ -0,0 +1,87 @@
# Packet Structure Tests Implementation
## Overview
This implementation provides automatically generated tests for packet structures defined in XML files. The tests validate that packet definitions are correct and would catch issues like incorrect packet lengths (as mentioned in PR #622).
## Files Created
### 1. `src/Network/Packets/GenerateTests.xslt`
XSLT transformation that generates C# test code from XML packet definitions. Features:
- Validates fixed-length packets/structures against their declared lengths
- Tests variable-length packets' GetRequiredSize methods
- Validates field boundaries to prevent buffer overruns
- Generates syntactically correct C# test code with proper naming
### 2. `tests/MUnique.OpenMU.Network.Packets.Tests/`
New test project that:
- Automatically generates test files during build (when `ci` is not set)
- Integrates with existing test infrastructure (NUnit, StyleCop, etc.)
- Added to main solution file for CI/CD integration
### 3. Test Files Generated
- `ClientToServerPacketTests.cs` - Tests for client-to-server packets
- `ServerToClientPacketTests.cs` - Tests for server-to-client packets
- `ChatServerPacketTests.cs` - Tests for chat server packets
- `ConnectServerPacketTests.cs` - Tests for connect server packets
## Types of Validation
### Fixed-Length Validation
```csharp
// Validates declared length matches calculated size
const int expectedLength = 20; // From XML
const int actualLength = PlayerShopItem.Length; // From generated struct
Assert.That(actualLength, Is.EqualTo(expectedLength));
// Validates field boundaries
Assert.That(fieldIndex + fieldSize, Is.LessThanOrEqualTo(expectedLength));
```
### Variable-Length Validation
```csharp
// Tests GetRequiredSize method accuracy
const string testString = "TestData";
var calculatedSize = StoredItem.GetRequiredSize(testString);
var expectedSize = Encoding.UTF8.GetByteCount(testString) + 1 + baseOffset;
Assert.That(calculatedSize, Is.EqualTo(expectedSize));
```
### Field Boundary Validation
```csharp
// Ensures fields don't exceed packet boundaries
Assert.That(fieldIndex + fieldSize, Is.LessThanOrEqualTo(packetLength));
```
## How It Works
1. **Build Process**: During build, XSLT transformations read XML packet definitions
2. **Code Generation**: Generates comprehensive test methods for each packet/structure
3. **Validation**: Tests run during normal test execution, catching definition errors
4. **Integration**: Fully integrated with existing CI/CD pipeline
## Benefits
- **Automatic Detection**: Catches packet definition errors at build time
- **Comprehensive Coverage**: Tests all packet types and structures
- **Zero Maintenance**: Tests are automatically updated when XML definitions change
- **Early Error Detection**: Prevents runtime issues from malformed packets
## Usage
The tests run automatically as part of the normal build and test process. No manual intervention required.
To run tests manually:
```bash
dotnet test tests/MUnique.OpenMU.Network.Packets.Tests/
```
## Example Issues Caught
The generated tests would catch issues like:
- Packet length declared as 10 but fields require 12 bytes
- Field starting at index 8 with size 4 in a 10-byte packet
- Incorrect GetRequiredSize calculations
- Overlapping field definitions
This addresses the core issue mentioned in PR #622 where packet structures were defined incorrectly.

154
docs/Packets/Appearance.md Normal file
View File

@@ -0,0 +1,154 @@
# Appearance
The appearance of characters is serialized as follows.
Webzen did a "good" job saving one bit here and there, so it's a bit complicated
sometimes (e.g. wings and pets).
It seems like the structure is historically grown.
## Structure
Please read this table like a stream of bits. E.g. if there are 8 bits of 1 byte
are specified in a row, the highest bits come first.
| Byte index | Length | Data type | Description |
|----------|---------|-------------|------------|
| 0 | 4 | bit | Character class |
| 0 | 4 | bit | Character pose, see below |
| 1 | 1 | byte | Left hand item index. 0xFF if empty. |
| 2 | 1 | byte | Right hand item index. 0xFF if empty. |
| 3 | 4 | bit | Helm item index (lower 4 bits). See byte index 9 and 13 for the rest. |
| 3 | 4 | bit | Armor item index (lower 4 bits). See byte index 9 and 14 for the rest. |
| 4 | 4 | bit | Pants item index (lower 4 bits). See byte index 9 and 14 for the rest. |
| 4 | 4 | bit | Gloves item index (lower 4 bits). See byte index 9 and 15 for the rest. |
| 5 | 4 | bit | Boots item index (lower 4 bits). See byte index 9 and 15 for the rest. |
| 5 | 4 | bit | Pets and wings flags, see below |
| 6 | 3 | bit | Left hand item level |
| 6 | 3 | bit | Right hand item level |
| 6~7 | 3 | bit | Helm item level |
| 7 | 3 | bit | Armor item level |
| 7 | 3 | bit | Pants item level |
| 7~8 | 3 | bit | Gloves item level |
| 8 | 3 | bit | Boots item level |
| 8 | 3 | bit | unused |
| 9 | 1 | bit | Helm item index (5th bit) |
| 9 | 1 | bit | Armor item index (5th bit) |
| 9 | 1 | bit | Pants item index (5th bit) |
| 9 | 1 | bit | Gloves item index (5th bit) |
| 9 | 1 | bit | Boots item index (5th bit) |
| 9 | 3 | bit | Wing item index (see table below) |
| 10 | 1 | bit | Helm excellent option flag |
| 10 | 1 | bit | Armor excellent option flag |
| 10 | 1 | bit | Pants excellent option flag |
| 10 | 1 | bit | Gloves excellent option flag |
| 10 | 1 | bit | Boots excellent option flag |
| 10 | 1 | bit | Left hand item excellent option flag |
| 10 | 1 | bit | Right hand item excellent option flag |
| 10 | 1 | bit | Dinorant flag |
| 11 | 1 | bit | Helm ancient option flag |
| 11 | 1 | bit | Armor ancient option flag |
| 11 | 1 | bit | Pants ancient option flag |
| 11 | 1 | bit | Gloves ancient option flag |
| 11 | 1 | bit | Boots ancient option flag |
| 11 | 1 | bit | Left hand item ancient option flag |
| 11 | 1 | bit | Right hand item ancient option flag |
| 11 | 1 | bit | Full ancient set flag |
| 12 | 3 | bit | Left hand item group. 111 = empty |
| 12 | 1 | bit | unused or empty flag? |
| 12 | 1 | bit | unused |
| 12 | 1 | bit | Fenrir flag |
| 12 | 1 | bit | unused |
| 12 | 1 | bit | Dark horse flag |
| 13 | 3 | bit | Right hand item group. 111 = empty |
| 13 | 1 | bit | unused or empty flag? |
| 13 | 4 | bit | Helm item index (6-9th bit). 0xF = empty |
| 14 | 4 | bit | Armor item index (6-9th bit). 0xF = empty |
| 14 | 4 | bit | Pants item index (6-9th bit). 0xF = empty |
| 15 | 4 | bit | Gloves item index (6-9th bit). 0xF = empty |
| 15 | 4 | bit | Boots item index (6-9th bit). 0xF = empty |
| 16 | 6 | bit | Pet item index, see below |
| 16 | 1 | bit | Blue fenrir flag |
| 16 | 1 | bit | Black fenrir flag |
| 17 | 4 | bit | Small Wing item index, see below |
| 17 | 3 | bit | unused |
| 17 | 1 | bit | Gold fenrir flag |
### Character pose
| Value | Meaning |
|-------|---------|
| 0 | Standing (default) |
| 1 | unused |
| 2 | Sitting (e.g. on a trunk) |
| 3 | Leaning (e.g. against a wall) |
| 4 | Hanging (at these strange things in Noria) |
Note: At Season 10 and above, the values are different.
Sitting is at 1, Leaning 2, Hanging 3. So, they removed the unused value.
### Item level calculation
The item levels are calculated with the following formula: ([Item Level] - 1) / 2.
This works until item level of 15, because it fits into 4 bits.
### Item indexes
The bytes are represented in binary format. X means the bit is used by something
else (see table above).
#### Pet items
| Item | 5th byte | 10th byte | 12th byte |
|-----------------------|----------|-----------|-----------|
| Guardian Angel | xxxxxx00 |
| Imp | xxxxxx01 |
| Unicorn | xxxxxx10 |
| Dinorant | xxxxxx11 | xxxxxxx1 ||
| Fenrir | xxxxxx11 | | xxxxxx1x |
| None | xxxxxx11 | xxxxxxx0 |
And some other pets:
| Item | 16th byte |
|-----------------------|-----------|
| Pet Panda | 111000xx |
| Pet Unicorn | 101000xx |
| Skeleton | 011000xx |
| Rudolph | 100000xx |
| Spirit of Guardian | 010000xx |
| Demon | 001000xx |
#### Wings
| Item | Character Class | 5th byte | 9th byte |
|-----------------------|-----------------|----------|----------|
| Wings of Elf | Fairy Elf | xxxx01xx | xxxxx001 |
| Wings of Heaven | Dark Wizard | xxxx01xx | xxxxx010 |
| Wings of Satan | Dark Knight | xxxx01xx | xxxxx011 |
| Wings of Mistery | Summoner | xxxx01xx | xxxxx100 |
| Wings of Spirit | Muse Elf | xxxx10xx | xxxxx001 |
| Wings of Soul | Soul Master | xxxx10xx | xxxxx010 |
| Wings of Dragon | Blade Knight | xxxx10xx | xxxxx011 |
| Wings of Darkness | Magic Gladiator | xxxx10xx | xxxxx100 |
| Cape of Lord | Dark Lord | xxxx10xx | xxxxx101 |
| Wings of Despair | Bloody Summoner | xxxx10xx | xxxxx110 |
| Cape of Fighter | Rage Fighter | xxxx10xx | xxxxx111 |
| Wing of Storm | Blade Master | xxxx11xx | xxxxx001 |
| Wing of Eternal | Grand Master | xxxx11xx | xxxxx010 |
| Wing of Illusion | High Elf | xxxx11xx | xxxxx011 |
| Wing of Ruin | Duel Master | xxxx11xx | xxxxx100 |
| Cape of Emperor | Lord Emperor | xxxx11xx | xxxxx101 |
| Wing of Dimension | Dimension Master| xxxx11xx | xxxxx110 |
| Cape of Overrule | Fist Master | xxxx11xx | xxxxx111 |
| None | | xxxx00xx | xxxxx000 |
#### Small Wings
| Wing Item | 5th byte | 17th byte |
|-----------------------|----------|-------|
| Small Cape of Lord | xxxx11xx | 0x20 |
| Small Wings of Mistery| xxxx11xx | 0x40 |
| Small Wings of Elf | xxxx11xx | 0x60 |
| Small Wings of Heaven | xxxx11xx | 0x80 |
| Small Wings of Satan | xxxx11xx | 0xA0 |
| Small Cloak of Warrior| xxxx11xx | 0xC0 |

View File

@@ -0,0 +1,18 @@
# C1 00 01 - Hello (by server)
## Is sent when
This packet is sent by the server after the client connected to the server.
## Causes the following actions on the client side
A game client will request the server list. The launcher would request the patch state.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 4 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x00 | Packet header - packet type identifier |
| 3 | 1 | Byte | 0x01 | Packet header - sub packet type identifier |

View File

@@ -0,0 +1,19 @@
# C1 00 - Authenticate (by client)
## Is sent when
This packet is sent by the client after it connected to the server, to authenticate itself.
## Causes the following actions on the server side
The server will check the token. If it's correct, the client gets added to the requested chat room.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 16 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x00 | Packet header - packet type identifier |
| 4 | 2 | ShortLittleEndian | | RoomId |
| 6 | 10 | Binary | | Token; A token (integer number), formatted as string and "encrypted" with the 3-byte XOR key (FC CF AB). |

View File

@@ -0,0 +1,29 @@
# C1 00 - ChatMessage (by server)
## Is sent when
A player sends a chat message.
## Causes the following actions on the client side
The message is shown in the chat box and above the character of the sender.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | | Packet header - length of the packet |
| 2 | 1 | Byte | 0x00 | Packet header - packet type identifier |
| 2 | 1 | ChatMessageType | | Type |
| 3 | 10 | String | | Sender |
| 13 | | String | | Message |
### ChatMessageType Enum
Defines the type of a chat message.
| Value | Name | Description |
|-------|------|-------------|
| 0 | Normal | The message is a normal chat message, e.g. public, within a party or guild. |
| 2 | Whisper | The message is sent privately to the receiving player. |

View File

@@ -0,0 +1,19 @@
# C1 00 - PublicChatMessage (by client)
## Is sent when
A player sends a public chat message.
## Causes the following actions on the server side
The message is forwarded to all surrounding players, including the sender.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | | Packet header - length of the packet |
| 2 | 1 | Byte | 0x00 | Packet header - packet type identifier |
| 3 | 10 | String | | Character |
| 13 | | String | | Message |

View File

@@ -0,0 +1,20 @@
# C1 01 00 - ChatRoomClientJoined (by server)
## Is sent when
This packet is sent by the server after another chat client joined the chat room.
## Causes the following actions on the client side
The client will add the client in its list (if over 2 clients are connected to the same room), or show its name in the title bar.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 15 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x01 | Packet header - packet type identifier |
| 3 | 1 | Byte | 0x00 | Packet header - sub packet type identifier |
| 4 | 1 | Byte | | ClientIndex |
| 5 | 10 | String | | Name |

View File

@@ -0,0 +1,20 @@
# C1 01 01 - ChatRoomClientLeft (by server)
## Is sent when
This packet is sent by the server after a chat client left the chat room.
## Causes the following actions on the client side
The client will remove the client from its list, or mark its name in the title bar as offline.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 15 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x01 | Packet header - packet type identifier |
| 3 | 1 | Byte | 0x01 | Packet header - sub packet type identifier |
| 4 | 1 | Byte | | ClientIndex |
| 5 | 10 | String | | Name |

View File

@@ -0,0 +1,17 @@
# C1 01 - LeaveChatRoom (by client)
## Is sent when
This packet is sent by the client when it leaves the chat room, before the connection closes.
## Causes the following actions on the server side
The server will remove the client from the chat room, notifying the remaining clients.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 3 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x01 | Packet header - packet type identifier |

View File

@@ -0,0 +1,19 @@
# C1 01 - ObjectMessage (by server)
## Is sent when
The server wants to show a message above any kind of character, even NPCs.
## Causes the following actions on the client side
The message is shown above the character.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | | Packet header - length of the packet |
| 2 | 1 | Byte | 0x01 | Packet header - packet type identifier |
| 3 | 2 | ShortBigEndian | | ObjectId |
| 5 | | String | | Message |

View File

@@ -0,0 +1,20 @@
# C1 02 - PatchCheckRequest (by client)
## Is sent when
This packet is sent by the client (launcher) to check if the patch version is high enough to be able to connect to the server.
## Causes the following actions on the server side
The connect server will check the version and sends a 'PatchVersionOkay' or a 'ClientNeedsPatch' message.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 6 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x02 | Packet header - packet type identifier |
| 3 | 1 | Byte | | MajorVersion |
| 4 | 1 | Byte | | MinorVersion |
| 5 | 1 | Byte | | PatchVersion |

View File

@@ -0,0 +1,17 @@
# C1 02 - PatchVersionOkay (by server)
## Is sent when
This packet is sent by the server after the client (launcher) requested the to check the patch version and it was high enough.
## Causes the following actions on the client side
The launcher will activate its start button.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 4 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x02 | Packet header - packet type identifier |

View File

@@ -0,0 +1,19 @@
# C1 02 - WhisperMessage (by client)
## Is sent when
A player sends a private chat message to a specific target player.
## Causes the following actions on the server side
The message is forwarded to the target player.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | | Packet header - length of the packet |
| 2 | 1 | Byte | 0x02 | Packet header - packet type identifier |
| 3 | 10 | String | | ReceiverName |
| 13 | | String | | Message |

View File

@@ -0,0 +1,20 @@
# C1 04 - ChatMessage
## Is sent when
This packet is sent by the server after another chat client sent a message to the current chat room.
## Causes the following actions on the client side
The client will show the message.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | | Packet header - length of the packet |
| 2 | 1 | Byte | 0x04 | Packet header - packet type identifier |
| 3 | 1 | Byte | | SenderIndex |
| 4 | 1 | Byte | | MessageLength |
| 5 | | Binary | | Message; The message. It's "encrypted" with the 3-byte XOR key (FC CF AB). |

View File

@@ -0,0 +1,20 @@
# C1 05 1 - ClientNeedsPatch (by server)
## Is sent when
This packet is sent by the server after the client (launcher) requested to check the patch version and it requires an update.
## Causes the following actions on the client side
The launcher will download the required patches and then activate the start button.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 138 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x05 | Packet header - packet type identifier |
| 3 | 1 | Byte | 0x1 | Packet header - sub packet type identifier |
| 4 | 1 | Byte | | PatchVersion |
| 6 | | String | | PatchAddress; The patch address, usually to a ftp server. The address is usually "encrypted" with the 3-byte XOR key (FC CF AB). |

View File

@@ -0,0 +1,17 @@
# C1 05 - KeepAlive (by client)
## Is sent when
This packet is sent by the client in a fixed interval.
## Causes the following actions on the server side
The server will keep the connection and chat room intact as long as the clients sends a message in a certain period of time.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 3 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x05 | Packet header - packet type identifier |

View File

@@ -0,0 +1,20 @@
# C1 07 - MagicEffectStatus (by server)
## Is sent when
A magic effect was added or removed to the own or another player.
## Causes the following actions on the client side
The user interface updates itself. If it's the effect of the own player, it's shown as icon at the top of the interface.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 7 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x07 | Packet header - packet type identifier |
| 3 | 1 | Boolean | | IsActive |
| 4 | 2 | ShortBigEndian | | PlayerId |
| 6 | 1 | Byte | | EffectId |

View File

@@ -0,0 +1,28 @@
# C1 0B - MapEventState (by server)
## Is sent when
The state of event is about to change.
## Causes the following actions on the client side
The event's effect is shown.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 5 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x0B | Packet header - packet type identifier |
| 3 | 1 | Boolean | | Enable |
| 4 | 1 | Events | | Event |
### Events Enum
Defines all events.
| Value | Name | Description |
|-------|------|-------------|
| 1 | RedDragon | Red dragon invasion. |
| 3 | GoldenDragon | Golden dragon invasion. |

View File

@@ -0,0 +1,29 @@
# C1 0D - ServerMessage (by server)
## Is sent when
## Causes the following actions on the client side
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | | Packet header - length of the packet |
| 2 | 1 | Byte | 0x0D | Packet header - packet type identifier |
| 3 | 1 | MessageType | | Type |
| 4 | | String | | Message |
### MessageType Enum
Defines a type of a server message.
| Value | Name | Description |
|-------|------|-------------|
| 0 | GoldenCenter | The message is shown as centered golden message in the client. |
| 1 | BlueNormal | The message is shown as a blue system message. |
| 2 | GuildNotice | The message is a guild notice, centered in green. |

View File

@@ -0,0 +1,19 @@
# C1 0F - WeatherStatusUpdate (by server)
## Is sent when
The weather on the current map has been changed or the player entered the map.
## Causes the following actions on the client side
The game client updates the weather effects.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 4 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x0F | Packet header - packet type identifier |
| 3 | 4 bit | Byte | | Weather; A random value between 0 and 2 (inclusive). |
| 3 | 4 bit | Byte | | Variation; A random value between 0 and 9 (inclusive). |

View File

@@ -0,0 +1,21 @@
# C1 10 - ObjectWalked075 (by server)
## Is sent when
An object in the observed scope (including the own player) walked to another position.
## Causes the following actions on the client side
The object is animated to walk to the new position.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 8 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x10 | Packet header - packet type identifier |
| 3 | 2 | ShortBigEndian | | ObjectId |
| 5 | 1 | Byte | | TargetX |
| 6 | 1 | Byte | | TargetY |
| 7 | 4 bit | Byte | | TargetRotation |

View File

@@ -0,0 +1,22 @@
# C1 10 - WalkRequest075 (by client)
## Is sent when
A player wants to walk on the game map.
## Causes the following actions on the server side
The player gets moved on the map, visible for other surrounding players.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | | Packet header - length of the packet |
| 2 | 1 | Byte | 0x10 | Packet header - packet type identifier |
| 3 | 1 | Byte | | SourceX |
| 4 | 1 | Byte | | SourceY |
| 5 | 4 bit | Byte | | StepCount |
| 5 | 4 bit | Byte | | TargetRotation |
| 6 | | Binary | | Directions; The directions of the walking path. The target is calculated by taking the source coordinates and applying the directions to it. |

View File

@@ -0,0 +1,20 @@
# C1 11 - HitRequest (by client)
## Is sent when
A player attacks a target without using a skill.
## Causes the following actions on the server side
Damage is calculated and the target is hit, if the attack was successful. A response is sent back with the caused damage, and all surrounding players get an animation message.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 7 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x11 | Packet header - packet type identifier |
| 3 | 2 | ShortBigEndian | | TargetId |
| 5 | 1 | Byte | | AttackAnimation |
| 6 | 1 | Byte | | LookingDirection |

View File

@@ -0,0 +1,42 @@
# C1 11 - ObjectHitExtended (by server)
## Is sent when
An object got hit in two cases: 1. When the own player is hit; 2. When the own player attacked some other object which got hit.
## Causes the following actions on the client side
The damage is shown at the object which received the hit.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 16 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x11 | Packet header - packet type identifier |
| 3 << 0 | 4 bit | DamageKind | | Kind |
| 3 << 4 | 1 bit | Boolean | | IsRageFighterStreakHit |
| 3 << 5 | 1 bit | Boolean | | IsRageFighterStreakFinalHit |
| 3 << 6 | 1 bit | Boolean | | IsDoubleDamage |
| 3 << 7 | 1 bit | Boolean | | IsTripleDamage |
| 4 | 2 | ShortLittleEndian | | ObjectId |
| 6 | 1 | Byte | | HealthStatus; Gets or sets the status of the remaining health in fractions of 1/250. |
| 7 | 1 | Byte | | ShieldStatus; Gets or sets the status of the remaining shield in fractions of 1/250. |
| 8 | 4 | IntegerLittleEndian | | HealthDamage |
| 12 | 4 | IntegerLittleEndian | | ShieldDamage |
### DamageKind Enum
Defines the kind of the damage.
| Value | Name | Description |
|-------|------|-------------|
| 0 | NormalRed | Red color, used by normal damage. |
| 1 | IgnoreDefenseCyan | Cyan color, usually used by ignore defense damage. |
| 2 | ExcellentLightGreen | Light green color, usually used by excellent damage. |
| 3 | CriticalBlue | Blue color, usually used by critical damage. |
| 4 | ReflectedLightPink | Light pink color, usually used by reflected damage. |
| 5 | PoisonDarkGreen | Dark green color, usually used by poison damage. |
| 6 | DarkPink | Dark pink color. |
| 7 | White | White color. |

View File

@@ -0,0 +1,41 @@
# C1 11 - ObjectHit (by server)
## Is sent when
An object got hit in two cases: 1. When the own player is hit; 2. When the own player attacked some other object which got hit.
## Causes the following actions on the client side
The damage is shown at the object which received the hit.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 10 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x11 | Packet header - packet type identifier |
| 2 | 1 | Byte | | HeaderCode |
| 3 | 2 | ShortBigEndian | | ObjectId |
| 5 | 2 | ShortBigEndian | | HealthDamage |
| 7 << 0 | 4 bit | DamageKind | | Kind |
| 7 << 4 | 1 bit | Boolean | | IsRageFighterStreakHit |
| 7 << 5 | 1 bit | Boolean | | IsRageFighterStreakFinalHit |
| 7 << 6 | 1 bit | Boolean | | IsDoubleDamage |
| 7 << 7 | 1 bit | Boolean | | IsTripleDamage |
| 8 | 2 | ShortBigEndian | | ShieldDamage |
### DamageKind Enum
Defines the kind of the damage.
| Value | Name | Description |
|-------|------|-------------|
| 0 | NormalRed | Red color, used by normal damage. |
| 1 | IgnoreDefenseCyan | Cyan color, usually used by ignore defense damage. |
| 2 | ExcellentLightGreen | Light green color, usually used by excellent damage. |
| 3 | CriticalBlue | Blue color, usually used by critical damage. |
| 4 | ReflectedLightPink | Light pink color, usually used by reflected damage. |
| 5 | PoisonDarkGreen | Dark green color, usually used by poison damage. |
| 6 | DarkPink | Dark pink color. |
| 7 | White | White color. |

View File

@@ -0,0 +1,29 @@
# C1 14 - MapObjectOutOfScope (by server)
## Is sent when
One or more objects (player, npc, etc.) on the map got out of scope, e.g. when the own player moved away from it/them or the object itself moved.
## Causes the following actions on the client side
The game client removes the objects from the game map.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | | Packet header - length of the packet |
| 2 | 1 | Byte | 0x14 | Packet header - packet type identifier |
| 3 | 1 | Byte | | ObjectCount |
| 4 | ObjectId.Length * ObjectCount | Array of ObjectId | | Objects |
### ObjectId Structure
Contains the id of a object.
Length: 2 Bytes
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 2 | ShortBigEndian | | Id |

View File

@@ -0,0 +1,19 @@
# C1 15 - InstantMoveRequest (by client)
## Is sent when
It's sent when the player performs specific skills.
## Causes the following actions on the server side
Usually, the player is moved instantly to the specified coordinates on the current map. In OpenMU, this request is not handled, because it allows hackers to "teleport" to any coordinates.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 5 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x15 | Packet header - packet type identifier |
| 3 | 1 | Byte | | TargetX |
| 4 | 1 | Byte | | TargetY |

View File

@@ -0,0 +1,21 @@
# C1 15 - ObjectMoved (by server)
## Is sent when
An object in the observed scope (including the own player) moved instantly.
## Causes the following actions on the client side
The position of the object is updated on client side.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 8 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x15 | Packet header - packet type identifier |
| 2 | 1 | Byte | | HeaderCode |
| 3 | 2 | ShortBigEndian | | ObjectId |
| 5 | 1 | Byte | | PositionX |
| 6 | 1 | Byte | | PositionY |

View File

@@ -0,0 +1,20 @@
# C1 17 - ObjectGotKilled (by server)
## Is sent when
An observed object was killed.
## Causes the following actions on the client side
The object is shown as dead.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 9 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x17 | Packet header - packet type identifier |
| 3 | 2 | ShortBigEndian | | KilledId |
| 5 | 2 | ShortBigEndian | | SkillId |
| 7 | 2 | ShortBigEndian | | KillerId |

View File

@@ -0,0 +1,19 @@
# C1 18 - AnimationRequest (by client)
## Is sent when
A player does any kind of animation.
## Causes the following actions on the server side
The animation number and rotation is forwarded to all surrounding players.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 5 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x18 | Packet header - packet type identifier |
| 3 | 1 | Byte | | Rotation |
| 4 | 1 | Byte | | AnimationNumber |

View File

@@ -0,0 +1,21 @@
# C1 18 - ObjectAnimation (by server)
## Is sent when
An object performs an animation.
## Causes the following actions on the client side
The animation is shown for the specified object.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 9 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x18 | Packet header - packet type identifier |
| 3 | 2 | ShortBigEndian | | ObjectId |
| 5 | 1 | Byte | | Direction |
| 6 | 1 | Byte | | Animation |
| 7 | 2 | ShortBigEndian | | TargetId |

View File

@@ -0,0 +1,21 @@
# C1 19 - SkillAnimation075 (by server)
## Is sent when
An object performs a skill which is directly targeted to another object.
## Causes the following actions on the client side
The animation is shown on the user interface.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 8 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x19 | Packet header - packet type identifier |
| 3 | 1 | Byte | | SkillId |
| 4 | 2 | ShortBigEndian | | PlayerId |
| 6 | 2 | ShortBigEndian | | TargetId |
| 6 << 7 | 1 bit | Boolean | | EffectApplied |

View File

@@ -0,0 +1,19 @@
# C1 19 - TargetedSkill075 (by client)
## Is sent when
A player performs a skill with a target, e.g. attacking or buffing.
## Causes the following actions on the server side
Damage is calculated and the target is hit, if the attack was successful. A response is sent back with the caused damage, and all surrounding players get an animation message.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 6 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x19 | Packet header - packet type identifier |
| 3 | 1 | Byte | | SkillIndex; The index of the skill in the skill list. |
| 4 | 2 | ShortBigEndian | | TargetId |

View File

@@ -0,0 +1,19 @@
# C1 1B - MagicEffectCancelRequest (by client)
## Is sent when
A player cancels a specific magic effect of a skill, usually 'Infinity Arrow' and 'Wizardy Enhance'.
## Causes the following actions on the server side
The effect is cancelled and an update is sent to the player and all surrounding players.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 7 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x1B | Packet header - packet type identifier |
| 3 | 2 | ShortBigEndian | | SkillId |
| 5 | 2 | ShortBigEndian | | PlayerId |

View File

@@ -0,0 +1,19 @@
# C1 1B - MagicEffectCancelled075 (by server)
## Is sent when
A player cancelled a specific magic effect of a skill (Infinity Arrow, Wizardry Enhance), or an effect was removed due a timeout (Ice, Poison) or antidote.
## Causes the following actions on the client side
The effect is removed from the target object.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 6 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x1B | Packet header - packet type identifier |
| 3 | 1 | Byte | | SkillId |
| 4 | 2 | ShortBigEndian | | TargetId |

View File

@@ -0,0 +1,19 @@
# C1 1B - MagicEffectCancelled (by server)
## Is sent when
A player cancelled a specific magic effect of a skill (Infinity Arrow, Wizardry Enhance), or an effect was removed due a timeout (Ice, Poison) or antidote.
## Causes the following actions on the client side
The effect is removed from the target object.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | 7 | Packet header - length of the packet |
| 2 | 1 | Byte | 0x1B | Packet header - packet type identifier |
| 3 | 2 | ShortBigEndian | | SkillId |
| 5 | 2 | ShortBigEndian | | TargetId |

View File

@@ -0,0 +1,32 @@
# C1 1D - AreaSkillHit075 (by client)
## Is sent when
An area skill was performed and the client decided to hit one or more targets.
## Causes the following actions on the server side
The server is calculating the damage and applying it to the targets. The attacker gets a response back with the caused damage.
## Structure
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 1 | Byte | 0xC1 | [Packet type](PacketTypes.md) |
| 1 | 1 | Byte | | Packet header - length of the packet |
| 2 | 1 | Byte | 0x1D | Packet header - packet type identifier |
| 3 | 1 | Byte | | SkillIndex; The index of the skill in the skill list. |
| 4 | 1 | Byte | | TargetX |
| 5 | 1 | Byte | | TargetY |
| 6 | 1 | Byte | | TargetCount; The number of targets which are hit. |
| 7 | TargetData.Length * TargetCount | Array of TargetData | | Targets |
### TargetData Structure
Contains the data of the target
Length: 2 Bytes
| Index | Length | Data Type | Value | Description |
|-------|--------|-----------|-------|-------------|
| 0 | 2 | ShortBigEndian | | TargetId |

Some files were not shown because too many files have changed in this diff Show More