Skip to content

Commit 3203ce5

Browse files
authored
Tool and grand exchange fixes (#1051)
* Fix map viewer zoom wobble #16 * Add map viewer coordinate setting #16 * Small render fixes * Fix grand exchange instant sell giving cash back Fix grand exchange ranges not syncing Fix not being able to buy and sell items without trade limits * Replace map dumper gouraud triangle renderer to fix tile rendering closes #710
1 parent 110d2d3 commit 3203ce5

7 files changed

Lines changed: 138 additions & 121 deletions

File tree

engine/src/main/kotlin/world/gregs/voidps/engine/data/exchange/OpenOffers.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@ class OpenOffers(
4242
}
4343
}
4444

45-
fun active(offer: ExchangeOffer): Boolean = (if (offer.sell) selling(offer.item) else buying(offer.item))[offer.price]?.any { it.id == offer.id } ?: false
45+
fun active(offer: ExchangeOffer): Boolean {
46+
val map = if (offer.sell) selling(offer.item) else buying(offer.item)
47+
return map[offer.price]?.any { it.id == offer.id } ?: false
48+
}
4649

4750
fun add(offer: ExchangeOffer) {
4851
add(offer.id, offer.item, offer.price, offer.state.sell)

game/src/main/kotlin/content/social/trade/exchange/GrandExchange.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ class GrandExchange(
205205
val marketPrice = history.marketPrice(offer.item)
206206
val instantPrice = marketPrice + (marketPrice * percent).toInt()
207207
if (offer.price >= instantPrice) {
208-
exchange(offer, pending.account, instantPrice, mutableListOf(OpenOffer(remaining = offer.amount, lastActive = 0)))
208+
exchange(offer, pending.account, offer.price, mutableListOf(OpenOffer(remaining = offer.amount, lastActive = 0)))
209209
break
210210
}
211211
}
@@ -240,6 +240,7 @@ class GrandExchange(
240240
} else {
241241
player.removeVarbit("grand_exchange_ranges", "slot_$index")
242242
}
243+
player.sendVariable("grand_exchange_ranges")
243244
val itemDef = ItemDefinitions.get(offer.item)
244245
player.client?.grandExchange(index, offer.state.int, itemDef.id, offer.price, offer.amount, offer.completed, offer.coins)
245246
}
@@ -253,9 +254,12 @@ class GrandExchange(
253254
val required = offer.amount - offer.completed
254255
val available = open.remaining
255256
var traded = if (required >= available) available else required
256-
traded = traded.coerceAtMost(limits.limit(if (offer.sell) open.account else account, offer.item))
257-
if (traded <= 0) {
258-
return false
257+
val limit = limits.limit(if (offer.sell) open.account else account, offer.item)
258+
if (limit != -1) {
259+
traded = traded.coerceAtMost(limit)
260+
if (traded <= 0) {
261+
return false
262+
}
259263
}
260264
// Update existing offer
261265
claim(offer.id, account, offer.item, traded, traderPrice, offer.price, offer.sell)

tools/src/main/kotlin/world/gregs/voidps/tools/map/render/draw/TileLevel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ class TileLevel(
257257
var ignore = false
258258
if (colours.size == 2 && vertexIndices1.size == 2 && (colours[0] == colours[1] || textures[0] != -1 && textures[0] == textures[1])) {
259259
ignore = true
260-
for (i in 1..1) {
260+
for (i in vertexIndices1.indices) {
261261
val xOffset = xOffsets[vertexIndices1[i]]
262262
val yOffset = yOffsets[vertexIndices1[i]]
263263
if (xOffset != 0 && xOffset != tileUnits || yOffset != 0 && yOffset != tileUnits) {

tools/src/main/kotlin/world/gregs/voidps/tools/map/render/raster/Raster.kt

Lines changed: 53 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,13 @@ import java.awt.Point
66
import java.awt.image.BufferedImage
77
import java.io.File
88
import javax.imageio.ImageIO
9-
import kotlin.math.abs
109
import kotlin.math.max
1110
import kotlin.math.min
1211

1312
class Raster(private val bi: BufferedImage) {
1413
val width: Int = bi.width
1514
val height: Int = bi.height
1615

17-
private var minX = 0
18-
private var minY = 0
19-
2016
fun get(x: Int, y: Int): Int = bi.getRGB(x, y)
2117

2218
fun set(x: Int, y: Int, value: Int) {
@@ -33,110 +29,67 @@ class Raster(private val bi: BufferedImage) {
3329
val minY = min(y1, min(y2, y3))
3430
val maxY = max(y1, max(y2, y3))
3531

36-
val used = Array(maxX - minX + 1) { BooleanArray(maxY - minY + 1) }
37-
38-
this.minX = minX
39-
this.minY = minY
40-
41-
bresenhamLine(used, x1, y1, x2, y2, colour1, colour2)
42-
bresenhamLine(used, x1, y1, x3, y3, colour1, colour3)
43-
bresenhamLine(used, x2, y2, x3, y3, colour2, colour3)
32+
val area = (x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1)
33+
if (area == 0) {
34+
return
35+
}
4436

45-
// Find right point
46-
for (y in minY until maxY) {
47-
for (x in minX until maxX) {
48-
if (used[x - this.minX][y - this.minY]) {
49-
val leftColour = get(x, y)
50-
for (rx in maxX downTo minX) {
51-
if (used[rx - this.minX][y - this.minY]) {
52-
bresenhamLine(used, x, y, rx, y, leftColour, get(rx, y))
53-
break
54-
}
37+
val exclude = if (area > 0) -1 else 1
38+
val bias1 = edgeBias(x2, y2, x3, y3, area, exclude)
39+
val bias2 = edgeBias(x3, y3, x1, y1, area, exclude)
40+
val bias3 = edgeBias(x1, y1, x2, y2, area, exclude)
41+
42+
val r1 = colour1 shr 16 and 0xff
43+
val g1 = colour1 shr 8 and 0xff
44+
val b1 = colour1 and 0xff
45+
val r2 = colour2 shr 16 and 0xff
46+
val g2 = colour2 shr 8 and 0xff
47+
val b2 = colour2 and 0xff
48+
val r3 = colour3 shr 16 and 0xff
49+
val g3 = colour3 shr 8 and 0xff
50+
val b3 = colour3 and 0xff
51+
52+
val areaF = area.toFloat()
53+
for (y in minY..maxY) {
54+
if (y !in 0..<height) {
55+
continue
56+
}
57+
for (x in minX..maxX) {
58+
if (x !in 0..<width) {
59+
continue
60+
}
61+
val e1 = (x3 - x2) * (y - y2) - (y3 - y2) * (x - x2)
62+
val e2 = (x1 - x3) * (y - y3) - (y1 - y3) * (x - x3)
63+
val e3 = (x2 - x1) * (y - y1) - (y2 - y1) * (x - x1)
64+
if (area > 0) {
65+
if (e1 + bias1 < 0 || e2 + bias2 < 0 || e3 + bias3 < 0) {
66+
continue
67+
}
68+
} else {
69+
if (e1 + bias1 > 0 || e2 + bias2 > 0 || e3 + bias3 > 0) {
70+
continue
5571
}
56-
break
5772
}
73+
val w1 = e1 / areaF
74+
val w2 = e2 / areaF
75+
val w3 = 1f - w1 - w2
76+
val r = (w1 * r1 + w2 * r2 + w3 * r3).toInt().coerceIn(0, 255)
77+
val g = (w1 * g1 + w2 * g2 + w3 * g3).toInt().coerceIn(0, 255)
78+
val b = (w1 * b1 + w2 * b2 + w3 * b3).toInt().coerceIn(0, 255)
79+
set(x, y, -16777216 or (r shl 16) or (g shl 8) or b)
5880
}
5981
}
6082
}
6183

62-
private fun bresenhamLine(used: Array<BooleanArray>, x0: Int, y0: Int, x1: Int, y1: Int, colour1: Int, colour2: Int) {
63-
var x0 = x0
64-
var y0 = y0
65-
66-
val deltaWidth = x1 - x0
67-
val deltaHeight = y1 - y0
68-
69-
var dx0 = 0
70-
var dy0 = 0
71-
var dx1 = 0
72-
var dy1 = 0
73-
74-
if (deltaWidth < 0) {
75-
dx0 = -1
76-
} else if (deltaWidth > 0) {
77-
dx0 = 1
78-
}
79-
if (deltaHeight < 0) {
80-
dy0 = -1
81-
} else if (deltaHeight > 0) {
82-
dy0 = 1
83-
}
84-
if (deltaWidth < 0) {
85-
dx1 = -1
86-
} else if (deltaWidth > 0) {
87-
dx1 = 1
88-
}
89-
90-
var longest = abs(deltaWidth)
91-
var shortest = abs(deltaHeight)
92-
93-
if (longest <= shortest) {
94-
longest = abs(deltaHeight)
95-
shortest = abs(deltaWidth)
96-
if (deltaHeight < 0) {
97-
dy1 = -1
98-
} else if (deltaHeight > 0) {
99-
dy1 = 1
100-
}
101-
dx1 = 0
102-
}
103-
var numerator = longest shr 1
104-
val red1 = colour1 shr 16 and 0xff
105-
val green1 = colour1 shr 8 and 0xff
106-
val blue1 = colour1 and 0xff
107-
val red2 = colour2 shr 16 and 0xff
108-
val green2 = colour2 shr 8 and 0xff
109-
val blue2 = colour2 and 0xff
110-
111-
val rStep = (red2 - red1).toFloat() / longest
112-
val gStep = (green2 - green1).toFloat() / longest
113-
val bStep = (blue2 - blue1).toFloat() / longest
114-
115-
for (i in 0..longest) {
116-
// Colour computation
117-
val r = (red1 + rStep * i).toDouble()
118-
val g = (green1 + gStep * i).toDouble()
119-
val b = (blue1 + bStep * i).toDouble()
120-
121-
// Pixel writing
122-
if (y0 > -1 && x0 > -1 && y0 < height && x0 < width) {
123-
val pixel = (abs(r).toInt() shl 16 or (abs(g).toInt() shl 8) or abs(b).toInt())
124-
if (pixel > 0) {
125-
set(x0, y0, -16777216 or pixel)
126-
}
127-
used[x0 - minX][y0 - minY] = true
128-
}
129-
130-
numerator += shortest
131-
if (numerator >= longest) {
132-
numerator -= longest
133-
x0 += dx0
134-
y0 += dy0
135-
} else {
136-
x0 += dx1
137-
y0 += dy1
138-
}
84+
private fun edgeBias(ax: Int, ay: Int, bx: Int, by: Int, area: Int, exclude: Int): Int {
85+
val dx = bx - ax
86+
val dy = by - ay
87+
val topLeft = if (area > 0) {
88+
dy < 0 || (dy == 0 && dx < 0)
89+
} else {
90+
dy > 0 || (dy == 0 && dx > 0)
13991
}
92+
return if (topLeft) 0 else exclude
14093
}
14194

14295
companion object {

tools/src/main/kotlin/world/gregs/voidps/tools/map/view/draw/MapView.kt

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,14 @@ class MapView : JPanel() {
4040
/*
4141
Offset from view 0, 0 to top left of world map
4242
*/
43-
var offsetX = 0
44-
var offsetY = 0
43+
private var offsetXd = 0.0
44+
private var offsetYd = 0.0
45+
var offsetX: Int
46+
get() = offsetXd.toInt()
47+
set(value) { offsetXd = value.toDouble() }
48+
var offsetY: Int
49+
get() = offsetYd.toInt()
50+
set(value) { offsetYd = value.toDouble() }
4551
var level = 0
4652
private set
4753

@@ -140,6 +146,10 @@ class MapView : JPanel() {
140146

141147
fun viewToImageY(viewY: Int) = viewY - offsetY
142148

149+
fun viewToImageXd(viewX: Int): Double = viewX - offsetXd
150+
151+
fun viewToImageYd(viewY: Int): Double = viewY - offsetYd
152+
143153
fun imageToViewX(imageX: Int) = imageX + offsetX
144154

145155
fun imageToViewY(imageY: Int) = imageY + offsetY
@@ -204,6 +214,23 @@ class MapView : JPanel() {
204214
update()
205215
}
206216

217+
/**
218+
* Aligns the viewport so that the map position currently under ([viewX], [viewY]) stays
219+
* fixed after a scale change. [preZoomImageX]/[preZoomImageY] are the exact (fractional)
220+
* image-space coordinates captured *before* the scale was updated (i.e. `viewX - offsetXd`).
221+
*
222+
* Uses doubles throughout to avoid the integer-division truncation that snaps the anchor
223+
* to a tile corner and causes drift across multiple zoom steps.
224+
*
225+
* Also refreshes the hover overlay at ([viewX], [viewY]) after updating the offset, so
226+
* the highlight doesn't bounce while the mouse is stationary.
227+
*/
228+
fun alignToImage(viewX: Int, viewY: Int, preZoomImageX: Double, preZoomImageY: Double, oldScale: Int) {
229+
offsetXd = viewX - preZoomImageX * scale / oldScale
230+
offsetYd = viewY - preZoomImageY * scale / oldScale
231+
update(viewX, viewY)
232+
}
233+
207234
fun offset(mapX: Int, mapY: Int, level: Int = 0) {
208235
offsetX += mapToImageX(mapX)
209236
offsetY += mapToImageY(mapY)

tools/src/main/kotlin/world/gregs/voidps/tools/map/view/interact/MouseZoom.kt

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,29 @@ class MouseZoom(private val view: MapView, private val type: ZoomType) : MouseWh
99
private set
1010

1111
override fun mouseWheelMoved(e: MouseWheelEvent) {
12-
val mapX = when (type) {
13-
ZoomType.Centre -> view.viewToMapX(view.getCentreX())
14-
ZoomType.Mouse -> view.viewToMapX(e.x)
12+
val anchorViewX = when (type) {
13+
ZoomType.Centre -> view.getCentreX()
14+
ZoomType.Mouse -> e.x
1515
}
16-
val mapY = when (type) {
17-
ZoomType.Centre -> view.viewToMapY(view.getCentreY())
18-
ZoomType.Mouse -> view.viewToMapY(e.y)
16+
val anchorViewY = when (type) {
17+
ZoomType.Centre -> view.getCentreY()
18+
ZoomType.Mouse -> e.y
1919
}
20+
val imageX = view.viewToImageXd(anchorViewX)
21+
val imageY = view.viewToImageYd(anchorViewY)
22+
2023
val offset = e.unitsToScroll.coerceIn(-1, 1)
2124
val previous = this.scale
2225
this.scale = (this.scale - offset).coerceIn(ZOOM_MIN, ZOOM_MAX)
2326
if (this.scale != previous) {
24-
view.updateZoom(e.x, e.y)
2527
when (type) {
26-
ZoomType.Centre -> view.centreOn(mapX, mapY)
27-
ZoomType.Mouse -> view.align(e.x, e.y, mapX, mapY)
28+
ZoomType.Centre -> {
29+
val mapX = (imageX / previous).toInt()
30+
val mapY = (imageY / previous).toInt()
31+
view.updateZoom(anchorViewX, anchorViewY)
32+
view.centreOn(mapX, mapY)
33+
}
34+
ZoomType.Mouse -> view.alignToImage(anchorViewX, anchorViewY, imageX, imageY, previous)
2835
}
2936
}
3037
}

tools/src/main/kotlin/world/gregs/voidps/tools/map/view/ui/OptionsPane.kt

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,30 @@ class OptionsPane(private val view: MapView) : JPanel() {
1010
private val tileY = JTextField("16383")
1111
private val tileLevel = JTextField("4")
1212

13+
// Guard flag: true while we are programmatically updating fields from a camera move,
14+
// so the ActionListeners don't fire and cause an infinite loop.
15+
private var updatingFromCamera = false
16+
1317
fun updatePosition(mapX: Int, mapY: Int, level: Int) {
14-
tileX.text = mapX.toString()
15-
tileY.text = mapY.toString()
16-
tileLevel.text = level.toString()
17-
region.text = Region.id(mapX / 64, mapY / 64).toString()
18+
updatingFromCamera = true
19+
try {
20+
tileX.text = mapX.toString()
21+
tileY.text = mapY.toString()
22+
tileLevel.text = level.toString()
23+
region.text = Region.id(mapX / 64, mapY / 64).toString()
24+
} finally {
25+
updatingFromCamera = false
26+
}
27+
}
28+
29+
private fun navigateToFields() {
30+
if (updatingFromCamera) return
31+
val x = tileX.text.toIntOrNull() ?: return
32+
val y = tileY.text.toIntOrNull() ?: return
33+
val lvl = tileLevel.text.toIntOrNull()?.coerceIn(0, 3) ?: return
34+
view.centreOn(x, view.flipMapY(y), lvl)
35+
view.updateLevel(lvl)
36+
region.text = Region.id(x / 64, y / 64).toString()
1837
}
1938

2039
init {
@@ -28,6 +47,10 @@ class OptionsPane(private val view: MapView) : JPanel() {
2847
}
2948
add(region)
3049

50+
tileX.addActionListener { navigateToFields() }
51+
tileY.addActionListener { navigateToFields() }
52+
tileLevel.addActionListener { navigateToFields() }
53+
3154
val coordinates = JPanel().apply {
3255
layout = BoxLayout(this, BoxLayout.X_AXIS)
3356
add(tileX)

0 commit comments

Comments
 (0)