// Returns the fraction of the segment that was visible func (l *Los) TestSeg(seg linear.Seg2) float64 { seg.P = seg.P.Sub(l.in.Pos) seg.Q = seg.Q.Sub(l.in.Pos) wrap := len(l.in.Buffer.ZBuffer) a1 := math.Atan2(seg.P.Y, seg.P.X) a2 := math.Atan2(seg.Q.Y, seg.Q.X) if a1 > a2 { a1, a2 = a2, a1 seg.P, seg.Q = seg.Q, seg.P } if a2-a1 > math.Pi { a1, a2 = a2, a1 seg.P, seg.Q = seg.Q, seg.P } start := int(((a1 / (2 * math.Pi)) + 0.5) * float64(len(l.in.Buffer.ZBuffer))) end := int(((a2 / (2 * math.Pi)) + 0.5) * float64(len(l.in.Buffer.ZBuffer))) count := 0.0 visible := 0.0 for i := start % wrap; i != end%wrap; i = (i + 1) % wrap { dist2 := float32(rays[i].Isect(seg).Mag2()) if dist2 < l.in.Buffer.ZBuffer[i] { visible += 1.0 } count += 1.0 } return visible / count }
func (l *Los) DrawSeg(seg linear.Seg2, source string) { seg.P = seg.P.Sub(l.in.Pos) seg.Q = seg.Q.Sub(l.in.Pos) wrap := len(l.in.Buffer.ZBuffer) a1 := math.Atan2(seg.P.Y, seg.P.X) a2 := math.Atan2(seg.Q.Y, seg.Q.X) if a1 > a2 { a1, a2 = a2, a1 seg.P, seg.Q = seg.Q, seg.P } if a2-a1 > math.Pi { a1, a2 = a2, a1 seg.P, seg.Q = seg.Q, seg.P } start := int(((a1 / (2 * math.Pi)) + 0.5) * float64(len(l.in.Buffer.ZBuffer))) end := int(((a2 / (2 * math.Pi)) + 0.5) * float64(len(l.in.Buffer.ZBuffer))) for i := start % wrap; i != end%wrap; i = (i + 1) % wrap { dist2 := float32(rays[i].Isect(seg).Mag2()) // dist = rays[i].Isect(seg).Mag2() if dist2 < l.in.Buffer.ZBuffer[i] { l.in.Buffer.ZBuffer[i] = dist2 l.in.Buffer.SBuffer[i] = source } } }
func (p *lightningBoltProc) Think(g *game.Game) { p.NumThinks++ if p.NumThinks < p.BuildThinks { return } perp := p.Seg.Ray().Cross().Norm().Scale(p.Width / 2) for _, ent := range g.Ents { entSeg := linear.Seg2{ ent.Pos().Sub(perp), ent.Pos().Add(perp), } if entSeg.DoesIsect(p.Seg) { ent.Stats().ApplyDamage(stats.Damage{stats.DamageFire, p.Dps * p.Power}) } } // for _, ent := range g.Ents { // if ent.Pos().Sub(p.Pos).Mag2() <= p.CurrentRadius*p.CurrentRadius { // ent.Stats().ApplyDamage(stats.Damage{stats.DamageFire, p.Dps}) // } // } }
func fireDoLine(c *cmwc.Cmwc, pos linear.Vec2, angle, stored float64, speed int, level *game.Level) fireExplosion { rng := rand.New(c) ray := (linear.Vec2{1, 0}) // ray.Scale(math.Abs(rng.NormFloat64()/10) + 50) scale := (stored/5 + 50) * (1 + rng.Float64()*(0.2+stored/2000)) ray = ray.Rotate(angle).Rotate(rng.NormFloat64() * (0.2 + stored/7500)).Scale(scale) seg := linear.Seg2{pos, pos.Add(ray)} base.DoOrdered(level.Room.Walls, func(a, b string) bool { return a < b }, func(_ string, poly linear.Poly) { for i := range poly { if seg.DoesIsect(poly.Seg(i)) { isect := seg.Isect(poly.Seg(i)) seg.Q = isect } } }) p1 := rng.Intn(speed) p2 := rng.Intn(speed) p3 := rng.Intn(speed) return fireExplosion{ Pos: seg.Q, Radius: rng.Float64()*40 + 30, Timer: 0, Start: 1*speed + p1, Peak: 4*speed + p1 + p2, End: 5*speed + p1 + p2 + p3, } }
func (e addRiftWalkFireEvent) Apply(_g interface{}) { g := _g.(*game.Game) player, ok := g.Ents[e.PlayerGid].(*game.PlayerEnt) if !ok { return } proc := player.Processes[100+e.ProcessId] if proc == nil { return } rwProc, ok := proc.(*riftWalkProcess) if !ok { return } if rwProc.Stored.Magnitude() <= rwProc.Threshold { return } dist, radius := rwProc.GetVals() rwProc.Stored = game.Mana{} movement := (linear.Vec2{dist, 0}).Rotate(player.Angle) dest := player.Pos().Add(movement) inside := false // Check if the player would end up inside a polygon for _, poly := range g.Levels[player.Level()].Room.Walls { if linear.VecInsideConvexPoly(dest, poly) { inside = true break } } // If the player would end up inside a polygon then intersect the segment the // player would move along with all line segements and move the player to the // nearest intersection point. if inside { moveSeg := linear.Seg2{player.Pos(), dest} closest := dest var collisionSeg linear.Seg2 for _, poly := range g.Levels[player.Level()].Room.Walls { for i := range poly { seg := poly.Seg(i) if seg.DoesIsect(moveSeg) { isect := seg.Isect(moveSeg) if isect.Sub(player.Pos()).Mag2() < closest.Sub(player.Pos()).Mag2() { closest = isect collisionSeg = seg } } } } if collisionSeg.Ray().Mag2() == 0 { dest = closest.Sub(movement.Norm().Scale(player.Stats().Size() * 2)) } else { nudge := collisionSeg.Ray().Norm().Cross().Scale(player.Stats().Size()) dest = closest.Add(nudge) } } for _, ent := range g.Ents { if ent == player { continue } doDamage := false if ent.Pos().Sub(dest).Mag() <= radius+ent.Stats().Size() { angle := dest.Sub(ent.Pos()).Angle() ent.ApplyForce((linear.Vec2{-rwProc.Force, 0}).Rotate(angle)) doDamage = true } else { ray := dest.Sub(player.Pos()) perp := ray.Cross().Norm() scaledPerp := perp.Scale(ent.Stats().Size() + player.Stats().Size()) sideRay0 := linear.Seg2{player.Pos().Add(scaledPerp), dest.Add(scaledPerp)} sideRay1 := linear.Seg2{player.Pos().Sub(scaledPerp), dest.Sub(scaledPerp)} if sideRay0.Left(ent.Pos()) != sideRay1.Left(ent.Pos()) { // We know the ent lies between sideRays 0 and 1, now we need to make // sure it lies between src and dst. forward := ent.Pos().Sub(dest) backward := ent.Pos().Sub(player.Pos()) if (forward.Dot(ray) < 0) != (backward.Dot(ray) < 0) { if (linear.Seg2{player.Pos(), dest}).Left(ent.Pos()) { ent.ApplyForce(perp.Scale(rwProc.Force)) } else { ent.ApplyForce(perp.Scale(-rwProc.Force)) } doDamage = true } } } if doDamage { ent.Stats().ApplyDamage(stats.Damage{stats.DamageFire, 50}) } } player.SetPos(dest) }
func GenerateRoom(dx, dy, radius float64, grid int, seed int64) Room { var room Room room.Walls = make(map[string]linear.Poly) nextIdInt = 0 room.Dx = int(dx) room.Dy = int(dy) c := cmwc.MakeGoodCmwc() if seed == 0 { c.SeedWithDevRand() n := c.Int63() c = cmwc.MakeGoodCmwc() base.Log().Printf("SEED: %v", n) c.Seed(n) } else { c.Seed(seed) } sanity := int(math.Sqrt(dx * dy)) r := rand.New(c) var poss []linear.Vec2 for sanity > 0 { pos := linear.Vec2{r.Float64() * (dx - radius + 1), r.Float64() * (dy - radius + 1)} good := true for _, p := range poss { if p.Sub(pos).Mag() < 2*(2*radius) { good = false break } } if !good { sanity-- continue } poss = append(poss, pos) } // Find the pair of points that maximizes distance maxDist := 0.0 for i := range poss { for j := range poss { dist := poss[i].Sub(poss[j]).Mag() if dist > maxDist { maxDist = dist } } } var a, b int minDist := maxDist * 3 / 4 hits := 1.0 for i := range poss { for j := range poss { if i == j { continue } dist := poss[i].Sub(poss[j]).Mag() if dist > minDist && r.Float64() < 1.0/hits { a, b = i, j hits = hits + 1 } } } room.Starts = []linear.Vec2{poss[a], poss[b]} for _, start := range room.Starts { var data mobaRoomSideData data.Base = start room.Moba.SideData = append(room.Moba.SideData, data) } var data mobaRoomSideData for i, pos := range poss { if i == a || i == b { continue } data.Towers = append(data.Towers, pos) } room.Moba.SideData = append(room.Moba.SideData, data) sanity = int(math.Pow(dx*dy, 0.20)) var segs []linear.Seg2 for sanity > 0 { a := linear.Vec2{r.Float64() * (dx), r.Float64() * (dy)} length := gridify(r.Float64()*radius+(radius), grid) angle := float64(r.Intn(4)) * 3.1415926535 / 2 ray := (linear.Vec2{1, 0}).Rotate(angle) seg := linear.Seg2{a, a.Add(ray.Scale(length))} seg.P.X = gridify(seg.P.X, grid) seg.P.Y = gridify(seg.P.Y, grid) seg.Q.X = gridify(seg.Q.X, grid) seg.Q.Y = gridify(seg.Q.Y, grid) good := true if seg.P.X <= 0 || seg.P.X >= dx || seg.P.Y <= 0 || seg.P.Y >= dy { good = false } if seg.Q.X <= 0 || seg.Q.X >= dx || seg.Q.Y <= 0 || seg.Q.Y >= dy { good = false } if seg.P.X == seg.Q.X && seg.P.Y == seg.Q.Y { good = false } // Can't get too close to a circle for _, p := range poss { if distFromPointToSeg(p, seg) < radius/2 { good = false break } } // Check to make sure this segment isn't coincident with any othe segment. // To avoid annoying degeneracies we'll rotate the segment slightly. rot := linear.Seg2{seg.P, seg.Ray().Rotate(0.01).Add(seg.P)} for _, cur := range segs { if rot.DoesIsect(cur) { good = false break } } if !good { sanity-- continue } segs = append(segs, seg) } for _, s := range segs { right := s.Ray().Cross().Norm().Scale(-float64(grid)) s2 := linear.Seg2{s.Q.Add(right), s.P.Add(right)} room.Walls[nextId()] = linear.Poly{s.P, s.Q, s2.P, s2.Q} } room.Walls[nextId()] = linear.Poly{ linear.Vec2{0, 0}, linear.Vec2{dx, 0}, linear.Vec2{dx, dy}, linear.Vec2{0, dy}, } room.NextId = nextIdInt return room }
func distFromPointToSeg(p linear.Vec2, s linear.Seg2) float64 { s.P = s.P.Sub(p) s.Q = s.Q.Sub(p) cross := s.Ray().Cross() crossSeg := linear.Seg2{Q: cross} if crossSeg.Left(s.P) != crossSeg.Left(s.Q) { return s.DistFromOrigin() } da := s.P.Mag() db := s.Q.Mag() if da < db { return da } return db }
func (b *BaseEnt) Think(g *Game) { // This will clear out old conditions b.StatsInst.Think() var dead []int // Calling DoOrdered is too slow, so we just sort the Gids ourselves and go // through them in order. pids := make([]int, len(b.Processes))[0:0] for pid := range b.Processes { pids = append(pids, pid) } sort.Ints(pids) for _, pid := range pids { proc := b.Processes[pid] proc.Think(g) if proc.Dead() { dead = append(dead, pid) } else { b.StatsInst.ApplyCondition(proc) } } // Removed dead processes from the ent for _, id := range dead { delete(b.Processes, id) } if b.Delta.Speed < -1.0 { b.Delta.Speed = -1.0 } if b.Delta.Speed > 1.0 { b.Delta.Speed = 1.0 } // TODO: Speed is a complete misnomer now - fix it! force := b.Delta.Speed * (linear.Vec2{1, 0}).Rotate(b.Target.Angle).Dot((linear.Vec2{1, 0}).Rotate(b.Angle_)) b.ApplyForce((linear.Vec2{1, 0}).Rotate(b.Angle_).Scale(force * b.Stats().MaxAcc())) mangle := math.Atan2(b.Velocity.Y, b.Velocity.X) friction := g.Friction b.Velocity = b.Velocity.Scale( math.Pow(friction, 1+3*math.Abs(math.Sin(b.Angle_-mangle)))) if b.Velocity.Mag2() < 0.01 { b.Velocity = linear.Vec2{0, 0} } else { size := b.Stats().Size() sizeSq := size * size // We pretend that the player is started from a little behind wherever they // actually are. This makes it a lot easier to get collisions to make sense // from frame to frame. epsilon := b.Velocity.Norm().Scale(size / 2) move := linear.Seg2{b.Position.Sub(epsilon), b.Position.Add(b.Velocity)} prev := b.Position walls := g.local.temp.WallCache.GetWalls(int(b.Position.X), int(b.Position.Y)) for _, wall := range walls { // Don't bother with back-facing segments if wall.Right(b.Position) { continue } // Check against the segment itself if wall.Ray().Cross().Dot(move.Ray()) <= 0 { shiftNorm := wall.Ray().Cross().Norm() shift := shiftNorm.Scale(size) col := linear.Seg2{shift.Add(wall.P), shift.Add(wall.Q)} if move.DoesIsect(col) { cross := col.Ray().Cross() fix := linear.Seg2{move.Q, cross.Add(move.Q)} isect := fix.Isect(col) move.Q = isect } } } for _, wall := range walls { // Check against the leading vertex { v := wall.P originMove := linear.Seg2{move.P.Sub(v), move.Q.Sub(v)} originPerp := linear.Seg2{linear.Vec2{}, move.Ray().Cross()} dist := originMove.DistFromOrigin() if originPerp.DoesIsect(originMove) && dist < size { // Stop passthrough isect := originMove.Isect(originPerp).Add(v) diff := math.Sqrt(sizeSq - dist*dist) finalLength := isect.Sub(move.P).Mag() - diff move.Q = move.Ray().Norm().Scale(finalLength).Add(move.P) } else if v.Sub(move.Q).Mag2() < sizeSq { move.Q = move.Q.Sub(v).Norm().Scale(size).Add(v) } } } b.Position = move.Q b.Velocity = b.Position.Sub(prev) } if math.Abs(b.Angle_+b.Target.Angle-math.Pi) < 0.01 { b.Angle_ += 0.1 } else { frac := 0.80 curDir := (linear.Vec2{1, 0}).Rotate(b.Angle_).Scale(frac) targetDir := (linear.Vec2{1, 0}).Rotate(b.Target.Angle).Scale(1.0 - frac) newDir := curDir.Add(targetDir) if newDir.Mag() > 0.01 { b.Angle_ = newDir.Angle() } } }
func (p *Player) Think(g *Game) { if p.Exile_frames > 0 { p.Exile_frames-- return } // This will clear out old conditions p.Stats.Think() var dead []int for i, process := range p.Processes { process.Think(g) if process.Phase() == PhaseComplete { dead = append(dead, i) } } for _, i := range dead { delete(p.Processes, i) } // And here we add back in all processes that are still alive. for _, process := range p.Processes { p.Stats.ApplyCondition(process) } if p.Delta.Speed > p.Stats.MaxAcc() { p.Delta.Speed = p.Stats.MaxAcc() } if p.Delta.Speed < -p.Stats.MaxAcc() { p.Delta.Speed = -p.Stats.MaxAcc() } if p.Delta.Angle < -p.Stats.MaxTurn() { p.Delta.Angle = -p.Stats.MaxTurn() } if p.Delta.Angle > p.Stats.MaxTurn() { p.Delta.Angle = p.Stats.MaxTurn() } in_lava := false for _, lava := range g.Room.Lava { if vecInsideConvexPoly(p.Pos(), lava) { in_lava = true } } if in_lava { p.Stats.ApplyDamage(stats.Damage{stats.DamageFire, 5}) } p.Vx += p.Delta.Speed * math.Cos(p.Angle) p.Vy += p.Delta.Speed * math.Sin(p.Angle) mangle := math.Atan2(p.Vy, p.Vx) friction := g.Friction if in_lava { friction = g.Friction_lava } p.Vx *= math.Pow(friction, 1+3*math.Abs(math.Sin(p.Angle-mangle))) p.Vy *= math.Pow(friction, 1+3*math.Abs(math.Sin(p.Angle-mangle))) move := linear.MakeSeg2(p.X, p.Y, p.X+p.Vx, p.Y+p.Vy) size := 12.0 px := p.X py := p.Y p.X += p.Vx p.Y += p.Vy for _, poly := range g.Room.Walls { for i := range poly { // First check against the leading vertex { v := poly[i] dist := v.DistToLine(move) if v.Sub(move.Q).Mag() < size { dist = v.Sub(move.Q).Mag() // Add a little extra here otherwise a player can sneak into geometry // through the corners ray := move.Q.Sub(v).Norm().Scale(size + 0.1) final := v.Add(ray) move.Q.X = final.X move.Q.Y = final.Y } else if dist < size { // TODO: This tries to prevent passthrough but has other problems // cross := move.Ray().Cross() // perp := linear.Seg2{v, cross.Sub(v)} // if perp.Left(move.P) != perp.Left(move.Q) { // shift := perp.Ray().Norm().Scale(size - dist) // move.Q.X += shift.X // move.Q.Y += shift.Y // } } } // Now check against the segment itself w := poly.Seg(i) if w.Ray().Cross().Dot(move.Ray()) <= 0 { shift := w.Ray().Cross().Norm().Scale(size) col := linear.Seg2{shift.Add(w.P), shift.Add(w.Q)} if move.DoesIsect(col) { cross := col.Ray().Cross() fix := linear.Seg2{move.Q, cross.Add(move.Q)} isect := fix.Isect(col) move.Q.X = isect.X move.Q.Y = isect.Y } } } } p.X = move.Q.X p.Y = move.Q.Y p.Vx = p.X - px p.Vy = p.Y - py p.Angle += p.Delta.Angle if p.Angle < 0 { p.Angle += math.Pi * 2 } if p.Angle > math.Pi*2 { p.Angle -= math.Pi * 2 } p.Delta.Angle = 0 p.Delta.Speed = 0 }