From 292dc40fd1a6c5b732706dc0bd137cdb62b6e1cc Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 29 Jun 2026 12:48:45 +0100 Subject: [PATCH 01/15] add infinity builtin var --- src/vm_builtins.c | 3 +++ src/vm_builtins.h | 1 + 2 files changed, 4 insertions(+) diff --git a/src/vm_builtins.c b/src/vm_builtins.c index 8969b5e5..89ad82d3 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -340,6 +340,7 @@ static const BuiltinVarEntry BUILTIN_VAR_TABLE[] = { { "image_speed", BUILTIN_VAR_IMAGE_SPEED }, { "image_xscale", BUILTIN_VAR_IMAGE_XSCALE }, { "image_yscale", BUILTIN_VAR_IMAGE_YSCALE }, + { "infinity", BUILTIN_VAR_INFINITY }, { "instance_count", BUILTIN_VAR_INSTANCE_COUNT }, { "instance_id", BUILTIN_VAR_INSTANCE_ID }, { "keyboard_key", BUILTIN_VAR_KEYBOARD_KEY }, @@ -1114,6 +1115,8 @@ RValue VMBuiltins_getVariable(VMContext* ctx, Instance* inst, int16_t builtinVar return RValue_makeBool(false); case BUILTIN_VAR_PI: return RValue_makeReal(3.14159265358979323846); + case BUILTIN_VAR_INFINITY: + return RValue_makeReal(INFINITY); case BUILTIN_VAR_UNDEFINED: return RValue_makeUndefined(); diff --git a/src/vm_builtins.h b/src/vm_builtins.h index 184236ad..c94ba125 100644 --- a/src/vm_builtins.h +++ b/src/vm_builtins.h @@ -196,6 +196,7 @@ typedef enum { // Constants BUILTIN_VAR_TRUE, BUILTIN_VAR_FALSE, + BUILTIN_VAR_INFINITY, BUILTIN_VAR_PI, BUILTIN_VAR_UNDEFINED, From a55f6f03ba82d16f544d5a8aa7f037640e25feda Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 29 Jun 2026 14:17:29 +0100 Subject: [PATCH 02/15] more shit used by ch5, tilemap_set is brokey --- src/collision.h | 12 ++- src/vm_builtins.c | 226 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 236 insertions(+), 2 deletions(-) diff --git a/src/collision.h b/src/collision.h index c266ccfc..2ee041bc 100644 --- a/src/collision.h +++ b/src/collision.h @@ -233,8 +233,9 @@ static inline bool Collision_circleOverlapsInstance(Runner* runner, Instance* in return dx * dx + dy * dy <= rSq; } -// Liang-Barsky clip of a parametric line p(t) = p1 + t*(p2-p1), t in [0,1], against an axis-aligned rect [rx1,rx2] x [ry1,ry2]. Returns true if the segment intersects the rect. -static inline bool Collision_segmentVsAARect(GMLReal x1, GMLReal y1, GMLReal x2, GMLReal y2, GMLReal rx1, GMLReal ry1, GMLReal rx2, GMLReal ry2) { +// Liang-Barsky clip of a parametric line p(t) = p1 + t*(p2-p1), t in [0,1], against an axis-aligned rect [rx1,rx2] x [ry1,ry2]. +// Returns true if the segment intersects the rect, and writes the clipped parametric range to *outTEnter/*outTExit. +static inline bool Collision_segmentVsAARectClip(GMLReal x1, GMLReal y1, GMLReal x2, GMLReal y2, GMLReal rx1, GMLReal ry1, GMLReal rx2, GMLReal ry2, GMLReal* outTEnter, GMLReal* outTExit) { GMLReal tEnter = 0.0, tExit = 1.0; GMLReal dx = x2 - x1, dy = y2 - y1; GMLReal p[4] = { -dx, dx, -dy, dy }; @@ -252,9 +253,16 @@ static inline bool Collision_segmentVsAARect(GMLReal x1, GMLReal y1, GMLReal x2, } if (tEnter > tExit) return false; } + *outTEnter = tEnter; + *outTExit = tExit; return true; } +static inline bool Collision_segmentVsAARect(GMLReal x1, GMLReal y1, GMLReal x2, GMLReal y2, GMLReal rx1, GMLReal ry1, GMLReal rx2, GMLReal ry2) { + GMLReal tEnter, tExit; + return Collision_segmentVsAARectClip(x1, y1, x2, y2, rx1, ry1, rx2, ry2, &tEnter, &tExit); +} + // Line segment (x1,y1)-(x2,y2) vs instance collision rect. static inline bool Collision_lineOverlapsInstance(Runner* runner, Instance* inst, GMLReal x1, GMLReal y1, GMLReal x2, GMLReal y2) { InstanceBBox bbox = Collision_computeBBox(runner, inst); diff --git a/src/vm_builtins.c b/src/vm_builtins.c index 89ad82d3..7423d365 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -2604,6 +2604,15 @@ static RValue builtin_tan(MAYBE_UNUSED VMContext* ctx, RValue* args, int32_t arg return RValue_makeReal(GMLReal_tan(RValue_toReal(args[0]))); } +static RValue builtin_dot_product(MAYBE_UNUSED VMContext* ctx, RValue* args, int32_t argCount) { + if (4 > argCount) return RValue_makeReal(0.0); + GMLReal x1 = RValue_toReal(args[0]); + GMLReal y1 = RValue_toReal(args[1]); + GMLReal x2 = RValue_toReal(args[2]); + GMLReal y2 = RValue_toReal(args[3]); + return RValue_makeReal(x1 * x2 + y1 * y2); +} + static RValue builtin_point_distance(MAYBE_UNUSED VMContext* ctx, RValue* args, int32_t argCount) { if (4 > argCount) return RValue_makeReal(0.0); GMLReal dx = RValue_toReal(args[2]) - RValue_toReal(args[0]); @@ -11362,6 +11371,92 @@ static RValue builtin_collision_circle(VMContext* ctx, RValue* args, int32_t arg return RValue_makeReal((GMLReal) resultId); } +static RValue builtin_collision_line_list(VMContext* ctx, RValue* args, int32_t argCount) { + if (9 > argCount) return RValue_makeReal(0.0); + + Runner* runner = ctx->runner; + GMLReal x1 = RValue_toReal(args[0]); + GMLReal y1 = RValue_toReal(args[1]); + GMLReal x2 = RValue_toReal(args[2]); + GMLReal y2 = RValue_toReal(args[3]); + int32_t target = VM_resolveInstanceTarget(ctx, RValue_toInt32(args[4])); + int32_t prec = RValue_toInt32(args[5]); + int32_t notme = RValue_toInt32(args[6]); + int32_t listId = RValue_toInt32(args[7]); + // arg 8 (ordered) ignored here too; appended in iteration order + + if (target == INSTANCE_NOONE) return RValue_makeReal(0.0); + DsList* list = dsListGet(runner, listId); + if (list == nullptr) return RValue_makeReal(0.0); + + if (runner->collisionCompatibilityMode) { + x1 = compatRoundCoord(x1); y1 = compatRoundCoord(y1); + x2 = compatRoundCoord(x2); y2 = compatRoundCoord(y2); + } + + GMLReal bx1 = GMLReal_fmin(x1, x2); + GMLReal by1 = GMLReal_fmin(y1, y2); + GMLReal bx2 = GMLReal_fmax(x1, x2); + GMLReal by2 = GMLReal_fmax(y1, y2); + + Instance* self = ctx->currentInstance; + int32_t count = 0; + + SpatialGrid_syncGrid(runner, runner->spatialGrid); + SpatialGridQuery query = SpatialGrid_prepareQuery(runner, bx1, by1, bx2, by2, target); + + for (int32_t gx = query.range.minGridX; query.range.maxGridX >= gx; gx++) { + for (int32_t gy = query.range.minGridY; query.range.maxGridY >= gy; gy++) { + Instance** cell = runner->spatialGrid->grid[SpatialGrid_cellIndex(runner->spatialGrid, gx, gy)]; + int32_t cellLen = (int32_t) arrlen(cell); + repeat(cellLen, ci) { + Instance* inst = cell[ci]; + if (!inst->active) continue; + if (notme && inst == self) continue; + if (inst->lastCollisionQueryId == query.queryId) continue; + inst->lastCollisionQueryId = query.queryId; + + if (!query.matchAll && query.filterByObject && !VM_isObjectOrDescendant(ctx->dataWin, inst->objectIndex, target)) continue; + if (!query.matchAll && query.filterByInstanceId && inst->instanceId != (uint32_t) target) continue; + + InstanceBBox bbox = Collision_computeBBox(ctx->runner, inst); + + GMLReal tEnter, tExit; + if (!Collision_segmentVsAARectClip(x1, y1, x2, y2, bbox.left, bbox.top, bbox.right, bbox.bottom, &tEnter, &tExit)) continue; + + if (prec != 0) { + Sprite* spr = Collision_getSprite(ctx->dataWin, inst); + if (Collision_hasFrameMasks(spr)) { + // Walk only the portion of the segment that's actually + // inside the bbox (clipped by tEnter/tExit), stepping + // roughly one pixel at a time, checking the mask. + GMLReal dx = x2 - x1; + GMLReal dy = y2 - y1; + GMLReal segLen = GMLReal_sqrt(dx * dx + dy * dy); + int32_t steps = (int32_t) GMLReal_ceil(segLen * (tExit - tEnter)); + if (steps < 1) steps = 1; + + bool found = false; + for (int32_t s = 0; s <= steps && !found; s++) { + GMLReal t = tEnter + (tExit - tEnter) * ((GMLReal) s / (GMLReal) steps); + GMLReal px = x1 + dx * t; + GMLReal py = y1 + dy * t; + found = Collision_pointInInstance(spr, inst, px, py); + } + if (!found) continue; + } + } + + arrput(list->items, RValue_makeReal((GMLReal) inst->instanceId)); + count++; + } + } + } + + return RValue_makeReal((GMLReal) count); +} + + // collision_rectangle_list(x1, y1, x2, y2, obj, prec, notme, list, ordered) -> count static RValue builtin_collision_rectangle_list(VMContext* ctx, RValue* args, int32_t argCount) { if (8 > argCount) return RValue_makeReal(0.0); @@ -11446,6 +11541,105 @@ static RValue builtin_collision_rectangle_list(VMContext* ctx, RValue* args, int return RValue_makeReal((GMLReal) count); } +// collision_circle_list(x, y, radius, obj, prec, notme, list, ordered) -> count +static RValue builtin_collision_circle_list(VMContext* ctx, RValue* args, int32_t argCount) { + if (8 > argCount) return RValue_makeReal(0.0); + + Runner* runner = ctx->runner; + GMLReal cx = RValue_toReal(args[0]); + GMLReal cy = RValue_toReal(args[1]); + GMLReal radius = RValue_toReal(args[2]); + int32_t targetObjIndex = VM_resolveInstanceTarget(ctx, RValue_toInt32(args[3])); + int32_t prec = RValue_toInt32(args[4]); + int32_t notme = RValue_toInt32(args[5]); + int32_t listId = RValue_toInt32(args[6]); + + if (targetObjIndex == INSTANCE_NOONE) return RValue_makeReal(0.0); + DsList* list = dsListGet(runner, listId); + if (list == nullptr) return RValue_makeReal(0.0); + if (0 > radius) radius = -radius; + GMLReal radiusSq = radius * radius; + + Instance* self = ctx->currentInstance; + if (runner->collisionCompatibilityMode) { + GMLReal qx1r = compatRoundCoord(cx - radius); + GMLReal qy1r = compatRoundCoord(cy - radius); + GMLReal qx2r = compatRoundCoord(cx + radius); + GMLReal qy2r = compatRoundCoord(cy + radius); + cx = (qx1r + qx2r) * 0.5; + cy = (qy1r + qy2r) * 0.5; + // Genuine collision_circle has qx2-qx1 == qy2-qy1 == 2r; use the smaller in case of rounding asymmetry. + GMLReal rx = (qx2r - qx1r) * 0.5; + GMLReal ry = (qy2r - qy1r) * 0.5; + radius = rx < ry ? rx : ry; + radiusSq = radius * radius; + } + + GMLReal qx1 = cx - radius; + GMLReal qy1 = cy - radius; + GMLReal qx2 = cx + radius; + GMLReal qy2 = cy + radius; + + SpatialGrid_syncGrid(runner, runner->spatialGrid); + SpatialGridQuery query = SpatialGrid_prepareQuery(runner, qx1, qy1, qx2, qy2, targetObjIndex); + + int32_t count = 0; + for (int32_t gx = query.range.minGridX; query.range.maxGridX >= gx; gx++) { + for (int32_t gy = query.range.minGridY; query.range.maxGridY >= gy; gy++) { + Instance** cell = runner->spatialGrid->grid[SpatialGrid_cellIndex(runner->spatialGrid, gx, gy)]; + int32_t cellLen = (int32_t) arrlen(cell); + repeat(cellLen, ci) { + Instance* inst = cell[ci]; + if (!inst->active) continue; + if (notme && inst == self) continue; + if (inst->lastCollisionQueryId == query.queryId) continue; + inst->lastCollisionQueryId = query.queryId; + + if (!query.matchAll && query.filterByObject && !VM_isObjectOrDescendant(ctx->dataWin, inst->objectIndex, targetObjIndex)) continue; + if (!query.matchAll && query.filterByInstanceId && inst->instanceId != (uint32_t) targetObjIndex) continue; + + if (!Collision_circleOverlapsInstance(ctx->runner, inst, cx, cy, radius)) continue; + + if (prec != 0) { + Sprite* spr = Collision_getSprite(ctx->dataWin, inst); + if (Collision_hasFrameMasks(spr)) { + InstanceBBox bbox = Collision_computeBBox(ctx->runner, inst); + GMLReal iLeft = GMLReal_fmax(qx1, bbox.left); + GMLReal iRight = GMLReal_fmin(qx2, bbox.right); + GMLReal iTop = GMLReal_fmax(qy1, bbox.top); + GMLReal iBottom = GMLReal_fmin(qy2, bbox.bottom); + + bool found = false; + int32_t startX = (int32_t) GMLReal_floor(iLeft); + int32_t endX = (int32_t) GMLReal_ceil(iRight); + int32_t startY = (int32_t) GMLReal_floor(iTop); + int32_t endY = (int32_t) GMLReal_ceil(iBottom); + + for (int32_t py = startY; endY > py && !found; py++) { + for (int32_t px = startX; endX > px && !found; px++) { + GMLReal wpx = (GMLReal) px + 0.5; + GMLReal wpy = (GMLReal) py + 0.5; + GMLReal ddx = wpx - cx; + GMLReal ddy = wpy - cy; + if (ddx * ddx + ddy * ddy > radiusSq) continue; + if (Collision_pointInInstance(spr, inst, wpx, wpy)) { + found = true; + } + } + } + if (!found) continue; + } + } + + arrput(list->items, RValue_makeReal((GMLReal) inst->instanceId)); + count++; + } + } + } + + return RValue_makeReal((GMLReal) count); +} + // collision_point(x, y, obj, prec, notme) static RValue builtin_collision_point(VMContext* ctx, RValue* args, int32_t argCount) { if (5 > argCount) return RValue_makeReal((GMLReal) INSTANCE_NOONE); @@ -13461,6 +13655,34 @@ static RValue builtin_tilemap_get_at_pixel(VMContext* ctx, RValue* args, MAYBE_U return RValue_makeReal((GMLReal) cell); } +static RValue builtin_tilemap_set(VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t argCount) { + if (4 > argCount) return RValue_makeBool(false); + Runner* runner = ctx->runner; + RuntimeLayer* runtimeLayer = nullptr; + RoomLayerTilesData* data = findTilemapData(runner, RValue_toInt32(args[0]), &runtimeLayer); + requireNotNullMessage(runtimeLayer, "Missing Runtime Layer! Bug?"); + + int32_t cellX = RValue_toInt32(args[2]); + int32_t cellY = RValue_toInt32(args[3]); + + if (cellX < 0 || cellY < 0 || cellX >= (int32_t)data->tilesX || cellY >= (int32_t)data->tilesY) { + return RValue_makeBool(false); + } + + Background* tileset = &runner->dataWin->bgnd.backgrounds[data->backgroundIndex]; + + uint32_t cell = (uint32_t) RValue_toInt32(args[1]); + uint32_t tileIndex = (cell >> 0) & TILEINDEX_SHIFTEDMASK; + if (tileset != nullptr && tileset->gms2TileCount != 0 && tileIndex >= tileset->gms2TileCount) { + fprintf(stderr, "VM: [%s] tilemap_set() - tile index outside tile set count\n", ctx->currentCodeName); + return RValue_makeBool(false); + } + + int32_t cellIndex = (cellY * data->tilesX) + cellX; + data->tileData[cellIndex] = cell; + return RValue_makeBool(true); +} + // tilemap_set_at_pixel(tilemapElementId, tiledata, x, y): writes the raw tile cell value at the given room-space pixel coordinate. Returns whether the write happened. // (see GameMaker-HTML5 Function_Layers.js) static RValue builtin_tilemap_set_at_pixel(VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t argCount) { @@ -15606,6 +15828,7 @@ void VMBuiltins_registerAll(VMContext* ctx) { VM_registerBuiltin(ctx, "clamp", builtin_clamp); VM_registerBuiltin(ctx, "lerp", builtin_lerp); VM_registerBuiltin(ctx, "tan", builtin_tan); + VM_registerBuiltin(ctx, "dot_product", builtin_dot_product); VM_registerBuiltin(ctx, "point_distance", builtin_point_distance); VM_registerBuiltin(ctx, "point_in_rectangle", builtin_point_in_rectangle); VM_registerBuiltin(ctx, "point_in_circle", builtin_point_in_circle); @@ -16266,7 +16489,9 @@ void VMBuiltins_registerAll(VMContext* ctx) { VM_registerBuiltin(ctx, "place_free", builtin_place_free); VM_registerBuiltin(ctx, "place_empty", builtin_place_empty); if (isGMS2) { + VM_registerBuiltin(ctx, "collision_line_list", builtin_collision_line_list); VM_registerBuiltin(ctx, "collision_rectangle_list", builtin_collision_rectangle_list); + VM_registerBuiltin(ctx, "collision_circle_list", builtin_collision_circle_list); VM_registerBuiltin(ctx, "instance_place_list", builtin_instance_place_list); } @@ -16350,6 +16575,7 @@ void VMBuiltins_registerAll(VMContext* ctx) { VM_registerBuiltin(ctx, "tile_get_flip", builtin_tile_get_flip); VM_registerBuiltin(ctx, "tile_get_rotate", builtin_tile_get_rotate); VM_registerBuiltin(ctx, "tile_set_empty", builtin_tile_set_empty); + VM_registerBuiltin(ctx, "tilemap_set", builtin_tilemap_set); VM_registerBuiltin(ctx, "tilemap_set_at_pixel", builtin_tilemap_set_at_pixel); #endif VM_registerBuiltin(ctx, "layer_create", builtin_layer_create); From d5fe236aa7dcb0bd44f293b3242e18bf1a247132 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 29 Jun 2026 15:56:41 +0100 Subject: [PATCH 03/15] that one function with the long ass name --- src/vm_builtins.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vm_builtins.c b/src/vm_builtins.c index 7423d365..c8a3cdfe 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -13678,8 +13678,7 @@ static RValue builtin_tilemap_set(VMContext* ctx, RValue* args, MAYBE_UNUSED int return RValue_makeBool(false); } - int32_t cellIndex = (cellY * data->tilesX) + cellX; - data->tileData[cellIndex] = cell; + data->tileData[coerceTileCellsToTilemapBoundsAndConvertToArrayIndex(cellX, cellY, data)] = cell; return RValue_makeBool(true); } From 1f68d0461e9b6d5cc3692715570845b246a650ad Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 29 Jun 2026 15:57:05 +0100 Subject: [PATCH 04/15] whoops --- src/vm_builtins.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vm_builtins.c b/src/vm_builtins.c index c8a3cdfe..438919b1 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -13678,7 +13678,7 @@ static RValue builtin_tilemap_set(VMContext* ctx, RValue* args, MAYBE_UNUSED int return RValue_makeBool(false); } - data->tileData[coerceTileCellsToTilemapBoundsAndConvertToArrayIndex(cellX, cellY, data)] = cell; + data->tileData[coerceTileCellsToTilemapBoundsAndConvertToArrayIndex(data, cellX, cellY)] = cell; return RValue_makeBool(true); } From afcebb2b06bfb6d748216ea2b46a13251f732ea7 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Mon, 29 Jun 2026 16:03:47 +0100 Subject: [PATCH 05/15] layer_script_whatever, they're kinda stubby for now but --- src/runner.h | 2 ++ src/vm_builtins.c | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/runner.h b/src/runner.h index c6c4796e..1ac5b12f 100644 --- a/src/runner.h +++ b/src/runner.h @@ -244,6 +244,8 @@ typedef struct { float vSpeed; bool dynamic; // true = created at runtime via layer_create char* dynamicName; // owned + int32_t beginScript; + int32_t endScript; RuntimeLayerElement* elements; // stb_ds array } RuntimeLayer; diff --git a/src/vm_builtins.c b/src/vm_builtins.c index 438919b1..c0886822 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -12925,6 +12925,30 @@ static RValue builtin_layer_destroy(VMContext* ctx, RValue* args, MAYBE_UNUSED i return RValue_makeUndefined(); } +static RValue builtin_layer_script_begin(VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t argCount) { + int32_t layerId = resolveLayerIdArg(ctx->runner, args[0]); + int32_t scriptIndex = RValue_toInt32(args[1]); + + RuntimeLayer* runtimeLayer = Runner_findRuntimeLayerById(ctx->runner, layerId); + if (runtimeLayer == nullptr) return RValue_makeUndefined(); + + runtimeLayer->beginScript = scriptIndex; + + return RValue_makeUndefined(); +} + +static RValue builtin_layer_script_end(VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t argCount) { + int32_t layerId = resolveLayerIdArg(ctx->runner, args[0]); + int32_t scriptIndex = RValue_toInt32(args[1]); + + RuntimeLayer* runtimeLayer = Runner_findRuntimeLayerById(ctx->runner, layerId); + if (runtimeLayer == nullptr) return RValue_makeUndefined(); + + runtimeLayer->endScript = scriptIndex; + + return RValue_makeUndefined(); +} + static RValue builtin_layer_background_create(VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t argCount) { Runner* runner = ctx->runner; int32_t layerId = resolveLayerIdArg(runner, args[0]); @@ -16579,6 +16603,8 @@ void VMBuiltins_registerAll(VMContext* ctx) { #endif VM_registerBuiltin(ctx, "layer_create", builtin_layer_create); VM_registerBuiltin(ctx, "layer_destroy", builtin_layer_destroy); + VM_registerBuiltin(ctx, "layer_script_begin", builtin_layer_script_begin); + VM_registerBuiltin(ctx, "layer_script_end", builtin_layer_script_end); VM_registerBuiltin(ctx, "layer_background_create", builtin_layer_background_create); VM_registerBuiltin(ctx, "layer_background_exists", builtin_layer_background_exists); VM_registerBuiltin(ctx, "layer_background_visible", builtin_layer_background_visible); From c51eee6fed0438e88c969ca12e645eb33e110bc3 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Tue, 30 Jun 2026 12:37:38 +0100 Subject: [PATCH 06/15] sprite_set_bbox_mode --- src/collision.h | 16 ++++++++-------- src/vm_builtins.c | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/collision.h b/src/collision.h index 2ee041bc..eeec9080 100644 --- a/src/collision.h +++ b/src/collision.h @@ -36,10 +36,10 @@ static inline InstanceBBox Collision_computeBBox(Runner* runner, Instance* inst) Sprite* spr = Collision_getSprite(runner->dataWin, inst); if (spr == nullptr) return (InstanceBBox){0, 0, 0, 0, false}; - GMLReal marginL = (GMLReal) spr->marginLeft; - GMLReal marginR = (GMLReal) (spr->marginRight + 1); - GMLReal marginT = (GMLReal) spr->marginTop; - GMLReal marginB = (GMLReal) (spr->marginBottom + 1); + GMLReal marginL = (spr->bboxMode == 1) ? 0.0 : (GMLReal) spr->marginLeft; + GMLReal marginR = (spr->bboxMode == 1) ? (GMLReal) spr->width : (GMLReal) (spr->marginRight + 1); + GMLReal marginT = (spr->bboxMode == 1) ? 0.0 : (GMLReal) spr->marginTop; + GMLReal marginB = (spr->bboxMode == 1) ? (GMLReal) spr->height : (GMLReal) (spr->marginBottom + 1); GMLReal originX = (GMLReal) spr->originX; GMLReal originY = (GMLReal) spr->originY; @@ -115,10 +115,10 @@ static inline InstanceOBB Collision_instanceOBB(Sprite* spr, Instance* inst) { InstanceOBB obb; obb.x = inst->x; obb.y = inst->y; - GMLReal marginL = (GMLReal) spr->marginLeft; - GMLReal marginR = (GMLReal) (spr->marginRight + 1); - GMLReal marginT = (GMLReal) spr->marginTop; - GMLReal marginB = (GMLReal) (spr->marginBottom + 1); + GMLReal marginL = spr->bboxMode == 1 ? 0.0 : (GMLReal) spr->marginLeft; + GMLReal marginR = spr->bboxMode == 1 ? (GMLReal) spr->width : (GMLReal) (spr->marginRight + 1); + GMLReal marginT = spr->bboxMode == 1 ? 0.0 : (GMLReal) spr->marginTop; + GMLReal marginB = spr->bboxMode == 1 ? (GMLReal) spr->height : (GMLReal) (spr->marginBottom + 1); GMLReal originX = (GMLReal) spr->originX; GMLReal originY = (GMLReal) spr->originY; obb.lx0 = inst->imageXscale * (marginL - originX); diff --git a/src/vm_builtins.c b/src/vm_builtins.c index 5d3cc7e2..3edb6641 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -10623,6 +10623,42 @@ static RValue builtin_sprite_get_name(VMContext* ctx, RValue* args, MAYBE_UNUSED return RValue_makeString(name != nullptr ? name : ""); } +// sprite_set_bbox_mode(sprite_index, mode) +static RValue builtin_sprite_set_bbox_mode(VMContext* ctx, RValue* args, int32_t argCount) { + if (argCount < 2) return RValue_makeUndefined(); + + int32_t spriteIndex = RValue_toInt32(args[0]); + uint32_t mode = (uint32_t) RValue_toReal(args[1]); + + Runner* runner = ctx->runner; + DataWin* dw = runner->dataWin; + + if (spriteIndex < 0 || (uint32_t)spriteIndex >= dw->sprt.count) { + return RValue_makeUndefined(); + } + + Sprite* spr = &dw->sprt.sprites[spriteIndex]; + + if (spr->bboxMode == mode) { + return RValue_makeUndefined(); + } + + spr->bboxMode = mode; + + int32_t instanceCount = (int32_t)arrlen(runner->instances); + for (int32_t i = 0; i < instanceCount; i++) { + Instance* inst = runner->instances[i]; + if (!inst->active || inst->destroyed) continue; + + int32_t activeMask = (inst->maskIndex >= 0) ? inst->maskIndex : inst->spriteIndex; + if (activeMask == spriteIndex) { + SpatialGrid_markInstanceAsDirty(runner->spatialGrid, inst); + } + } + + return RValue_makeUndefined(); +} + // sprite_set_offset(sprite_index, xoff, yoff) static RValue builtin_sprite_set_offset(VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t argCount) { int32_t spriteIndex = (int32_t) RValue_toReal(args[0]); @@ -16502,6 +16538,7 @@ void VMBuiltins_registerAll(VMContext* ctx) { VM_registerBuiltin(ctx, "sprite_get_bbox_right", builtin_sprite_get_bbox_right); VM_registerBuiltin(ctx, "sprite_get_bbox_top", builtin_sprite_get_bbox_top); VM_registerBuiltin(ctx, "sprite_get_bbox_bottom", builtin_sprite_get_bbox_bottom); + VM_registerBuiltin(ctx, "sprite_set_bbox_mode", builtin_sprite_set_bbox_mode); VM_registerBuiltin(ctx, "sprite_set_offset", builtin_sprite_set_offset); VM_registerBuiltin(ctx, "sprite_create_from_surface", builtin_sprite_create_from_surface); VM_registerBuiltin(ctx, "sprite_delete", builtin_sprite_delete); From 62cb8b0b36993f17300e08916a9b075b79b92a1a Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Tue, 30 Jun 2026 13:09:16 +0100 Subject: [PATCH 07/15] draw_get_halign, draw_get_valign TEN FEET TWENTY THE FLOWER MANNNN --- src/vm_builtins.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/vm_builtins.c b/src/vm_builtins.c index 3edb6641..f74cb42b 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -10251,6 +10251,22 @@ static RValue builtin_draw_get_font(VMContext* ctx, MAYBE_UNUSED RValue* args, M return RValue_makeInt32(-1); } +static RValue builtin_draw_get_halign(VMContext* ctx, MAYBE_UNUSED RValue* args, MAYBE_UNUSED int32_t argCount) { + Runner* runner = ctx->runner; + if (runner->renderer != nullptr) { + return RValue_makeInt32(runner->renderer->drawHalign); + } + return RValue_makeInt32(-1); +} + +static RValue builtin_draw_get_valign(VMContext* ctx, MAYBE_UNUSED RValue* args, MAYBE_UNUSED int32_t argCount) { + Runner* runner = ctx->runner; + if (runner->renderer != nullptr) { + return RValue_makeInt32(runner->renderer->drawValign); + } + return RValue_makeInt32(-1); +} + static RValue builtin_motion_add(VMContext* ctx, RValue* args, int32_t argCount) { if (2 > argCount) return RValue_makeUndefined(); @@ -16503,6 +16519,9 @@ void VMBuiltins_registerAll(VMContext* ctx) { VM_registerBuiltin(ctx, "draw_get_color", builtin_draw_get_color); VM_registerBuiltin(ctx, "draw_get_alpha", builtin_draw_get_alpha); VM_registerBuiltin(ctx, "draw_get_font", builtin_draw_get_font); + VM_registerBuiltin(ctx, "draw_get_halign", builtin_draw_get_halign); + VM_registerBuiltin(ctx, "draw_get_valign", builtin_draw_get_valign); + // Motion VM_registerBuiltin(ctx, "motion_add", builtin_motion_add); From 57294b4192f3622144faf00b21940996138ff122 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Tue, 30 Jun 2026 13:20:00 +0100 Subject: [PATCH 08/15] audio_get_name Get a chance! --- src/vm_builtins.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/vm_builtins.c b/src/vm_builtins.c index f74cb42b..55218d0b 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -6307,6 +6307,21 @@ static RValue builtin_sound_play(VMContext* ctx, RValue* args, MAYBE_UNUSED int3 return RValue_makeReal((GMLReal) instanceId); } +static RValue builtin_audio_get_name(VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t argCount) { + AudioSystem* audio = getAudioSystem(ctx); + if (audio == nullptr || audio->vtable == nullptr || 1 > argCount) return RValue_makeString(""); + if (args[0].type == RVALUE_UNDEFINED) return RValue_makeString(""); + + int32_t soundIndex = RValue_toInt32(args[0]); + if (0 > soundIndex) return RValue_makeString(""); + + DataWin* dw = audio->audioGroups[0]; + if (dw->sond.count <= (uint32_t) soundIndex) + return RValue_makeString(""); + + return RValue_makeString(dw->sond.sounds[soundIndex].name); +} + // same as builtin_sound_play with loop enabled static RValue builtin_sound_loop(VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t argCount) { AudioSystem* audio = getAudioSystem(ctx); @@ -16195,6 +16210,7 @@ void VMBuiltins_registerAll(VMContext* ctx) { // Audio VM_registerBuiltin(ctx, "audio_system_is_available", builtin_audio_system_is_available); VM_registerBuiltin(ctx, "audio_exists", builtin_audio_exists); + VM_registerBuiltin(ctx, "audio_get_name", builtin_audio_get_name); VM_registerBuiltin(ctx, "audio_channel_num", builtin_audio_channel_num); VM_registerBuiltin(ctx, "audio_play_sound", builtin_audio_play_sound); VM_registerBuiltin(ctx, "audio_stop_sound", builtin_audio_stop_sound); From 79eecbe5599b40a88d2ddfb7ed77f92bb57d1479 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Tue, 30 Jun 2026 13:30:32 +0100 Subject: [PATCH 09/15] audio_get_name: search all audio groups? --- src/vm_builtins.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/vm_builtins.c b/src/vm_builtins.c index 55218d0b..29ff7e20 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -6315,11 +6315,16 @@ static RValue builtin_audio_get_name(VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t soundIndex = RValue_toInt32(args[0]); if (0 > soundIndex) return RValue_makeString(""); - DataWin* dw = audio->audioGroups[0]; - if (dw->sond.count <= (uint32_t) soundIndex) - return RValue_makeString(""); - - return RValue_makeString(dw->sond.sounds[soundIndex].name); + int i; + repeat(arrlen(audio->audioGroups), i) { + DataWin* dw = audio->audioGroups[i]; + if (dw->sond.count <= (uint32_t) soundIndex) { + continue; + } else { + return RValue_makeString(dw->sond.sounds[soundIndex].name); + } + } + return RValue_makeString(""); } // same as builtin_sound_play with loop enabled From 87ee3ac6ccf6321ddc90802570956a7db65ee75a Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Tue, 30 Jun 2026 13:31:49 +0100 Subject: [PATCH 10/15] bri'ish --- src/vm_builtins.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vm_builtins.c b/src/vm_builtins.c index 29ff7e20..6242c176 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -16911,7 +16911,7 @@ void VMBuiltins_registerAll(VMContext* ctx) { VM_registerBuiltin(ctx,"gpu_set_alphatestenable", builtin_gpu_set_alphatestenable); VM_registerBuiltin(ctx,"gpu_set_alphatestref", builtin_gpu_set_alphatestref); VM_registerBuiltin(ctx,"gpu_set_colorwriteenable", builtin_gpu_set_colorwriteenable); - VM_registerBuiltin(ctx,"gpu_get_colorwriteenable", builtin_gpu_get_colorwriteenable); + VM_registerBuiltin(ctx,"gpu_get_colourwriteenable", builtin_gpu_get_colorwriteenable); VM_registerBuiltin(ctx,"gpu_set_fog", builtin_gpu_set_fog); if (!isGMS2) { VM_registerBuiltin(ctx,"draw_set_blend_mode", builtin_gpu_set_blendmode); From 98bd68080fe99a1cd5f191e694b123bf45625c8f Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Tue, 30 Jun 2026 14:14:40 +0100 Subject: [PATCH 11/15] gpu_get_blendmode and family --- src/gl/gl_renderer.c | 35 +++++++++++++++++++++++++---- src/gl/gl_renderer.h | 7 ++++++ src/gl_legacy/gl_legacy_renderer.c | 36 ++++++++++++++++++++++++++---- src/gl_legacy/gl_legacy_renderer.h | 7 ++++++ src/ps2/gs_renderer.c | 22 ++++++++++++++++++ src/ps2/gs_renderer.h | 7 ++++++ src/renderer.h | 10 +++++++++ src/vm_builtins.c | 33 ++++++++++++++++++++++++++- 8 files changed, 148 insertions(+), 9 deletions(-) diff --git a/src/gl/gl_renderer.c b/src/gl/gl_renderer.c index 61399411..ca435a83 100644 --- a/src/gl/gl_renderer.c +++ b/src/gl/gl_renderer.c @@ -2424,15 +2424,40 @@ static void glDeleteSprite(Renderer* renderer, int32_t spriteIndex) { fprintf(stderr, "GL: Deleted sprite %d\n", spriteIndex); } +static BlendFactors glGpuGetBlendFactors(Renderer* renderer) { + GLRenderer* gl = (GLRenderer*)renderer; + return (BlendFactors){ + gl->currentSFactor, + gl->currentDFactor, + gl->currentSFactorAlpha, + gl->currentDFactorAlpha + }; +} + +static int32_t glGpuGetBlendMode(Renderer* renderer) { + GLRenderer* gl = (GLRenderer*) renderer; + return gl->currentBlendMode; +} + static void glGpuSetBlendMode(Renderer* renderer, int32_t mode) { - flushBatch((GLRenderer*) renderer); + GLRenderer* gl = (GLRenderer*) renderer; + flushBatch(gl); glBlendEquation(GLCommon_blendModeToEquation(mode)); - glBlendFunc(GLCommon_blendModeToSFactor(mode), GLCommon_blendModeToDFactor(mode)); + gl->currentBlendMode = mode; + gl->currentSFactor = GLCommon_blendModeToSFactor(mode); + gl->currentDFactor = GLCommon_blendModeToDFactor(mode); + glBlendFunc(gl->currentSFactor, gl->currentDFactor); } static void glGpuSetBlendModeExt(Renderer* renderer, int32_t sfactor, int32_t dfactor, int32_t sfactor_alpha, int32_t dfactor_alpha) { - flushBatch((GLRenderer*) renderer); - glBlendFuncSeparate(GLCommon_blendFactorToGL(sfactor), GLCommon_blendFactorToGL(dfactor), GLCommon_blendFactorToGL(sfactor_alpha), GLCommon_blendFactorToGL(dfactor_alpha)); + GLRenderer* gl = (GLRenderer*) renderer; + flushBatch(gl); + gl->currentBlendMode = bm_complex; + gl->currentSFactor = GLCommon_blendModeToSFactor(sfactor); + gl->currentDFactor = GLCommon_blendModeToDFactor(dfactor); + gl->currentSFactorAlpha = GLCommon_blendModeToSFactor(sfactor_alpha); + gl->currentDFactorAlpha = GLCommon_blendModeToDFactor(dfactor_alpha); + glBlendFuncSeparate(gl->currentSFactor, gl->currentDFactor, gl->currentSFactorAlpha, gl->currentDFactorAlpha); } static void glGpuSetBlendEnable(Renderer* renderer, bool enable) { @@ -2728,6 +2753,8 @@ Renderer* GLRenderer_create(void) { glVtable.clearScreen = glClearScreen; glVtable.createSpriteFromSurface = glCreateSpriteFromSurface; glVtable.deleteSprite = glDeleteSprite; + glVtable.gpuGetBlendFactors = glGpuGetBlendFactors; + glVtable.gpuGetBlendMode = glGpuGetBlendMode; glVtable.gpuSetBlendMode = glGpuSetBlendMode; glVtable.gpuSetBlendModeExt = glGpuSetBlendModeExt; glVtable.gpuSetBlendEnable = glGpuSetBlendEnable; diff --git a/src/gl/gl_renderer.h b/src/gl/gl_renderer.h index b0c480d2..7012e86c 100644 --- a/src/gl/gl_renderer.h +++ b/src/gl/gl_renderer.h @@ -76,6 +76,13 @@ typedef struct { int32_t* surfaceHeight; uint32_t surfaceCount; + // Blending mode + factors + int32_t currentBlendMode; + int32_t currentSFactor; + int32_t currentDFactor; + int32_t currentSFactorAlpha; + int32_t currentDFactorAlpha; + bool isGL3; // TRUE if running on OpenGL (ES) 3.x+ bool isGLES; // TRUE if running on OpenGL ES (GLES) } GLRenderer; diff --git a/src/gl_legacy/gl_legacy_renderer.c b/src/gl_legacy/gl_legacy_renderer.c index 67ea84fc..69b4cfc6 100644 --- a/src/gl_legacy/gl_legacy_renderer.c +++ b/src/gl_legacy/gl_legacy_renderer.c @@ -1379,13 +1379,39 @@ static void glDeleteSprite(Renderer* renderer, int32_t spriteIndex) { fprintf(stderr, "GL: Deleted sprite %d\n", spriteIndex); } -static void glGpuSetBlendMode(MAYBE_UNUSED Renderer* renderer, int32_t mode) { +static BlendFactors glGpuGetBlendFactors(Renderer* renderer) { + GLLegacyRenderer* gl = (GLLegacyRenderer*)renderer; + return (BlendFactors){ + gl->currentSFactor, + gl->currentDFactor, + gl->currentSFactorAlpha, + gl->currentDFactorAlpha + }; +} + +static int32_t glGpuGetBlendMode(Renderer* renderer) { + GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; + return gl->currentBlendMode; +} + +static void glGpuSetBlendMode(Renderer* renderer, int32_t mode) { + GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; + + gl->currentBlendMode = mode; + gl->currentSFactor = GLCommon_blendModeToSFactor(mode); + gl->currentDFactor = GLCommon_blendModeToDFactor(mode); glBlendEquation(GLCommon_blendModeToEquation(mode)); - glBlendFunc(GLCommon_blendModeToSFactor(mode), GLCommon_blendModeToDFactor(mode)); + glBlendFunc(gl->currentSFactor, gl->currentDFactor); } -static void glGpuSetBlendModeExt(MAYBE_UNUSED Renderer* renderer, int32_t sfactor, int32_t dfactor, int32_t sfactor_alpha, int32_t dfactor_alpha) { - glBlendFuncSeparate(GLCommon_blendFactorToGL(sfactor), GLCommon_blendFactorToGL(dfactor), GLCommon_blendFactorToGL(sfactor_alpha), GLCommon_blendFactorToGL(dfactor_alpha)); +static void glGpuSetBlendModeExt(Renderer* renderer, int32_t sfactor, int32_t dfactor, int32_t sfactor_alpha, int32_t dfactor_alpha) { + GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; + gl->currentBlendMode = bm_complex; + gl->currentSFactor = GLCommon_blendFactorToGL(sfactor); + gl->currentDFactor = GLCommon_blendFactorToGL(dfactor); + gl->currentSFactorAlpha = GLCommon_blendFactorToGL(sfactor_alpha); + gl->currentDFactorAlpha = GLCommon_blendFactorToGL(dfactor_alpha); + glBlendFuncSeparate(gl->currentSFactor, gl->currentDFactor, gl->currentSFactorAlpha, gl->currentDFactorAlpha); } static void glGpuSetBlendEnable(Renderer* renderer, bool enable) { @@ -1765,6 +1791,8 @@ Renderer* GLLegacyRenderer_create(void) { glVtable.clearScreen = glClearScreen; glVtable.createSpriteFromSurface = glCreateSpriteFromSurface; glVtable.deleteSprite = glDeleteSprite; + glVtable.gpuGetBlendFactors = glGpuGetBlendFactors; + glVtable.gpuGetBlendMode = glGpuGetBlendMode; glVtable.gpuSetBlendMode = glGpuSetBlendMode; glVtable.gpuSetBlendModeExt = glGpuSetBlendModeExt; glVtable.gpuSetBlendEnable = glGpuSetBlendEnable; diff --git a/src/gl_legacy/gl_legacy_renderer.h b/src/gl_legacy/gl_legacy_renderer.h index f2524064..c0551914 100644 --- a/src/gl_legacy/gl_legacy_renderer.h +++ b/src/gl_legacy/gl_legacy_renderer.h @@ -42,6 +42,13 @@ typedef struct { int32_t* surfaceWidth; int32_t* surfaceHeight; uint32_t surfaceCount; + + // Blending mode + factors + int32_t currentBlendMode; + int32_t currentSFactor; + int32_t currentDFactor; + int32_t currentSFactorAlpha; + int32_t currentDFactorAlpha; } GLLegacyRenderer; bool GLLegacyRenderer_ensureTextureLoaded(GLLegacyRenderer* gl, uint32_t pageId); diff --git a/src/ps2/gs_renderer.c b/src/ps2/gs_renderer.c index 027615c9..d8c59fc5 100644 --- a/src/ps2/gs_renderer.c +++ b/src/ps2/gs_renderer.c @@ -2374,6 +2374,21 @@ static u64 gmsBlendModeToGSAlpha(int32_t mode) { } } +static BlendFactors gsGpuGetBlendFactors(Renderer* renderer) { + GsRenderer* gs = (GsRenderer*)renderer; + return (BlendFactors){ + gl->currentSFactor, + gl->currentDFactor, + gl->currentSFactorAlpha, + gl->currentDFactorAlpha + }; +} + +static int32_t gsGpuGetBlendMode(Renderer* renderer) { + GsRenderer* gs = (GsRenderer*) renderer; + return gs->currentBlendMode; +} + static void gsGpuSetBlendMode(Renderer* renderer, int32_t mode) { GsRenderer* gs = (GsRenderer*) renderer; gs->currentBlendAlpha = gmsBlendModeToGSAlpha(mode); @@ -2382,6 +2397,11 @@ static void gsGpuSetBlendMode(Renderer* renderer, int32_t mode) { static void gsGpuSetBlendModeExt(Renderer* renderer, int32_t sfactor, int32_t dfactor, MAYBE_UNUSED int32_t sfactor_alpha, MAYBE_UNUSED int32_t dfactor_alpha) { GsRenderer* gs = (GsRenderer*) renderer; + gs->currentBlendMode = bm_complex; + gs->currentSFactor = sfactor; + gs->currentDFactor = dfactor; + gs->currentSFactorAlpha = sfactor_alpha; + gs->currentDFactorAlpha = dfactor_alpha; u64 alpha; if (!gmsFactorPairToGSAlpha(sfactor, dfactor, &alpha) && !gs->blendModeWarned) { fprintf(stderr, "GsRenderer: blend mode (sf=%d, df=%d) not exactly representable on PS2; approximating\n", sfactor, dfactor); @@ -3099,6 +3119,8 @@ Renderer* GsRenderer_create(GSGLOBAL* gsGlobal, int64_t eeAtlasCacheMiB) { gsVtable.clearScreen = gsClearScreen; gsVtable.createSpriteFromSurface = gsCreateSpriteFromSurface; gsVtable.deleteSprite = gsDeleteSprite; + gsVtable.gpuGetBlendFactors = gsGpuGetBlendFactors; + gsVtable.gpuGetBlendMode = gsGpuGetBlendMode; gsVtable.gpuSetBlendMode = gsGpuSetBlendMode; gsVtable.gpuSetBlendModeExt = gsGpuSetBlendModeExt; gsVtable.gpuSetBlendEnable = gsGpuSetBlendEnable; diff --git a/src/ps2/gs_renderer.h b/src/ps2/gs_renderer.h index 2e26f0ac..173548d7 100644 --- a/src/ps2/gs_renderer.h +++ b/src/ps2/gs_renderer.h @@ -193,6 +193,13 @@ typedef struct { float savedOffsetY; int32_t savedViewX; int32_t savedViewY; + + // Blending mode + factors + int32_t currentBlendMode; + int32_t currentSFactor; + int32_t currentDFactor; + int32_t currentSFactorAlpha; + int32_t currentDFactorAlpha; } GsRenderer; Renderer* GsRenderer_create(GSGLOBAL* gsGlobal, int64_t eeAtlasCacheMiB); diff --git a/src/renderer.h b/src/renderer.h index b18c5728..b434bb3b 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -65,6 +65,13 @@ typedef struct Renderer Renderer; typedef struct Runner Runner; #endif +typedef struct { + int32_t src; + int32_t dst; + int32_t srcAlpha; + int32_t dstAlpha; +} BlendFactors; + typedef struct { void (*init)(Renderer* renderer, DataWin* dataWin); void (*destroy)(Renderer* renderer); @@ -94,6 +101,8 @@ typedef struct { void (*clearScreen)(Renderer* renderer, uint32_t color, float alpha); int32_t (*createSpriteFromSurface)(Renderer* renderer, int32_t surfaceID, int32_t x, int32_t y, int32_t w, int32_t h, bool removeback, bool smooth, int32_t xorig, int32_t yorig); void (*deleteSprite)(Renderer* renderer, int32_t spriteIndex); + BlendFactors (*gpuGetBlendFactors)(Renderer* renderer); + int32_t (*gpuGetBlendMode)(Renderer* renderer); void (*gpuSetBlendMode)(Renderer* renderer, int32_t mode); void (*gpuSetBlendModeExt)(Renderer* renderer, int32_t sfactor, int32_t dfactor, int32_t sfactor_alpha, int32_t dfactor_alpha); void (*gpuSetBlendEnable)(Renderer* renderer, bool enable); @@ -172,6 +181,7 @@ struct Renderer { Runner* runner; Matrix4f gmlMatrices[MATRICES_MAX]; int32_t currentShader; + BlendFactors blendFactors; }; // ===[ Shared Helpers (platform-agnostic) ]=== diff --git a/src/vm_builtins.c b/src/vm_builtins.c index 6242c176..7285841a 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -1,5 +1,6 @@ #include "vm_builtins.h" #include "binary_utils.h" +#include "gml_array.h" #include "instance.h" #include "json_reader.h" #include "json_writer.h" @@ -6315,7 +6316,6 @@ static RValue builtin_audio_get_name(VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t soundIndex = RValue_toInt32(args[0]); if (0 > soundIndex) return RValue_makeString(""); - int i; repeat(arrlen(audio->audioGroups), i) { DataWin* dw = audio->audioGroups[i]; if (dw->sond.count <= (uint32_t) soundIndex) { @@ -15491,6 +15491,32 @@ static RValue builtin_asset_get_index(VMContext* ctx, RValue* args, int32_t argC return RValue_makeReal(value); } +static RValue builtin_gpu_get_blendmode(VMContext* ctx, RValue* args, int32_t argCount) { + return RValue_makeInt32(ctx->runner->renderer->vtable->gpuGetBlendMode(ctx->runner->renderer)); +} + +static RValue builtin_gpu_get_blendmode_ext(VMContext* ctx, RValue* args, int32_t argCount) { + BlendFactors factors = ctx->runner->renderer->vtable->gpuGetBlendFactors(ctx->runner->renderer); + + RValue arr = RValue_makeArray(GMLArray_create(ctx->dataWin->gen8.wadVersion, 2)); + GMLArray_setOnArrayRef(&arr, 0, RValue_makeInt32(factors.src)); + GMLArray_setOnArrayRef(&arr, 1, RValue_makeInt32(factors.dst)); + + return arr; +} + +static RValue builtin_gpu_get_blendmode_ext_sepalpha(VMContext* ctx, RValue* args, int32_t argCount) { + BlendFactors factors = ctx->runner->renderer->vtable->gpuGetBlendFactors(ctx->runner->renderer); + + RValue arr = RValue_makeArray(GMLArray_create(ctx->dataWin->gen8.wadVersion, 4)); + GMLArray_setOnArrayRef(&arr, 0, RValue_makeInt32(factors.src)); + GMLArray_setOnArrayRef(&arr, 1, RValue_makeInt32(factors.dst)); + GMLArray_setOnArrayRef(&arr, 2, RValue_makeInt32(factors.srcAlpha)); + GMLArray_setOnArrayRef(&arr, 3, RValue_makeInt32(factors.dstAlpha)); + + return arr; +} + static RValue builtin_gpu_set_blendmode(VMContext* ctx, RValue* args, int32_t argCount) { int mode = RValue_toReal(args[0]); ctx->runner->renderer->vtable->gpuSetBlendMode(ctx->runner->renderer, mode); @@ -16903,6 +16929,9 @@ void VMBuiltins_registerAll(VMContext* ctx) { } VM_registerBuiltin(ctx, "object_is_ancestor", builtin_object_is_ancestor); VM_registerBuiltin(ctx, "asset_get_index", builtin_asset_get_index); + VM_registerBuiltin(ctx, "gpu_get_blendmode", builtin_gpu_get_blendmode); + VM_registerBuiltin(ctx, "gpu_get_blendmode_ext", builtin_gpu_get_blendmode_ext); + VM_registerBuiltin(ctx, "gpu_get_blendmode_ext_sepalpha", builtin_gpu_get_blendmode_ext_sepalpha); VM_registerBuiltin(ctx,"gpu_set_blendmode", builtin_gpu_set_blendmode); VM_registerBuiltin(ctx,"gpu_set_blendmode_ext", builtin_gpu_set_blendmode_ext); VM_registerBuiltin(ctx,"gpu_set_blendmode_ext_sepalpha", builtin_gpu_set_blendmode_ext_sepalpha); @@ -16911,6 +16940,8 @@ void VMBuiltins_registerAll(VMContext* ctx) { VM_registerBuiltin(ctx,"gpu_set_alphatestenable", builtin_gpu_set_alphatestenable); VM_registerBuiltin(ctx,"gpu_set_alphatestref", builtin_gpu_set_alphatestref); VM_registerBuiltin(ctx,"gpu_set_colorwriteenable", builtin_gpu_set_colorwriteenable); + VM_registerBuiltin(ctx,"gpu_set_colourwriteenable", builtin_gpu_set_colorwriteenable); + VM_registerBuiltin(ctx,"gpu_get_colorwriteenable", builtin_gpu_get_colorwriteenable); VM_registerBuiltin(ctx,"gpu_get_colourwriteenable", builtin_gpu_get_colorwriteenable); VM_registerBuiltin(ctx,"gpu_set_fog", builtin_gpu_set_fog); if (!isGMS2) { From 62f277a617b5fd448b95a72a2cab218a1a047fbc Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Tue, 30 Jun 2026 14:15:56 +0100 Subject: [PATCH 12/15] fix ps2 --- src/ps2/gs_renderer.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ps2/gs_renderer.c b/src/ps2/gs_renderer.c index d8c59fc5..1fa77133 100644 --- a/src/ps2/gs_renderer.c +++ b/src/ps2/gs_renderer.c @@ -2377,10 +2377,10 @@ static u64 gmsBlendModeToGSAlpha(int32_t mode) { static BlendFactors gsGpuGetBlendFactors(Renderer* renderer) { GsRenderer* gs = (GsRenderer*)renderer; return (BlendFactors){ - gl->currentSFactor, - gl->currentDFactor, - gl->currentSFactorAlpha, - gl->currentDFactorAlpha + gs->currentSFactor, + gs->currentDFactor, + gs->currentSFactorAlpha, + gs->currentDFactorAlpha }; } From 2bb585ed9a542cf429f0c1d88e06ee95c4b4b757 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Tue, 30 Jun 2026 15:42:48 +0100 Subject: [PATCH 13/15] rectify setBlendModeExt factor conversion fixes the shit squad not rendering properly in the chapter 4 sanctuaries --- src/gl/gl_renderer.c | 22 ++++++++++++++++------ src/gl_legacy/gl_legacy_renderer.c | 19 ++++++++++++++----- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/gl/gl_renderer.c b/src/gl/gl_renderer.c index ca435a83..2c90e9b3 100644 --- a/src/gl/gl_renderer.c +++ b/src/gl/gl_renderer.c @@ -2442,10 +2442,14 @@ static int32_t glGpuGetBlendMode(Renderer* renderer) { static void glGpuSetBlendMode(Renderer* renderer, int32_t mode) { GLRenderer* gl = (GLRenderer*) renderer; flushBatch(gl); - glBlendEquation(GLCommon_blendModeToEquation(mode)); + gl->currentBlendMode = mode; gl->currentSFactor = GLCommon_blendModeToSFactor(mode); gl->currentDFactor = GLCommon_blendModeToDFactor(mode); + gl->currentSFactorAlpha = gl->currentSFactor; + gl->currentDFactorAlpha = gl->currentDFactor; + + glBlendEquation(GLCommon_blendModeToEquation(mode)); glBlendFunc(gl->currentSFactor, gl->currentDFactor); } @@ -2453,11 +2457,17 @@ static void glGpuSetBlendModeExt(Renderer* renderer, int32_t sfactor, int32_t df GLRenderer* gl = (GLRenderer*) renderer; flushBatch(gl); gl->currentBlendMode = bm_complex; - gl->currentSFactor = GLCommon_blendModeToSFactor(sfactor); - gl->currentDFactor = GLCommon_blendModeToDFactor(dfactor); - gl->currentSFactorAlpha = GLCommon_blendModeToSFactor(sfactor_alpha); - gl->currentDFactorAlpha = GLCommon_blendModeToDFactor(dfactor_alpha); - glBlendFuncSeparate(gl->currentSFactor, gl->currentDFactor, gl->currentSFactorAlpha, gl->currentDFactorAlpha); + gl->currentSFactor = sfactor; + gl->currentDFactor = dfactor; + gl->currentSFactorAlpha = sfactor_alpha; + gl->currentDFactorAlpha = dfactor_alpha; + + glBlendFuncSeparate( + GLCommon_blendFactorToGL(sfactor), + GLCommon_blendFactorToGL(dfactor), + GLCommon_blendFactorToGL(sfactor_alpha), + GLCommon_blendFactorToGL(dfactor_alpha) + ); } static void glGpuSetBlendEnable(Renderer* renderer, bool enable) { diff --git a/src/gl_legacy/gl_legacy_renderer.c b/src/gl_legacy/gl_legacy_renderer.c index 69b4cfc6..26e553fb 100644 --- a/src/gl_legacy/gl_legacy_renderer.c +++ b/src/gl_legacy/gl_legacy_renderer.c @@ -1400,18 +1400,27 @@ static void glGpuSetBlendMode(Renderer* renderer, int32_t mode) { gl->currentBlendMode = mode; gl->currentSFactor = GLCommon_blendModeToSFactor(mode); gl->currentDFactor = GLCommon_blendModeToDFactor(mode); + gl->currentSFactorAlpha = gl->currentSFactor; + gl->currentDFactorAlpha = gl->currentDFactor; glBlendEquation(GLCommon_blendModeToEquation(mode)); glBlendFunc(gl->currentSFactor, gl->currentDFactor); } static void glGpuSetBlendModeExt(Renderer* renderer, int32_t sfactor, int32_t dfactor, int32_t sfactor_alpha, int32_t dfactor_alpha) { GLLegacyRenderer* gl = (GLLegacyRenderer*) renderer; + gl->currentBlendMode = bm_complex; - gl->currentSFactor = GLCommon_blendFactorToGL(sfactor); - gl->currentDFactor = GLCommon_blendFactorToGL(dfactor); - gl->currentSFactorAlpha = GLCommon_blendFactorToGL(sfactor_alpha); - gl->currentDFactorAlpha = GLCommon_blendFactorToGL(dfactor_alpha); - glBlendFuncSeparate(gl->currentSFactor, gl->currentDFactor, gl->currentSFactorAlpha, gl->currentDFactorAlpha); + gl->currentSFactor = sfactor; + gl->currentDFactor = dfactor; + gl->currentSFactorAlpha = sfactor_alpha; + gl->currentDFactorAlpha = dfactor_alpha; + + glBlendFuncSeparate( + GLCommon_blendFactorToGL(sfactor), + GLCommon_blendFactorToGL(dfactor), + GLCommon_blendFactorToGL(sfactor_alpha), + GLCommon_blendFactorToGL(dfactor_alpha) + ); } static void glGpuSetBlendEnable(Renderer* renderer, bool enable) { From 60aefdd2122ffe45fe249913af630d8b40ea0b6f Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Tue, 30 Jun 2026 20:54:10 +0100 Subject: [PATCH 14/15] (unrelated) audio_set_master_gain --- src/audio/miniaudio/ma_audio_system.c | 24 ++++++++++++++-- src/audio/miniaudio/ma_audio_system.h | 3 ++ src/audio/openal/al_audio_system.c | 7 +++++ src/audio/ps2/ps2_audio_system.c | 7 +++++ src/audio/web/web_audio_system.c | 10 +++++++ src/audio/web/web_audio_system.h | 2 ++ src/audio_system.h | 1 + src/vm_builtins.c | 40 +++++++++++++++++---------- 8 files changed, 76 insertions(+), 18 deletions(-) diff --git a/src/audio/miniaudio/ma_audio_system.c b/src/audio/miniaudio/ma_audio_system.c index 5771442f..564be8f1 100644 --- a/src/audio/miniaudio/ma_audio_system.c +++ b/src/audio/miniaudio/ma_audio_system.c @@ -124,12 +124,22 @@ static void maInit(AudioSystem* audio, DataWin* dataWin, FileSystem* fileSystem) memset(ma->instances, 0, sizeof(ma->instances)); ma->nextInstanceCounter = 0; + repeat(MAX_LISTENERS, i) { + ma_sound_group_init(&ma->engine, 0, NULL, &ma->listenerGroups[i]); + ma_sound_group_set_volume(&ma->listenerGroups[i], 1.0f); + ma->listenerGains[i] = 1.0f; + } + fprintf(stderr, "Audio: miniaudio engine initialized\n"); } static void maDestroy(AudioSystem* audio) { MaAudioSystem* ma = (MaAudioSystem*) audio; + repeat(MAX_LISTENERS, i) { + ma_sound_group_uninit(&ma->listenerGroups[i]); + } + // Uninit all active sound instances repeat(MAX_SOUND_INSTANCES, i) { if (ma->instances[i].active) { @@ -232,7 +242,7 @@ static int32_t maPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t prior if (isStream) { // Stream audio: load from file path stored in stream entry - result = ma_sound_init_from_file(&ma->engine, streamPath, MA_SOUND_FLAG_ASYNC, nullptr, nullptr, &slot->maSound); + result = ma_sound_init_from_file(&ma->engine, streamPath, MA_SOUND_FLAG_ASYNC, &ma->listenerGroups[0], nullptr, &slot->maSound); if (result != MA_SUCCESS) { fprintf(stderr, "Audio: Failed to load stream file '%s' (error %d)\n", streamPath, result); return -1; @@ -261,7 +271,7 @@ static int32_t maPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t prior } slot->ownsDecoder = true; - result = ma_sound_init_from_data_source(&ma->engine, &slot->decoder, 0, nullptr, &slot->maSound); + result = ma_sound_init_from_data_source(&ma->engine, &slot->decoder, 0, &ma->listenerGroups[0], &slot->maSound); if (result != MA_SUCCESS) { fprintf(stderr, "Audio: Failed to init sound from decoder for '%s' (error %d)\n", sound->name, result); ma_decoder_uninit(&slot->decoder); @@ -275,7 +285,7 @@ static int32_t maPlaySound(AudioSystem* audio, int32_t soundIndex, int32_t prior return -1; } - result = ma_sound_init_from_file(&ma->engine, path, MA_SOUND_FLAG_ASYNC, nullptr, nullptr, &slot->maSound); + result = ma_sound_init_from_file(&ma->engine, path, MA_SOUND_FLAG_ASYNC, &ma->listenerGroups[0], nullptr, &slot->maSound); if (result != MA_SUCCESS) { fprintf(stderr, "Audio: Failed to load file for '%s' at '%s' (error %d)\n", sound->name, path, result); free(path); @@ -701,6 +711,13 @@ static void maSetMasterGain(AudioSystem* audio, float gain) { ma_engine_set_volume(&ma->engine, gain); } +static void maSetMasterGainForListener(AudioSystem* audio, float gain, int32_t id) { + MaAudioSystem* ma = (MaAudioSystem*) audio; + if (id < 0 || id >= MAX_LISTENERS) return; + ma->listenerGains[id] = gain; + ma_sound_group_set_volume(&ma->listenerGroups[id], gain); +} + static void maSetChannelCount(MAYBE_UNUSED AudioSystem* audio, MAYBE_UNUSED int32_t count) { // miniaudio handles channel management internally, this is a no-op } @@ -842,6 +859,7 @@ MaAudioSystem* MaAudioSystem_create(DataWin* dataWin) { maAudioSystemVtable.setTrackPosition = maSetTrackPosition; maAudioSystemVtable.getSoundLength = maGetSoundLength; maAudioSystemVtable.setMasterGain = maSetMasterGain; + maAudioSystemVtable.setMasterGainForListener = maSetMasterGainForListener; maAudioSystemVtable.setChannelCount = maSetChannelCount; maAudioSystemVtable.groupLoad = maGroupLoad; maAudioSystemVtable.groupIsLoaded = maGroupIsLoaded; diff --git a/src/audio/miniaudio/ma_audio_system.h b/src/audio/miniaudio/ma_audio_system.h index 3ee5d99d..77f53401 100644 --- a/src/audio/miniaudio/ma_audio_system.h +++ b/src/audio/miniaudio/ma_audio_system.h @@ -10,6 +10,7 @@ #define MAX_AUDIO_STREAMS 32 // This is the index space that the native runner uses #define AUDIO_STREAM_INDEX_BASE 300000 +#define MAX_LISTENERS 4 typedef struct { bool active; @@ -41,6 +42,8 @@ typedef struct { int32_t nextInstanceCounter; FileSystem* fileSystem; AudioStreamEntry streams[MAX_AUDIO_STREAMS]; + ma_sound_group listenerGroups[MAX_LISTENERS]; + float listenerGains[MAX_LISTENERS]; } MaAudioSystem; MaAudioSystem* MaAudioSystem_create(DataWin* dataWin); diff --git a/src/audio/openal/al_audio_system.c b/src/audio/openal/al_audio_system.c index d67479fb..a2223b03 100644 --- a/src/audio/openal/al_audio_system.c +++ b/src/audio/openal/al_audio_system.c @@ -792,6 +792,12 @@ static float maGetSoundLength(AudioSystem* audio, int32_t soundOrInstance) { return seconds; } +static void maSetMasterGainForListener(AudioSystem* audio, float gain, int32_t id) { + (void)audio; + (void)id; + alListenerf(AL_GAIN, gain); +} + static void maSetMasterGain(AudioSystem* audio, float gain) { (void)audio; alListenerf(AL_GAIN, gain); @@ -917,6 +923,7 @@ AlAudioSystem* AlAudioSystem_create(void) { AlAudioSystemVtable.setTrackPosition = maSetTrackPosition; AlAudioSystemVtable.getSoundLength = maGetSoundLength; AlAudioSystemVtable.setMasterGain = maSetMasterGain; + AlAudioSystemVtable.setMasterGainForListener = maSetMasterGainForListener; AlAudioSystemVtable.setChannelCount = maSetChannelCount; AlAudioSystemVtable.groupLoad = maGroupLoad; AlAudioSystemVtable.groupIsLoaded = maGroupIsLoaded; diff --git a/src/audio/ps2/ps2_audio_system.c b/src/audio/ps2/ps2_audio_system.c index 79ac37d5..29bf9fa7 100644 --- a/src/audio/ps2/ps2_audio_system.c +++ b/src/audio/ps2/ps2_audio_system.c @@ -1269,6 +1269,12 @@ static void ps2SetMasterGain(AudioSystem* audio, float gain) { ps2->masterGain = gain; } +static void ps2SetMasterGainForListener(AudioSystem* audio, float gain, int32_t id) { + if (id != 0) return; + Ps2AudioSystem* ps2 = (Ps2AudioSystem*) audio; + ps2->masterGain = gain; +} + static void ps2SetChannelCount(MAYBE_UNUSED AudioSystem* audio, MAYBE_UNUSED int32_t count) { // No-op: software mixer handles all channels internally } @@ -1340,6 +1346,7 @@ Ps2AudioSystem* Ps2AudioSystem_create(void) { ps2AudioSystemVtable.setTrackPosition = ps2SetTrackPosition; ps2AudioSystemVtable.getSoundLength = ps2GetSoundLength; ps2AudioSystemVtable.setMasterGain = ps2SetMasterGain; + ps2AudioSystemVtable.setMasterGainForListener = ps2SetMasterGainForListener; ps2AudioSystemVtable.setChannelCount = ps2SetChannelCount; ps2AudioSystemVtable.groupLoad = ps2GroupLoad; ps2AudioSystemVtable.groupIsLoaded = ps2GroupIsLoaded; diff --git a/src/audio/web/web_audio_system.c b/src/audio/web/web_audio_system.c index b07718d8..47991322 100644 --- a/src/audio/web/web_audio_system.c +++ b/src/audio/web/web_audio_system.c @@ -575,6 +575,15 @@ static void webSetMasterGain(AudioSystem* audio, float gain) { ma_engine_set_volume(&ma->engine, gain); } + +static void webSetMasterGainForListener(AudioSystem* audio, float gain, int32_t id) { + WebAudioSystem* ma = (WebAudioSystem*) audio; + if (!ma->engineReady) return; + if (id < 0 || id >= MAX_LISTENERS) return; + ma->listenerGains[id] = gain; + ma_sound_group_set_volume(&ma->listenerGroups[id], gain); +} + static void webSetChannelCount(MAYBE_UNUSED AudioSystem* audio, MAYBE_UNUSED int32_t count) {} static void webGroupLoad(AudioSystem* audio, int32_t groupIndex) { @@ -701,6 +710,7 @@ WebAudioSystem* WebAudioSystem_create(DataWin* dataWin, int32_t sampleRate) { webAudioSystemVtable.setTrackPosition = webSetTrackPosition; webAudioSystemVtable.getSoundLength = webGetSoundLength; webAudioSystemVtable.setMasterGain = webSetMasterGain; + webAudioSystemVtable.setMasterGainForListener = webSetMasterGainForListener; webAudioSystemVtable.setChannelCount = webSetChannelCount; webAudioSystemVtable.groupLoad = webGroupLoad; webAudioSystemVtable.groupIsLoaded = webGroupIsLoaded; diff --git a/src/audio/web/web_audio_system.h b/src/audio/web/web_audio_system.h index f9262098..2a605170 100644 --- a/src/audio/web/web_audio_system.h +++ b/src/audio/web/web_audio_system.h @@ -39,6 +39,8 @@ typedef struct { int32_t nextInstanceCounter; FileSystem* fileSystem; WebAudioStreamEntry streams[WEB_MAX_AUDIO_STREAMS]; + ma_sound_group listenerGroups[MAX_LISTENERS]; + float listenerGains[MAX_LISTENERS]; } WebAudioSystem; // Creates a no-device miniaudio engine that mixes into a buffer when WebAudioSystem_pullFrames is called. diff --git a/src/audio_system.h b/src/audio_system.h index 69c9d9d4..e28c3229 100644 --- a/src/audio_system.h +++ b/src/audio_system.h @@ -36,6 +36,7 @@ typedef struct { // Returns 0.0 if unknown (e.g. stream not yet loaded or invalid index). float (*getSoundLength)(AudioSystem* audio, int32_t soundOrInstance); void (*setMasterGain)(AudioSystem* audio, float gain); + void (*setMasterGainForListener)(AudioSystem* audio, float gain, int32_t listenerId); void (*setChannelCount)(AudioSystem* audio, int32_t count); void (*groupLoad)(AudioSystem* audio, int32_t groupIndex); bool (*groupIsLoaded)(AudioSystem* audio, int32_t groupIndex); diff --git a/src/vm_builtins.c b/src/vm_builtins.c index 7285841a..79772d78 100644 --- a/src/vm_builtins.c +++ b/src/vm_builtins.c @@ -341,7 +341,7 @@ static const BuiltinVarEntry BUILTIN_VAR_TABLE[] = { { "image_speed", BUILTIN_VAR_IMAGE_SPEED }, { "image_xscale", BUILTIN_VAR_IMAGE_XSCALE }, { "image_yscale", BUILTIN_VAR_IMAGE_YSCALE }, - { "infinity", BUILTIN_VAR_INFINITY }, + { "infinity", BUILTIN_VAR_INFINITY }, { "instance_count", BUILTIN_VAR_INSTANCE_COUNT }, { "instance_id", BUILTIN_VAR_INSTANCE_ID }, { "keyboard_key", BUILTIN_VAR_KEYBOARD_KEY }, @@ -2232,7 +2232,7 @@ static RValue builtin_string_starts_with(MAYBE_UNUSED VMContext* ctx, RValue* ar if (2 > argCount) return RValue_makeInt32(0); char* substr = RValue_toString(args[0]); char* str = RValue_toString(args[1]); - + bool ret = (strncmp(str, substr, strlen(substr)) == 0); free(substr); @@ -6469,6 +6469,15 @@ static RValue builtin_audio_master_gain(VMContext* ctx, RValue* args, MAYBE_UNUS return RValue_makeUndefined(); } +static RValue builtin_audio_set_master_gain(VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t argCount) { + AudioSystem* audio = getAudioSystem(ctx); + if (audio == nullptr) return RValue_makeUndefined(); + int32_t id = RValue_toInt32(args[0]); + float gain = (float) RValue_toReal(args[1]); + audio->vtable->setMasterGainForListener(audio, gain, id); + return RValue_makeUndefined(); +} + static RValue builtin_audio_group_load(VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t argCount) { AudioSystem* audio = getAudioSystem(ctx); if (audio == nullptr) return RValue_makeUndefined(); @@ -10686,7 +10695,7 @@ static RValue builtin_sprite_set_bbox_mode(VMContext* ctx, RValue* args, int32_t Instance* inst = runner->instances[i]; if (!inst->active || inst->destroyed) continue; - int32_t activeMask = (inst->maskIndex >= 0) ? inst->maskIndex : inst->spriteIndex; + int32_t activeMask = (inst->maskIndex >= 0) ? inst->maskIndex : inst->spriteIndex; if (activeMask == spriteIndex) { SpatialGrid_markInstanceAsDirty(runner->spatialGrid, inst); } @@ -13002,24 +13011,24 @@ static RValue builtin_layer_destroy(VMContext* ctx, RValue* args, MAYBE_UNUSED i static RValue builtin_layer_script_begin(VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t argCount) { int32_t layerId = resolveLayerIdArg(ctx->runner, args[0]); int32_t scriptIndex = RValue_toInt32(args[1]); - + RuntimeLayer* runtimeLayer = Runner_findRuntimeLayerById(ctx->runner, layerId); if (runtimeLayer == nullptr) return RValue_makeUndefined(); - + runtimeLayer->beginScript = scriptIndex; - + return RValue_makeUndefined(); } static RValue builtin_layer_script_end(VMContext* ctx, RValue* args, MAYBE_UNUSED int32_t argCount) { int32_t layerId = resolveLayerIdArg(ctx->runner, args[0]); int32_t scriptIndex = RValue_toInt32(args[1]); - + RuntimeLayer* runtimeLayer = Runner_findRuntimeLayerById(ctx->runner, layerId); if (runtimeLayer == nullptr) return RValue_makeUndefined(); - + runtimeLayer->endScript = scriptIndex; - + return RValue_makeUndefined(); } @@ -13361,7 +13370,7 @@ static RValue builtin_layer_element_move(VMContext* ctx, RValue* args, MAYBE_UNU break; } } - + return RValue_makeUndefined(); } @@ -15497,23 +15506,23 @@ static RValue builtin_gpu_get_blendmode(VMContext* ctx, RValue* args, int32_t ar static RValue builtin_gpu_get_blendmode_ext(VMContext* ctx, RValue* args, int32_t argCount) { BlendFactors factors = ctx->runner->renderer->vtable->gpuGetBlendFactors(ctx->runner->renderer); - + RValue arr = RValue_makeArray(GMLArray_create(ctx->dataWin->gen8.wadVersion, 2)); GMLArray_setOnArrayRef(&arr, 0, RValue_makeInt32(factors.src)); GMLArray_setOnArrayRef(&arr, 1, RValue_makeInt32(factors.dst)); - + return arr; } static RValue builtin_gpu_get_blendmode_ext_sepalpha(VMContext* ctx, RValue* args, int32_t argCount) { BlendFactors factors = ctx->runner->renderer->vtable->gpuGetBlendFactors(ctx->runner->renderer); - + RValue arr = RValue_makeArray(GMLArray_create(ctx->dataWin->gen8.wadVersion, 4)); GMLArray_setOnArrayRef(&arr, 0, RValue_makeInt32(factors.src)); GMLArray_setOnArrayRef(&arr, 1, RValue_makeInt32(factors.dst)); GMLArray_setOnArrayRef(&arr, 2, RValue_makeInt32(factors.srcAlpha)); GMLArray_setOnArrayRef(&arr, 3, RValue_makeInt32(factors.dstAlpha)); - + return arr; } @@ -16254,6 +16263,7 @@ void VMBuiltins_registerAll(VMContext* ctx) { VM_registerBuiltin(ctx, "audio_sound_get_gain", builtin_audio_sound_get_gain); VM_registerBuiltin(ctx, "audio_sound_get_pitch", builtin_audio_sound_get_pitch); VM_registerBuiltin(ctx, "audio_master_gain", builtin_audio_master_gain); + VM_registerBuiltin(ctx, "audio_set_master_gain", builtin_audio_set_master_gain); VM_registerBuiltin(ctx, "audio_group_load", builtin_audio_group_load); VM_registerBuiltin(ctx, "audio_group_is_loaded", builtin_audio_group_is_loaded); if (!isGMS2) { @@ -16568,7 +16578,7 @@ void VMBuiltins_registerAll(VMContext* ctx) { VM_registerBuiltin(ctx, "draw_get_font", builtin_draw_get_font); VM_registerBuiltin(ctx, "draw_get_halign", builtin_draw_get_halign); VM_registerBuiltin(ctx, "draw_get_valign", builtin_draw_get_valign); - + // Motion VM_registerBuiltin(ctx, "motion_add", builtin_motion_add); From f4b32878709f1724139a1e416b15c38157337676 Mon Sep 17 00:00:00 2001 From: cobaltgit Date: Tue, 30 Jun 2026 22:15:00 +0100 Subject: [PATCH 15/15] whoops --- src/audio/web/web_audio_system.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/audio/web/web_audio_system.h b/src/audio/web/web_audio_system.h index 2a605170..bf0e3080 100644 --- a/src/audio/web/web_audio_system.h +++ b/src/audio/web/web_audio_system.h @@ -9,6 +9,7 @@ #define WEB_SOUND_INSTANCE_ID_BASE 100000 #define WEB_MAX_AUDIO_STREAMS 32 #define WEB_AUDIO_STREAM_INDEX_BASE 300000 +#define MAX_LISTENERS 4 typedef struct { bool active;