implement comma separated rules for packet parser

This commit is contained in:
Sky Johnson 2025-07-26 15:09:11 -05:00
parent fcb6a82135
commit 35fc2b667b
2 changed files with 148 additions and 8 deletions

View File

@ -68,6 +68,37 @@ StatTypes []uint8 `eq2:"int8,len=5"`
StatValues []any `eq2:"int32,type2=float,type2criteria=stat_type_%i!=6"` StatValues []any `eq2:"int32,type2=float,type2criteria=stat_type_%i!=6"`
``` ```
### Comma-separated conditions
Multiple variables can be checked in a single condition using comma-separated lists:
```go
// Parse only if NONE of the listed variables are set
NumEffects uint8 `eq2:"int8,ifvariablenotset=header_info_header_unknown_0_0,header_unknown_0"`
// Parse if ANY of the listed variables are set
BonusData []byte `eq2:"char,len=20,ifvariableset=has_bonus,has_special,has_extra"`
// Parse if ANY of the listed flags are set
OptionalField uint32 `eq2:"int32,ifflag=debug_mode,test_mode,dev_mode"`
// Parse if ALL of the listed flags are NOT set
ProductionData []byte `eq2:"char,len=50,ifflagnotset=debug_mode,test_mode"`
// Multiple equals conditions (ANY must be true)
TypeData any `eq2:"int32,ifequals=type=1,category=special"`
// Multiple not-equals conditions (ALL must be true)
Value uint16 `eq2:"int16,ifnotequals=status=disabled,flag=hidden"`
```
**Comma-separated logic rules:**
- `ifvariableset` - TRUE if ANY variable is set
- `ifvariablenotset` - TRUE if ALL variables are NOT set
- `ifflag` - TRUE if ANY flag is set
- `ifflagnotset` - TRUE if ALL flags are NOT set
- `ifequals` - TRUE if ANY condition matches
- `ifnotequals` - TRUE if ALL conditions are true (none match)
### Type switching (when EQ2 reuses the same bytes for different things) ### Type switching (when EQ2 reuses the same bytes for different things)
```go ```go
// Normally parse as int32, but if StatType != 6, parse as float instead // Normally parse as int32, but if StatType != 6, parse as float instead
@ -135,6 +166,12 @@ type CharacterData struct {
Buffs []BuffData `eq2:"array,arraysize=BuffCount"` Buffs []BuffData `eq2:"array,arraysize=BuffCount"`
// Only parse extended data if first buff exists // Only parse extended data if first buff exists
ExtendedBuffData []byte `eq2:"char,len=20,ifvariableset=buffs_0"` ExtendedBuffData []byte `eq2:"char,len=20,ifvariableset=buffs_0"`
// Comma-separated conditions example
HeaderFlags uint8 `eq2:"int8"`
// Parse effects only if neither unknown field is set
NumEffects uint8 `eq2:"int8,ifvariablenotset=header_info_header_unknown_0_0,header_unknown_0"`
Effects []EffectData `eq2:"array,arraysize=NumEffects"`
} }
type InventoryItem struct { type InventoryItem struct {
@ -157,6 +194,11 @@ type BuffData struct {
BuffID uint32 `eq2:"int32"` BuffID uint32 `eq2:"int32"`
Duration uint16 `eq2:"int16"` Duration uint16 `eq2:"int16"`
} }
type EffectData struct {
Effect common.EQ2String16 `eq2:"string16"`
Percentage uint8 `eq2:"int8"`
}
``` ```
## Advanced conditional patterns ## Advanced conditional patterns
@ -179,6 +221,14 @@ type ComplexPacket struct {
PlayerName string `eq2:"string16"` PlayerName string `eq2:"string16"`
ShortName string `eq2:"string8,if=player_name!<=8"` ShortName string `eq2:"string8,if=player_name!<=8"`
LongDesc string `eq2:"string32,if=player_name!>15"` LongDesc string `eq2:"string32,if=player_name!>15"`
// Comma-separated multi-condition examples
DebugInfo []byte `eq2:"char,len=100,ifflag=debug_mode,test_mode,dev_mode"`
ProdData []byte `eq2:"char,len=50,ifflagnotset=debug_mode,test_mode,dev_mode"`
// Multiple variable checks
OptionalData []byte `eq2:"char,len=20,ifvariableset=has_optional,has_extended"`
CleanupData []byte `eq2:"char,len=10,ifvariablenotset=dirty_flag,temp_flag,cache_flag"`
} }
``` ```
@ -191,6 +241,8 @@ If you've got EQ2's XML packet definitions, the conversion is pretty straightfor
| `Type="int32"` | `eq2:"int32"` | | `Type="int32"` | `eq2:"int32"` |
| `ArraySizeVariable="count"` | `arraysize=Count` | | `ArraySizeVariable="count"` | `arraysize=Count` |
| `IfVariableSet="flag"` | `ifvariableset=Flag` | | `IfVariableSet="flag"` | `ifvariableset=Flag` |
| `IfVariableNotSet="var1,var2"` | `ifvariablenotset=var1,var2` |
| `IfFlag="flag1,flag2"` | `ifflag=flag1,flag2` |
| `Size="5"` | `len=5` | | `Size="5"` | `len=5` |
| `Type2Criteria="field!=value"` | `type2criteria=Field!=value` | | `Type2Criteria="field!=value"` | `type2criteria=Field!=value` |
| `Type2Criteria="name!>10"` | `type2criteria=name!>10` | | `Type2Criteria="name!>10"` | `type2criteria=name!>10` |

View File

@ -12,32 +12,30 @@ func (p *Parser) evaluateAllConditions(fieldTag *FieldTag, _ reflect.Value) bool
return false return false
} }
if fieldTag.IfVariableSet != "" && !p.isVariableSet(fieldTag.IfVariableSet) { if fieldTag.IfVariableSet != "" && !p.evaluateVariableSetCondition(fieldTag.IfVariableSet) {
return false return false
} }
if fieldTag.IfVariableNotSet != "" && p.isVariableSet(fieldTag.IfVariableNotSet) { if fieldTag.IfVariableNotSet != "" && !p.evaluateVariableNotSetCondition(fieldTag.IfVariableNotSet) {
return false return false
} }
if fieldTag.IfFlag != "" && !p.flags[fieldTag.IfFlag] { if fieldTag.IfFlag != "" && !p.evaluateFlagCondition(fieldTag.IfFlag) {
return false return false
} }
if fieldTag.IfFlagNotSet != "" && p.flags[fieldTag.IfFlagNotSet] { if fieldTag.IfFlagNotSet != "" && !p.evaluateFlagNotSetCondition(fieldTag.IfFlagNotSet) {
return false return false
} }
if fieldTag.IfEquals != "" { if fieldTag.IfEquals != "" {
parts := strings.Split(fieldTag.IfEquals, "=") if !p.evaluateEqualsConditions(fieldTag.IfEquals) {
if len(parts) == 2 && !p.evaluateEqualsCondition(parts[0], parts[1]) {
return false return false
} }
} }
if fieldTag.IfNotEquals != "" { if fieldTag.IfNotEquals != "" {
parts := strings.Split(fieldTag.IfNotEquals, "=") if !p.evaluateNotEqualsConditions(fieldTag.IfNotEquals) {
if len(parts) == 2 && p.evaluateEqualsCondition(parts[0], parts[1]) {
return false return false
} }
} }
@ -45,6 +43,96 @@ func (p *Parser) evaluateAllConditions(fieldTag *FieldTag, _ reflect.Value) bool
return true return true
} }
// evaluateVariableSetCondition handles comma-separated variable set conditions
func (p *Parser) evaluateVariableSetCondition(variables string) bool {
varList := p.splitCommaSeparated(variables)
// Return true if ANY variable is set
for _, variable := range varList {
if p.isVariableSet(variable) {
return true
}
}
return false
}
// evaluateVariableNotSetCondition handles comma-separated variable not set conditions
func (p *Parser) evaluateVariableNotSetCondition(variables string) bool {
varList := p.splitCommaSeparated(variables)
// Return true if ALL variables are not set
for _, variable := range varList {
if p.isVariableSet(variable) {
return false
}
}
return true
}
// evaluateFlagCondition handles comma-separated flag conditions
func (p *Parser) evaluateFlagCondition(flags string) bool {
flagList := p.splitCommaSeparated(flags)
// Return true if ANY flag is set
for _, flag := range flagList {
if p.flags[flag] {
return true
}
}
return false
}
// evaluateFlagNotSetCondition handles comma-separated flag not set conditions
func (p *Parser) evaluateFlagNotSetCondition(flags string) bool {
flagList := p.splitCommaSeparated(flags)
// Return true if ALL flags are not set
for _, flag := range flagList {
if p.flags[flag] {
return false
}
}
return true
}
// evaluateEqualsConditions handles comma-separated equals conditions
func (p *Parser) evaluateEqualsConditions(conditions string) bool {
conditionList := p.splitCommaSeparated(conditions)
// Return true if ANY condition is true
for _, condition := range conditionList {
parts := strings.Split(condition, "=")
if len(parts) == 2 && p.evaluateEqualsCondition(strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])) {
return true
}
}
return false
}
// evaluateNotEqualsConditions handles comma-separated not equals conditions
func (p *Parser) evaluateNotEqualsConditions(conditions string) bool {
conditionList := p.splitCommaSeparated(conditions)
// Return true if ALL conditions are true (all not equal)
for _, condition := range conditionList {
parts := strings.Split(condition, "=")
if len(parts) == 2 {
if p.evaluateEqualsCondition(strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])) {
return false // This condition is false (they are equal)
}
}
}
return true
}
// splitCommaSeparated splits a comma-separated string and trims whitespace
func (p *Parser) splitCommaSeparated(input string) []string {
if input == "" {
return []string{}
}
parts := strings.Split(input, ",")
result := make([]string, len(parts))
for i, part := range parts {
result[i] = strings.TrimSpace(part)
}
return result
}
// evaluateCondition evaluates conditions for conditional fields // evaluateCondition evaluates conditions for conditional fields
func (p *Parser) evaluateCondition(condition *FieldCondition) bool { func (p *Parser) evaluateCondition(condition *FieldCondition) bool {
if condition.Type == "simple" { if condition.Type == "simple" {