57 Commits

Author SHA1 Message Date
wish
5f265ee291 Merge pull request #144 from gab3-dev/patch-1
Fix typos in Docker README
2025-10-29 00:00:32 +11:00
Gabriel Henrique
2a297e693f Fix typos in Docker README
"soruce" to "source"
2025-10-28 09:33:52 -03:00
wish
531b3d2fa6 Remove unused import 'math' from handlers_guild.go
Removed unused 'math' import from handlers_guild.go
2025-08-29 22:56:46 +10:00
wish
7459dede49 fix guild poogie outfit unlock 2025-08-28 23:27:56 +10:00
wish
b2768e5af8 Merge pull request #138 from Mezeporta/dependabot/go_modules/golang.org/x/net-0.38.0 2025-08-22 11:26:43 +10:00
dependabot[bot]
c13d6e6116 Bump golang.org/x/net from 0.33.0 to 0.38.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.33.0 to 0.38.0.
- [Commits](https://github.com/golang/net/compare/v0.33.0...v0.38.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-version: 0.38.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-22 01:11:38 +00:00
wish
425a245af5 Merge pull request #137 from Mezeporta/dependabot/go_modules/golang.org/x/crypto-0.35.0 2025-08-22 11:10:30 +10:00
dependabot[bot]
da43ad0cad Bump golang.org/x/crypto from 0.31.0 to 0.35.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.31.0 to 0.35.0.
- [Commits](https://github.com/golang/crypto/compare/v0.31.0...v0.35.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.35.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-22 01:09:07 +00:00
wish
8f3624d589 Update README.md 2025-05-16 18:10:13 +10:00
wish
8d1c6a79e2 S6 compatibility fix 2025-05-04 01:47:30 +10:00
wish
d83c784452 Create 26-fix-mail.sql 2025-04-10 23:35:59 +10:00
wish
ee7b099deb Create 25-fix-rasta-id.sql 2025-04-10 23:33:40 +10:00
wish
ef3212da11 Rename fix-weekly-stamps.sql to 24-fix-weekly-stamps.sql 2025-04-10 23:33:08 +10:00
wish
3d0114ce22 fix MhfAcquireCafeItem cost in G1-G5.2 2025-04-01 20:58:33 +11:00
wish
c539905c1d implement SysWaitStageBinary timeout 2025-03-16 23:04:42 +11:00
wish
69a2a7ca3b MhfReadMercenaryW changes 2025-03-16 15:51:10 +11:00
wish
7e58a26693 Merge remote-tracking branch 'origin/main' 2025-03-10 11:38:23 +11:00
wish
8c219be30f fix InfoGuild response on <G10 2025-03-10 11:38:00 +11:00
wish
f9cfb07760 Merge pull request #134 from ZeruLight/fix/packet-queue
fix/packet-queue
2025-03-09 14:41:10 +11:00
wish
f2862ea4b8 prevent concurrent map write to questCache 2025-03-08 11:41:57 +11:00
wish
3e71c308f4 minor MhfInfoGuild changes 2025-03-06 23:01:22 +11:00
wish
3c0d29ed41 fix invalidateSessions 2025-02-27 20:06:05 +11:00
wish
09f09e230d Merge pull request #133 from ZeruLight/main 2025-02-24 12:28:10 +11:00
wish
5028355cfc prevent nil pointer in MhfGetGuildManageRight 2025-02-23 21:46:00 +11:00
wish
ba04b79bd8 partially revert guildMembers query 2025-02-23 19:29:02 +11:00
wish
06c01153f6 Merge branch 'main' into fix/packet-queue 2025-02-18 03:27:01 +11:00
wish
296bc36dbc Merge pull request #131 from ZeruLight/dependabot/go_modules/golang.org/x/net-0.33.0
Bump golang.org/x/net from 0.23.0 to 0.33.0
2025-02-18 03:23:19 +11:00
wish
79b65281f9 Merge branch 'main' into fix/packet-queue 2025-02-18 03:21:11 +11:00
wish
d17d97fefc update workflow 2025-02-18 03:20:43 +11:00
dependabot[bot]
0bf39b9caf Bump golang.org/x/net from 0.23.0 to 0.33.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.23.0 to 0.33.0.
- [Commits](https://github.com/golang/net/compare/v0.23.0...v0.33.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 16:19:53 +00:00
wish
51ae16541f update workflow 2025-02-18 03:18:54 +11:00
wish
d86f50bd29 Merge branch 'main' into fix/packet-queue 2025-02-18 03:16:41 +11:00
wish
7c61f70590 add invalidateSessions 2025-02-18 03:16:18 +11:00
wish
d1dfc3fbb1 packet queue fix proposal 2025-02-18 03:12:09 +11:00
wish
b3305d1185 Update handlers_cafe.go 2025-01-15 00:38:12 +11:00
wish
4eed6a9738 add playtime chat command 2024-11-09 18:20:49 +11:00
wish
ab7bb0d004 revert packet concatenation 2024-11-09 17:39:14 +11:00
wish
7bf2fd5b8f revert packet concatenation 2024-11-09 17:22:43 +11:00
wish
366ec833d5 Merge pull request #130 from Brentdbr/ncafe
modified NetcafeDefaults to contain the retail Netcafe point rewards.
2024-10-20 11:32:58 +11:00
Brentdbr
a4fa55f9c9 modified NetcafeDefaults to contain the retail Netcafe point rewards. 2024-10-20 02:26:47 +02:00
wish
2215afca2b Merge pull request #121 from ZeruLight/dependabot/go_modules/golang.org/x/net-0.23.0
Bump golang.org/x/net from 0.18.0 to 0.23.0
2024-10-20 11:08:30 +11:00
wish
4348aa02a8 temporarily fix rasta expiration 2024-10-09 00:51:51 +11:00
wish
8191994acb add LoopDelay config option 2024-10-08 22:26:00 +11:00
wish
436e30f83d remove duplicate updateRights call 2024-10-08 21:07:13 +11:00
wish
34e84f31df ignore empty packet buffer 2024-10-08 21:06:52 +11:00
wish
1432e8f2b8 Merge remote-tracking branch 'origin/main' 2024-10-08 20:43:20 +11:00
wish
edd357fe50 concatenate packets during send 2024-10-08 20:42:42 +11:00
stratic-dev
ae32951671 Add troubleshooting for setup on docker and add opcode dec and hex to logger 2024-10-05 22:56:53 +01:00
stratic-dev
2d48d63263 added ./bin to ignore when building dockerfile 2024-10-05 04:36:37 +01:00
wish
b20969ddc6 emulate retail semaphore logic 2024-10-03 21:56:06 +10:00
wish
8f68e10f1d Merge remote-tracking branch 'origin/main' 2024-10-01 23:02:08 +10:00
wish
2c5896814f update ico resources 2024-10-01 23:01:15 +10:00
wish
8a55c5ff89 fix inflated festa rewards 2024-09-27 01:12:16 +10:00
wish
7d760bd3b4 fix EntranceServer clan member list limits 2024-08-08 22:16:39 +10:00
wish
04008fceb8 rewrite ReadMercenaryW handler 2024-08-08 22:16:09 +10:00
wish
1ab6940b01 add extra fields to Distributions 2024-08-08 22:14:35 +10:00
dependabot[bot]
ef99cc7659 Bump golang.org/x/net from 0.18.0 to 0.23.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.18.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.18.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-19 12:40:55 +00:00
36 changed files with 300 additions and 189 deletions

1
.dockerignore Normal file
View File

@@ -0,0 +1 @@
bin/

View File

@@ -10,16 +10,17 @@ on:
- 'go.mod'
- 'go.sum'
- 'main.go'
- '.github/workflows/go.yml'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: '1.21'
@@ -27,7 +28,7 @@ jobs:
run: env GOOS=linux GOARCH=amd64 go build -v
- name: Upload Linux-amd64 artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Linux-amd64
path: |
@@ -42,7 +43,7 @@ jobs:
run: env GOOS=windows GOARCH=amd64 go build -v
- name: Upload Windows-amd64 artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: Windows-amd64
path: |

View File

@@ -28,7 +28,7 @@ If you want to modify or compile Erupe yourself, please read on.
## Installation
1. Bring up a fresh database by using the [backup file attached with the latest release](https://github.com/ZeruLight/Erupe/releases/latest/download/SCHEMA.sql).
2. Run each script under [patch-schema](./patch-schema) as they introduce newer schema.
2. Run each script under [patch-schema](./schemas/patch-schema) as they introduce newer schema.
3. Edit [config.json](./config.json) such that the database password matches your PostgreSQL setup.
4. Run `go build` or `go run .` to compile Erupe.

View File

@@ -21,6 +21,7 @@
"QuestCacheExpiry": 300,
"CommandPrefix": "!",
"AutoCreateAccount": true,
"LoopDelay": 50,
"DefaultCourses": [1, 23, 24],
"EarthStatus": 0,
"EarthID": 0,
@@ -169,6 +170,11 @@
"Enabled": true,
"Description": "Toggle the Quest timer",
"Prefix": "timer"
}, {
"Name": "Playtime",
"Enabled": true,
"Description": "Show your playtime",
"Prefix": "playtime"
}
],
"Courses": [

View File

@@ -81,6 +81,7 @@ type Config struct {
QuestCacheExpiry int // Number of seconds to keep quest data cached
CommandPrefix string // The prefix for commands
AutoCreateAccount bool // Automatically create accounts if they don't exist
LoopDelay int // Delay in milliseconds between each loop iteration
DefaultCourses []uint16
EarthStatus int32
EarthID int32

View File

@@ -1,7 +1,7 @@
# Docker for erupe
## Building the container
Run the following from the route of the soruce folder. In this example we give it the tag of dev to seperate it from any other container verions.
Run the following from the route of the source folder. In this example we give it the tag of dev to seperate it from any other container verions.
```bash
docker build . -t erupe:dev
```
@@ -64,3 +64,7 @@ if you want all the logs and you want it to be in an attached state
```bash
docker-compose up
```
# Troubleshooting
Q: My Postgres will not populate. A: You're setup.sh is maybe saved as CRLF it needs to be saved as LF.

BIN
erupe.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 88 KiB

10
go.mod
View File

@@ -1,6 +1,6 @@
module erupe-ce
go 1.21
go 1.23.0
require (
github.com/bwmarrin/discordgo v0.27.1
@@ -10,9 +10,9 @@ require (
github.com/lib/pq v1.10.9
github.com/spf13/viper v1.17.0
go.uber.org/zap v1.26.0
golang.org/x/crypto v0.17.0
golang.org/x/crypto v0.36.0
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
golang.org/x/text v0.14.0
golang.org/x/text v0.23.0
)
require (
@@ -31,8 +31,8 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sys v0.31.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

16
go.sum
View File

@@ -220,8 +220,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -289,8 +289,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -345,8 +345,8 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -356,8 +356,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View File

@@ -31,7 +31,7 @@ func (m *MsgMhfAcquireCafeItem) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cl
m.ItemType = bf.ReadUint16()
m.ItemID = bf.ReadUint16()
m.Quant = bf.ReadUint16()
if _config.ErupeConfig.RealClientMode >= _config.G1 {
if _config.ErupeConfig.RealClientMode >= _config.G6 {
m.PointCost = bf.ReadUint32()
} else {
m.PointCost = uint32(bf.ReadUint16())

Binary file not shown.

View File

@@ -1,13 +1,15 @@
BEGIN;
TRUNCATE public.cafebonus;
INSERT INTO public.cafebonus (time_req, item_type, item_id, quantity)
VALUES
(1800, 17, 0, 250),
(3600, 17, 0, 500),
(7200, 17, 0, 1000),
(10800, 17, 0, 1500),
(18000, 17, 0, 1750),
(28800, 17, 0, 3000),
(43200, 17, 0, 4000);
(1800, 17, 0, 50),
(3600, 17, 0, 100),
(7200, 17, 0, 200),
(10800, 17, 0, 300),
(18000, 17, 0, 350),
(28800, 17, 0, 500),
(43200, 17, 0, 500);
END;

View File

@@ -0,0 +1,6 @@
BEGIN;
ALTER TABLE distribution ADD COLUMN rights INTEGER;
ALTER TABLE distribution ADD COLUMN selection BOOLEAN;
END;

View File

@@ -3,4 +3,4 @@ BEGIN;
ALTER TABLE IF EXISTS public.stamps RENAME hl_next TO hl_checked;
ALTER TABLE IF EXISTS public.stamps RENAME ex_next TO ex_checked;
END;
END;

View File

@@ -0,0 +1,5 @@
BEGIN;
CREATE SEQUENCE IF NOT EXISTS public.rasta_id_seq;
END;

View File

@@ -0,0 +1,5 @@
BEGIN;
ALTER TABLE mail ADD COLUMN IF NOT EXISTS is_sys_message BOOLEAN NOT NULL DEFAULT false;
END;

View File

@@ -88,7 +88,7 @@ func updateRights(s *Session) {
Rights: s.courses,
UnkSize: 0,
}
s.QueueSendMHF(update)
s.QueueSendMHFNonBlocking(update)
}
func handleMsgHead(s *Session, p mhfpacket.MHFPacket) {}
@@ -143,7 +143,6 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) {
s.token = pkt.LoginTokenString
s.Unlock()
updateRights(s)
bf := byteframe.NewByteFrame()
bf.WriteUint32(uint32(TimeAdjusted().Unix())) // Unix timestamp
@@ -193,7 +192,7 @@ func logoutPlayer(s *Session) {
for _, sess := range s.server.sessions {
for rSlot := range stage.reservedClientSlots {
if sess.charID == rSlot && sess.stage != nil && sess.stage.id[3:5] != "Qs" {
sess.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{})
sess.QueueSendMHFNonBlocking(&mhfpacket.MsgSysStageDestruct{})
}
}
}

View File

@@ -166,7 +166,7 @@ func handleMsgMhfReceiveCafeDurationBonus(s *Session, p mhfpacket.MHFPacket) {
FROM characters ch
WHERE ch.id = $1
) >= time_req`, s.charID, TimeAdjusted().Unix()-s.sessionStart)
if err != nil {
if err != nil || !mhfcourse.CourseExists(30, s.courses) {
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} else {
for rows.Next() {

View File

@@ -82,7 +82,7 @@ func sendServerChatMessage(s *Session, message string) {
RawDataPayload: bf.Data(),
}
s.QueueSendMHF(castedBin)
s.QueueSendMHFNonBlocking(castedBin)
}
func parseChatCommand(s *Session, command string) {
@@ -198,7 +198,7 @@ func parseChatCommand(s *Session, command string) {
temp.Build(deleteNotif, s.clientContext)
}
deleteNotif.WriteUint16(uint16(network.MSG_SYS_END))
s.QueueSend(deleteNotif.Data())
s.QueueSendNonBlocking(deleteNotif.Data())
time.Sleep(500 * time.Millisecond)
reloadNotif := byteframe.NewByteFrame()
for _, session := range s.server.sessions {
@@ -233,7 +233,7 @@ func parseChatCommand(s *Session, command string) {
temp.Build(reloadNotif, s.clientContext)
}
reloadNotif.WriteUint16(uint16(network.MSG_SYS_END))
s.QueueSend(reloadNotif.Data())
s.QueueSendNonBlocking(reloadNotif.Data())
} else {
sendDisabledCommandMessage(s, commands["Reload"])
}
@@ -381,7 +381,7 @@ func parseChatCommand(s *Session, command string) {
payload.WriteInt16(int16(x)) // X
payload.WriteInt16(int16(y)) // Y
payloadBytes := payload.Data()
s.QueueSendMHF(&mhfpacket.MsgSysCastedBinary{
s.QueueSendMHFNonBlocking(&mhfpacket.MsgSysCastedBinary{
CharID: s.charID,
MessageType: BinaryMessageTypeState,
RawDataPayload: payloadBytes,
@@ -407,6 +407,13 @@ func parseChatCommand(s *Session, command string) {
} else {
sendDisabledCommandMessage(s, commands["Discord"])
}
case commands["Playtime"].Prefix:
if commands["Playtime"].Enabled || s.isOp() {
playtime := s.playtime + uint32(time.Now().Sub(s.playtimeTime).Seconds())
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.playtime, playtime/60/60, playtime/60%60, playtime%60))
} else {
sendDisabledCommandMessage(s, commands["Playtime"])
}
case commands["Help"].Prefix:
if commands["Help"].Enabled || s.isOp() {
for _, command := range commands {
@@ -532,7 +539,7 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
char := s.server.FindSessionByCharID(targetID)
if char != nil {
char.QueueSendMHF(resp)
char.QueueSendMHFNonBlocking(resp)
}
}
default:

View File

@@ -23,6 +23,7 @@ const (
pGalleryData // +1748
pToreData // +240
pGardenData // +68
pPlaytime // +4
pWeaponType // +1
pWeaponID // +2
pHR // +2
@@ -45,6 +46,7 @@ type CharacterSaveData struct {
GalleryData []byte
ToreData []byte
GardenData []byte
Playtime uint32
WeaponType uint8
WeaponID uint16
HR uint16
@@ -59,6 +61,7 @@ func getPointers() map[SavePointer]int {
pointers := map[SavePointer]int{pGender: 81, lBookshelfData: 5576}
switch _config.ErupeConfig.RealClientMode {
case _config.ZZ:
pointers[pPlaytime] = 128356
pointers[pWeaponID] = 128522
pointers[pWeaponType] = 128789
pointers[pHouseTier] = 129900
@@ -74,6 +77,7 @@ func getPointers() map[SavePointer]int {
case _config.Z2, _config.Z1, _config.G101, _config.G10, _config.G91, _config.G9, _config.G81, _config.G8,
_config.G7, _config.G61, _config.G6, _config.G52, _config.G51, _config.G5, _config.GG, _config.G32, _config.G31,
_config.G3, _config.G2, _config.G1:
pointers[pPlaytime] = 92356
pointers[pWeaponID] = 92522
pointers[pWeaponType] = 92789
pointers[pHouseTier] = 93900
@@ -87,6 +91,7 @@ func getPointers() map[SavePointer]int {
pointers[pRP] = 106614
pointers[pKQF] = 110720
case _config.F5, _config.F4:
pointers[pPlaytime] = 60356
pointers[pWeaponID] = 60522
pointers[pWeaponType] = 60789
pointers[pHouseTier] = 61900
@@ -98,6 +103,7 @@ func getPointers() map[SavePointer]int {
pointers[pGardenData] = 74424
pointers[pRP] = 74614
case _config.S6:
pointers[pPlaytime] = 12356
pointers[pWeaponID] = 12522
pointers[pWeaponType] = 12789
pointers[pHouseTier] = 13900
@@ -231,6 +237,7 @@ func (save *CharacterSaveData) updateStructWithSaveData() {
save.GalleryData = save.decompSave[save.Pointers[pGalleryData] : save.Pointers[pGalleryData]+1748]
save.ToreData = save.decompSave[save.Pointers[pToreData] : save.Pointers[pToreData]+240]
save.GardenData = save.decompSave[save.Pointers[pGardenData] : save.Pointers[pGardenData]+68]
save.Playtime = binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pPlaytime] : save.Pointers[pPlaytime]+4])
save.WeaponType = save.decompSave[save.Pointers[pWeaponType]]
save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pWeaponID] : save.Pointers[pWeaponID]+2])
save.HR = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHR] : save.Pointers[pHR]+2])

View File

@@ -54,6 +54,9 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
}
characterSaveData.updateStructWithSaveData()
s.playtime = characterSaveData.Playtime
s.playtimeTime = time.Now()
// Bypass name-checker if new
if characterSaveData.IsNewCharacter == true {
s.Name = characterSaveData.Name

View File

@@ -13,6 +13,7 @@ import (
type Distribution struct {
ID uint32 `db:"id"`
Deadline time.Time `db:"deadline"`
Rights uint32 `db:"rights"`
TimesAcceptable uint16 `db:"times_acceptable"`
TimesAccepted uint16 `db:"times_accepted"`
MinHR int16 `db:"min_hr"`
@@ -23,7 +24,7 @@ type Distribution struct {
MaxGR int16 `db:"max_gr"`
EventName string `db:"event_name"`
Description string `db:"description"`
Data []byte `db:"data"`
Selection bool `db:"selection"`
}
func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) {
@@ -32,7 +33,7 @@ func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) {
var itemDists []Distribution
bf := byteframe.NewByteFrame()
rows, err := s.server.db.Queryx(`
SELECT d.id, event_name, description, times_acceptable,
SELECT d.id, event_name, description, COALESCE(rights, 0) AS rights, COALESCE(selection, false) AS selection, times_acceptable,
COALESCE(min_hr, -1) AS min_hr, COALESCE(max_hr, -1) AS max_hr,
COALESCE(min_sr, -1) AS min_sr, COALESCE(max_sr, -1) AS max_sr,
COALESCE(min_gr, -1) AS min_gr, COALESCE(max_gr, -1) AS max_gr,
@@ -60,7 +61,7 @@ func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) {
for _, dist := range itemDists {
bf.WriteUint32(dist.ID)
bf.WriteUint32(uint32(dist.Deadline.Unix()))
bf.WriteUint32(0) // Unk
bf.WriteUint32(dist.Rights)
bf.WriteUint16(dist.TimesAcceptable)
bf.WriteUint16(dist.TimesAccepted)
if _config.ErupeConfig.RealClientMode >= _config.G9 {
@@ -79,7 +80,11 @@ func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint16(0) // Unk
}
if _config.ErupeConfig.RealClientMode >= _config.G8 {
bf.WriteUint8(0) // Unk
if dist.Selection {
bf.WriteUint8(2) // Selection
} else {
bf.WriteUint8(0)
}
}
if _config.ErupeConfig.RealClientMode >= _config.G7 {
bf.WriteUint16(0) // Unk

View File

@@ -292,7 +292,7 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
} else {
bf.WriteUint32(s.server.erupeConfig.GameplayOptions.MaximumFP)
}
bf.WriteUint16(500)
bf.WriteUint16(100) // Reward multiplier (%)
var temp uint32
bf.WriteUint16(4)

View File

@@ -8,7 +8,6 @@ import (
"erupe-ce/common/mhfitem"
_config "erupe-ce/config"
"fmt"
"math"
"sort"
"strings"
"time"
@@ -748,8 +747,7 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
case mhfpacket.OperateGuildChangePugi3:
handleChangePugi(s, uint8(pkt.Data1.ReadUint32()), guild, 3)
case mhfpacket.OperateGuildUnlockOutfit:
// TODO: This doesn't implement blocking, if someone unlocked the same outfit at the same time
s.server.db.Exec(`UPDATE guilds SET pugi_outfits=pugi_outfits+$1 WHERE id=$2`, int(math.Pow(float64(pkt.Data1.ReadUint32()), 2)), guild.ID)
s.server.db.Exec(`UPDATE guilds SET pugi_outfits=$1 WHERE id=$2`, pkt.Data1.ReadUint32(), guild.ID)
case mhfpacket.OperateGuildDonateRoom:
quantity := uint16(pkt.Data1.ReadUint32())
bf.WriteBytes(handleDonateRP(s, quantity, guild, 2))
@@ -971,14 +969,21 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint8(0)
bf.WriteUint8(0)
bf.WriteBool(!guild.Recruiting)
flags := uint8(0)
if !guild.Recruiting {
flags |= 0x01
}
//if guild.Suspended {
// flags |= 0x02
//}
bf.WriteUint8(flags)
if characterGuildData == nil || characterGuildData.IsApplicant {
bf.WriteUint16(0x00)
bf.WriteUint16(0)
} else if guild.LeaderCharID == s.charID {
bf.WriteUint16(0x01)
bf.WriteUint16(1)
} else {
bf.WriteUint16(0x02)
bf.WriteUint16(2)
}
bf.WriteUint32(uint32(guild.CreatedAt.Unix()))
@@ -1098,20 +1103,25 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint32(applicant.CharID)
bf.WriteUint32(0)
bf.WriteUint16(applicant.HR)
bf.WriteUint16(applicant.GR)
if s.server.erupeConfig.RealClientMode >= _config.G10 {
bf.WriteUint16(applicant.GR)
}
ps.Uint8(bf, applicant.Name, true)
}
}
type UnkGuildInfo struct {
Unk0 uint8
type Activity struct {
Pass uint8
Unk1 uint8
Unk2 uint8
}
unkGuildInfo := []UnkGuildInfo{}
bf.WriteUint8(uint8(len(unkGuildInfo)))
for _, info := range unkGuildInfo {
bf.WriteUint8(info.Unk0)
activity := []Activity{
// 1,0,0 = ok
// 0,0,0 = ng
}
bf.WriteUint8(uint8(len(activity)))
for _, info := range activity {
bf.WriteUint8(info.Pass)
bf.WriteUint8(info.Unk1)
bf.WriteUint8(info.Unk2)
}
@@ -1159,7 +1169,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} else {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 5))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
}
}
@@ -1526,7 +1536,7 @@ func handleMsgMhfGetGuildManageRight(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetGuildManageRight)
guild, err := GetGuildInfoByCharacterId(s, s.charID)
if guild == nil && s.prevGuildID != 0 {
if guild == nil || s.prevGuildID != 0 {
guild, err = GetGuildInfoByID(s, s.prevGuildID)
s.prevGuildID = 0
if guild == nil || err != nil {

View File

@@ -61,35 +61,41 @@ func (gm *GuildMember) Save(s *Session) error {
}
const guildMembersSelectSQL = `
SELECT * FROM (
SELECT
g.id AS guild_id,
joined_at,
COALESCE((SELECT SUM(souls) FROM festa_submissions fs WHERE fs.character_id=c.id), 0) AS souls,
COALESCE(rp_today, 0) AS rp_today,
COALESCE(rp_yesterday, 0) AS rp_yesterday,
c.name,
c.id AS character_id,
COALESCE(order_index, 0) AS order_index,
c.last_login,
COALESCE(recruiter, false) AS recruiter,
COALESCE(avoid_leadership, false) AS avoid_leadership,
c.hr,
c.gr,
c.weapon_id,
c.weapon_type,
EXISTS(SELECT 1 FROM guild_applications ga WHERE ga.character_id=c.id AND application_type='applied') AS is_applicant,
CASE WHEN g.leader_id = c.id THEN true ELSE false END AS is_leader
SELECT
COALESCE(g.id, 0) AS guild_id,
joined_at,
COALESCE((SELECT SUM(souls) FROM festa_submissions fs WHERE fs.character_id=c.id), 0) AS souls,
COALESCE(rp_today, 0) AS rp_today,
COALESCE(rp_yesterday, 0) AS rp_yesterday,
c.name,
c.id AS character_id,
COALESCE(order_index, 0) AS order_index,
c.last_login,
COALESCE(recruiter, false) AS recruiter,
COALESCE(avoid_leadership, false) AS avoid_leadership,
c.hr,
c.gr,
c.weapon_id,
c.weapon_type,
CASE WHEN g.leader_id = c.id THEN true ELSE false END AS is_leader,
character.is_applicant
FROM (
SELECT character_id, true as is_applicant, guild_id
FROM guild_applications ga
WHERE ga.application_type = 'applied'
UNION
SELECT character_id, false as is_applicant, guild_id
FROM guild_characters gc
LEFT JOIN characters c ON c.id = gc.character_id
LEFT JOIN guilds g ON g.id = gc.guild_id
) AS subquery
) character
JOIN characters c on character.character_id = c.id
LEFT JOIN guild_characters gc ON gc.character_id = character.character_id
LEFT JOIN guilds g ON g.id = gc.guild_id
`
func GetGuildMembers(s *Session, guildID uint32, applicants bool) ([]*GuildMember, error) {
rows, err := s.server.db.Queryx(fmt.Sprintf(`
%s
WHERE guild_id = $1 AND is_applicant = $2
WHERE character.guild_id = $1 AND is_applicant = $2
`, guildMembersSelectSQL), guildID, applicants)
if err != nil {
@@ -115,7 +121,7 @@ func GetGuildMembers(s *Session, guildID uint32, applicants bool) ([]*GuildMembe
}
func GetCharacterGuildData(s *Session, charID uint32) (*GuildMember, error) {
rows, err := s.server.db.Queryx(fmt.Sprintf("%s WHERE character_id=$1", guildMembersSelectSQL), charID)
rows, err := s.server.db.Queryx(fmt.Sprintf("%s WHERE character.character_id=$1", guildMembersSelectSQL), charID)
if err != nil {
s.logger.Error(fmt.Sprintf("failed to retrieve membership data for character '%d'", charID))

View File

@@ -185,7 +185,7 @@ func SendMailNotification(s *Session, m *Mail, recipient *Session) {
castedBinary.Build(bf, s.clientContext)
recipient.QueueSendMHF(castedBinary)
recipient.QueueSendMHFNonBlocking(castedBinary)
}
func getCharacterName(s *Session, charID uint32) string {

View File

@@ -21,7 +21,6 @@ func handleMsgMhfLoadPartner(s *Session, p mhfpacket.MHFPacket) {
data = make([]byte, 9)
}
doAckBufSucceed(s, pkt.AckHandle, data)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfSavePartner(s *Session, p mhfpacket.MHFPacket) {
@@ -157,60 +156,60 @@ func handleMsgMhfSaveMercenary(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReadMercenaryW)
if pkt.Op > 0 {
bf := byteframe.NewByteFrame()
var pactID uint32
var name string
var cid uint32
bf := byteframe.NewByteFrame()
s.server.db.QueryRow("SELECT pact_id FROM characters WHERE id=$1", s.charID).Scan(&pactID)
if pactID > 0 {
s.server.db.QueryRow("SELECT name, id FROM characters WHERE rasta_id = $1", pactID).Scan(&name, &cid)
bf.WriteUint8(1) // numLends
bf.WriteUint32(pactID)
bf.WriteUint32(cid)
bf.WriteBool(false) // ?
bf.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -8).Unix()))
bf.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -1).Unix()))
bf.WriteBytes(stringsupport.PaddedString(name, 18, true))
} else {
bf.WriteUint8(0)
}
if pkt.Op < 2 {
var loans uint8
temp := byteframe.NewByteFrame()
rows, _ := s.server.db.Query("SELECT name, id, pact_id FROM characters WHERE pact_id=(SELECT rasta_id FROM characters WHERE id=$1)", s.charID)
for rows.Next() {
loans++
rows.Scan(&name, &cid, &pactID)
temp.WriteUint32(pactID)
temp.WriteUint32(cid)
temp.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -8).Unix()))
temp.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -1).Unix()))
temp.WriteBytes(stringsupport.PaddedString(name, 18, true))
}
bf.WriteUint8(loans)
bf.WriteBytes(temp.Data())
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
return
}
var data []byte
var gcp uint32
s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id=$1", s.charID).Scan(&data)
s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id=$1", s.charID).Scan(&gcp)
resp := byteframe.NewByteFrame()
resp.WriteUint16(0)
if len(data) == 0 {
resp.WriteBool(false)
var pactID, cid uint32
var name string
s.server.db.QueryRow("SELECT pact_id FROM characters WHERE id=$1", s.charID).Scan(&pactID)
if pactID > 0 {
s.server.db.QueryRow("SELECT name, id FROM characters WHERE rasta_id = $1", pactID).Scan(&name, &cid)
bf.WriteUint8(1) // numLends
bf.WriteUint32(pactID)
bf.WriteUint32(cid)
bf.WriteBool(true) // Escort enabled
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
bf.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * 7).Unix()))
bf.WriteBytes(stringsupport.PaddedString(name, 18, true))
} else {
resp.WriteBool(true)
resp.WriteBytes(data)
bf.WriteUint8(0)
}
resp.WriteUint32(gcp)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
if pkt.Op != 2 && pkt.Op != 5 {
var loans uint8
temp := byteframe.NewByteFrame()
rows, _ := s.server.db.Query("SELECT name, id, pact_id FROM characters WHERE pact_id=(SELECT rasta_id FROM characters WHERE id=$1)", s.charID)
for rows.Next() {
err := rows.Scan(&name, &cid, &pactID)
if err != nil {
continue
}
loans++
temp.WriteUint32(pactID)
temp.WriteUint32(cid)
temp.WriteUint32(uint32(TimeAdjusted().Unix()))
temp.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * 7).Unix()))
temp.WriteBytes(stringsupport.PaddedString(name, 18, true))
}
bf.WriteUint8(loans)
bf.WriteBytes(temp.Data())
if pkt.Op != 1 && pkt.Op != 4 {
var data []byte
var gcp uint32
s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id=$1", s.charID).Scan(&data)
s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id=$1", s.charID).Scan(&gcp)
if len(data) == 0 {
bf.WriteBool(false)
} else {
bf.WriteBool(true)
bf.WriteBytes(data)
}
bf.WriteUint32(gcp)
}
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfReadMercenaryM(s *Session, p mhfpacket.MHFPacket) {

View File

@@ -85,6 +85,10 @@ func BackportQuest(data []byte) []byte {
}
}
}
if _config.ErupeConfig.RealClientMode <= _config.S6 {
binary.LittleEndian.PutUint32(data[16:20], binary.LittleEndian.Uint32(data[8:12]))
}
return data
}
@@ -238,8 +242,10 @@ func loadQuestFile(s *Session, questId int) []byte {
}
questBody.WriteBytes(newStrings.Data())
s.server.questCacheLock.Lock()
s.server.questCacheData[questId] = questBody.Data()
s.server.questCacheTime[questId] = time.Now()
s.server.questCacheLock.Unlock()
return questBody.Data()
}

View File

@@ -129,12 +129,12 @@ func (s *Session) notifyRavi() {
raviNotif.WriteUint16(0x0010) // End it.
if s.server.erupeConfig.GameplayOptions.LowLatencyRaviente {
for session := range sema.clients {
session.QueueSend(raviNotif.Data())
session.QueueSendNonBlocking(raviNotif.Data())
}
} else {
for session := range sema.clients {
if session.charID == s.charID {
session.QueueSend(raviNotif.Data())
session.QueueSendNonBlocking(raviNotif.Data())
}
}
}

View File

@@ -65,6 +65,15 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysCreateAcquireSemaphore)
SemaphoreID := pkt.SemaphoreID
if s.server.HasSemaphore(s) {
s.semaphoreMode = !s.semaphoreMode
}
if s.semaphoreMode {
s.semaphoreID[1]++
} else {
s.semaphoreID[0]++
}
newSemaphore, exists := s.server.semaphore[SemaphoreID]
if !exists {
s.server.semaphoreLock.Lock()
@@ -77,7 +86,7 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
maxPlayers: 127,
}
} else {
s.server.semaphore[SemaphoreID] = NewSemaphore(s.server, SemaphoreID, 1)
s.server.semaphore[SemaphoreID] = NewSemaphore(s, SemaphoreID, 1)
}
newSemaphore = s.server.semaphore[SemaphoreID]
s.server.semaphoreLock.Unlock()
@@ -103,7 +112,7 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
func handleMsgSysAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysAcquireSemaphore)
if sema, exists := s.server.semaphore[pkt.SemaphoreID]; exists {
sema.clients[s] = s.charID
sema.host = s
bf := byteframe.NewByteFrame()
bf.WriteUint32(sema.id)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())

View File

@@ -59,7 +59,7 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) {
s.Unlock()
// Tell the client to cleanup its current stage objects.
s.QueueSendMHF(&mhfpacket.MsgSysCleanupObject{})
s.QueueSendMHFNonBlocking(&mhfpacket.MsgSysCleanupObject{})
// Confirm the stage entry.
doAckSimpleSucceed(s, ackHandle, []byte{0x00, 0x00, 0x00, 0x00})
@@ -112,9 +112,8 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) {
s.stage.RUnlock()
}
newNotif.WriteUint16(0x0010) // End it.
if len(newNotif.Data()) > 2 {
s.QueueSend(newNotif.Data())
s.QueueSendNonBlocking(newNotif.Data())
}
}
@@ -238,7 +237,7 @@ func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) {
for charID := range s.reservationStage.reservedClientSlots {
session := s.server.FindSessionByCharID(charID)
if session != nil {
session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{})
session.QueueSendMHFNonBlocking(&mhfpacket.MsgSysStageDestruct{})
}
}
@@ -362,7 +361,7 @@ func handleMsgSysWaitStageBinary(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
return
}
for {
for i := 0; i < 10; i++ {
s.logger.Debug("MsgSysWaitStageBinary before lock and get stage")
stage.Lock()
stageBinary, gotBinary := stage.rawBinaryData[stageBinaryKey{pkt.BinaryType0, pkt.BinaryType1}]
@@ -370,13 +369,15 @@ func handleMsgSysWaitStageBinary(s *Session, p mhfpacket.MHFPacket) {
s.logger.Debug("MsgSysWaitStageBinary after lock and get stage")
if gotBinary {
doAckBufSucceed(s, pkt.AckHandle, stageBinary)
break
return
} else {
s.logger.Debug("Waiting stage binary", zap.Uint8("BinaryType0", pkt.BinaryType0), zap.Uint8("pkt.BinaryType1", pkt.BinaryType1))
time.Sleep(1 * time.Second)
continue
}
}
s.logger.Warn("MsgSysWaitStageBinary stage binary timeout")
doAckBufSucceed(s, pkt.AckHandle, []byte{})
} else {
s.logger.Warn("Failed to get stage", zap.String("StageID", pkt.StageID))
}

View File

@@ -75,6 +75,7 @@ type Server struct {
raviente *Raviente
questCacheLock sync.RWMutex
questCacheData map[int][]byte
questCacheTime map[int]time.Time
}
@@ -207,6 +208,7 @@ func (s *Server) Start() error {
go s.acceptClients()
go s.manageSessions()
go s.invalidateSessions()
// Start the discord bot for chat integration.
if s.erupeConfig.Discord.Enabled && s.discordBot != nil {
@@ -278,6 +280,21 @@ func (s *Server) manageSessions() {
}
}
func (s *Server) invalidateSessions() {
for {
if s.isShuttingDown {
break
}
for _, sess := range s.sessions {
if time.Now().Sub(sess.lastPacket) > time.Second*time.Duration(30) {
s.logger.Info("session timeout", zap.String("Name", sess.Name))
logoutPlayer(sess)
}
}
time.Sleep(time.Second * 10)
}
}
// BroadcastMHF queues a MHFPacket to be sent to all sessions.
func (s *Server) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) {
// Broadcast the data.
@@ -424,24 +441,13 @@ func (s *Server) FindObjectByChar(charID uint32) *Object {
return nil
}
func (s *Server) NextSemaphoreID() uint32 {
for {
exists := false
s.semaphoreIndex = s.semaphoreIndex + 1
if s.semaphoreIndex > 0xFFFF {
s.semaphoreIndex = 1
}
for _, semaphore := range s.semaphore {
if semaphore.id == s.semaphoreIndex {
exists = true
break
}
}
if !exists {
break
func (s *Server) HasSemaphore(ses *Session) bool {
for _, semaphore := range s.semaphore {
if semaphore.host == ses {
return true
}
}
return s.semaphoreIndex
return false
}
func (s *Server) Season() uint8 {

View File

@@ -10,6 +10,7 @@ type i18n struct {
noOp string
disabled string
reload string
playtime string
kqf struct {
get string
set struct {
@@ -175,6 +176,8 @@ func getLangStrings(s *Server) i18n {
i.commands.noOp = "You don't have permission to use this command"
i.commands.disabled = "%s command is disabled"
i.commands.reload = "Reloading players..."
i.commands.playtime = "Playtime: %d hours %d minutes %d seconds"
i.commands.kqf.get = "KQF: %x"
i.commands.kqf.set.error = "Error in command. Format: %s set xxxxxxxxxxxxxxxx"
i.commands.kqf.set.success = "KQF set, please switch Land/World"

View File

@@ -22,15 +22,18 @@ type Semaphore struct {
// Max Players for Semaphore
maxPlayers uint16
host *Session
}
// NewSemaphore creates a new Semaphore with intialized values
func NewSemaphore(s *Server, ID string, MaxPlayers uint16) *Semaphore {
func NewSemaphore(s *Session, ID string, MaxPlayers uint16) *Semaphore {
sema := &Semaphore{
name: ID,
id: s.NextSemaphoreID(),
id: s.GetSemaphoreID(),
clients: make(map[*Session]uint32),
maxPlayers: MaxPlayers,
host: s,
}
return sema
}

View File

@@ -4,6 +4,7 @@ import (
"encoding/binary"
"encoding/hex"
"erupe-ce/common/mhfcourse"
_config "erupe-ce/config"
"fmt"
"io"
"net"
@@ -15,6 +16,7 @@ import (
"erupe-ce/network"
"erupe-ce/network/clientctx"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
@@ -48,7 +50,12 @@ type Session struct {
kqf []byte
kqfOverride bool
semaphore *Semaphore // Required for the stateful MsgSysUnreserveStage packet.
playtime uint32
playtimeTime time.Time
semaphore *Semaphore // Required for the stateful MsgSysUnreserveStage packet.
semaphoreMode bool
semaphoreID []uint16
// A stack containing the stage movement history (push on enter/move, pop on back)
stageMoveStack *stringstack.StringStack
@@ -79,6 +86,7 @@ func NewSession(server *Server, conn net.Conn) *Session {
sessionStart: TimeAdjusted().Unix(),
stageMoveStack: stringstack.New(),
ackStart: make(map[uint32]time.Time),
semaphoreID: make([]uint16, 2),
}
s.SetObjectID()
return s
@@ -96,22 +104,9 @@ func (s *Session) Start() {
// QueueSend queues a packet (raw []byte) to be sent.
func (s *Session) QueueSend(data []byte) {
s.logMessage(binary.BigEndian.Uint16(data[0:2]), data, "Server", s.Name)
select {
case s.sendPackets <- packet{data, false}:
// Enqueued data
default:
s.logger.Warn("Packet queue too full, flushing!")
var tempPackets []packet
for len(s.sendPackets) > 0 {
tempPacket := <-s.sendPackets
if !tempPacket.nonBlocking {
tempPackets = append(tempPackets, tempPacket)
}
}
for _, tempPacket := range tempPackets {
s.sendPackets <- tempPacket
}
s.sendPackets <- packet{data, false}
err := s.cryptConn.SendPacket(append(data, []byte{0x00, 0x10}...))
if err != nil {
s.logger.Warn("Failed to send packet")
}
}
@@ -138,6 +133,19 @@ func (s *Session) QueueSendMHF(pkt mhfpacket.MHFPacket) {
s.QueueSend(bf.Data())
}
// QueueSendMHFNonBlocking queues a MHFPacket to be sent, dropping the packet entirely if the queue is full.
func (s *Session) QueueSendMHFNonBlocking(pkt mhfpacket.MHFPacket) {
// Make the header
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(pkt.Opcode()))
// Build the packet onto the byteframe.
pkt.Build(bf, s.clientContext)
// Queue it.
s.QueueSendNonBlocking(bf.Data())
}
// QueueAck is a helper function to queue an MSG_SYS_ACK with the given ack handle and data.
func (s *Session) QueueAck(ackHandle uint32, data []byte) {
bf := byteframe.NewByteFrame()
@@ -150,26 +158,26 @@ func (s *Session) QueueAck(ackHandle uint32, data []byte) {
func (s *Session) sendLoop() {
var pkt packet
for {
var buf []byte
if s.closed {
return
}
for len(s.sendPackets) > 0 {
pkt = <-s.sendPackets
err := s.cryptConn.SendPacket(append(pkt.data, []byte{0x00, 0x10}...))
buf = append(buf, pkt.data...)
}
if len(buf) > 0 {
err := s.cryptConn.SendPacket(append(buf, []byte{0x00, 0x10}...))
if err != nil {
s.logger.Warn("Failed to send packet")
}
}
time.Sleep(5 * time.Millisecond)
time.Sleep(time.Duration(_config.ErupeConfig.LoopDelay) * time.Millisecond)
}
}
func (s *Session) recvLoop() {
for {
if time.Now().Add(-30 * time.Second).After(s.lastPacket) {
logoutPlayer(s)
return
}
if s.closed {
logoutPlayer(s)
return
@@ -185,7 +193,7 @@ func (s *Session) recvLoop() {
return
}
s.handlePacketGroup(pkt)
time.Sleep(5 * time.Millisecond)
time.Sleep(time.Duration(_config.ErupeConfig.LoopDelay) * time.Millisecond)
}
}
@@ -272,7 +280,7 @@ func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipien
} else {
fmt.Printf("[%s] -> [%s]\n", sender, recipient)
}
fmt.Printf("Opcode: %s\n", opcodePID)
fmt.Printf("Opcode: (Dec: %d Hex: 0x%04X Name: %s) \n", opcode, opcode, opcodePID)
if s.server.erupeConfig.DebugOptions.LogMessageData {
if len(data) <= s.server.erupeConfig.DebugOptions.MaxHexdumpLength {
fmt.Printf("Data [%d bytes]:\n%s\n", len(data), hex.Dump(data))
@@ -310,6 +318,14 @@ func (s *Session) NextObjectID() uint32 {
return bf.ReadUint32()
}
func (s *Session) GetSemaphoreID() uint32 {
if s.semaphoreMode {
return 0x000E0000 + uint32(s.semaphoreID[1])
} else {
return 0x000F0000 + uint32(s.semaphoreID[0])
}
}
func (s *Session) isOp() bool {
var op bool
err := s.server.db.QueryRow(`SELECT op FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&op)

View File

@@ -86,7 +86,7 @@ func encodeServerInfo(config *_config.Config, s *Server, local bool) []byte {
}
}
bf.WriteUint32(uint32(channelserver.TimeAdjusted().Unix()))
bf.WriteUint32(60)
bf.WriteUint32(uint32(s.erupeConfig.GameplayOptions.ClanMemberLimits[len(s.erupeConfig.GameplayOptions.ClanMemberLimits)-1][1]))
return bf.Data()
}