diff --git a/game/player.go b/game/player.go index 177142c..49b9565 100644 --- a/game/player.go +++ b/game/player.go @@ -5,6 +5,7 @@ import ( _ "embed" "image" _ "image/png" + "math" "github.com/hajimehoshi/ebiten/v2" "github.com/moltenwolfcub/Forest-Game/assets" @@ -57,15 +58,17 @@ func (p Player) DrawAt(screen *ebiten.Image, pos image.Point) { func (p Player) Overlaps(layer GameContext, other []image.Rectangle) bool { return DefaultHitboxOverlaps(layer, p, other) } -func (p Player) Origin(GameContext) image.Point { - return p.hitbox.Min +func (p Player) Origin(layer GameContext) image.Point { + bounds := p.findBounds(layer) + return bounds.Min } -func (p Player) Size(GameContext) image.Point { - return p.hitbox.Size() +func (p Player) Size(layer GameContext) image.Point { + bounds := p.findBounds(layer) + return bounds.Size() } func (p Player) GetHitbox(layer GameContext) []image.Rectangle { switch layer { - case Collision: + case Collision, Interaction: baseSize := p.hitbox.Size().Y / 2 playerRect := image.Rectangle{ @@ -82,6 +85,19 @@ func (p Player) GetHitbox(layer GameContext) []image.Rectangle { } } +func (p Player) findBounds(layer GameContext) image.Rectangle { + minX, minY := math.MaxFloat64, math.MaxFloat64 + maxX, maxY := -math.MaxFloat64, -math.MaxFloat64 + for _, seg := range p.GetHitbox(layer) { + minX = math.Min(float64(seg.Min.X), minX) + minY = math.Min(float64(seg.Min.Y), minY) + maxX = math.Max(float64(seg.Max.X), maxX) + maxY = math.Max(float64(seg.Max.Y), maxY) + } + bounds := image.Rect(int(minX), int(minY), int(maxX), int(maxY)) + return bounds +} + func (p Player) GetZ() int { return 0 } @@ -97,22 +113,123 @@ func (p *Player) Update(collidables []HasHitbox, climbables []Climbable, rivers func (p *Player) handleInteractions(interactables []HasHitbox) { if p.RiverJumping { - var objectToJump HasHitbox = nil - for _, c := range interactables { - if p.Overlaps(Interaction, c.GetHitbox(Interaction)) { - objectToJump = c - break - } + newPos, found := p.GetSmallestJump(interactables) + + if found { + p.hitbox = p.hitbox.Sub(p.hitbox.Min).Add(newPos) + offset := p.Origin(Collision).Y - p.hitbox.Min.Y + p.hitbox = p.hitbox.Sub(image.Pt(0, offset)) } - if objectToJump == nil { - return + } +} + +func (p Player) GetSmallestJump(jumpables []HasHitbox) (point image.Point, found bool) { + origin := p.Origin(Collision) + size := p.Size(Collision) + + smallestJumpDist := math.MaxFloat64 + smallestJump := image.Point{} + for _, jumpable := range jumpables { + if !jumpable.Overlaps(Interaction, p.GetHitbox(Collision)) { + continue } - //for now jump to top corner will need to properly re-implement at some point - newPos := objectToJump.Origin(Collision).Sub(image.Point{p.hitbox.Dx(), p.hitbox.Dy()}) + for id, seg := range jumpable.GetHitbox(Interaction) { + if !p.Overlaps(Collision, []image.Rectangle{seg}) { + continue + } + segHitbox := jumpable.GetHitbox(Collision)[id] + + if origin.X >= segHitbox.Max.X { //right + newPoint := testJump(jumpable, segHitbox, + func(segment image.Rectangle) image.Point { + return image.Pt(segment.Min.X, origin.Y).Sub(image.Pt(size.X, 0)) + }, + func(origin image.Point) image.Rectangle { + return image.Rectangle{origin, origin.Add(size)} + }, + ) + + updateJumpIfSmaller(origin, newPoint, &smallestJumpDist, &smallestJump) + } else if origin.X <= segHitbox.Min.X { //left + newPoint := testJump(jumpable, segHitbox, + func(segment image.Rectangle) image.Point { + return image.Pt(segment.Max.X, origin.Y) + }, + func(origin image.Point) image.Rectangle { + return image.Rectangle{origin, origin.Add(size)} + }, + ) + + updateJumpIfSmaller(origin, newPoint, &smallestJumpDist, &smallestJump) + } + + if origin.Y >= segHitbox.Max.Y { //bottom + newPoint := testJump(jumpable, segHitbox, + func(segment image.Rectangle) image.Point { + return image.Pt(origin.X, segment.Min.Y).Sub(image.Pt(0, size.Y)) + }, + func(origin image.Point) image.Rectangle { + return image.Rectangle{origin, origin.Add(size)} + }, + ) + + updateJumpIfSmaller(origin, newPoint, &smallestJumpDist, &smallestJump) + } else if origin.Y <= segHitbox.Min.Y { //top + newPoint := testJump(jumpable, segHitbox, + func(segment image.Rectangle) image.Point { + return image.Pt(origin.X, segment.Max.Y) + }, + func(origin image.Point) image.Rectangle { + return image.Rectangle{origin, origin.Add(size)} + }, + ) + + updateJumpIfSmaller(origin, newPoint, &smallestJumpDist, &smallestJump) + } + } + } + return smallestJump, smallestJumpDist != math.MaxFloat64 +} + +// Finds the location the player would jump to taking into account multiple +// segments that might need to be jumped. If the player would land in another +// segment it continues to test further jumps on that new segment. +// +// Once that new land location is found it gets returned. +// +// makeJump returns the origin of the new player hitbox after jumping the given +// segment. This function handles how the jump should be made (what direction) +// +// pRect generates a version of the player's hitbox to test for collisions after +// each jump without actually moving the player yet. It should just return a hitbox +// of the player's size with it's origin at the provided point. +func testJump(fullObj HasHitbox, jumpSeg image.Rectangle, makeJump func(image.Rectangle) image.Point, pRect func(image.Point) image.Rectangle) image.Point { + newPoint := makeJump(jumpSeg) + + newRect := pRect(newPoint) + for fullObj.Overlaps(Collision, []image.Rectangle{newRect}) { + for _, newSegTest := range fullObj.GetHitbox(Collision) { + if !newRect.Overlaps(newSegTest) { + continue + } + newPoint = makeJump(newSegTest) + break + } + newRect = pRect(newPoint) + } + return newPoint +} - p.hitbox = p.hitbox.Sub(p.hitbox.Min).Add(newPos) +// Calculates jump distance between points before and new and updates the +// pointers with the new distance and point respectively if the new distance +// is smaller than the previous smallest. +func updateJumpIfSmaller(before image.Point, new image.Point, dist *float64, point *image.Point) { + delta := math.Hypot(float64(before.X-new.X), float64(before.Y-new.Y)) + if delta < *dist { + *point = new + *dist = delta } }