Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aca976f667 | |||
| 80ae7531fe | |||
| be22de56ca | |||
| d7352b818d | |||
| 669b8d7455 | |||
| ee49b82f29 |
-257
@@ -1,257 +0,0 @@
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
#uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||
.pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# Ruff stuff:
|
||||
.ruff_cache/
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
# ---> C
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
*.ko
|
||||
*.obj
|
||||
*.elf
|
||||
|
||||
# Linker output
|
||||
*.ilk
|
||||
*.map
|
||||
*.exp
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.a
|
||||
*.la
|
||||
*.lo
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
|
||||
# Debug files
|
||||
*.dSYM/
|
||||
*.su
|
||||
*.idb
|
||||
*.pdb
|
||||
|
||||
# Kernel Module Compile Results
|
||||
*.mod*
|
||||
*.cmd
|
||||
.tmp_versions/
|
||||
modules.order
|
||||
Module.symvers
|
||||
Mkfile.old
|
||||
dkms.conf
|
||||
|
||||
# ---> Go
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
# env file
|
||||
.env
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
|
||||
- `hearthmod/` contains legacy orchestration scripts and original docs.
|
||||
- Core C services live in `hm_gameserver/`, `hm_lobbyserver/`, and shared code in `hm_base/`.
|
||||
- Web UI is in `hm_web/` (Python `web.py`, templates, and static assets).
|
||||
- Card rendering lives in `hm_sunwell/` (Node-based renderer).
|
||||
- TLS proxy and web server components live in `hm_stud/` and `hm_nginx/`.
|
||||
- Database snapshot/seed data is in `hm_database/`.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
|
||||
- `make -C hm_base target=game` builds the shared base library.
|
||||
- `make -C hm_gameserver` builds the gameserver binary.
|
||||
- `make -C hm_lobbyserver` builds the lobbyserver binary.
|
||||
- `bash hearthmod/host_ctl_ubuntu.sh start <ip>` starts the legacy stack.
|
||||
|
||||
When adding new workflows, prefer a single entrypoint script (e.g., `./dev`) or
|
||||
container orchestration; keep legacy scripts untouched unless required.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
|
||||
- C code uses 4-space indentation and snake_case identifiers.
|
||||
- Python uses 4-space indentation; prefer explicit names over abbreviations.
|
||||
- Avoid one-letter variable names except for simple loop counters.
|
||||
- Centralize ports and credentials in config or env variables.
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
- There is no dedicated test suite today.
|
||||
- If you add tests, keep them close to the component (e.g., `hm_web/tests/`).
|
||||
- Favor smoke tests that validate service ports and Couchbase connectivity.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
|
||||
- Git history is limited here; use concise imperative commit messages.
|
||||
- Pull requests should include a short summary and testing notes.
|
||||
- If you touch configuration or workflows, update the README.
|
||||
|
||||
## Modernization Roadmap (Maintainability)
|
||||
|
||||
The long-term goal is to replace components one at a time while keeping protocol
|
||||
compatibility. Suggested phases:
|
||||
|
||||
- Baseline: document current behavior and add logs/health checks.
|
||||
- Gateway: add a thin protocol gateway to route traffic to legacy services.
|
||||
- Lobby: reimplement lobby features behind the gateway with canary routing.
|
||||
- Game: reimplement game loop and compare state snapshots for parity.
|
||||
- Data: introduce a new data layer, mirror writes, then flip reads.
|
||||
- Decommission: remove unused legacy components once traffic is migrated.
|
||||
@@ -0,0 +1,64 @@
|
||||
Proposed Direction
|
||||
|
||||
- Single core language: Go for all server runtime (gateway + lobby + game logic).
|
||||
- Web/UI: TypeScript (Next.js or simple React/Vite) only.
|
||||
- Goal: Keep binary protocol compatibility via a Gateway while the Core Monolith evolves.
|
||||
|
||||
———
|
||||
|
||||
Architecture (Monolith First)
|
||||
|
||||
[Game Client] ⇄ [Go Gateway] ⇄ [Go Core Monolith]
|
||||
├─ Lobby module
|
||||
├─ Matchmaking module
|
||||
├─ Game loop module
|
||||
└─ Data access layer
|
||||
|
||||
- Gateway: speaks binary protocol; maps opcodes to internal RPC/handlers.
|
||||
- Core Monolith: simple Go service with clear module boundaries; no network protocol details here.
|
||||
- Data: Postgres + Redis (optional) behind a DAL.
|
||||
|
||||
———
|
||||
|
||||
Phase 0: Baseline Observability (immediate)
|
||||
|
||||
- Add request/session IDs at the lobby entrypoint.
|
||||
- Add JSON logs (opcode, account, session, latency).
|
||||
- Add packet capture/replay harness (proxy or gateway shim).
|
||||
|
||||
This sets up parity testing for any rewrite.
|
||||
|
||||
———
|
||||
|
||||
Phase 1: Go Gateway (binary protocol fidelity)
|
||||
|
||||
- Implement binary parsing and encoding in Go.
|
||||
- Map each opcode to a handler interface.
|
||||
- For now, proxy handlers to legacy C servers (so clients keep working).
|
||||
|
||||
This becomes the compatibility anchor.
|
||||
|
||||
———
|
||||
|
||||
Phase 2: Go Core Monolith (first rewrite)
|
||||
|
||||
- Implement core modules in Go and progressively cut traffic over.
|
||||
- Start with lobby/auth/deck APIs (lower risk).
|
||||
- Add game loop later with deterministic state checks.
|
||||
|
||||
———
|
||||
|
||||
Phase 3: Data Migration
|
||||
|
||||
- Introduce Postgres schemas.
|
||||
- Mirror writes (old → new), then flip reads.
|
||||
- Eventually remove Couchbase.
|
||||
|
||||
———
|
||||
|
||||
Why this fits your preferences
|
||||
|
||||
- Only Go + TS.
|
||||
- Monolith first with future separation if needed.
|
||||
- Fully off C while maintaining client compatibility.
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
# Protocol Notes
|
||||
|
||||
## Opcode Decoder Map (Client)
|
||||
|
||||
Source: `hs-client/Assembly-CSharp/ConnectAPI.cs`.
|
||||
|
||||
- `116` → `PongPacketDecoder`
|
||||
- `169` → `Deadend`
|
||||
- `167` → `DeadendUtil`
|
||||
- `123` → `DebugConsoleCommand`
|
||||
- `124` → `DebugConsoleResponse`
|
||||
- `14` → `AllOptions`
|
||||
- `5` → `DebugMessage`
|
||||
- `17` → `EntityChoices`
|
||||
- `13` → `EntitiesChosen`
|
||||
- `16` → `GameSetup`
|
||||
- `19` → `PowerHistory`
|
||||
- `15` → `UserUI`
|
||||
- `9` → `TurnTimer`
|
||||
- `10` → `NAckOption`
|
||||
- `12` → `GameCanceled`
|
||||
- `23` → `ServerResult`
|
||||
- `24` → `SpectatorNotify`
|
||||
- `289` → `Disconnected`
|
||||
- `202` → `DeckList`
|
||||
- `207` → `Collection`
|
||||
- `215` → `GetDeckContentsResponse`
|
||||
- `216` → `DBAction`
|
||||
- `217` → `DeckCreated`
|
||||
- `218` → `DeckDeleted`
|
||||
- `219` → `DeckRenamed`
|
||||
- `212` → `ProfileNotices`
|
||||
- `224` → `BoosterList`
|
||||
- `226` → `BoosterContent`
|
||||
- `208` → `GamesInfo`
|
||||
- `231` → `ProfileDeckLimit`
|
||||
- `262` → `ArcaneDustBalance`
|
||||
- `278` → `GoldBalance`
|
||||
- `233` → `ProfileProgress`
|
||||
- `270` → `PlayerRecords`
|
||||
- `271` → `RewardProgress`
|
||||
- `232` → `MedalInfo`
|
||||
- `241` → `ClientOptions`
|
||||
- `246` → `DraftBeginning`
|
||||
- `247` → `DraftRetired`
|
||||
- `248` → `DraftChoicesAndContents`
|
||||
- `249` → `DraftChosen`
|
||||
- `288` → `DraftRewardsAcked`
|
||||
- `251` → `DraftError`
|
||||
- `252` → `Achieves`
|
||||
- `285` → `ValidateAchieveResponse`
|
||||
- `282` → `CancelQuestResponse`
|
||||
- `264` → `GuardianVars`
|
||||
- `260` → `CardValues`
|
||||
- `258` → `BoughtSoldCard`
|
||||
- `269` → `MassDisenchantResponse`
|
||||
- `265` → `BattlePayStatusResponse`
|
||||
- `295` → `ThirdPartyPurchaseStatusResponse`
|
||||
- `272` → `PurchaseMethod`
|
||||
- `275` → `CancelPurchaseResponse`
|
||||
- `256` → `PurchaseResponse`
|
||||
- `238` → `BattlePayConfigResponse`
|
||||
- `280` → `PurchaseWithGoldResponse`
|
||||
- `283` → `HeroXP`
|
||||
- `254` → `NoOpPacketDecoder`
|
||||
- `286` → `PlayQueue`
|
||||
- `330` → `CheckAccountLicensesResponse`
|
||||
- `331` → `CheckGameLicensesResponse`
|
||||
- `236` → `CardBacks`
|
||||
- `292` → `SetCardBackResponse`
|
||||
- `296` → `SetProgressResponse`
|
||||
- `299` → `TriggerEventResponse`
|
||||
- `300` → `NotSoMassiveLoginReply`
|
||||
- `304` → `AssetsVersionResponse`
|
||||
- `306` → `AdventureProgressResponse`
|
||||
- `307` → `UpdateLoginComplete`
|
||||
- `311` → `AccountLicenseAchieveResponse`
|
||||
- `315` → `SubscribeResponse`
|
||||
- `316` → `TavernBrawlInfo`
|
||||
- `317` → `TavernBrawlPlayerRecordResponse`
|
||||
- `318` → `FavoriteHeroesResponse`
|
||||
- `320` → `SetFavoriteHeroResponse`
|
||||
- `324` → `DebugCommandResponse`
|
||||
- `325` → `AccountLicensesInfoResponse`
|
||||
- `326` → `GenericResponse`
|
||||
- `328` → `ClientRequestResponse`
|
||||
- `322` → `GetAssetResponse`
|
||||
|
||||
## Opcode Encoder Map (Client → Server)
|
||||
|
||||
Game server outbound (QueueGamePacket):
|
||||
|
||||
- `22` → `SpectatorHandshake`
|
||||
- `168` → `Handshake`
|
||||
- `1` → `GetGameState`
|
||||
- `115` → `Ping`
|
||||
- `11` → `Concede`
|
||||
- `3` → `ChooseEntities`
|
||||
- `2` → `ChooseOption`
|
||||
- `15` → `UserUI` (emote + mouse)
|
||||
- `25` → `InviteToSpectate`
|
||||
- `26` → `RemoveSpectators`
|
||||
- `123` → `DebugConsoleCommand`
|
||||
|
||||
Debug console outbound (QueueDebugPacket):
|
||||
|
||||
- `124` → `DebugConsoleResponse`
|
||||
|
||||
Util server outbound (ClientRequestManager/UtilOutbound):
|
||||
|
||||
- `319` → `SetFavoriteHero`
|
||||
- `279` → `PurchaseWithGold`
|
||||
- `312` → `StartThirdPartyPurchase`
|
||||
- `293` → `SubmitThirdPartyReceipt`
|
||||
- `294` → `GetThirdPartyPurchaseStatus`
|
||||
- `250` → `GetPurchaseMethod`
|
||||
- `273` → `DoPurchase`
|
||||
- `274` → `CancelPurchase`
|
||||
- `237` → `GetBattlePayConfig`
|
||||
- `255` → `GetBattlePayStatus`
|
||||
- `268` → `MassDisenchantRequest`
|
||||
- `235` → `DraftBegin`
|
||||
- `242` → `DraftRetire`
|
||||
- `287` → `DraftAckRewards`
|
||||
- `244` → `DraftGetPicksAndContents`
|
||||
- `245` → `DraftMakePick`
|
||||
- `201` → `GetAccountInfo`
|
||||
- `327` → `GenericRequestList`
|
||||
- `205` → `UpdateLogin`
|
||||
- `214` → `GetDeckContents`
|
||||
- `209` → `CreateDeck`
|
||||
- `210` → `DeleteDeck`
|
||||
- `211` → `RenameDeck`
|
||||
- `332` → `DeckSetTemplateSource`
|
||||
- `222` → `DeckSetData`
|
||||
- `213` → `AckNotice`
|
||||
- `225` → `OpenBooster`
|
||||
- `230` → `SetProgress`
|
||||
- `298` → `TriggerLaunchDayEvent`
|
||||
- `303` → `GetAssetsVersion`
|
||||
- `308` → `AckWingProgress`
|
||||
- `309` → `AcknowledgeBanner`
|
||||
- `310` → `SetAdventureOptions`
|
||||
- `223` → `AckCardSeen`
|
||||
- `240` → `GetOptions`
|
||||
- `239` → `SetOptions`
|
||||
- `253` → `GetAchieves`
|
||||
- `284` → `ValidateAchieve`
|
||||
- `281` → `CancelQuest`
|
||||
- `243` → `AckAchieveProgress`
|
||||
- `297` → `CheckAccountLicenseAchieve`
|
||||
- `305` → `GetAdventureProgress`
|
||||
- `257` → `BuySellCard`
|
||||
- `267` → `CheckAccountLicenses`
|
||||
- `276` → `CheckGameLicenses`
|
||||
- `291` → `SetCardBack`
|
||||
- `321` → `GetAssetRequest`
|
||||
- `329` → `Unsubscribe`
|
||||
- `322` → `DebugCommandRequest`
|
||||
|
||||
## Learnings
|
||||
|
||||
- Client maintains deferred response maps for async requests in `Network.cs`.
|
||||
- Opcode map is a key compatibility anchor for the Go gateway.
|
||||
|
||||
## Protobuf Definitions (Source)
|
||||
|
||||
The repo does not contain the protobuf class definitions for most message
|
||||
types. They live in the client assemblies that were not decompiled here.
|
||||
To generate Go structs, extract `.proto` from the client install:
|
||||
|
||||
- `PegasusGame.dll` (game server packets)
|
||||
- `PegasusUtil.dll` (utility/account/collection packets)
|
||||
- `SpectatorProto.dll` (spectator packets)
|
||||
- `BobNetProto.dll` (misc/legacy)
|
||||
- `PegasusShared.dll` (shared types: `CardDef`, `BnetId`, enums)
|
||||
|
||||
Suggested mapping (verify by decompiling those DLLs):
|
||||
|
||||
- `PegasusGame`: `GameSetup`, `PowerHistory`, `EntityChoices`, `EntitiesChosen`,
|
||||
`UserUI`, `TurnTimer`, `NAckOption`, `GameCanceled`, `ServerResult`,
|
||||
`Disconnected`, `Handshake`, `GetGameState`, `Ping`, `Concede`,
|
||||
`ChooseEntities`, `ChooseOption`.
|
||||
- `SpectatorProto`: `SpectatorHandshake`, `SpectatorNotify`, `InviteToSpectate`,
|
||||
`RemoveSpectators`.
|
||||
- `PegasusUtil`: everything in the decoder/encoder maps that relates to
|
||||
collection, login, decks, purchases, achievements, and assets (most of the
|
||||
`UtilOutbound` messages).
|
||||
- `BobNetProto`: `Deadend`, `DeadendUtil` (verify).
|
||||
@@ -1,2 +1,77 @@
|
||||
# hsmod
|
||||
# Hearthmod Modernization Notes
|
||||
|
||||
This workspace contains the Hearthmod stack (game server, lobby server, web UI,
|
||||
and supporting components). The current goal is to stabilize the development
|
||||
environment and improve maintainability without changing client protocol
|
||||
compatibility.
|
||||
|
||||
## Component-by-Component Replacement Plan
|
||||
|
||||
Replacing services incrementally is the safest path. It preserves existing
|
||||
behavior while enabling modernization behind compatibility boundaries.
|
||||
|
||||
1. **Baseline and observability**: document startup order, ports, and data flows;
|
||||
add basic health checks and structured logs.
|
||||
2. **Gateway layer**: introduce a thin gateway that speaks the legacy protocol
|
||||
and routes to existing lobby/game services.
|
||||
3. **Lobby replacement**: implement lobby endpoints behind the gateway, canary
|
||||
traffic to validate parity.
|
||||
4. **Game server replacement**: implement the game loop and compare deterministic
|
||||
state snapshots with the legacy server before shifting traffic.
|
||||
5. **Data layer swap**: add a DAL in new services, mirror writes to a new store,
|
||||
then flip reads after validation.
|
||||
6. **Decommission legacy**: remove unused services and dependencies once traffic
|
||||
is fully migrated.
|
||||
|
||||
## Maintainability Priorities
|
||||
|
||||
- Keep configuration centralized (env or config files).
|
||||
- Prefer a single entrypoint script or container orchestration for local dev.
|
||||
- Add small smoke tests to validate service health and database connectivity.
|
||||
|
||||
## Local Dev (C Services)
|
||||
|
||||
If Couchbase is running on `localhost:8091` with bucket `hbs` (password `aci`),
|
||||
use the repo-local entrypoint to build and run the core servers:
|
||||
|
||||
```sh
|
||||
./dev build
|
||||
./dev start
|
||||
./dev logs
|
||||
```
|
||||
|
||||
Override targets via env vars:
|
||||
|
||||
```sh
|
||||
HM_GAMESERVER_IP=127.0.0.1 HM_GAMESERVER_PORT=3724 ./dev start
|
||||
```
|
||||
|
||||
Stop with:
|
||||
|
||||
```sh
|
||||
./dev stop
|
||||
```
|
||||
|
||||
For the full Ubuntu install flow (Couchbase + web + client), use
|
||||
`hearthmod/host_ctl_ubuntu.sh`.
|
||||
|
||||
## Docker Dev
|
||||
|
||||
Docker runs Couchbase plus the C servers. Start with:
|
||||
|
||||
```sh
|
||||
./dev docker-build
|
||||
./dev docker-start
|
||||
./dev docker-logs
|
||||
```
|
||||
|
||||
Stop with:
|
||||
|
||||
```sh
|
||||
./dev docker-stop
|
||||
```
|
||||
|
||||
## Protocol Notes
|
||||
|
||||
See `PROTOCOL-NOTES.md` for the current opcode decoder map and client-derived
|
||||
learnings used for compatibility work.
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
|
||||
LOG_DIR=${HM_LOG_DIR:-"$ROOT_DIR/hm_log"}
|
||||
GAMESERVER_IP=${HM_GAMESERVER_IP:-"127.0.0.1"}
|
||||
GAMESERVER_PORT=${HM_GAMESERVER_PORT:-"3724"}
|
||||
|
||||
GAMESERVER_BIN="$ROOT_DIR/hm_gameserver/hm_gameserver"
|
||||
LOBBYSERVER_BIN="$ROOT_DIR/hm_lobbyserver/hm_lobbyserver"
|
||||
|
||||
GAMESERVER_PID="$LOG_DIR/hm_gameserver.pid"
|
||||
LOBBYSERVER_PID="$LOG_DIR/hm_lobbyserver.pid"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: ./dev <command>
|
||||
|
||||
Commands:
|
||||
build Build gameserver and lobbyserver
|
||||
start Start gameserver and lobbyserver
|
||||
stop Stop running servers
|
||||
status Show server status
|
||||
logs Tail server logs
|
||||
docker-build Build docker images
|
||||
docker-start Start dockerized stack
|
||||
docker-stop Stop dockerized stack
|
||||
docker-status Show docker stack status
|
||||
docker-logs Tail dockerized logs
|
||||
|
||||
Environment:
|
||||
HM_LOG_DIR Log directory (default: ./hm_log)
|
||||
HM_GAMESERVER_IP Lobby points to this IP (default: 127.0.0.1)
|
||||
HM_GAMESERVER_PORT Lobby points to this port (default: 3724)
|
||||
HM_SKIP_COUCHBASE_CHECK=1 Skip Couchbase availability check
|
||||
EOF
|
||||
}
|
||||
|
||||
docker_compose() {
|
||||
if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
|
||||
docker compose "$@"
|
||||
return
|
||||
fi
|
||||
|
||||
if command -v docker-compose >/dev/null 2>&1; then
|
||||
docker-compose "$@"
|
||||
return
|
||||
fi
|
||||
|
||||
echo "docker compose not found" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
require_bin() {
|
||||
if ! command -v "$1" >/dev/null 2>&1; then
|
||||
echo "Missing required binary: $1" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_couchbase() {
|
||||
if [ "${HM_SKIP_COUCHBASE_CHECK:-}" = "1" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
if ! curl -s --max-time 2 http://localhost:8091/pools >/dev/null; then
|
||||
echo "Couchbase not reachable on localhost:8091." >&2
|
||||
echo "Start Couchbase and restore bucket 'hbs' or set HM_SKIP_COUCHBASE_CHECK=1." >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "curl not found; cannot verify Couchbase availability." >&2
|
||||
echo "Install curl or set HM_SKIP_COUCHBASE_CHECK=1." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
build() {
|
||||
require_bin make
|
||||
make -C "$ROOT_DIR/hm_gameserver"
|
||||
make -C "$ROOT_DIR/hm_lobbyserver"
|
||||
}
|
||||
|
||||
start() {
|
||||
check_couchbase
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
if [ ! -x "$GAMESERVER_BIN" ] || [ ! -x "$LOBBYSERVER_BIN" ]; then
|
||||
echo "Binaries missing; run ./dev build first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "$GAMESERVER_PID" ] || [ -f "$LOBBYSERVER_PID" ]; then
|
||||
echo "PID files exist; run ./dev stop first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
nohup "$GAMESERVER_BIN" > "$LOG_DIR/hm_gameserver.out" 2>&1 &
|
||||
echo $! > "$GAMESERVER_PID"
|
||||
|
||||
nohup "$LOBBYSERVER_BIN" \
|
||||
--gameserver="$GAMESERVER_IP" \
|
||||
--gameserver_port="$GAMESERVER_PORT" \
|
||||
> "$LOG_DIR/hm_lobbyserver.out" 2>&1 &
|
||||
echo $! > "$LOBBYSERVER_PID"
|
||||
|
||||
echo "Servers started. Logs in $LOG_DIR"
|
||||
}
|
||||
|
||||
stop() {
|
||||
if [ -f "$GAMESERVER_PID" ]; then
|
||||
kill "$(cat "$GAMESERVER_PID")" 2>/dev/null || true
|
||||
rm -f "$GAMESERVER_PID"
|
||||
fi
|
||||
|
||||
if [ -f "$LOBBYSERVER_PID" ]; then
|
||||
kill "$(cat "$LOBBYSERVER_PID")" 2>/dev/null || true
|
||||
rm -f "$LOBBYSERVER_PID"
|
||||
fi
|
||||
}
|
||||
|
||||
status() {
|
||||
if [ -f "$GAMESERVER_PID" ] && kill -0 "$(cat "$GAMESERVER_PID")" 2>/dev/null; then
|
||||
echo "hm_gameserver running (pid $(cat "$GAMESERVER_PID"))"
|
||||
else
|
||||
echo "hm_gameserver not running"
|
||||
fi
|
||||
|
||||
if [ -f "$LOBBYSERVER_PID" ] && kill -0 "$(cat "$LOBBYSERVER_PID")" 2>/dev/null; then
|
||||
echo "hm_lobbyserver running (pid $(cat "$LOBBYSERVER_PID"))"
|
||||
else
|
||||
echo "hm_lobbyserver not running"
|
||||
fi
|
||||
}
|
||||
|
||||
logs() {
|
||||
tail -f "$LOG_DIR/hm_gameserver.out" "$LOG_DIR/hm_lobbyserver.out"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
build)
|
||||
build
|
||||
;;
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
status)
|
||||
status
|
||||
;;
|
||||
logs)
|
||||
logs
|
||||
;;
|
||||
docker-build)
|
||||
docker_compose build
|
||||
;;
|
||||
docker-start)
|
||||
docker_compose up -d
|
||||
;;
|
||||
docker-stop)
|
||||
docker_compose down
|
||||
;;
|
||||
docker-status)
|
||||
docker_compose ps
|
||||
;;
|
||||
docker-logs)
|
||||
docker_compose logs -f --tail=200 gameserver lobbyserver
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,58 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
couchbase:
|
||||
image: couchbase:community-6.6.0
|
||||
ports:
|
||||
- "8091-8096:8091-8096"
|
||||
- "11210:11210"
|
||||
volumes:
|
||||
- couchbase-data:/opt/couchbase/var
|
||||
|
||||
couchbase-init:
|
||||
image: couchbase:community-6.6.0
|
||||
depends_on:
|
||||
- couchbase
|
||||
entrypoint: ["/bin/bash", "/init-couchbase.sh"]
|
||||
volumes:
|
||||
- ./docker/couchbase-init.sh:/init-couchbase.sh:ro
|
||||
environment:
|
||||
COUCHBASE_HOST: couchbase
|
||||
COUCHBASE_ADMIN_USER: Administrator
|
||||
COUCHBASE_ADMIN_PASS: password
|
||||
COUCHBASE_BUCKET: hbs
|
||||
COUCHBASE_BUCKET_PASS: aci
|
||||
|
||||
gameserver:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile
|
||||
depends_on:
|
||||
- couchbase-init
|
||||
working_dir: /workspace
|
||||
volumes:
|
||||
- .:/workspace
|
||||
environment:
|
||||
HM_COUCHBASE_HOST: couchbase
|
||||
command: ["bash", "-lc", "make -C hm_gameserver && ./hm_gameserver/hm_gameserver"]
|
||||
ports:
|
||||
- "3724:3724"
|
||||
|
||||
lobbyserver:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile
|
||||
depends_on:
|
||||
- couchbase-init
|
||||
- gameserver
|
||||
working_dir: /workspace
|
||||
volumes:
|
||||
- .:/workspace
|
||||
environment:
|
||||
HM_COUCHBASE_HOST: couchbase
|
||||
command: ["bash", "-lc", "make -C hm_lobbyserver && ./hm_lobbyserver/hm_lobbyserver --gameserver=gameserver --gameserver_port=3724"]
|
||||
ports:
|
||||
- "45678:45678"
|
||||
|
||||
volumes:
|
||||
couchbase-data:
|
||||
@@ -0,0 +1,25 @@
|
||||
FROM ubuntu:18.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg \
|
||||
software-properties-common \
|
||||
&& add-apt-repository universe \
|
||||
&& curl -fsSL https://packages.couchbase.com/ubuntu/couchbase.key \
|
||||
| gpg --dearmor -o /usr/share/keyrings/couchbase.gpg \
|
||||
&& echo "deb [signed-by=/usr/share/keyrings/couchbase.gpg] https://packages.couchbase.com/ubuntu bionic bionic/main" \
|
||||
> /etc/apt/sources.list.d/couchbase.list \
|
||||
&& apt-get update && apt-get install -y \
|
||||
build-essential \
|
||||
cmake \
|
||||
libev-dev \
|
||||
libjson-c-dev \
|
||||
libcouchbase-dev \
|
||||
python \
|
||||
python-pip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /workspace
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
HOST=${COUCHBASE_HOST:-couchbase}
|
||||
ADMIN_USER=${COUCHBASE_ADMIN_USER:-Administrator}
|
||||
ADMIN_PASS=${COUCHBASE_ADMIN_PASS:-password}
|
||||
BUCKET=${COUCHBASE_BUCKET:-hbs}
|
||||
BUCKET_PASS=${COUCHBASE_BUCKET_PASS:-aci}
|
||||
|
||||
echo "Waiting for Couchbase at ${HOST}:8091..."
|
||||
until curl -s "http://${HOST}:8091/pools" >/dev/null; do
|
||||
sleep 2
|
||||
done
|
||||
|
||||
status=$(curl -s -o /dev/null -w "%{http_code}" "http://${HOST}:8091/pools/default")
|
||||
if [ "$status" != "200" ]; then
|
||||
couchbase-cli cluster-init -c "${HOST}:8091" \
|
||||
--cluster-username "${ADMIN_USER}" \
|
||||
--cluster-password "${ADMIN_PASS}" \
|
||||
--cluster-ramsize 512 \
|
||||
--services data
|
||||
fi
|
||||
|
||||
if ! couchbase-cli bucket-list -c "${HOST}:8091" -u "${ADMIN_USER}" -p "${ADMIN_PASS}" | grep -q "${BUCKET}"; then
|
||||
couchbase-cli bucket-create -c "${HOST}:8091" \
|
||||
-u "${ADMIN_USER}" -p "${ADMIN_PASS}" \
|
||||
--bucket="${BUCKET}" \
|
||||
--bucket-password="${BUCKET_PASS}" \
|
||||
--bucket-type=couchbase \
|
||||
--bucket-ramsize=200 \
|
||||
--bucket-replica=1 \
|
||||
--wait
|
||||
fi
|
||||
|
||||
echo "Couchbase initialized."
|
||||
Submodule
+1
Submodule hearthmod added at a15949d603
Submodule
+1
Submodule hm_base added at 48079d6c74
Submodule
+1
Submodule hm_client added at e4a2c8baac
Submodule
+1
Submodule hm_database added at 8f07d16612
Submodule
+1
Submodule hm_gameserver added at 4bf34a228b
Submodule
+1
Submodule hm_lobbyserver added at 6537d9989a
Submodule
+1
Submodule hm_nginx added at 8fac3084e2
Submodule
+1
Submodule hm_stud added at 5ef075eda2
Submodule
+1
Submodule hm_sunwell added at 8d9f22cdb2
Submodule
+1
Submodule hm_web added at a198222e7c
Submodule
+1
Submodule hs-client added at 67b3ddf12c
Reference in New Issue
Block a user