fix(game): gate group visibility by visibility range, report battle classes
Bring the report's foreign-group and foreign-class visibility in line with the rules (game/rules.txt "Движение" and the report sections): - incoming groups (heading to one of the recipient's planets) are shown only within the recipient's visibility range (driveTech*30); beyond it a group is hidden even though it is inbound; - the unidentified-group list now uses the visibility range (it used the flight range, driveTech*40), excludes groups heading to the recipient's planets (those belong to the incoming list), and reports each group once (it previously emitted an entry per in-range owned planet); - ship classes met in a battle the recipient took part in or witnessed now appear in OtherShipClass, with the design looked up from the owner race's ship types (the battle report carries only the class name). The rules already describe this behaviour and the report wire shape is unchanged, so no documentation change. Tests added for all three. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -135,7 +135,7 @@ func (c *Cache) ReportRace(ri int, rep *mr.Report, battles []*mr.BattleReport, b
|
||||
|
||||
// ship classes
|
||||
c.ReportLocalShipClass(ri, rep)
|
||||
c.ReportOtherShipClass(ri, rep)
|
||||
c.ReportOtherShipClass(ri, rep, battles)
|
||||
|
||||
// battles
|
||||
c.ReportBattle(ri, rep, battles)
|
||||
@@ -249,7 +249,7 @@ func (c *Cache) ReportLocalShipClass(ri int, report *mr.Report) {
|
||||
slices.SortFunc(report.LocalShipClass, func(a, b mr.ShipClass) int { return cmp.Compare(a.Name, b.Name) })
|
||||
}
|
||||
|
||||
func (c *Cache) ReportOtherShipClass(ri int, rep *mr.Report) {
|
||||
func (c *Cache) ReportOtherShipClass(ri int, rep *mr.Report, battles []*mr.BattleReport) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
|
||||
@@ -273,26 +273,46 @@ func (c *Cache) ReportOtherShipClass(ri int, rep *mr.Report) {
|
||||
return false
|
||||
}
|
||||
|
||||
// add visible ship classes from battles
|
||||
// for bi := range battle {
|
||||
// for si := range battle[bi].Ships {
|
||||
// g := battle[bi].Ships[si]
|
||||
// if skip(g.OwnerID, g.ClassName) {
|
||||
// continue
|
||||
// }
|
||||
|
||||
// sliceIndexValidate(&rep.OtherShipClass, i)
|
||||
// rep.OtherShipClass[i].Race = c.g.Race[c.RaceIndex(g.OwnerID)].Name
|
||||
// rep.OtherShipClass[i].Name = g.ClassName
|
||||
// rep.OtherShipClass[i].Drive = g.DriveTech
|
||||
// rep.OtherShipClass[i].Armament = g.ClassArmament
|
||||
// rep.OtherShipClass[i].Weapons = g.WeaponsTech
|
||||
// rep.OtherShipClass[i].Shields = g.ShieldsTech
|
||||
// rep.OtherShipClass[i].Cargo = g.CargoTech
|
||||
// rep.OtherShipClass[i].Mass = g.ClassMass
|
||||
// i++
|
||||
// }
|
||||
// }
|
||||
// Ship classes seen in battles the recipient took part in or witnessed.
|
||||
// The battle report carries the class name and owner race; the class
|
||||
// design is looked up from that race's ship types, which stay present in
|
||||
// the state even though the groups themselves are deleted before reports
|
||||
// are generated.
|
||||
for bi := range battles {
|
||||
br := battles[bi]
|
||||
visible := false
|
||||
for k := range br.Races {
|
||||
if br.Races[k] == r.ID {
|
||||
visible = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !visible {
|
||||
continue
|
||||
}
|
||||
for si := range br.Ships {
|
||||
bg := br.Ships[si]
|
||||
ownerIdx, err := c.raceIndex(bg.Race)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ownerID := c.g.Race[ownerIdx].ID
|
||||
st, _, ok := c.ShipClass(ownerIdx, bg.ClassName)
|
||||
if !ok || skip(ownerID, st.Name) {
|
||||
continue
|
||||
}
|
||||
sliceIndexValidate(&rep.OtherShipClass, i)
|
||||
rep.OtherShipClass[i].Race = bg.Race
|
||||
rep.OtherShipClass[i].Name = st.Name
|
||||
rep.OtherShipClass[i].Drive = mr.F(st.Drive.F())
|
||||
rep.OtherShipClass[i].Armament = st.Armament
|
||||
rep.OtherShipClass[i].Weapons = mr.F(st.Weapons.F())
|
||||
rep.OtherShipClass[i].Shields = mr.F(st.Shields.F())
|
||||
rep.OtherShipClass[i].Cargo = mr.F(st.Cargo.F())
|
||||
rep.OtherShipClass[i].Mass = mr.F(st.EmptyMass())
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// add visible ships from owned and observed planets
|
||||
for pn := range rep.OnPlanetGroupCache {
|
||||
@@ -397,6 +417,22 @@ func (c *Cache) ReportIncomingGroup(ri int, rep *mr.Report) {
|
||||
if !p2.OwnedBy(r.ID) {
|
||||
continue
|
||||
}
|
||||
// Beyond the visibility range (driveTech*30) of every owned planet the
|
||||
// group is not shown at all, even though it heads to one of them.
|
||||
visible := false
|
||||
for pi := range c.g.Map.Planet {
|
||||
op := &c.g.Map.Planet[pi]
|
||||
if !op.OwnedBy(r.ID) {
|
||||
continue
|
||||
}
|
||||
if d, ok := rep.InSpaceGroupRangeCache[sgi][op.Number]; ok && d <= r.VisibilityDistance() {
|
||||
visible = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !visible {
|
||||
continue
|
||||
}
|
||||
|
||||
distance := calc.ShortDistance(c.g.Map.Width, c.g.Map.Height, p1.X.F(), p1.Y.F(), p2.X.F(), p2.Y.F())
|
||||
var speed, mass float64
|
||||
@@ -685,33 +721,44 @@ func (c *Cache) ReportOtherGroup(ri int, rep *mr.Report) {
|
||||
func (c *Cache) ReportUnidentifiedGroup(ri int, rep *mr.Report) {
|
||||
c.validateRaceIndex(ri)
|
||||
r := &c.g.Race[ri]
|
||||
flightDistance := r.FlightDistance()
|
||||
visibility := r.VisibilityDistance()
|
||||
|
||||
clear(rep.UnidentifiedGroup)
|
||||
|
||||
i := 0
|
||||
for sgi := range rep.InSpaceGroupRangeCache {
|
||||
sg := c.ShipGroup(sgi)
|
||||
if sg.OwnerID == rep.RaceID {
|
||||
if sg.OwnerID == r.ID {
|
||||
continue
|
||||
}
|
||||
if sg.StateInSpace == nil {
|
||||
panic(fmt.Sprintf("pre-calculated distance group not in space: i=%d", sgi))
|
||||
}
|
||||
// Groups heading to one of the recipient's planets are listed in full
|
||||
// under "incoming groups"; the unidentified list is for the rest.
|
||||
if c.MustPlanet(sg.Destination).OwnedBy(r.ID) {
|
||||
continue
|
||||
}
|
||||
// Shown once, and only within the visibility range (driveTech*30) of at
|
||||
// least one of the recipient's planets.
|
||||
visible := false
|
||||
for pi := range c.g.Map.Planet {
|
||||
p := &c.g.Map.Planet[pi]
|
||||
if !p.OwnedBy(r.ID) {
|
||||
continue
|
||||
}
|
||||
if v, ok := rep.InSpaceGroupRangeCache[sgi][p.Number]; !ok {
|
||||
panic(fmt.Sprintf("distance cache not pre-calculated: i=%d p=#%d", sgi, p.Number))
|
||||
} else if v <= flightDistance {
|
||||
sliceIndexValidate(&rep.UnidentifiedGroup, i)
|
||||
rep.UnidentifiedGroup[i].X = mr.F(sg.StateInSpace.X.F())
|
||||
rep.UnidentifiedGroup[i].Y = mr.F(sg.StateInSpace.Y.F())
|
||||
i++
|
||||
if v, ok := rep.InSpaceGroupRangeCache[sgi][p.Number]; ok && v <= visibility {
|
||||
visible = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !visible {
|
||||
continue
|
||||
}
|
||||
sliceIndexValidate(&rep.UnidentifiedGroup, i)
|
||||
rep.UnidentifiedGroup[i].X = mr.F(sg.StateInSpace.X.F())
|
||||
rep.UnidentifiedGroup[i].Y = mr.F(sg.StateInSpace.Y.F())
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user