package sim import ( "halcyon/config" "halcyon/fixed" "halcyon/world" ) // tickCtx is per-tick shared context; asteroid positions are computed lazily // and cached because several ships may query them in one tick. type tickCtx struct { s *State cfg config.Config tick int now int64 stPos fixed.Vec stVel fixed.Vec res *DayResult sold *[world.NumOre]int64 astPos, astVel []fixed.Vec astDone []bool } func newTickCtx(s *State, cfg config.Config, tick int, now int64, stPos, stVel fixed.Vec, res *DayResult, sold *[world.NumOre]int64) *tickCtx { n := len(s.Asteroids) return &tickCtx{s: s, cfg: cfg, tick: tick, now: now, stPos: stPos, stVel: stVel, res: res, sold: sold, astPos: make([]fixed.Vec, n), astVel: make([]fixed.Vec, n), astDone: make([]bool, n)} } func (tc *tickCtx) asteroid(i int) (fixed.Vec, fixed.Vec) { if !tc.astDone[i] { tc.astPos[i], tc.astVel[i] = tc.s.Asteroids[i].Orbit.State(tc.now) tc.astDone[i] = true } return tc.astPos[i], tc.astVel[i] } // shipBus is one ship's view of its peripherals for one tick. type shipBus struct { tc *tickCtx sh *Ship } func sat32(v int64) int32 { if v > 1<<31-1 { return 1<<31 - 1 } if v < -1<<31 { return -1 << 31 } return int32(v) } func kmInt(f fixed.F) int32 { return sat32(f.Floor()) } // mps converts km/s to whole m/s. func mps(f fixed.F) int32 { return sat32(f.MulInt(1000).Floor()) } func (b *shipBus) target() (pos, vel fixed.Vec, ok bool) { id := b.sh.Target if id < 1 || id > int64(len(b.tc.s.Asteroids)) { return pos, vel, false } pos, vel = b.tc.asteroid(int(id - 1)) return pos, vel, true } // axis returns component (port-base) of v (0=X, 1=Y, 2=Z) using conv, for // ports laid out as three consecutive X, Y, Z registers. func axis(v fixed.Vec, port, base uint16, conv func(fixed.F) int32) int32 { switch port - base { case 0: return conv(v.X) case 1: return conv(v.Y) } return conv(v.Z) } func (b *shipBus) In(port uint16) int32 { sh, tc := b.sh, b.tc switch { case port == PortTurn: return int32(tc.tick) case port == PortDay: return sat32(tc.s.Day) case port == PortTurns: return int32(tc.cfg.TicksPerDay) case port >= PortPosX && port <= PortPosZ: return axis(sh.Pos, port, PortPosX, kmInt) case port >= PortVelX && port <= PortVelZ: return axis(sh.Vel, port, PortVelX, mps) case port == PortFuel: return sat32(sh.Fuel.Floor()) case port == PortMass: return sat32(tc.s.shipMass(sh).Floor()) case port == PortScanNearest: scanRange := fixed.FromInt(ScanRangeKm) best, bestD := int64(0), fixed.Max for i := range tc.s.Asteroids { p, _ := tc.asteroid(i) if d := p.Sub(sh.Pos).Len(); d <= scanRange && d < bestD { best, bestD = int64(i+1), d } } return int32(best) case port >= PortScanRelX && port <= PortScanDist: p, v, ok := b.target() if !ok { return 0 } rel, relV := p.Sub(sh.Pos), v.Sub(sh.Vel) if rel.Len() > fixed.FromInt(ScanRangeKm) { return 0 } switch { case port <= PortScanRelZ: return axis(rel, port, PortScanRelX, kmInt) case port <= PortScanRelVZ: return axis(relV, port, PortScanRelVX, mps) } return kmInt(rel.Len()) case port >= PortScanOre && port < PortScanOre+8: if a := tc.s.asteroid(sh.Target); a != nil && port-PortScanOre < uint16(world.NumOre) { if p, _, _ := b.target(); p.Sub(sh.Pos).Len() > fixed.FromInt(ScanRangeKm) { return 0 } return sat32(a.Ore[port-PortScanOre]) } return 0 case port == PortConts: return int32(tc.s.towOf(sh.ID).Count) case port == PortContKg: return sat32(tc.s.towOf(sh.ID).OreTotal) case port == PortContRoom: return sat32(tc.s.towOf(sh.ID).Room) case port == PortContMass: return sat32(tc.s.towOf(sh.ID).Mass) case port == PortNearby: return int32(tc.s.nearbyFree(sh)) case port >= PortContOre && port < PortContOre+8: if port-PortContOre < uint16(world.NumOre) { return sat32(tc.s.towOf(sh.ID).Ore[port-PortContOre]) } return 0 case port >= PortStnRelX && port <= PortStnRelVZ: rel, relV := tc.stPos.Sub(sh.Pos), tc.stVel.Sub(sh.Vel) if port <= PortStnRelZ { return axis(rel, port, PortStnRelX, kmInt) } return axis(relV, port, PortStnRelVX, mps) case port == PortCredits: return sat32(sh.Earned) case port == PortUplinkNew: if sh.UplinkNew { return 1 } return 0 case port == PortUplinkLen: return sh.UplinkLen case port == PortMathAtan2: a := fixed.Atan2(fixed.FromInt(int64(sh.MathY)), fixed.FromInt(int64(sh.MathX))) return sat32(a.MulInt(1000).Floor()) case port == PortMathHypot: return sat32(fixed.Hypot(fixed.FromInt(int64(sh.MathX)), fixed.FromInt(int64(sh.MathY))).Floor()) case port == PortMathNorm3: return sat32(fixed.Norm3(fixed.FromInt(int64(sh.MathX)), fixed.FromInt(int64(sh.MathY)), fixed.FromInt(int64(sh.MathZ))).Floor()) } return 0 } func (b *shipBus) Out(port uint16, v int32) { sh, tc := b.sh, b.tc switch port { case PortThrottle: sh.Throttle = v case PortAzimuth: sh.Azimuth = v case PortPitch: sh.Pitch = v case PortScanSelect: sh.Target = int64(v) case PortMine: sh.Mining = v != 0 case PortAttach: if v != 0 { tc.s.attach(sh, tc) } case PortDetach: if v != 0 { tc.s.detach(sh, tc) } case PortOrder: tc.s.order(sh, int(v), tc) case PortUplinkNew: sh.UplinkNew = false case PortMathX: sh.MathX = v case PortMathY: sh.MathY = v case PortMathZ: sh.MathZ = v } } func itoa(v int64) string { if v == 0 { return "0" } neg := v < 0 if neg { v = -v } var b [20]byte i := len(b) for v > 0 { i-- b[i] = byte('0' + v%10) v /= 10 } if neg { i-- b[i] = '-' } return string(b[i:]) }