Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/a2k/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ func fields(e game.Event) string {
fmt.Fprintf(&b, "%.1f", x)
case time.Time:
b.WriteString(x.Local().Format("15:04:05.000"))
case []byte:
fmt.Fprintf(&b, "%x", x)
case string:
if strings.ContainsAny(x, " \"=") {
x = fmt.Sprintf("%q", x)
Expand Down
4 changes: 3 additions & 1 deletion cmd/a2k/stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,13 @@ func writeTable(w io.Writer, a *a2kit.Analysis, marks []time.Time, window time.D
}

// note says what is known of a type.
// The lock counts it, and it comes whether the player acts or not.
// The lock counts it, and it comes whether the player acts or not. A type that is guessed is read, but the lock does not count it.
func note(op wire.Opcode, background bool) string {
var notes []string
if op.Known() {
notes = append(notes, "known")
} else if op.Guessed() {
notes = append(notes, "guessed")
}
if background {
notes = append(notes, "background")
Expand Down
11 changes: 10 additions & 1 deletion game/combat.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package game

// Hit is the damage one skill deals one target, from 04 38.
//
// Damage is 0 on a hit that deals none, which is a form of its own and never otherwise 0. Skills
// 4001 and 4002 of the player send it with the player for the target as well, and others do.
type Hit struct {
Actor, Target Entity
Skill Skill
Expand All @@ -11,11 +14,17 @@ type Hit struct {
Mods, Direction byte // with some hits, else 0; their bits are not read
}

// Cast opens a skill use, from 02 38. Its position is the target's.
// Cast opens a skill use, from 02 38. Its position is the target's, and the zero value on the short
// form, which also comes with another entity as the target.
//
// For skills 4001 and 4002 Pos is not a position: its X and Y are a vector of length 100 and Z is 0.
// Angle is a direction in degrees, as far as its range can tell.
type Cast struct {
Actor, Target Entity
Skill Skill
Angle float32
Pos
Duration uint32 // a guess: milliseconds, 500 to 10000, and 0 on the short form
}

// CastEnd closes the skill use a Cast opened, from 06 38.
Expand Down
155 changes: 155 additions & 0 deletions game/guess.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package game

import "time"

// What is in this file is read from one recording and named by a guess. A name, a field or a
// layout here may change when another recording says otherwise. Each doc comment says what the
// guess rests on.

// Ref names an entity and nothing more.
type Ref struct{ Entity Entity }

// Tagged is a message that starts with an entity, followed by the bytes a2k does not read yet.
type Tagged struct {
Entity Entity
Data []byte
}

// Signal is a message of one to four bytes and no entity, which holds a small number, 0 so far.
type Signal struct{ Code uint32 }

// Target is the entity a mob has picked to attack, from 35 38, with Target 0 when it lets go.
//
// Guess: it follows a mob's first Cast on the player by a few tenths of a second, and ends with
// a 0 when the fight is over.
type Target struct{ Entity, Target Entity }

// Combo is a chain of the player's skills, from 18 38: the skill that was used, and the one that
// can follow it. From is 0 when the message, 19 38, names only the next skill.
//
// Guess: the three skills 9865 to 9867 and the next three 9868 to 9870 follow each other in the
// order a basic attack chain would, one of these each time the player casts one.
type Combo struct{ From, To Skill }

// SkillUse is the player's own Cast, again, from 01 38.
//
// Guess: it comes with each of the player's own Cast, with its skill. Info is 0 on most, and
// 0x1813 on a few that came late.
type SkillUse struct {
Skill Skill
Info uint16
}

// Windup announces a Cast before it comes, from 09 38, as a mob's skill is about to land.
//
// Guess: it comes 500 to 600 ms before the Cast of the same skill from the same mob, and carries
// a number of the same size in Duration, in milliseconds.
type Windup struct {
Actor, Target Entity
Skill Skill
Angle float32
Pos
Duration uint32
}

// Engage names the mob that is about to pick the player as its Target, from 34 38. The entity is 0 on some.
//
// Guess: it comes right before Target, with the same mob in it.
type Engage struct{ Entity Entity }

// Threat is the other end of a fight with a mob, from 31 38: the mob, and a number that varies.
//
// Guess: it comes with the mob's Target letting go, and its number, a u16, is not understood.
type Threat struct {
Entity Entity
Value uint16
}

// Attributes is an entity's whole table of attributes, from 49 36 for the player's own, which
// has no entity, and 4A 36 for another's.
//
// Guess: the ids are the game's attribute ids and a value goes with each. Ids 0x5d and 0x59
// hold 1500 and 2500 on a new character, and 0x60 and 0x61 hold 100000, as Stats' 1 and 3 do.
type Attributes struct {
Entity Entity
Values []Attribute
}

// Attribute is one of Attributes' ids and its value.
type Attribute struct {
ID uint16
Value int32
}

// Height is an entity's height, from 46 37.
//
// Guess: the value is the Z of the entity's position, to the unit, in every sample.
type Height struct {
Entity Entity
Z float32
}

// Effect is something put on an entity, from 2A 38, and the change of one from 2B 38, a buff or
// a debuff.
//
// Guess: Instance counts up with each one, an effect's ID repeats, and what Value holds changes
// with it, 5000 or 300 or 200, which looks like its strength or its length. Time is when, and
// Source is who. The kinds seen are 01, 11 and 13: 02 adds a Skill and 10 adds a position.
type Effect struct {
Entity Entity
Kind byte
Instance uint32
ID uint32
Value uint32 // 0xFFFFFFFF with the permanent ones
Time time.Time
Source Entity
Skill Skill
Refresh bool // 2B 38
Pos
}

// Area is a skill that lands somewhere, from 03 38, and from 0E 38 with a second position.
//
// Guess: a mob's skill, with the player for its target, comes right after the Cast of a mob.
// The layout of what follows the position is not understood, and is not read.
type Area struct {
Actor, Target Entity
Skill Skill
Instance uint32
Pos
Pos2 Pos // 0E 38 only
}

// Cutscene starts a scripted scene for the player, from 21 36, at a position, and with the scene's name.
//
// Guess: its names are Cutscene_L_A_QM_1001 and Cutscene_L_A_Seq_1278, and 15 8D names the same one a moment later.
type Cutscene struct {
Kind uint32 // 1 to 5
ID uint32 // 100001 mostly
Name string // a name comes with some of them
Pos
}

// Script is a named trigger of the game's scripts, from 15 8D: a cutscene, a talk, or a step of the tutorial.
//
// Guess: its names are Cutscene_L_A_Seq_1273, Talk_L_Starter_InstanceLayer_1_08 and Tutorial_Basic_05.
type Script struct {
Flag, Kind byte // Kind is 09 for a cutscene, 13 for a talk, 2E for a tutorial
Name string
}

func (Ref) event() {}
func (Tagged) event() {}
func (Signal) event() {}
func (Target) event() {}
func (Combo) event() {}
func (SkillUse) event() {}
func (Windup) event() {}
func (Engage) event() {}
func (Threat) event() {}
func (Attributes) event() {}
func (Height) event() {}
func (Effect) event() {}
func (Area) event() {}
func (Cutscene) event() {}
func (Script) event() {}
9 changes: 8 additions & 1 deletion game/lobby.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,16 @@ func (d Redirect) Addr() (netip.AddrPort, bool) {
return netip.AddrPortFrom(ip, d.Port), err == nil
}

// Join is the server the client picked in the lobby, sent before the Redirect to it.
type Join struct {
Server uint16 // the ID in Servers, the same as the Redirect's
}

// Account is who logged in to the lobby.
type Account struct {
ID string // the account's number
Server uint16 // likely the one joined last, the ID in Servers
Faction byte // the side it plays, 1 for Light (Elyos) and 2 for Dark (Asmodians), as in Server.Faction
Session [2]string // two GUIDs, new at every login (unsure which one is which, or what they are used for)
}

Expand All @@ -98,7 +104,7 @@ type Character struct {
Level uint32 // the character's level
Exp uint32 // @TODO: not sure what this is, but it's sent for every character
Code uint32 // 5 to 44 so far, not read
Flags byte // 1, and 2 on one placeholder, not read
Flags byte // likely its side, 1 for Light (Elyos) and 2 for Dark (Asmodians) as in Server.Faction, not read
Mask byte // its two bits of the byte after every fourth character, not read
Time time.Time // likely when it was last played
}
Expand All @@ -109,5 +115,6 @@ func (c Character) String() string { return fmt.Sprintf("%d:%s/%d", c.Server, c.
func (Servers) event() {}
func (LobbyPing) event() {}
func (Redirect) event() {}
func (Join) event() {}
func (Account) event() {}
func (Characters) event() {}
Loading