diff options
| author | fusion32 <marcopuzziello@gmail.com> | 2025-10-18 20:37:00 -0300 |
|---|---|---|
| committer | fusion32 <marcopuzziello@gmail.com> | 2025-10-18 20:39:12 -0300 |
| commit | 48375acd7d6581b42b354618ed694a9d1cd25439 (patch) | |
| tree | bd4fa70e4d39b3d637b218bbe7f52b86a96a1522 | |
| parent | cd93a7c2bf927fd747fc188bdda98ea03b1f5e72 (diff) | |
| download | web-48375acd7d6581b42b354618ed694a9d1cd25439.tar.gz web-48375acd7d6581b42b354618ed694a9d1cd25439.zip | |
overall improvements + keep up with querymanager v0.2
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | common.go | 67 | ||||
| -rw-r--r-- | config.cfg.dist (renamed from config.cfg) | 0 | ||||
| -rw-r--r-- | main.go | 2 | ||||
| -rw-r--r-- | query.go | 16 | ||||
| -rw-r--r-- | templates.go | 8 | ||||
| -rw-r--r-- | templates/character_create.tmpl | 2 | ||||
| -rw-r--r-- | templates/character_profile.tmpl | 2 | ||||
| -rw-r--r-- | templates/world_info.tmpl | 17 | ||||
| -rw-r--r-- | templates/world_list.tmpl | 6 |
10 files changed, 104 insertions, 17 deletions
@@ -3,3 +3,4 @@ bin build local *.log +config.cfg @@ -9,6 +9,7 @@ import ( "strings" "time" "unicode" + "unicode/utf8" ) // TReadBuffer @@ -83,7 +84,11 @@ func (ReadBuffer *TReadBuffer) ReadString() string { Result := "" if ReadBuffer.CanRead(Length) { - Result = string(ReadBuffer.Buffer[ReadBuffer.Position:][:Length]) + // IMPORTANT(fusion): The game server uses LATIN1 encoding, which forces + // the query manager to use LATIN1 encoding for text, at least on the + // protocol level. + Input := ReadBuffer.Buffer[ReadBuffer.Position:][:Length] + Result = string(Latin1ToUTF8(Input)) } ReadBuffer.Position += Length return Result @@ -157,7 +162,11 @@ func (WriteBuffer *TWriteBuffer) Write32BE(Value uint32) { } func (WriteBuffer *TWriteBuffer) WriteString(String string) { - Length := len(String) + // IMPORTANT(fusion): The game server uses LATIN1 encoding, which forces + // the query manager to use LATIN1 encoding for text, at least on the + // protocol level. + Output := UTF8ToLatin1([]byte(String)) + Length := len(Output) if Length < 0xFFFF { WriteBuffer.Write16(uint16(Length)) } else { @@ -166,7 +175,7 @@ func (WriteBuffer *TWriteBuffer) WriteString(String string) { } if WriteBuffer.CanWrite(Length) { - copy(WriteBuffer.Buffer[WriteBuffer.Position:], String) + copy(WriteBuffer.Buffer[WriteBuffer.Position:], Output) } WriteBuffer.Position += Length } @@ -370,7 +379,7 @@ func WorldTypeString(Type int) string { } } -func TimestampString(Timestamp int) string { +func FormatTimestamp(Timestamp int) string { String := "Never" if Timestamp > 0 { Time := time.Unix(int64(Timestamp), 0) @@ -378,3 +387,53 @@ func TimestampString(Timestamp int) string { } return String } + +func FormatDurationSince(Timestamp int) string { + String := "N/A" + if Timestamp > 0{ + Duration := time.Since(time.Unix(int64(Timestamp), 0)) + String = Duration.Truncate(time.Second).String() + } + return String +} + +func UTF8FindNextLeadingByte(Buffer []byte) int { + Offset := 0 + for Offset < len(Buffer) { + // NOTE(fusion): Allow the first byte to be a leading byte, in case we + // just want to advance from one leading byte to another. + if(Offset > 0 && utf8.RuneStart(Buffer[Offset])){ + break + } + Offset += 1 + } + return Offset +} + +func UTF8ToLatin1(Buffer []byte) []byte { + ReadPos := 0 + Result := []byte{} + for ReadPos < len(Buffer) { + Codepoint, Size := utf8.DecodeRune(Buffer[ReadPos:]) + if Codepoint != utf8.RuneError { + ReadPos += Size + }else{ + ReadPos += UTF8FindNextLeadingByte(Buffer[ReadPos:]) + } + + if Codepoint >= 0 && Codepoint <= 0xFF { + Result = append(Result, byte(Codepoint)) + }else{ + Result = append(Result, '?') + } + } + return Result +} + +func Latin1ToUTF8(Buffer []byte) []byte { + Result := []byte{} + for ReadPos := range Buffer { + Result = utf8.AppendRune(Result, rune(Buffer[ReadPos])) + } + return Result +} diff --git a/config.cfg b/config.cfg.dist index 81fd054..81fd054 100644 --- a/config.cfg +++ b/config.cfg.dist @@ -563,7 +563,7 @@ func HandleWorld(Context *THttpRequestContext) { } func main() { - g_Log.Print("Tibia Web Server v0.1") + g_Log.Print("Tibia Web Server v0.2") if !ReadConfig("config.cfg", WebKVCallback) { return } @@ -45,8 +45,10 @@ type ( Type string NumPlayers int MaxPlayers int - OnlineRecord int - OnlineRecordTime string + OnlinePeak int + OnlinePeakTimestamp int + LastStartup int + LastShutdown int } TAccountSummary struct { @@ -77,7 +79,7 @@ type ( Level int Profession string Residence string - LastLogin string + LastLogin int PremiumDays int Online bool Deleted bool @@ -389,7 +391,7 @@ func (Connection *TQueryManagerConnection) GetCharacterProfile(CharacterName str Character.Level = int(ReadBuffer.Read16()) Character.Profession = ReadBuffer.ReadString() Character.Residence = ReadBuffer.ReadString() - Character.LastLogin = TimestampString(int(ReadBuffer.Read32())) + Character.LastLogin = int(ReadBuffer.Read32()) Character.PremiumDays = int(ReadBuffer.Read16()) Character.Online = ReadBuffer.ReadFlag() Character.Deleted = ReadBuffer.ReadFlag() @@ -422,8 +424,10 @@ func (Connection *TQueryManagerConnection) GetWorlds() (Result int, Worlds []TWo Worlds[Index].Type = WorldTypeString(int(ReadBuffer.Read8())) Worlds[Index].NumPlayers = int(ReadBuffer.Read16()) Worlds[Index].MaxPlayers = int(ReadBuffer.Read16()) - Worlds[Index].OnlineRecord = int(ReadBuffer.Read16()) - Worlds[Index].OnlineRecordTime = TimestampString(int(ReadBuffer.Read32())) + Worlds[Index].OnlinePeak = int(ReadBuffer.Read16()) + Worlds[Index].OnlinePeakTimestamp = int(ReadBuffer.Read32()) + Worlds[Index].LastStartup = int(ReadBuffer.Read32()) + Worlds[Index].LastShutdown = int(ReadBuffer.Read32()) } } default: diff --git a/templates.go b/templates.go index 1f47782..5281c14 100644 --- a/templates.go +++ b/templates.go @@ -58,7 +58,13 @@ var ( func InitTemplates() bool { var Err error - g_Templates, Err = template.ParseGlob("templates/*.tmpl") + + CustomFuncs := template.FuncMap{ + "FormatTimestamp": FormatTimestamp, + "FormatDurationSince": FormatDurationSince, + } + + g_Templates, Err = template.New("").Funcs(CustomFuncs).ParseGlob("templates/*.tmpl") if Err != nil { g_LogErr.Printf("Failed to parse templates: %v", Err) return false diff --git a/templates/character_create.tmpl b/templates/character_create.tmpl index c243f2d..1a235dd 100644 --- a/templates/character_create.tmpl +++ b/templates/character_create.tmpl @@ -1,6 +1,6 @@ {{template "_header.tmpl" .Common}} <form class="box" action="/character/create" method="POST"> - <h1>Create Account</h1> + <h1>Create Character</h1> <label for="character_name">NAME</label> <input id="character_name" type="text" name="name"/> diff --git a/templates/character_profile.tmpl b/templates/character_profile.tmpl index 824e908..2711e03 100644 --- a/templates/character_profile.tmpl +++ b/templates/character_profile.tmpl @@ -35,7 +35,7 @@ </tr> <tr> <th>Last Login:</th> - <td>{{.LastLogin}}</td> + <td>{{FormatTimestamp .LastLogin}}</td> </tr> <tr> <th>Account Status:</th> diff --git a/templates/world_info.tmpl b/templates/world_info.tmpl index 578a0f7..353b674 100644 --- a/templates/world_info.tmpl +++ b/templates/world_info.tmpl @@ -16,13 +16,24 @@ <td>{{.NumPlayers}}</td> </tr> <tr> - <th>Online Record:</th> - {{if .OnlineRecord}} - <td>{{.OnlineRecord}} players (on {{.OnlineRecordTime}})</td> + <th>Online Peak:</th> + {{if eq .OnlinePeak 1}} + <td>{{.OnlinePeak}} player (on {{FormatTimestamp .OnlinePeakTimestamp}})</td> + {{else if gt .OnlinePeak 1}} + <td>{{.OnlinePeak}} players (on {{FormatTimestamp .OnlinePeakTimestamp}})</td> {{else}} <td>None</td> {{end}} </tr> + <tr> + {{if gt .LastStartup .LastShutdown}} + <th>Uptime:</th> + <td>{{FormatDurationSince .LastStartup}}</td> + {{else}} + <th>Downtime:</th> + <td style="color: #A11;">{{FormatDurationSince .LastShutdown}}</td> + {{end}} + </tr> </table> <a class="button" href="/killstatistics?world={{.Name}}">Kill Statistics</a> {{else}} diff --git a/templates/world_list.tmpl b/templates/world_list.tmpl index de9786c..07494b6 100644 --- a/templates/world_list.tmpl +++ b/templates/world_list.tmpl @@ -7,12 +7,18 @@ <th>Name</th> <th>Type</th> <th>Players Online</th> + <th>Status</th> </tr> {{range .Worlds}} <tr> <td><a href="/world?name={{.Name}}">{{.Name}}</a></td> <td>{{.Type}}</td> <td>{{.NumPlayers}}</td> + {{if gt .LastStartup .LastShutdown}} + <td style="color: #1A1;">Online</td> + {{else}} + <td style="color: #A11;">Offline</td> + {{end}} </tr> {{end}} </table> |
